Auto merge of #117873 - quininer:android-emutls, r=Amanieu

Add emulated TLS support

This is a reopen of https://github.com/rust-lang/rust/pull/96317 . many android devices still only use 128 pthread keys, so using emutls can be helpful.

Currently LLVM uses emutls by default for some targets (such as android, openbsd), but rust does not use it, because `has_thread_local` is false.

This commit has some changes to allow users to enable emutls:

1. add `-Zhas-thread-local` flag to specify that std uses `#[thread_local]` instead of pthread key.
2. when using emutls, decorate symbol names to find thread local symbol correctly.
3. change `-Zforce-emulated-tls` to `-Ztls-model=emulated` to explicitly specify whether to generate emutls.

r? `@Amanieu`
This commit is contained in:
bors 2023-12-09 05:32:35 +00:00
commit 608f32435a
17 changed files with 71 additions and 28 deletions

View File

@ -569,5 +569,6 @@ fn to_gcc_tls_mode(tls_model: TlsModel) -> gccjit::TlsModel {
TlsModel::LocalDynamic => gccjit::TlsModel::LocalDynamic,
TlsModel::InitialExec => gccjit::TlsModel::InitialExec,
TlsModel::LocalExec => gccjit::TlsModel::LocalExec,
TlsModel::Emulated => gccjit::TlsModel::GlobalDynamic,
}
}

View File

@ -39,7 +39,7 @@ impl OwnedTargetMachine {
split_dwarf_file: &CStr,
output_obj_file: &CStr,
debug_info_compression: &CStr,
force_emulated_tls: bool,
use_emulated_tls: bool,
args_cstr_buff: &[u8],
) -> Result<Self, LlvmError<'static>> {
assert!(args_cstr_buff.len() > 0);
@ -71,7 +71,7 @@ impl OwnedTargetMachine {
split_dwarf_file.as_ptr(),
output_obj_file.as_ptr(),
debug_info_compression.as_ptr(),
force_emulated_tls,
use_emulated_tls,
args_cstr_buff.as_ptr() as *const c_char,
args_cstr_buff.len(),
)

View File

@ -33,7 +33,7 @@ use rustc_session::config::{self, Lto, OutputType, Passes, SplitDwarfKind, Switc
use rustc_session::Session;
use rustc_span::symbol::sym;
use rustc_span::InnerSpan;
use rustc_target::spec::{CodeModel, RelocModel, SanitizerSet, SplitDebuginfo};
use rustc_target::spec::{CodeModel, RelocModel, SanitizerSet, SplitDebuginfo, TlsModel};
use crate::llvm::diagnostic::OptimizationDiagnosticKind;
use libc::{c_char, c_int, c_uint, c_void, size_t};
@ -223,7 +223,7 @@ pub fn target_machine_factory(
let path_mapping = sess.source_map().path_mapping().clone();
let force_emulated_tls = sess.target.force_emulated_tls;
let use_emulated_tls = matches!(sess.tls_model(), TlsModel::Emulated);
// copy the exe path, followed by path all into one buffer
// null terminating them so we can use them as null terminated strings
@ -297,7 +297,7 @@ pub fn target_machine_factory(
&split_dwarf_file,
&output_obj_file,
&debuginfo_compression,
force_emulated_tls,
use_emulated_tls,
&args_cstr_buff,
)
})

View File

@ -120,6 +120,7 @@ fn to_llvm_tls_model(tls_model: TlsModel) -> llvm::ThreadLocalMode {
TlsModel::LocalDynamic => llvm::ThreadLocalMode::LocalDynamic,
TlsModel::InitialExec => llvm::ThreadLocalMode::InitialExec,
TlsModel::LocalExec => llvm::ThreadLocalMode::LocalExec,
TlsModel::Emulated => llvm::ThreadLocalMode::GeneralDynamic,
}
}

View File

@ -306,7 +306,9 @@ impl CodegenBackend for LlvmCodegenBackend {
}
PrintKind::TlsModels => {
writeln!(out, "Available TLS models:");
for name in &["global-dynamic", "local-dynamic", "initial-exec", "local-exec"] {
for name in
&["global-dynamic", "local-dynamic", "initial-exec", "local-exec", "emulated"]
{
writeln!(out, " {name}");
}
writeln!(out);

View File

@ -2159,7 +2159,7 @@ extern "C" {
SplitDwarfFile: *const c_char,
OutputObjFile: *const c_char,
DebugInfoCompression: *const c_char,
ForceEmulatedTls: bool,
UseEmulatedTls: bool,
ArgsCstrBuff: *const c_char,
ArgsCstrBuffLen: usize,
) -> *mut TargetMachine;

View File

@ -1748,7 +1748,9 @@ fn exported_symbols_for_non_proc_macro(tcx: TyCtxt<'_>, crate_type: CrateType) -
let export_threshold = symbol_export::crates_export_threshold(&[crate_type]);
for_each_exported_symbols_include_dep(tcx, crate_type, |symbol, info, cnum| {
if info.level.is_below_threshold(export_threshold) {
symbols.push(symbol_export::symbol_name_for_instance_in_crate(tcx, symbol, cnum));
symbols.push(symbol_export::exporting_symbol_name_for_instance_in_crate(
tcx, symbol, cnum,
));
}
});

View File

@ -16,7 +16,7 @@ use rustc_middle::ty::{self, SymbolName, TyCtxt};
use rustc_middle::ty::{GenericArgKind, GenericArgsRef};
use rustc_middle::util::Providers;
use rustc_session::config::{CrateType, OomStrategy};
use rustc_target::spec::SanitizerSet;
use rustc_target::spec::{SanitizerSet, TlsModel};
pub fn threshold(tcx: TyCtxt<'_>) -> SymbolExportLevel {
crates_export_threshold(tcx.crate_types())
@ -564,6 +564,12 @@ pub fn linking_symbol_name_for_instance_in_crate<'tcx>(
let mut undecorated = symbol_name_for_instance_in_crate(tcx, symbol, instantiating_crate);
// thread local will not be a function call,
// so it is safe to return before windows symbol decoration check.
if let Some(name) = maybe_emutls_symbol_name(tcx, symbol, &undecorated) {
return name;
}
let target = &tcx.sess.target;
if !target.is_like_windows {
// Mach-O has a global "_" suffix and `object` crate will handle it.
@ -624,6 +630,32 @@ pub fn linking_symbol_name_for_instance_in_crate<'tcx>(
format!("{prefix}{undecorated}{suffix}{args_in_bytes}")
}
pub fn exporting_symbol_name_for_instance_in_crate<'tcx>(
tcx: TyCtxt<'tcx>,
symbol: ExportedSymbol<'tcx>,
cnum: CrateNum,
) -> String {
let undecorated = symbol_name_for_instance_in_crate(tcx, symbol, cnum);
maybe_emutls_symbol_name(tcx, symbol, &undecorated).unwrap_or(undecorated)
}
fn maybe_emutls_symbol_name<'tcx>(
tcx: TyCtxt<'tcx>,
symbol: ExportedSymbol<'tcx>,
undecorated: &str,
) -> Option<String> {
if matches!(tcx.sess.tls_model(), TlsModel::Emulated)
&& let ExportedSymbol::NonGeneric(def_id) = symbol
&& tcx.is_thread_local_static(def_id)
{
// When using emutls, LLVM will add the `__emutls_v.` prefix to thread local symbols,
// and exported symbol name need to match this.
Some(format!("__emutls_v.{undecorated}"))
} else {
None
}
}
fn wasm_import_module_map(tcx: TyCtxt<'_>, cnum: CrateNum) -> FxHashMap<DefId, String> {
// Build up a map from DefId to a `NativeLib` structure, where
// `NativeLib` internally contains information about

View File

@ -410,7 +410,7 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine(
const char *SplitDwarfFile,
const char *OutputObjFile,
const char *DebugInfoCompression,
bool ForceEmulatedTls,
bool UseEmulatedTls,
const char *ArgsCstrBuff, size_t ArgsCstrBuffLen) {
auto OptLevel = fromRust(RustOptLevel);
@ -456,13 +456,9 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine(
Options.UseInitArray = UseInitArray;
#if LLVM_VERSION_LT(17, 0)
if (ForceEmulatedTls) {
Options.ExplicitEmulatedTLS = true;
Options.EmulatedTLS = true;
}
#else
Options.EmulatedTLS = ForceEmulatedTls || Trip.hasDefaultEmulatedTLS();
Options.ExplicitEmulatedTLS = true;
#endif
Options.EmulatedTLS = UseEmulatedTls;
if (TrapUnreachable) {
// Tell LLVM to codegen `unreachable` into an explicit trap instruction.

View File

@ -1283,7 +1283,7 @@ fn default_configuration(sess: &Session) -> Cfg {
ret.insert((sym::relocation_model, Some(relocation_model)));
}
ret.insert((sym::target_vendor, Some(Symbol::intern(vendor))));
if sess.target.has_thread_local {
if sess.opts.unstable_opts.has_thread_local.unwrap_or(sess.target.has_thread_local) {
ret.insert((sym::target_thread_local, None));
}
let mut has_atomic = false;

View File

@ -1624,6 +1624,8 @@ options! {
graphviz_font: String = ("Courier, monospace".to_string(), parse_string, [UNTRACKED],
"use the given `fontname` in graphviz output; can be overridden by setting \
environment variable `RUSTC_GRAPHVIZ_FONT` (default: `Courier, monospace`)"),
has_thread_local: Option<bool> = (None, parse_opt_bool, [TRACKED],
"explicitly enable the `cfg(target_thread_local)` directive"),
hir_stats: bool = (false, parse_bool, [UNTRACKED],
"print some statistics about AST and HIR (default: no)"),
human_readable_cgu_names: bool = (false, parse_bool, [TRACKED],

View File

@ -1,10 +1,11 @@
use crate::spec::{base, SanitizerSet, TargetOptions};
use crate::spec::{base, SanitizerSet, TargetOptions, TlsModel};
pub fn opts() -> TargetOptions {
let mut base = base::linux::opts();
base.os = "android".into();
base.is_like_android = true;
base.default_dwarf_version = 2;
base.tls_model = TlsModel::Emulated;
base.has_thread_local = false;
base.supported_sanitizers = SanitizerSet::ADDRESS;
// This is for backward compatibility, see https://github.com/rust-lang/rust/issues/49867

View File

@ -1,11 +1,11 @@
use crate::spec::{base, TargetOptions};
use crate::spec::{base, TargetOptions, TlsModel};
pub fn opts() -> TargetOptions {
let mut base = base::linux::opts();
base.env = "ohos".into();
base.crt_static_default = false;
base.force_emulated_tls = true;
base.tls_model = TlsModel::Emulated;
base.has_thread_local = false;
base

View File

@ -1,4 +1,4 @@
use crate::spec::{cvs, FramePointer, RelroLevel, TargetOptions};
use crate::spec::{cvs, FramePointer, RelroLevel, TargetOptions, TlsModel};
pub fn opts() -> TargetOptions {
TargetOptions {
@ -11,6 +11,7 @@ pub fn opts() -> TargetOptions {
frame_pointer: FramePointer::Always, // FIXME 43575: should be MayOmit...
relro_level: RelroLevel::Full,
default_dwarf_version: 2,
tls_model: TlsModel::Emulated,
..Default::default()
}
}

View File

@ -929,6 +929,7 @@ pub enum TlsModel {
LocalDynamic,
InitialExec,
LocalExec,
Emulated,
}
impl FromStr for TlsModel {
@ -942,6 +943,7 @@ impl FromStr for TlsModel {
"local-dynamic" => TlsModel::LocalDynamic,
"initial-exec" => TlsModel::InitialExec,
"local-exec" => TlsModel::LocalExec,
"emulated" => TlsModel::Emulated,
_ => return Err(()),
})
}
@ -954,6 +956,7 @@ impl ToJson for TlsModel {
TlsModel::LocalDynamic => "local-dynamic",
TlsModel::InitialExec => "initial-exec",
TlsModel::LocalExec => "local-exec",
TlsModel::Emulated => "emulated",
}
.to_json()
}
@ -2191,9 +2194,6 @@ pub struct TargetOptions {
/// Whether the target supports XRay instrumentation.
pub supports_xray: bool,
/// Forces the use of emulated TLS (__emutls_get_address)
pub force_emulated_tls: bool,
}
/// Add arguments for the given flavor and also for its "twin" flavors
@ -2409,7 +2409,6 @@ impl Default for TargetOptions {
entry_name: "main".into(),
entry_abi: Conv::C,
supports_xray: false,
force_emulated_tls: false,
}
}
}
@ -3113,7 +3112,6 @@ impl Target {
key!(entry_name);
key!(entry_abi, Conv)?;
key!(supports_xray, bool);
key!(force_emulated_tls, bool);
if base.is_builtin {
// This can cause unfortunate ICEs later down the line.
@ -3369,7 +3367,6 @@ impl ToJson for Target {
target_option_val!(entry_name);
target_option_val!(entry_abi);
target_option_val!(supports_xray);
target_option_val!(force_emulated_tls);
if let Some(abi) = self.default_adjusted_cabi {
d.insert("default-adjusted-cabi".into(), Abi::name(abi).to_json());

View File

@ -12,7 +12,13 @@
// compiling from a newer linux to an older linux, so we also have a
// fallback implementation to use as well.
#[allow(unexpected_cfgs)]
#[cfg(any(target_os = "linux", target_os = "fuchsia", target_os = "redox", target_os = "hurd"))]
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "fuchsia",
target_os = "redox",
target_os = "hurd"
))]
// FIXME: The Rust compiler currently omits weakly function definitions (i.e.,
// __cxa_thread_atexit_impl) and its metadata from LLVM IR.
#[no_sanitize(cfi, kcfi)]

View File

@ -20,6 +20,8 @@ loaded at program startup.
The TLS data must not be in a library loaded after startup (via `dlopen`).
- `local-exec` - model usable only if the TLS data is defined directly in the executable,
but not in a shared library, and is accessed only from that executable.
- `emulated` - Uses thread-specific data keys to implement emulated TLS.
It is like using a general-dynamic TLS model for all modes.
`rustc` and LLVM may use a more optimized model than specified if they know that we are producing
an executable rather than a library, or that the `static` item is private enough.