I've been investigating what it might take to get Rust to compile a threaded program against the wasm32-unknown-emscripten
compiler target. There is a program with all the support files over at:
https://github.com/gregbuchholz/threads. The simple example in src/main.rs:
use std::thread;
fn xs() {
for _ in 0 .. 10 {
println!("X");
}
}
fn main() {
let t1 = thread::spawn(xs);
t1.join().unwrap();
}
...works when compiling for an x86_64 target, but for --target=wasm32-unknown-emscripten
fails to compile with with an error from wasm-ld
complaining about relocation R_WASM_MEMORY_ADDR_TLS_SLEB cannot be used against non-TLS symbol 'std::io::stdio::OUTPUT_CAPTURE::__getit::__KEY::h776cf75763f0fad1'
, and relocation R_WASM_MEMORY_ADDR_TLS_SLEB cannot be used against non-TLS symbol 'std::sys_common::thread_info::THREAD_INFO::__getit::STATE::haca3e53312905f45'
. (full error message in error.txt)
...as near as I can tell, std::io::stdio::OUTPUT_CAPTURE
is wrapped in thread_local!()
(line 20) and std::sys_common::thread_info::THREAD_INFO
likewise (line 13).
I'm presuming that the error is trying to tell me that something told the
linker to place these items in the thread local storage area, but that they aren't properly marked as thread_local. Which seems to be somewhat confirmed by the wasm-ld source:
https://github.com/llvm/llvm-project/blob/main/lld/wasm/Relocations.cpp
...with the case
starting at line 113:
case R_WASM_MEMORY_ADDR_TLS_SLEB:
case R_WASM_MEMORY_ADDR_TLS_SLEB64:
// In single-threaded builds TLS is lowered away and TLS data can be
// merged with normal data and allowing TLS relocation in non-TLS
// segments.
if (config->sharedMemory) {
if (!sym->isTLS()) {
error(toString(file) + ": relocation " +
relocTypeToString(reloc.Type) +
" cannot be used against non-TLS symbol `" + toString(*sym) +
"`");
}
...that produces the above error message. So why does wasm-ld
think isTLS()
on those symbols is false? And maybe stranger, that case
doesn't appear to do anything useful in the event that isTLS() is true. It only ever produces error messages. But thread local storage works out-of-the-box with Emscripten and C, as shown by this program over in src/c_example/, and confirmed with Firefox and node. Dumping the assembly (emcc example.c -S -pthread) shows a ".section .tdata" in example.s.
The error message is the same with emscripten 2.0.34 and 3.0.0. In order to get this far, I've used a nightly Rust build (rustc 1.59.0-nightly (532d2b14c 2021-12-03)) to enable recompliation of std
with target-feature=+atomics,+bulk-memory
(otherwise it fails to link due to std
not being compiled for threads). The cargo invocation is:
cargo build --target=wasm32-unknown-emscripten --release -Z build-std=panic_abort,std
...and there are additional rustflags
needed in .cargo/config
:
[target.wasm32-unknown-emscripten]
rustflags = [
"-C", "target-feature=+atomics,+bulk-memory",
"-C", "link-args=src/gxx_personality_v0_stub.o -pthread -s PROXY_TO_PTHREAD"
]
...The gxx_personality_v0_stub.cpp
file will also need to be compiled:
em++ -c gxx_personality_v0_stub.cpp -pthread
...to overcome the issue described here.
I have been able to find a little additional information on
R_WASM_MEMORY_ADDR_SLEB
over at WebAssembly Object File Linking, but note that is not R_WASM_MEMORY_ADDR_TLS_SLEB
. I'm assuming that the
SLEB is Signed Little Endian Base.
I'm not sure if this due to missing an appropriate complier/linker flag
issue, or something on the rust side not quite right (maybe the thread_local!
macro needs to be specialized for emscripten?). Or if this is an issue on the LLVM/wasm-ld side. Or something with Emscripten. Quite a few moving parts here. It looks like there are configurations for wasm targets in __thread_local_inner
:
https://github.com/rust-lang/rust/blob/master/library/std/src/thread/local.rs
...it seems like the one starting on line 196 might be the "right" one for emscripten(?):
// If the platform has support for `#[thread_local]`, use it.
#[cfg(all(
target_thread_local,
not(all(target_family = "wasm", not(target_feature = "atomics"))),
))]
...at least with the belief that TLS works with Emscripten.
This issue might be related to issue #84224.
I'm not sure where to look to see how #[thread_local]
is implemented in rustc.
I would appreciate any pointers to more information about this issue, or a more appropriate forum for this question.
Thanks!