Skip to content

Commit f517527

Browse files
committed
Auto merge of #54951 - alexcrichton:more-wasm-threads, r=sfackler
std: Implement TLS for wasm32-unknown-unknown This adds an implementation of thread local storage for the `wasm32-unknown-unknown` target when the `atomics` feature is implemented. This, however, comes with a notable caveat of that it requires a new feature of the standard library, `wasm-bindgen-threads`, to be enabled. Thread local storage for wasm (when `atomics` are enabled and there's actually more than one thread) is powered by the assumption that an external entity can fill in some information for us. It's not currently clear who will fill in this information nor whose responsibility it should be long-term. In the meantime there's a strategy being gamed out in the `wasm-bindgen` project specifically, and the hope is that we can continue to test and iterate on the standard library without committing to a particular strategy yet. As to the details of `wasm-bindgen`'s strategy, LLVM doesn't currently have the ability to emit custom `global` values (thread locals in a `WebAssembly.Module`) so we leverage the `wasm-bindgen` CLI tool to do it for us. To that end we have a few intrinsics, assuming two global values: * `__wbindgen_current_id` - gets the current thread id as a 32-bit integer. It's `wasm-bindgen`'s responsibility to initialize this per-thread and then inform libstd of the id. Currently `wasm-bindgen` performs this initialization as part of the `start` function. * `__wbindgen_tcb_{get,set}` - in addition to a thread id it's assumed that there's a global available for simply storing a pointer's worth of information (a thread control block, which currently only contains thread local storage). This would ideally be a native `global` injected by LLVM, but we don't have a great way to support that right now. To reiterate, this is all intended to be unstable and purely intended for testing out Rust on the web with threads. The story is very likely to change in the future and we want to make sure that we're able to do that!
2 parents 24faa97 + cbe9f33 commit f517527

File tree

6 files changed

+122
-25
lines changed

6 files changed

+122
-25
lines changed

Diff for: src/libstd/Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,13 @@ jemalloc = ["alloc_jemalloc"]
4848
force_alloc_system = []
4949
panic-unwind = ["panic_unwind"]
5050
profiler = ["profiler_builtins"]
51+
52+
# An off-by-default feature which enables a linux-syscall-like ABI for libstd to
53+
# interoperate with the host environment. Currently not well documented and
54+
# requires rebuilding the standard library to use it.
5155
wasm_syscall = []
56+
57+
# An off-by-default features to enable libstd to assume that wasm-bindgen is in
58+
# the environment for hooking up some thread-related information like the
59+
# current thread id and accessing/getting the current thread's TCB
60+
wasm-bindgen-threads = []

Diff for: src/libstd/sys/wasm/mutex_atomics.rs

+10-13
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
use arch::wasm32::atomic;
1212
use cell::UnsafeCell;
1313
use mem;
14-
use sync::atomic::{AtomicUsize, AtomicU64, Ordering::SeqCst};
14+
use sync::atomic::{AtomicUsize, AtomicU32, Ordering::SeqCst};
15+
use sys::thread;
1516

1617
pub struct Mutex {
1718
locked: AtomicUsize,
@@ -70,7 +71,7 @@ impl Mutex {
7071
}
7172

7273
pub struct ReentrantMutex {
73-
owner: AtomicU64,
74+
owner: AtomicU32,
7475
recursions: UnsafeCell<u32>,
7576
}
7677

@@ -91,7 +92,7 @@ unsafe impl Sync for ReentrantMutex {}
9192
impl ReentrantMutex {
9293
pub unsafe fn uninitialized() -> ReentrantMutex {
9394
ReentrantMutex {
94-
owner: AtomicU64::new(0),
95+
owner: AtomicU32::new(0),
9596
recursions: UnsafeCell::new(0),
9697
}
9798
}
@@ -101,20 +102,20 @@ impl ReentrantMutex {
101102
}
102103

103104
pub unsafe fn lock(&self) {
104-
let me = thread_id();
105+
let me = thread::my_id();
105106
while let Err(owner) = self._try_lock(me) {
106-
let val = atomic::wait_i64(self.ptr(), owner as i64, -1);
107+
let val = atomic::wait_i32(self.ptr(), owner as i32, -1);
107108
debug_assert!(val == 0 || val == 1);
108109
}
109110
}
110111

111112
#[inline]
112113
pub unsafe fn try_lock(&self) -> bool {
113-
self._try_lock(thread_id()).is_ok()
114+
self._try_lock(thread::my_id()).is_ok()
114115
}
115116

116117
#[inline]
117-
unsafe fn _try_lock(&self, id: u64) -> Result<(), u64> {
118+
unsafe fn _try_lock(&self, id: u32) -> Result<(), u32> {
118119
let id = id.checked_add(1).unwrap(); // make sure `id` isn't 0
119120
match self.owner.compare_exchange(0, id, SeqCst, SeqCst) {
120121
// we transitioned from unlocked to locked
@@ -153,11 +154,7 @@ impl ReentrantMutex {
153154
}
154155

155156
#[inline]
156-
fn ptr(&self) -> *mut i64 {
157-
&self.owner as *const AtomicU64 as *mut i64
157+
fn ptr(&self) -> *mut i32 {
158+
&self.owner as *const AtomicU32 as *mut i32
158159
}
159160
}
160-
161-
fn thread_id() -> u64 {
162-
panic!("thread ids not implemented on wasm with atomics yet")
163-
}

Diff for: src/libstd/sys/wasm/thread.rs

+46
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,49 @@ pub mod guard {
6969
pub unsafe fn init() -> Option<Guard> { None }
7070
pub unsafe fn deinit() {}
7171
}
72+
73+
cfg_if! {
74+
if #[cfg(all(target_feature = "atomics", feature = "wasm-bindgen-threads"))] {
75+
#[link(wasm_import_module = "__wbindgen_thread_xform__")]
76+
extern {
77+
fn __wbindgen_current_id() -> u32;
78+
fn __wbindgen_tcb_get() -> u32;
79+
fn __wbindgen_tcb_set(ptr: u32);
80+
}
81+
pub fn my_id() -> u32 {
82+
unsafe { __wbindgen_current_id() }
83+
}
84+
85+
// These are currently only ever used in `thread_local_atomics.rs`, if
86+
// you'd like to use them be sure to update that and make sure everyone
87+
// agrees what's what.
88+
pub fn tcb_get() -> *mut u8 {
89+
use mem;
90+
assert_eq!(mem::size_of::<*mut u8>(), mem::size_of::<u32>());
91+
unsafe { __wbindgen_tcb_get() as *mut u8 }
92+
}
93+
94+
pub fn tcb_set(ptr: *mut u8) {
95+
unsafe { __wbindgen_tcb_set(ptr as u32); }
96+
}
97+
98+
// FIXME: still need something for hooking exiting a thread to free
99+
// data...
100+
101+
} else if #[cfg(target_feature = "atomics")] {
102+
pub fn my_id() -> u32 {
103+
panic!("thread ids not implemented on wasm with atomics yet")
104+
}
105+
106+
pub fn tcb_get() -> *mut u8 {
107+
panic!("thread local data not implemented on wasm with atomics yet")
108+
}
109+
110+
pub fn tcb_set(ptr: *mut u8) {
111+
panic!("thread local data not implemented on wasm with atomics yet")
112+
}
113+
} else {
114+
// stubbed out because no functions actually access these intrinsics
115+
// unless atomics are enabled
116+
}
117+
}

Diff for: src/libstd/sys/wasm/thread_local_atomics.rs

+46-7
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,61 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
use sys::thread;
12+
use sync::atomic::{AtomicUsize, Ordering::SeqCst};
13+
14+
const MAX_KEYS: usize = 128;
15+
static NEXT_KEY: AtomicUsize = AtomicUsize::new(0);
16+
17+
struct ThreadControlBlock {
18+
keys: [*mut u8; MAX_KEYS],
19+
}
20+
21+
impl ThreadControlBlock {
22+
fn new() -> ThreadControlBlock {
23+
ThreadControlBlock {
24+
keys: [0 as *mut u8; MAX_KEYS],
25+
}
26+
}
27+
28+
fn get() -> *mut ThreadControlBlock {
29+
let ptr = thread::tcb_get();
30+
if !ptr.is_null() {
31+
return ptr as *mut ThreadControlBlock
32+
}
33+
let tcb = Box::into_raw(Box::new(ThreadControlBlock::new()));
34+
thread::tcb_set(tcb as *mut u8);
35+
tcb
36+
}
37+
}
38+
1139
pub type Key = usize;
1240

13-
pub unsafe fn create(_dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
14-
panic!("TLS on wasm with atomics not implemented yet");
41+
pub unsafe fn create(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
42+
drop(dtor); // FIXME: need to figure out how to hook thread exit to run this
43+
let key = NEXT_KEY.fetch_add(1, SeqCst);
44+
if key >= MAX_KEYS {
45+
NEXT_KEY.store(MAX_KEYS, SeqCst);
46+
panic!("cannot allocate space for more TLS keys");
47+
}
48+
// offset by 1 so we never hand out 0. This is currently required by
49+
// `sys_common/thread_local.rs` where it can't cope with keys of value 0
50+
// because it messes up the atomic management.
51+
return key + 1
1552
}
1653

17-
pub unsafe fn set(_key: Key, _value: *mut u8) {
18-
panic!("TLS on wasm with atomics not implemented yet");
54+
pub unsafe fn set(key: Key, value: *mut u8) {
55+
(*ThreadControlBlock::get()).keys[key - 1] = value;
1956
}
2057

21-
pub unsafe fn get(_key: Key) -> *mut u8 {
22-
panic!("TLS on wasm with atomics not implemented yet");
58+
pub unsafe fn get(key: Key) -> *mut u8 {
59+
(*ThreadControlBlock::get()).keys[key - 1]
2360
}
2461

2562
pub unsafe fn destroy(_key: Key) {
26-
panic!("TLS on wasm with atomics not implemented yet");
63+
// FIXME: should implement this somehow, this isn't typically called but it
64+
// can be called if two threads race to initialize a TLS slot and one ends
65+
// up not being needed.
2766
}
2867

2968
#[inline]

Diff for: src/libstd/thread/local.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -172,16 +172,22 @@ macro_rules! __thread_local_inner {
172172
&'static $crate::cell::UnsafeCell<
173173
$crate::option::Option<$t>>>
174174
{
175-
#[cfg(target_arch = "wasm32")]
175+
#[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))]
176176
static __KEY: $crate::thread::__StaticLocalKeyInner<$t> =
177177
$crate::thread::__StaticLocalKeyInner::new();
178178

179179
#[thread_local]
180-
#[cfg(all(target_thread_local, not(target_arch = "wasm32")))]
180+
#[cfg(all(
181+
target_thread_local,
182+
not(all(target_arch = "wasm32", not(target_feature = "atomics"))),
183+
))]
181184
static __KEY: $crate::thread::__FastLocalKeyInner<$t> =
182185
$crate::thread::__FastLocalKeyInner::new();
183186

184-
#[cfg(all(not(target_thread_local), not(target_arch = "wasm32")))]
187+
#[cfg(all(
188+
not(target_thread_local),
189+
not(all(target_arch = "wasm32", not(target_feature = "atomics"))),
190+
))]
185191
static __KEY: $crate::thread::__OsLocalKeyInner<$t> =
186192
$crate::thread::__OsLocalKeyInner::new();
187193

@@ -302,7 +308,7 @@ impl<T: 'static> LocalKey<T> {
302308
/// On some platforms like wasm32 there's no threads, so no need to generate
303309
/// thread locals and we can instead just use plain statics!
304310
#[doc(hidden)]
305-
#[cfg(target_arch = "wasm32")]
311+
#[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))]
306312
pub mod statik {
307313
use cell::UnsafeCell;
308314
use fmt;

Diff for: src/libstd/thread/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ pub use self::local::{LocalKey, AccessError};
203203
// where available, but both are needed.
204204

205205
#[unstable(feature = "libstd_thread_internals", issue = "0")]
206-
#[cfg(target_arch = "wasm32")]
206+
#[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))]
207207
#[doc(hidden)] pub use self::local::statik::Key as __StaticLocalKeyInner;
208208
#[unstable(feature = "libstd_thread_internals", issue = "0")]
209209
#[cfg(target_thread_local)]

0 commit comments

Comments
 (0)