fatlotus opened issue #12219:
Please take this report with a grain of salt as I'm fairly new with WASM.
Test Case
#include <cassert> #include <cstdint> #include <cstdio> #include <unistd.h> extern "C" { __attribute__((import_module("wasi"), import_name("thread-spawn"))) int32_t spawn_a_thread(void *thread_data); } int magic_number = 0; [[clang::export_name("wasi_thread_start")]] void wasi_thread_start(int32_t tid, int *arg) { printf("Running on a thread. tid=%d, arg=%p, magic_number=%d\n", tid, arg, magic_number); } int main() { magic_number += 1; sleep(1); int thread_data; printf("thread_data=%p\n", &thread_data); int tid = spawn_a_thread(&thread_data); assert(tid > 0); sleep(1); printf("After\n"); }Here's what this compiles to (apologies, it's pretty long) with Homebrew Clang 21.1.8:
Steps to Reproduce
- wasmtime:
$ wasmtime run --wasi threads=y --wasm threads=y main.wat- WAMR:
$ iwasm --max-threads=2 main.wasmExpected Results
$ iwasm --max-threads=2 main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=1 AfterActual Results
$ wasmtime run --wasi threads=y --wasm threads=y main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=0 AfterIn particular, I think the thread should see a
magic_numberof 1, since it has been incremented by the main program before the thread started.Versions and Environment
Wasmtime version or commit: wasmtime 39.0.1
Operating system: macOS 14.6.1
Architecture: M1
Extra Info
There's some funny logic here (https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-threads/src/lib.rs#L165-L179) in the wasi threads crate, where the shared memory is somehow re-injected into the linker. In the clang-generated code, the shared memory isn't imported — maybe that means the linker doesn't inject the same shared memory region to each thread?
fatlotus added the bug label to Issue #12219.
fatlotus edited issue #12219:
Please take this report with a grain of salt as I'm fairly new with WASM.
Test Case
#include <cassert> #include <cstdint> #include <cstdio> #include <unistd.h> extern "C" { __attribute__((import_module("wasi"), import_name("thread-spawn"))) int32_t spawn_a_thread(void *thread_data); } int magic_number = 0; [[clang::export_name("wasi_thread_start")]] void wasi_thread_start(int32_t tid, int *arg) { printf("Running on a thread. tid=%d, arg=%p, magic_number=%d\n", tid, arg, magic_number); } int main() { magic_number += 1; sleep(1); int thread_data; printf("thread_data=%p\n", &thread_data); int tid = spawn_a_thread(&thread_data); assert(tid > 0); sleep(1); printf("After\n"); }Here's what this compiles to (apologies, it's pretty long) with Homebrew Clang 21.1.8:
Steps to Reproduce
- wasmtime:
$ wasmtime run --wasi threads=y --wasm threads=y main.wat- WAMR:
$ iwasm --max-threads=2 main.wasmExpected Results
$ iwasm --max-threads=2 main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=1 AfterActual Results
$ wasmtime run --wasi threads=y --wasm threads=y main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=0 AfterIn particular, I think the thread should see a
magic_numberof 1, since it has been incremented by the main program before the thread started.Versions and Environment
Wasmtime version or commit: wasmtime 39.0.1
Operating system: macOS 14.6.1
Architecture: M1
Extra Info
There's some funny logic here (https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-threads/src/lib.rs#L165-L179) in the wasi threads crate, where the shared memory is somehow re-injected into the linker. In the clang-generated code, the shared memory isn't imported — maybe that means the linker doesn't inject the same shared memory region to each thread?
bjorn3 commented on issue #12219:
Did you compile for the wasm32-wasip1 or wasm32-wasip1-threads target?
fatlotus commented on issue #12219:
I'm using
wasm32-wasip1-threads, but it reproduces on both targets. The full command line I'm using is:$ /opt/homebrew/opt/llvm/bin/clang++ --std=c++17 -g --target=wasm32-wasip1-threads --sysroot=/Users/jeremyarcher/Downloads/wasi-sdk-29.0-arm64-macos/share/wasi-sysroot main.cpp -o main.wasm(One other hypothesis I had was that the
startfunction might be reinitializing main memory when starting the secondInstancefor the new thread.)
fatlotus edited a comment on issue #12219:
I'm using
wasm32-wasip1-threads, but it reproduces on both targets. The full command line I'm using is:$ /opt/homebrew/opt/llvm/bin/clang++ --std=c++17 -g --target=wasm32-wasip1-threads --sysroot=~/Downloads/wasi-sdk-29.0-arm64-macos/share/wasi-sysroot main.cpp -o main.wasm(One other hypothesis I had was that the
startfunction might be reinitializing main memory when starting the secondInstancefor the new thread.)
bjorn3 commented on issue #12219:
Try linking with
-Wl,--import-memory,--export-memory
fatlotus commented on issue #12219:
Yep, that does the trick!
I wonder if that logic at the end of
add_to_linkerwas meant to be:for import in module.imports() { if let Some(m) = import.ty().memory() && m.is_shared() { let mem = SharedMemory::new(module.engine(), m.clone())?; linker.define(store, import.module(), import.name(), mem.clone())?; return Ok(()) } } return Err(anyhow!( "memory was not shared; a `wasi-threads` must import \ a shared memory as \"memory\"" ));That way this case would be an error.
fatlotus edited issue #12219:
Please take this report with a grain of salt as I'm fairly new with WASM.
Test Case
#include <cassert> #include <cstdint> #include <cstdio> #include <unistd.h> extern "C" { __attribute__((import_module("wasi"), import_name("thread-spawn"))) int32_t spawn_a_thread(void *thread_data); } int magic_number = 0; [[clang::export_name("wasi_thread_start")]] void wasi_thread_start(int32_t tid, int *arg) { printf("Running on a thread. tid=%d, arg=%p, magic_number=%d\n", tid, arg, magic_number); } int main() { magic_number += 1; sleep(1); int thread_data; printf("thread_data=%p\n", &thread_data); int tid = spawn_a_thread(&thread_data); assert(tid > 0); sleep(1); printf("After\n"); }Here's what this compiles to (apologies, it's pretty long) with Homebrew Clang 21.1.8:
Steps to Reproduce
- wasmtime:
$ wasmtime run --wasi threads=y --wasm threads=y main.wat- WAMR:
$ iwasm --max-threads=2 main.wasmExpected Results
$ iwasm --max-threads=2 main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=1 AfterActual Results
$ wasmtime run --wasi threads=y --wasm threads=y main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=0 AfterIn particular, I think the thread should see a
magic_numberof 1, since it has been incremented by the main program before the thread started.Versions and Environment
Wasmtime version or commit: wasmtime 39.0.1
Operating system: macOS 14.6.1
Architecture: M1
Extra Info
There's some funny logic here (https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-threads/src/lib.rs#L165-L179) in the wasi threads crate, where the shared memory is somehow re-injected into the linker. In the clang-generated code, the shared memory isn't imported — maybe that means the linker doesn't inject the same shared memory region to each thread?
fatlotus edited issue #12219:
Please take this report with a grain of salt as I'm fairly new with WASM.
Test Case
#include <cassert> #include <cstdint> #include <cstdio> #include <unistd.h> extern "C" { __attribute__((import_module("wasi"), import_name("thread-spawn"))) int32_t spawn_a_thread(void *thread_data); } int magic_number = 0; [[clang::export_name("wasi_thread_start")]] void wasi_thread_start(int32_t tid, int *arg) { printf("Running on a thread. tid=%d, arg=%p, magic_number=%d\n", tid, arg, magic_number); } int main() { magic_number += 1; sleep(1); int thread_data; printf("thread_data=%p\n", &thread_data); int tid = spawn_a_thread(&thread_data); assert(tid > 0); sleep(1); printf("After\n"); }Here's what this compiles to (apologies, it's pretty long) with Homebrew Clang 21.1.8:
Steps to Reproduce
- wasmtime:
$ wasmtime run --wasi threads=y --wasm threads=y main.wat- WAMR:
$ iwasm --max-threads=2 main.wasmExpected Results
$ iwasm --max-threads=2 main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=1 AfterActual Results
$ wasmtime run --wasi threads=y --wasm threads=y main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=0 AfterIn particular, I think the thread should see a
magic_numberof 1, since it has been incremented by the main program before the thread started.Versions and Environment
Wasmtime version or commit: wasmtime 39.0.1
Operating system: macOS 14.6.1
Architecture: M1
Extra Info
There's some funny logic here (https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-threads/src/lib.rs#L165-L179) in the wasi threads crate, where the shared memory is somehow re-injected into the linker. In the clang-generated code, the shared memory isn't imported — maybe that means the linker doesn't inject the same shared memory region to each thread?
alexcrichton closed issue #12219:
Please take this report with a grain of salt as I'm fairly new with WASM.
Test Case
#include <cassert> #include <cstdint> #include <cstdio> #include <unistd.h> extern "C" { __attribute__((import_module("wasi"), import_name("thread-spawn"))) int32_t spawn_a_thread(void *thread_data); } int magic_number = 0; [[clang::export_name("wasi_thread_start")]] void wasi_thread_start(int32_t tid, int *arg) { printf("Running on a thread. tid=%d, arg=%p, magic_number=%d\n", tid, arg, magic_number); } int main() { magic_number += 1; sleep(1); int thread_data; printf("thread_data=%p\n", &thread_data); int tid = spawn_a_thread(&thread_data); assert(tid > 0); sleep(1); printf("After\n"); }Here's what this compiles to (apologies, it's pretty long) with Homebrew Clang 21.1.8:
Steps to Reproduce
- wasmtime:
$ wasmtime run --wasi threads=y --wasm threads=y main.wat- WAMR:
$ iwasm --max-threads=2 main.wasmExpected Results
$ iwasm --max-threads=2 main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=1 AfterActual Results
$ wasmtime run --wasi threads=y --wasm threads=y main.wat thread_data=0x113b8 Running on a thread. tid=1, arg=0x113b8, magic_number=0 AfterIn particular, I think the thread should see a
magic_numberof 1, since it has been incremented by the main program before the thread started.Versions and Environment
Wasmtime version or commit: wasmtime 39.0.1
Operating system: macOS 14.6.1
Architecture: M1
Extra Info
There's some funny logic here (https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasi-threads/src/lib.rs#L165-L179) in the wasi threads crate, where the shared memory is somehow re-injected into the linker. In the clang-generated code, the shared memory isn't imported — maybe that means the linker doesn't inject the same shared memory region to each thread?
alexcrichton commented on issue #12219:
Thanks for the report, and I agree with @bjorn3 that the main issue here would be compiling the module to import memory. I agree that the error message could in theory be better, but Wasmtime isn't exactly positioned to have such an error message at this time. For example that would mean that
-Sthreadsrequires you to import a memory which means we couldn't ever turn-Sthreadson-by-default.Given that I think that this is otherwise solved, so I'm going to close this.
Last updated: Jan 09 2026 at 13:15 UTC