Skip to content

Commit dc50a63

Browse files
committed
std: Use native #[thread_local] TLS on wasm
This commit moves `thread_local!` on WebAssembly targets to using the `#[thread_local]` attribute in LLVM. This was recently implemented upstream and is [in the process of being documented][dox]. This change only takes affect if modules are compiled with `+atomics` which is currently unstable and a pretty esoteric method of compiling wasm artifacts. This "new power" of the wasm toolchain means that the old `wasm-bindgen-threads` feature of the standard library can be removed since it should now be possible to create a fully functioning threaded wasm module without intrusively dealing with libstd symbols or intrinsics. Yay! [dox]: WebAssembly/tool-conventions#116
1 parent a120caf commit dc50a63

File tree

10 files changed

+72
-135
lines changed

10 files changed

+72
-135
lines changed

src/librustc_codegen_ssa/back/linker.rs

+7
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,9 @@ impl<'a> WasmLd<'a> {
904904
// linker will synthesize this function, and so we need to make sure
905905
// that our usage of `--export` below won't accidentally cause this
906906
// function to get deleted.
907+
//
908+
// * `--export=*tls*` - when `#[thread_local]` symbols are used these
909+
// symbols are how the TLS segments are initialized and configured.
907910
let atomics = sess.opts.cg.target_feature.contains("+atomics") ||
908911
sess.target.target.options.features.contains("+atomics");
909912
if atomics {
@@ -912,6 +915,10 @@ impl<'a> WasmLd<'a> {
912915
cmd.arg("--import-memory");
913916
cmd.arg("--passive-segments");
914917
cmd.arg("--export=__wasm_init_memory");
918+
cmd.arg("--export=__wasm_init_tls");
919+
cmd.arg("--export=__tls_size");
920+
cmd.arg("--export=__tls_align");
921+
cmd.arg("--export=__tls_base");
915922
}
916923
WasmLd { cmd, sess, info }
917924
}

src/librustc_target/spec/wasm32_base.rs

+8
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ pub fn options() -> TargetOptions {
132132
// non-relative calls and such later on).
133133
relocation_model: "static".to_string(),
134134

135+
// When the atomics feature is activated then these two keys matter,
136+
// otherwise they're basically ignored by the standard library. In this
137+
// mode, however, the `#[thread_local]` attribute works (i.e.
138+
// `has_elf_tls`) and we need to get it to work by specifying
139+
// `local-exec` as that's all that's implemented in LLVM today for wasm.
140+
has_elf_tls: true,
141+
tls_model: "local-exec".to_string(),
142+
135143
.. Default::default()
136144
}
137145
}

src/libstd/Cargo.toml

-5
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,6 @@ panic_immediate_abort = ["core/panic_immediate_abort"]
7575
# requires rebuilding the standard library to use it.
7676
wasm_syscall = []
7777

78-
# An off-by-default features to enable libstd to assume that wasm-bindgen is in
79-
# the environment for hooking up some thread-related information like the
80-
# current thread id and accessing/getting the current thread's TCB
81-
wasm-bindgen-threads = []
82-
8378
# Enable std_detect default features for stdarch/crates/std_detect:
8479
# https://github.com/rust-lang/stdarch/blob/master/crates/std_detect/Cargo.toml
8580
std_detect_file_io = []

src/libstd/sys/wasi/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ pub mod stdio;
4747
pub mod thread;
4848
#[path = "../wasm/thread_local.rs"]
4949
pub mod thread_local;
50+
#[path = "../wasm/fast_thread_local.rs"]
51+
pub mod fast_thread_local;
5052
pub mod time;
5153
pub mod ext;
5254

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![unstable(feature = "thread_local_internals", issue = "0")]
2+
3+
pub unsafe fn register_dtor(_t: *mut u8, _dtor: unsafe extern fn(*mut u8)) {
4+
// FIXME: right now there is no concept of "thread exit", but this is likely
5+
// going to show up at some point in the form of an exported symbol that the
6+
// wasm runtime is oging to be expected to call. For now we basically just
7+
// ignore the arguments, but if such a function starts to exist it will
8+
// likely look like the OSX implementation in `unix/fast_thread_local.rs`
9+
}

src/libstd/sys/wasm/mod.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ pub mod stack_overflow;
3737
pub mod thread;
3838
pub mod time;
3939
pub mod stdio;
40+
pub mod thread_local;
41+
pub mod fast_thread_local;
4042

4143
pub use crate::sys_common::os_str_bytes as os_str;
4244

@@ -48,13 +50,10 @@ cfg_if::cfg_if! {
4850
pub mod mutex;
4951
#[path = "rwlock_atomics.rs"]
5052
pub mod rwlock;
51-
#[path = "thread_local_atomics.rs"]
52-
pub mod thread_local;
5353
} else {
5454
pub mod condvar;
5555
pub mod mutex;
5656
pub mod rwlock;
57-
pub mod thread_local;
5857
}
5958
}
6059

src/libstd/sys/wasm/thread.rs

+34-42
Original file line numberDiff line numberDiff line change
@@ -59,48 +59,40 @@ pub mod guard {
5959
pub unsafe fn init() -> Option<Guard> { None }
6060
}
6161

62-
cfg_if::cfg_if! {
63-
if #[cfg(all(target_feature = "atomics", feature = "wasm-bindgen-threads"))] {
64-
#[link(wasm_import_module = "__wbindgen_thread_xform__")]
65-
extern {
66-
fn __wbindgen_current_id() -> u32;
67-
fn __wbindgen_tcb_get() -> u32;
68-
fn __wbindgen_tcb_set(ptr: u32);
62+
// This is only used by atomics primitives when the `atomics` feature is
63+
// enabled. In that mode we currently just use our own thread-local to store our
64+
// current thread's ID, and then we lazily initialize it to something allocated
65+
// from a global counter.
66+
#[cfg(target_feature = "atomics")]
67+
pub fn my_id() -> u32 {
68+
use crate::sync::atomic::{AtomicU32, Ordering::SeqCst};
69+
70+
static NEXT_ID: AtomicU32 = AtomicU32::new(0);
71+
72+
#[thread_local]
73+
static mut MY_ID: u32 = 0;
74+
75+
unsafe {
76+
// If our thread ID isn't set yet then we need to allocate one. Do so
77+
// with with a simple "atomically add to a global counter" strategy.
78+
// This strategy doesn't handled what happens when the counter
79+
// overflows, however, so just abort everything once the counter
80+
// overflows and eventually we could have some sort of recycling scheme
81+
// (or maybe this is all totally irrelevant by that point!). In any case
82+
// though we're using a CAS loop instead of a `fetch_add` to ensure that
83+
// the global counter never overflows.
84+
if MY_ID == 0 {
85+
let mut cur = NEXT_ID.load(SeqCst);
86+
MY_ID = loop {
87+
let next = cur.checked_add(1).unwrap_or_else(|| {
88+
crate::arch::wasm32::unreachable()
89+
});
90+
match NEXT_ID.compare_exchange(cur, next, SeqCst, SeqCst) {
91+
Ok(_) => break next,
92+
Err(i) => cur = i,
93+
}
94+
};
6995
}
70-
pub fn my_id() -> u32 {
71-
unsafe { __wbindgen_current_id() }
72-
}
73-
74-
// These are currently only ever used in `thread_local_atomics.rs`, if
75-
// you'd like to use them be sure to update that and make sure everyone
76-
// agrees what's what.
77-
pub fn tcb_get() -> *mut u8 {
78-
use crate::mem;
79-
assert_eq!(mem::size_of::<*mut u8>(), mem::size_of::<u32>());
80-
unsafe { __wbindgen_tcb_get() as *mut u8 }
81-
}
82-
83-
pub fn tcb_set(ptr: *mut u8) {
84-
unsafe { __wbindgen_tcb_set(ptr as u32); }
85-
}
86-
87-
// FIXME: still need something for hooking exiting a thread to free
88-
// data...
89-
90-
} else if #[cfg(target_feature = "atomics")] {
91-
pub fn my_id() -> u32 {
92-
panic!("thread ids not implemented on wasm with atomics yet")
93-
}
94-
95-
pub fn tcb_get() -> *mut u8 {
96-
panic!("thread local data not implemented on wasm with atomics yet")
97-
}
98-
99-
pub fn tcb_set(_ptr: *mut u8) {
100-
panic!("thread local data not implemented on wasm with atomics yet")
101-
}
102-
} else {
103-
// stubbed out because no functions actually access these intrinsics
104-
// unless atomics are enabled
96+
MY_ID
10597
}
10698
}

src/libstd/sys/wasm/thread_local.rs

+9-23
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,26 @@
1-
use crate::boxed::Box;
2-
use crate::ptr;
3-
41
pub type Key = usize;
52

6-
struct Allocated {
7-
value: *mut u8,
8-
dtor: Option<unsafe extern fn(*mut u8)>,
9-
}
10-
113
#[inline]
12-
pub unsafe fn create(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
13-
Box::into_raw(Box::new(Allocated {
14-
value: ptr::null_mut(),
15-
dtor,
16-
})) as usize
4+
pub unsafe fn create(_dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
5+
panic!("should not be used on the wasm target");
176
}
187

198
#[inline]
20-
pub unsafe fn set(key: Key, value: *mut u8) {
21-
(*(key as *mut Allocated)).value = value;
9+
pub unsafe fn set(_key: Key, _value: *mut u8) {
10+
panic!("should not be used on the wasm target");
2211
}
2312

2413
#[inline]
25-
pub unsafe fn get(key: Key) -> *mut u8 {
26-
(*(key as *mut Allocated)).value
14+
pub unsafe fn get(_key: Key) -> *mut u8 {
15+
panic!("should not be used on the wasm target");
2716
}
2817

2918
#[inline]
30-
pub unsafe fn destroy(key: Key) {
31-
let key = Box::from_raw(key as *mut Allocated);
32-
if let Some(f) = key.dtor {
33-
f(key.value);
34-
}
19+
pub unsafe fn destroy(_key: Key) {
20+
panic!("should not be used on the wasm target");
3521
}
3622

3723
#[inline]
3824
pub fn requires_synchronized_create() -> bool {
39-
false
25+
panic!("should not be used on the wasm target");
4026
}

src/libstd/sys/wasm/thread_local_atomics.rs

-61
This file was deleted.

src/llvm-project

Submodule llvm-project updated 1288 files

0 commit comments

Comments
 (0)