Thread Local Storage, wasm-ld, thread_local!, and Emscripten

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!

I don't think the wasm32-unknown-emscripten target is supported/maintained anymore. Here's a relevant discussion.

Why do you need to target Emscripten? Are you trying to use C FFI? If not, you might want to try compiling for wasm32-unknown-unknown or wasm32-wasi instead.

Is there a definitive source for this? That seems more like a rumor. The issue pointed out in the Stack Overflow is easily worked around, and in fact is corrected for in the example I posted. When I looked into things the emscripten target seemed like what you want if you are writing a Rust program and want it to also run on the web. The other wasm targets seem like they are for the opposite purpose. You are building a web application, and you want to write some part of it in Rust. The other wasm targets also seem less mature than the emscripten one. Anyway, I'm writing a program using SDL2 (included with emscripten), and it would be nice if the web target also ran with multiple threads, like the desktop version.

All rustc wasm targets are currently defined as singlethreaded: rust/wasm_base.rs at 772d51f887fa407216860bf8ecf3f1a32fb795b4 · rust-lang/rust · GitHub

2 Likes

I'm not sure what you define as definitive, but this issue on the main Rust repository indicates that the Emscripten target has been broken for more than 2 years with no activity/intent to fix it for almost a year.

That looks like it must have been a problem with cargo-web, which does indeed look like a dead project. Either that, or things got fixed. But you can build rust programs with emscripten that work as of today, FWIW.

Ah, I see. I wonder if there is a TODO list for getting wasm32-unknown-emscripten over the multi-threaded hump. It seems like it may be 95% there already. Is posting to "internals" an appropriate place to ask about contributing to a target? Or is that handled by a different group/list/forum?

This issue has been corrected, see Emscripten issue #15891. So with the latest emscripten/llvm, you can now compile multithreaded Rust programs that run within the browser, and have all of the goodies from emscripten included. I've updated the simple example at: https://github.com/gregbuchholz/threads, so that it has all of the nitty-gritty details for getting the options correct for the compilation.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.