Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying the ELF TLS ABI #132480

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ impl OwnedTargetMachine {
output_obj_file: &CStr,
debug_info_compression: &CStr,
use_emulated_tls: bool,
enable_tlsdesc: bool,
args_cstr_buff: &[u8],
) -> Result<Self, LlvmError<'static>> {
assert!(args_cstr_buff.len() > 0);
Expand Down Expand Up @@ -71,6 +72,7 @@ impl OwnedTargetMachine {
output_obj_file.as_ptr(),
debug_info_compression.as_ptr(),
use_emulated_tls,
enable_tlsdesc,
args_cstr_buff.as_ptr() as *const c_char,
args_cstr_buff.len(),
)
Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_codegen_llvm/src/back/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use rustc_session::config::{
};
use rustc_span::InnerSpan;
use rustc_span::symbol::sym;
use rustc_target::spec::{CodeModel, RelocModel, SanitizerSet, SplitDebuginfo, TlsModel};
use rustc_target::spec::{CodeModel, RelocModel, SanitizerSet, SplitDebuginfo, TlsDialect, TlsModel};
use tracing::debug;

use crate::back::lto::ThinBuffer;
Expand Down Expand Up @@ -230,6 +230,8 @@ pub(crate) fn target_machine_factory(

let use_emulated_tls = matches!(sess.tls_model(), TlsModel::Emulated);

let enable_tlsdesc = matches!(sess.tls_dialect(), TlsDialect::Desc);

// copy the exe path, followed by path all into one buffer
// null terminating them so we can use them as null terminated strings
let args_cstr_buff = {
Expand Down Expand Up @@ -302,6 +304,7 @@ pub(crate) fn target_machine_factory(
&output_obj_file,
&debuginfo_compression,
use_emulated_tls,
enable_tlsdesc,
&args_cstr_buff,
)
})
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_codegen_llvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,17 @@ impl CodegenBackend for LlvmCodegenBackend {
}
writeln!(out).unwrap();
}
PrintKind::TlsDialect => {
writeln!(out, "Available TLS dialects:").unwrap();
for name in
&["trad", "desc"]
{
writeln!(out, " {name}").unwrap();
}
writeln!(out).unwrap();
}


PrintKind::StackProtectorStrategies => {
writeln!(
out,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_llvm/src/llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2230,6 +2230,7 @@ unsafe extern "C" {
OutputObjFile: *const c_char,
DebugInfoCompression: *const c_char,
UseEmulatedTls: bool,
EnableTlsDesc: bool,
ArgsCstrBuff: *const c_char,
ArgsCstrBuffLen: usize,
) -> *mut TargetMachine;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_driver_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@ fn print_crate_info(
RelocationModels
| CodeModels
| TlsModels
| TlsDialect
| TargetCPUs
| StackProtectorStrategies
| TargetFeatures => {
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine(
bool TrapUnreachable, bool Singlethread, bool VerboseAsm,
bool EmitStackSizeSection, bool RelaxELFRelocations, bool UseInitArray,
const char *SplitDwarfFile, const char *OutputObjFile,
const char *DebugInfoCompression, bool UseEmulatedTls,
const char *DebugInfoCompression, bool UseEmulatedTls, bool EnableTLSDESC,
const char *ArgsCstrBuff, size_t ArgsCstrBuffLen) {

auto OptLevel = fromRust(RustOptLevel);
Expand Down Expand Up @@ -456,6 +456,7 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine(

#if LLVM_VERSION_GE(19, 0)
Options.MCOptions.X86RelaxRelocations = RelaxELFRelocations;
Options.EnableTLSDESC = EnableTLSDESC;
#else
Options.RelaxELFRelocations = RelaxELFRelocations;
#endif
Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ pub enum PrintKind {
RelocationModels,
CodeModels,
TlsModels,
TlsDialect,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this added? Do you have any actual need to know which tls dialect will ne used by rustc on a given target?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it should probably be made available on nightly only so we can remove it again once the traditional tls dialect is no longer used anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this added? Do you have any actual need to know which tls dialect will ne used by rustc on a given target?

I followed the example for TlsModels, since they seem of equivalent diagnostic value to me. For Android RISC-V targets TLSDESC is the default, and I think it may be a requirement.

Also it should probably be made available on nightly only so we can remove it again once the traditional tls dialect is no longer used anywhere.

I'm not sure that will ever happen. TLSDESC requires support in the dynamic linker, so its imaginable that not all libcs will support that. For instance I've seen embedded targets that implement something like a loader service that support __tls_get_addr with overlays, but do not support TLSDESC.

At any rate, I'm not sure we will ever drop support for traditional TLS in LLVM. It's been discussed, but I think its unlikely until GNU toolchains drop support.

Copy link
Member

@bjorn3 bjorn3 Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that will ever happen. TLSDESC requires support in the dynamic linker, so its imaginable that not all libcs will support that.

It can be the default on targets that do support it and not having an option to enable it on targets that don't support it.

At any rate, I'm not sure we will ever drop support for traditional TLS in LLVM. It's been discussed, but I think its unlikely until GNU toolchains drop support.

I mean rustc dropping support for configuring it to anything other than the default for the target.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that will ever happen. TLSDESC requires support in the dynamic linker, so its imaginable that not all libcs will support that.

It can be the default on targets that do support it and not having an option to enable it on targets that don't support it.

I'm not sure how you can make that determination, since you can't know which libc someone is using ahead of time. Being able to configure and opt in/out is necessary, IMO.

At any rate, I'm not sure we will ever drop support for traditional TLS in LLVM. It's been discussed, but I think its unlikely until GNU toolchains drop support.

I mean rustc dropping support for configuring it to anything other than the default for the target.

Failing to expose a code generation option doesn't sound right to me. There are valid reasons someone may want to select a particular variant of an option differently than the default. Until LLVM (and I guess all backends?) drops support altogether, I'm not sure its a good idea for rustc to stop exposing that option altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a chance that LLVM ever drops the option, then no, we should not expose the option.

AFAIK, LLVM has no plans to drop support. Its hard to say never, but as a mater of practicality, I doubt it will be removed any time soon. If it does, we'd probably have a long period where its listed as "deprecated". Even then its possible it wouldn't actually be removed. There is precedent for that.

There will be options to pass arguments directly to codegen backends, and if those suffice, then great, but otherwise we should resist getting into the habit of gaslighting our users by tricking them into thinking they have power and then not being able to fulfill their requests later because of the whims of those codegen backends.

In general, passing -mllvm options often fails for LTO builds, because frontends tend to fail to pass those along correctly to the linker via -Wl,-mllvm,-option. This was particularly painful for RISC-V targets whose target features were not propagated to the linker correctly, and required extra build plumbing to solve. It's arguably a failing of LLVM for not encoding those options in the IR. How to solve this class of problems has been being discussed for a long time now, and I don't think we'll arrive at a solution any time soon.

It would be good to spell those out.

First, this comment was about compiler options in general. Defaults are usually chosen to make sense for the most common software. Projects may want to leverage knowledge they have about their system to improve performance, security, reduce size, etc. It's quite hard to guess what requirements people may have, and failing to expose options that allows them to deviate from the default feels like the wrong approach for a compiler.

For TLS dialects, I'd say performance is the most obvious motivation. Another thing to consider is that you may want to support different configurations for the same target triple. For instance, Android after some particular SDK version may want to drop TLSDESC support, but you may need to retain the ability to target earlier SDKs that predate TLSDESC support in bionic. Similarly, we know that Fuchsia's driver ABI will only support TLSDESC, but the rest of the system will likely be more flexible about supporting both TLSDESC and traditional TLS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance, Android after some particular SDK version may want to drop TLSDESC support, but you may need to retain the ability to target earlier SDKs that predate TLSDESC support in bionic.

Is Android planning to drop support for not using TLSDESC before the last LTS NDK that doesn't support TLSDESC is no longer supported? We only support the most recent LTS NDK.

Similarly, we know that Fuchsia's driver ABI will only support TLSDESC, but the rest of the system will likely be more flexible about supporting both TLSDESC and traditional TLS.

If Fuchsia has a different ABI between regular userspace and drivers, it would make sense to have separate targets, right? Does anyone care about supporting Fuchsia versions from before TLSDESC got introduced in the first place?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite hard to guess what requirements people may have, and failing to expose options that allows them to deviate from the default feels like the wrong approach for a compiler.

This argument generalizes too much. It could mean that every single decision the compiler could make should have a user-facing option.

Compiler flags are part of our user interface. Which means they need testing, we need to consider compatibility, ABI unsoundness, terrible linker errors and so on. So adding one should come with a stronger motivation than "it'd be nice to have a knob, even though there's no immediate need".

As bjorn says it's possible that this decision is better left to a combination of target specifications and the compiler checking if it's supported (e.g. based on LLVM version) and determining the correct setting based on that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance, Android after some particular SDK version may want to drop TLSDESC support, but you may need to retain the ability to target earlier SDKs that predate TLSDESC support in bionic.

Is Android planning to drop support for not using TLSDESC before the last LTS NDK that doesn't support TLSDESC is no longer supported? We only support the most recent LTS NDK.

I doubt it, but I'm not an Android maintainer and the precise details are outside of my area of expertise. I mentioned this, since similar concerns were brought up on the LLVM side w.r.t. emulated TLS and the NDK's need to maintain support for it until the NDK versions that used it were no longer supported. Those time horizons were typically long, IIRC. If Rust has a support story here tied to the NDK then maybe the concern I raised is moot.

Similarly, we know that Fuchsia's driver ABI will only support TLSDESC, but the rest of the system will likely be more flexible about supporting both TLSDESC and traditional TLS.

If Fuchsia has a different ABI between regular userspace and drivers, it would make sense to have separate targets, right?

Drivers in Fuchsia are still user-level programs, and I'm fairly sure we don't want to introduce another triple for them.

Does anyone care about supporting Fuchsia versions from before TLSDESC got introduced in the first place?

Fuchsia was brought up as an example of a scenario where its useful to have such knobs. I don't think the focus should be on Fuchsia specifically. FWIW, though, we do certainly support older versions of our SDK, though those are usually tied to a specific toolchain and set of libraries.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite hard to guess what requirements people may have, and failing to expose options that allows them to deviate from the default feels like the wrong approach for a compiler.

This argument generalizes too much. It could mean that every single decision the compiler could make should have a user-facing option.

There's an argument that it should, even if its not easy to access. Not all software is created equal, and not all compiler decisions are appropriate everywhere. I'm not suggesting that be made the case, just that in many cases not leaving a escape hatch for an optimization leads to worse performance, code size, or a security problem that has to be worked around.

Compiler flags are part of our user interface. Which means they need testing, we need to consider compatibility, ABI unsoundness, terrible linker errors and so on. So adding one should come with a stronger motivation than "it'd be nice to have a knob, even though there's no immediate need".

I don't view this as "nice to have," I view it as a requirement. Given the history of these options, it seems irrational to fail to give developers the choice of what to do if their needs differ from the default. I'm fine to leave the disagreement there, but I'm not doing this work because projects I support don't need it. Clearly the fact that I'm implementing it suggests some level of need.

As bjorn says it's possible that this decision is better left to a combination of target specifications and the compiler checking if it's supported (e.g. based on LLVM version) and determining the correct setting based on that.

I disagree. IMO, setting defaults based on the triple is sensible, but locking users in is not, especially when the backend supports both.

TargetSpec,
AllTargetSpecs,
NativeStaticLibs,
Expand Down Expand Up @@ -1956,6 +1957,7 @@ fn collect_print_requests(
("target-libdir", PrintKind::TargetLibdir),
("target-list", PrintKind::TargetList),
("target-spec-json", PrintKind::TargetSpec),
("tls-dialect", PrintKind::TlsDialect),
("tls-models", PrintKind::TlsModels),
// tidy-alphabetical-end
];
Expand Down Expand Up @@ -3018,7 +3020,7 @@ pub(crate) mod dep_tracking {
use rustc_target::spec::{
CodeModel, FramePointer, MergeFunctions, OnBrokenPipe, PanicStrategy, RelocModel,
RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, SymbolVisibility, TargetTriple,
TlsModel, WasmCAbi,
TlsDialect, TlsModel, WasmCAbi,
};

use super::{
Expand Down Expand Up @@ -3083,6 +3085,7 @@ pub(crate) mod dep_tracking {
FramePointer,
RelocModel,
CodeModel,
TlsDialect,
TlsModel,
InstrumentCoverage,
CoverageOptions,
Expand Down
14 changes: 13 additions & 1 deletion compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use rustc_span::{RealFileName, SourceFileHashAlgorithm};
use rustc_target::spec::{
CodeModel, FramePointer, LinkerFlavorCli, MergeFunctions, OnBrokenPipe, PanicStrategy,
RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, SymbolVisibility,
TargetTriple, TlsModel, WasmCAbi,
TargetTriple, TlsDialect, TlsModel, WasmCAbi,
};

use crate::config::*;
Expand Down Expand Up @@ -427,6 +427,8 @@ mod desc {
"one of supported code models (`rustc --print code-models`)";
pub(crate) const parse_tls_model: &str =
"one of supported TLS models (`rustc --print tls-models`)";
pub(crate) const parse_tls_dialect: &str =
"one of supported TLS dialects (`rustc --print tls-dialect`)";
pub(crate) const parse_target_feature: &str = parse_string;
pub(crate) const parse_terminal_url: &str =
"either a boolean (`yes`, `no`, `on`, `off`, etc), or `auto`";
Expand Down Expand Up @@ -1244,6 +1246,13 @@ mod parse {
}
true
}
pub(crate) fn parse_tls_dialect(slot: &mut Option<TlsDialect>, v: Option<&str>) -> bool {
match v.and_then(|s| TlsDialect::from_str(s).ok()) {
Some(tls_dialect) => *slot = Some(tls_dialect),
_ => return false,
}
true
}

pub(crate) fn parse_terminal_url(slot: &mut TerminalUrl, v: Option<&str>) -> bool {
*slot = match v {
Expand Down Expand Up @@ -2114,6 +2123,9 @@ written to standard error output)"),
#[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")]
tls_model: Option<TlsModel> = (None, parse_tls_model, [TRACKED],
"choose the TLS model to use (`rustc --print tls-models` for details)"),
#[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*tls_model

tls_dialect: Option<TlsDialect> = (None, parse_tls_dialect, [TRACKED],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe name this -Zuse-tlsdesc and have it take a bool? I don't suppose we get another dialect before the transition from traditional to tlsdesc TLS support is finished, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK w/ that, but I think its usually better to support an existing spelling when there are existing conventions in other compilers. We'd have to check w/ folks doing the GCC backend work, but its possible the variants would be easier to integrate for them.

We've talked about adding support for a 3 GOT slot variant of TLSDESC. That would require support from the linker and libc. There are advantages to doing it that way, like avoiding a level of indirection to read the descriptor for the dynamic cases, but I'm not sure how likely that is to ever happen. I'd like to see it, and was hoping that would be the ABI for RISC-V, but it would be a win for any architecture.

But I can change this to a boolean if you'd like.

"choose the TLS dialect to use (`rustc --print tls-dialect` for details)"),
trace_macros: bool = (false, parse_bool, [UNTRACKED],
"for every macro invocation, print its name and arguments (default: no)"),
track_diagnostics: bool = (false, parse_bool, [UNTRACKED],
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use rustc_target::asm::InlineAsmArch;
use rustc_target::spec::{
CodeModel, DebuginfoKind, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
SmallDataThresholdSupport, SplitDebuginfo, StackProtector, SymbolVisibility, Target,
TargetTriple, TlsModel,
TargetTriple, TlsDialect, TlsModel,
};

use crate::code_stats::CodeStats;
Expand Down Expand Up @@ -767,6 +767,10 @@ impl Session {
self.opts.unstable_opts.tls_model.unwrap_or(self.target.tls_model)
}

pub fn tls_dialect(&self) -> TlsDialect {
self.opts.unstable_opts.tls_dialect.unwrap_or(self.target.tls_dialect)
}

pub fn direct_access_external_data(&self) -> Option<bool> {
self.opts
.unstable_opts
Expand Down
34 changes: 34 additions & 0 deletions compiler/rustc_target/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,36 @@ impl ToJson for TlsModel {
}
}

#[derive(Clone, Copy, PartialEq, Hash, Debug)]
pub enum TlsDialect {
Trad, // Use traditional TLS (e.g. through tls_get_addr()).
Desc, // Use TLS descriptors (TLSDESC).
}

impl FromStr for TlsDialect {
type Err = ();

fn from_str(s: &str) -> Result<TlsDialect, ()> {
Ok(match s {
// There are only two variants, but the options spelling has been inconsistent across
// architectures in other compilers.
"trad" | "traditional" | "gnu" => TlsDialect::Trad,
"desc" | "descriptor" | "gnu2" => TlsDialect::Desc,
_ => return Err(()),
})
}
}

impl ToJson for TlsDialect {
fn to_json(&self) -> Json {
match *self {
TlsDialect::Trad => "trad",
TlsDialect::Desc => "desc",
}
.to_json()
}
}

/// Everything is flattened to a single enum to make the json encoding/decoding less annoying.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum LinkOutputKind {
Expand Down Expand Up @@ -2226,6 +2256,9 @@ pub struct TargetOptions {
/// TLS model to use. Options are "global-dynamic" (default), "local-dynamic", "initial-exec"
/// and "local-exec". This is similar to the -ftls-model option in GCC/Clang.
pub tls_model: TlsModel,
/// TLS dialect to use. Options are "trad" (default) for traditional TLS access and "desc"
/// for TLSDESC. This is similar to the -mtls-dialect option in GCC/Clang.
pub tls_dialect: TlsDialect,
/// Do not emit code that uses the "red zone", if the ABI has one. Defaults to false.
pub disable_redzone: bool,
/// Frame pointer mode for this target. Defaults to `MayOmit`.
Expand Down Expand Up @@ -2629,6 +2662,7 @@ impl Default for TargetOptions {
relocation_model: RelocModel::Pic,
code_model: None,
tls_model: TlsModel::GeneralDynamic,
tls_dialect: TlsDialect::Trad,
disable_redzone: false,
frame_pointer: FramePointer::MayOmit,
function_sections: true,
Expand Down
8 changes: 8 additions & 0 deletions tests/assembly/auxiliary/tlsdesc_aux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![crate_type = "lib"]

use std::cell::Cell;

thread_local! {
#[no_mangle]
pub static A: Cell<u64> = const { Cell::new(0) };
}
29 changes: 29 additions & 0 deletions tests/assembly/tlsdesc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Verifies that setting the tls-dialect result sin the correct set of assembly
// instructions.
//
// Note: tls-dialect flags have no changes to LLVM IR, and only affect which
// instruction sequences are emitted by the LLVM backend. Checking the assembly
// output is how we test the lowering in LLVM, and is the only way a frontend
// can determine if its code generation flags are set correctly.
//
//@ revisions: x64 x64-trad x64-desc
//
//@[x64] compile-flags: --target=x86_64-unknown-linux-gnu
//@[x64-trad] compile-flags: --target=x86_64-unknown-linux-gnu -Z tls-dialect=trad
//@[x64-desc] compile-flags: --target=x86_64-unknown-linux-gnu -Z tls-dialect=desc
//
//@ assembly-output: emit-asm
//@ aux-build:tlsdesc_aux.rs

#![crate_type = "lib"]

extern crate tlsdesc_aux as aux;

#[no_mangle]
fn get_aux() -> u64 {
// x64: __tls_get_addr
// x64-trad: __tls_get_addr
// x64-desc: tlsdesc
// x64-desc: tlscall
aux::A.with(|a| a.get())
}
2 changes: 1 addition & 1 deletion tests/ui/invalid-compile-flags/print.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: unknown print request: `yyyy`
|
= help: valid print requests are: `all-target-specs-json`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `deployment-target`, `file-names`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `tls-models`
= help: valid print requests are: `all-target-specs-json`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `deployment-target`, `file-names`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `tls-dialect`, `tls-models`

Loading