Skip to content

std: Implement TLS for wasm32-unknown-unknown #54951

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

Merged
merged 1 commit into from
Oct 13, 2018
Merged
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
9 changes: 9 additions & 0 deletions src/libstd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
23 changes: 10 additions & 13 deletions src/libstd/sys/wasm/mutex_atomics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -70,7 +71,7 @@ impl Mutex {
}

pub struct ReentrantMutex {
owner: AtomicU64,
owner: AtomicU32,
recursions: UnsafeCell<u32>,
}

Expand All @@ -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),
}
}
Expand All @@ -101,20 +102,20 @@ impl ReentrantMutex {
}

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
Expand Down Expand Up @@ -153,11 +154,7 @@ impl ReentrantMutex {
}

#[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")
}
46 changes: 46 additions & 0 deletions src/libstd/sys/wasm/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,49 @@ pub mod guard {
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
}
}
53 changes: 46 additions & 7 deletions src/libstd/sys/wasm/thread_local_atomics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

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

This can technically overflow after 4 billion repeated failures but that may not be worth worrying about?

Copy link
Member Author

Choose a reason for hiding this comment

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

Heh I'm not too worried about overflow but it's easy enough to add a store beneath this in the failure case back to MAX_KEYS, so done now!

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]
Expand Down
14 changes: 10 additions & 4 deletions src/libstd/thread/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,16 +172,22 @@ macro_rules! __thread_local_inner {
&'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();

Expand Down Expand Up @@ -302,7 +308,7 @@ impl<T: 'static> LocalKey<T> {
/// 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;
Expand Down
2 changes: 1 addition & 1 deletion src/libstd/thread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ pub use self::local::{LocalKey, AccessError};
// 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)]
Expand Down