mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-23 07:14:28 +00:00
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]: https://github.com/WebAssembly/tool-conventions/pull/116
This commit is contained in:
parent
a120caf8b4
commit
dc50a633f3
@ -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 {
|
||||
@ -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 }
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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 = []
|
||||
|
@ -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;
|
||||
|
||||
|
9
src/libstd/sys/wasm/fast_thread_local.rs
Normal file
9
src/libstd/sys/wasm/fast_thread_local.rs
Normal file
@ -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`
|
||||
}
|
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
pub fn my_id() -> u32 {
|
||||
unsafe { __wbindgen_current_id() }
|
||||
}
|
||||
// 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};
|
||||
|
||||
// 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 }
|
||||
}
|
||||
static NEXT_ID: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
pub fn tcb_set(ptr: *mut u8) {
|
||||
unsafe { __wbindgen_tcb_set(ptr as u32); }
|
||||
}
|
||||
#[thread_local]
|
||||
static mut MY_ID: u32 = 0;
|
||||
|
||||
// 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")
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
panic!("should not be used on the wasm target");
|
||||
}
|
||||
|
||||
#[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 set(_key: Key, _value: *mut u8) {
|
||||
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 get(_key: Key) -> *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
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
use crate::sys::thread;
|
||||
use crate::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: [core::ptr::null_mut(); 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 {
|
||||
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) {
|
||||
(*ThreadControlBlock::get()).keys[key - 1] = value;
|
||||
}
|
||||
|
||||
pub unsafe fn get(key: Key) -> *mut u8 {
|
||||
(*ThreadControlBlock::get()).keys[key - 1]
|
||||
}
|
||||
|
||||
pub unsafe fn destroy(_key: Key) {
|
||||
// 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]
|
||||
pub fn requires_synchronized_create() -> bool {
|
||||
false
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit f6446fa8e9629ffb1861303f17930c3aa83ef660
|
||||
Subproject commit 9b64ca5b7e1e3583978f9ac8af6d93b220a13d90
|
Loading…
Reference in New Issue
Block a user