Skip to content

Commit

Permalink
std: Use native #[thread_local] TLS on wasm
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alexcrichton committed Jul 25, 2019
1 parent a120caf commit dc50a63
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 135 deletions.
7 changes: 7 additions & 0 deletions src/librustc_codegen_ssa/back/linker.rs
Expand Up @@ -904,6 +904,9 @@ impl<'a> WasmLd<'a> {
// linker will synthesize this function, and so we need to make sure
// that our usage of `--export` below won't accidentally cause this
// function to get deleted.
//
// * `--export=*tls*` - when `#[thread_local]` symbols are used these
// symbols are how the TLS segments are initialized and configured.
let atomics = sess.opts.cg.target_feature.contains("+atomics") ||
sess.target.target.options.features.contains("+atomics");
if atomics {
Expand All @@ -912,6 +915,10 @@ impl<'a> WasmLd<'a> {
cmd.arg("--import-memory");
cmd.arg("--passive-segments");
cmd.arg("--export=__wasm_init_memory");
cmd.arg("--export=__wasm_init_tls");
cmd.arg("--export=__tls_size");
cmd.arg("--export=__tls_align");
cmd.arg("--export=__tls_base");
}
WasmLd { cmd, sess, info }
}
Expand Down
8 changes: 8 additions & 0 deletions src/librustc_target/spec/wasm32_base.rs
Expand Up @@ -132,6 +132,14 @@ pub fn options() -> TargetOptions {
// non-relative calls and such later on).
relocation_model: "static".to_string(),

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

.. Default::default()
}
}
5 changes: 0 additions & 5 deletions src/libstd/Cargo.toml
Expand Up @@ -75,11 +75,6 @@ panic_immediate_abort = ["core/panic_immediate_abort"]
# 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 = []

# Enable std_detect default features for stdarch/crates/std_detect:
# https://github.com/rust-lang/stdarch/blob/master/crates/std_detect/Cargo.toml
std_detect_file_io = []
Expand Down
2 changes: 2 additions & 0 deletions src/libstd/sys/wasi/mod.rs
Expand Up @@ -47,6 +47,8 @@ pub mod stdio;
pub mod thread;
#[path = "../wasm/thread_local.rs"]
pub mod thread_local;
#[path = "../wasm/fast_thread_local.rs"]
pub mod fast_thread_local;
pub mod time;
pub mod ext;

Expand Down
9 changes: 9 additions & 0 deletions src/libstd/sys/wasm/fast_thread_local.rs
@@ -0,0 +1,9 @@
#![unstable(feature = "thread_local_internals", issue = "0")]

pub unsafe fn register_dtor(_t: *mut u8, _dtor: unsafe extern fn(*mut u8)) {
// FIXME: right now there is no concept of "thread exit", but this is likely
// going to show up at some point in the form of an exported symbol that the
// wasm runtime is oging to be expected to call. For now we basically just
// ignore the arguments, but if such a function starts to exist it will
// likely look like the OSX implementation in `unix/fast_thread_local.rs`
}
5 changes: 2 additions & 3 deletions src/libstd/sys/wasm/mod.rs
Expand Up @@ -37,6 +37,8 @@ pub mod stack_overflow;
pub mod thread;
pub mod time;
pub mod stdio;
pub mod thread_local;
pub mod fast_thread_local;

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

Expand All @@ -48,13 +50,10 @@ cfg_if::cfg_if! {
pub mod mutex;
#[path = "rwlock_atomics.rs"]
pub mod rwlock;
#[path = "thread_local_atomics.rs"]
pub mod thread_local;
} else {
pub mod condvar;
pub mod mutex;
pub mod rwlock;
pub mod thread_local;
}
}

Expand Down
76 changes: 34 additions & 42 deletions src/libstd/sys/wasm/thread.rs
Expand Up @@ -59,48 +59,40 @@ pub mod guard {
pub unsafe fn init() -> Option<Guard> { None }
}

cfg_if::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);
// This is only used by atomics primitives when the `atomics` feature is
// enabled. In that mode we currently just use our own thread-local to store our
// current thread's ID, and then we lazily initialize it to something allocated
// from a global counter.
#[cfg(target_feature = "atomics")]
pub fn my_id() -> u32 {
use crate::sync::atomic::{AtomicU32, Ordering::SeqCst};

static NEXT_ID: AtomicU32 = AtomicU32::new(0);

#[thread_local]
static mut MY_ID: u32 = 0;

unsafe {
// If our thread ID isn't set yet then we need to allocate one. Do so
// with with a simple "atomically add to a global counter" strategy.
// This strategy doesn't handled what happens when the counter
// overflows, however, so just abort everything once the counter
// overflows and eventually we could have some sort of recycling scheme
// (or maybe this is all totally irrelevant by that point!). In any case
// though we're using a CAS loop instead of a `fetch_add` to ensure that
// the global counter never overflows.
if MY_ID == 0 {
let mut cur = NEXT_ID.load(SeqCst);
MY_ID = loop {
let next = cur.checked_add(1).unwrap_or_else(|| {
crate::arch::wasm32::unreachable()
});
match NEXT_ID.compare_exchange(cur, next, SeqCst, SeqCst) {
Ok(_) => break next,
Err(i) => cur = i,
}
};
}
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 crate::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
MY_ID
}
}
32 changes: 9 additions & 23 deletions src/libstd/sys/wasm/thread_local.rs
@@ -1,40 +1,26 @@
use crate::boxed::Box;
use crate::ptr;

pub type Key = usize;

struct Allocated {
value: *mut u8,
dtor: Option<unsafe extern fn(*mut u8)>,
}

#[inline]
pub unsafe fn create(dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
Box::into_raw(Box::new(Allocated {
value: ptr::null_mut(),
dtor,
})) as usize
pub unsafe fn create(_dtor: Option<unsafe extern fn(*mut u8)>) -> Key {
panic!("should not be used on the wasm target");
}

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

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

#[inline]
pub unsafe fn destroy(key: Key) {
let key = Box::from_raw(key as *mut Allocated);
if let Some(f) = key.dtor {
f(key.value);
}
pub unsafe fn destroy(_key: Key) {
panic!("should not be used on the wasm target");
}

#[inline]
pub fn requires_synchronized_create() -> bool {
false
panic!("should not be used on the wasm target");
}
61 changes: 0 additions & 61 deletions src/libstd/sys/wasm/thread_local_atomics.rs

This file was deleted.

2 changes: 1 addition & 1 deletion src/llvm-project
Submodule llvm-project updated 1288 files

0 comments on commit dc50a63

Please sign in to comment.