]> git.lizzy.rs Git - rust.git/commitdiff
std: Implement TLS for wasm32-unknown-unknown
authorAlex Crichton <alex@alexcrichton.com>
Wed, 10 Oct 2018 06:10:25 +0000 (23:10 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Thu, 11 Oct 2018 16:57:55 +0000 (09:57 -0700)
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!

src/libstd/Cargo.toml
src/libstd/sys/wasm/mutex_atomics.rs
src/libstd/sys/wasm/thread.rs
src/libstd/sys/wasm/thread_local_atomics.rs
src/libstd/thread/local.rs
src/libstd/thread/mod.rs

index bcdd1b4b088029fea45a223e733b9a27b60da69f..cd1e3438fc372f1d6726344e3510bbdb1ede821f 100644 (file)
@@ -48,4 +48,13 @@ jemalloc = ["alloc_jemalloc"]
 force_alloc_system = []
 panic-unwind = ["panic_unwind"]
 profiler = ["profiler_builtins"]
+
+# An off-by-default feature which enables a linux-syscall-like ABI for libstd to
+# interoperate with the host environment. Currently not well documented and
+# requires rebuilding the standard library to use it.
 wasm_syscall = []
+
+# An off-by-default features to enable libstd to assume that wasm-bindgen is in
+# the environment for hooking up some thread-related information like the
+# current thread id and accessing/getting the current thread's TCB
+wasm-bindgen-threads = []
index ced6c17ef9605aba311f71bb9825202558c3a1f7..762e807096fde5f2a310b06568f22e4361e8b6d0 100644 (file)
@@ -11,7 +11,8 @@
 use arch::wasm32::atomic;
 use cell::UnsafeCell;
 use mem;
-use sync::atomic::{AtomicUsize, AtomicU64, Ordering::SeqCst};
+use sync::atomic::{AtomicUsize, AtomicU32, Ordering::SeqCst};
+use sys::thread;
 
 pub struct Mutex {
     locked: AtomicUsize,
@@ -70,7 +71,7 @@ fn ptr(&self) -> *mut i32 {
 }
 
 pub struct ReentrantMutex {
-    owner: AtomicU64,
+    owner: AtomicU32,
     recursions: UnsafeCell<u32>,
 }
 
@@ -91,7 +92,7 @@ unsafe impl Sync for ReentrantMutex {}
 impl ReentrantMutex {
     pub unsafe fn uninitialized() -> ReentrantMutex {
         ReentrantMutex {
-            owner: AtomicU64::new(0),
+            owner: AtomicU32::new(0),
             recursions: UnsafeCell::new(0),
         }
     }
@@ -101,20 +102,20 @@ pub unsafe fn init(&mut self) {
     }
 
     pub unsafe fn lock(&self) {
-        let me = thread_id();
+        let me = thread::my_id();
         while let Err(owner) = self._try_lock(me) {
-            let val = atomic::wait_i64(self.ptr(), owner as i64, -1);
+            let val = atomic::wait_i32(self.ptr(), owner as i32, -1);
             debug_assert!(val == 0 || val == 1);
         }
     }
 
     #[inline]
     pub unsafe fn try_lock(&self) -> bool {
-        self._try_lock(thread_id()).is_ok()
+        self._try_lock(thread::my_id()).is_ok()
     }
 
     #[inline]
-    unsafe fn _try_lock(&self, id: u64) -> Result<(), u64> {
+    unsafe fn _try_lock(&self, id: u32) -> Result<(), u32> {
         let id = id.checked_add(1).unwrap(); // make sure `id` isn't 0
         match self.owner.compare_exchange(0, id, SeqCst, SeqCst) {
             // we transitioned from unlocked to locked
@@ -153,11 +154,7 @@ pub unsafe fn destroy(&self) {
     }
 
     #[inline]
-    fn ptr(&self) -> *mut i64 {
-        &self.owner as *const AtomicU64 as *mut i64
+    fn ptr(&self) -> *mut i32 {
+        &self.owner as *const AtomicU32 as *mut i32
     }
 }
-
-fn thread_id() -> u64 {
-    panic!("thread ids not implemented on wasm with atomics yet")
-}
index bef6c1f34905e8b84e56cd5d5778e84a8ca6e153..4ad89c42b92dca0bfa921c1caabd5d2aeb3d5730 100644 (file)
@@ -69,3 +69,49 @@ pub unsafe fn current() -> Option<Guard> { None }
     pub unsafe fn init() -> Option<Guard> { None }
     pub unsafe fn deinit() {}
 }
+
+cfg_if! {
+    if #[cfg(all(target_feature = "atomics", feature = "wasm-bindgen-threads"))] {
+        #[link(wasm_import_module = "__wbindgen_thread_xform__")]
+        extern {
+            fn __wbindgen_current_id() -> u32;
+            fn __wbindgen_tcb_get() -> u32;
+            fn __wbindgen_tcb_set(ptr: u32);
+        }
+        pub fn my_id() -> u32 {
+            unsafe { __wbindgen_current_id() }
+        }
+
+        // These are currently only ever used in `thread_local_atomics.rs`, if
+        // you'd like to use them be sure to update that and make sure everyone
+        // agrees what's what.
+        pub fn tcb_get() -> *mut u8 {
+            use mem;
+            assert_eq!(mem::size_of::<*mut u8>(), mem::size_of::<u32>());
+            unsafe { __wbindgen_tcb_get() as *mut u8 }
+        }
+
+        pub fn tcb_set(ptr: *mut u8) {
+            unsafe { __wbindgen_tcb_set(ptr as u32); }
+        }
+
+        // FIXME: still need something for hooking exiting a thread to free
+        // data...
+
+    } else if #[cfg(target_feature = "atomics")] {
+        pub fn my_id() -> u32 {
+            panic!("thread ids not implemented on wasm with atomics yet")
+        }
+
+        pub fn tcb_get() -> *mut u8 {
+            panic!("thread local data not implemented on wasm with atomics yet")
+        }
+
+        pub fn tcb_set(ptr: *mut u8) {
+            panic!("thread local data not implemented on wasm with atomics yet")
+        }
+    } else {
+        // stubbed out because no functions actually access these intrinsics
+        // unless atomics are enabled
+    }
+}
index 1394013b4a314e782eabe56ceebce8f85308be76..acfe60719f2f75206067f02bccc4972b5e341e13 100644 (file)
@@ -8,22 +8,61 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+use sys::thread;
+use sync::atomic::{AtomicUsize, Ordering::SeqCst};
+
+const MAX_KEYS: usize = 128;
+static NEXT_KEY: AtomicUsize = AtomicUsize::new(0);
+
+struct ThreadControlBlock {
+    keys: [*mut u8; MAX_KEYS],
+}
+
+impl ThreadControlBlock {
+    fn new() -> ThreadControlBlock {
+        ThreadControlBlock {
+            keys: [0 as *mut u8; MAX_KEYS],
+        }
+    }
+
+    fn get() -> *mut ThreadControlBlock {
+        let ptr = thread::tcb_get();
+        if !ptr.is_null() {
+            return ptr as *mut ThreadControlBlock
+        }
+        let tcb = Box::into_raw(Box::new(ThreadControlBlock::new()));
+        thread::tcb_set(tcb as *mut u8);
+        tcb
+    }
+}
+
 pub type Key = usize;
 
-pub unsafe fn create(_dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
-    panic!("TLS on wasm with atomics not implemented yet");
+pub unsafe fn create(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
+    drop(dtor); // FIXME: need to figure out how to hook thread exit to run this
+    let key = NEXT_KEY.fetch_add(1, SeqCst);
+    if key >= MAX_KEYS {
+        NEXT_KEY.store(MAX_KEYS, SeqCst);
+        panic!("cannot allocate space for more TLS keys");
+    }
+    // offset by 1 so we never hand out 0. This is currently required by
+    // `sys_common/thread_local.rs` where it can't cope with keys of value 0
+    // because it messes up the atomic management.
+    return key + 1
 }
 
-pub unsafe fn set(_key: Key, _value: *mut u8) {
-    panic!("TLS on wasm with atomics not implemented yet");
+pub unsafe fn set(key: Key, value: *mut u8) {
+    (*ThreadControlBlock::get()).keys[key - 1] = value;
 }
 
-pub unsafe fn get(_key: Key) -> *mut u8 {
-    panic!("TLS on wasm with atomics not implemented yet");
+pub unsafe fn get(key: Key) -> *mut u8 {
+    (*ThreadControlBlock::get()).keys[key - 1]
 }
 
 pub unsafe fn destroy(_key: Key) {
-    panic!("TLS on wasm with atomics not implemented yet");
+    // FIXME: should implement this somehow, this isn't typically called but it
+    // can be called if two threads race to initialize a TLS slot and one ends
+    // up not being needed.
 }
 
 #[inline]
index a170abb2628e521eafaf0f4f4050c813d82bd039..59f100fad1bb9a1eda0aa41d72fa42c6b7c506d6 100644 (file)
@@ -172,16 +172,22 @@ unsafe fn __getit() -> $crate::option::Option<
                 &'static $crate::cell::UnsafeCell<
                     $crate::option::Option<$t>>>
             {
-                #[cfg(target_arch = "wasm32")]
+                #[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))]
                 static __KEY: $crate::thread::__StaticLocalKeyInner<$t> =
                     $crate::thread::__StaticLocalKeyInner::new();
 
                 #[thread_local]
-                #[cfg(all(target_thread_local, not(target_arch = "wasm32")))]
+                #[cfg(all(
+                    target_thread_local,
+                    not(all(target_arch = "wasm32", not(target_feature = "atomics"))),
+                ))]
                 static __KEY: $crate::thread::__FastLocalKeyInner<$t> =
                     $crate::thread::__FastLocalKeyInner::new();
 
-                #[cfg(all(not(target_thread_local), not(target_arch = "wasm32")))]
+                #[cfg(all(
+                    not(target_thread_local),
+                    not(all(target_arch = "wasm32", not(target_feature = "atomics"))),
+                ))]
                 static __KEY: $crate::thread::__OsLocalKeyInner<$t> =
                     $crate::thread::__OsLocalKeyInner::new();
 
@@ -302,7 +308,7 @@ pub fn try_with<F, R>(&'static self, f: F) -> Result<R, AccessError>
 /// On some platforms like wasm32 there's no threads, so no need to generate
 /// thread locals and we can instead just use plain statics!
 #[doc(hidden)]
-#[cfg(target_arch = "wasm32")]
+#[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))]
 pub mod statik {
     use cell::UnsafeCell;
     use fmt;
index c8d54a63946a94751c67795ed100409df5af36c8..796b2bd3eed8709715ee96a99b319d4e56fa0d8a 100644 (file)
 // where available, but both are needed.
 
 #[unstable(feature = "libstd_thread_internals", issue = "0")]
-#[cfg(target_arch = "wasm32")]
+#[cfg(all(target_arch = "wasm32", not(target_feature = "atomics")))]
 #[doc(hidden)] pub use self::local::statik::Key as __StaticLocalKeyInner;
 #[unstable(feature = "libstd_thread_internals", issue = "0")]
 #[cfg(target_thread_local)]