Stream: git-wasmtime

Topic: wasmtime / issue #12219 WASM threads don't seem to share ...


view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 15:46):

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:

main.wat.txt

Steps to Reproduce

Expected Results

$ iwasm --max-threads=2 main.wat
thread_data=0x113b8
Running on a thread. tid=1, arg=0x113b8, magic_number=1
After

Actual 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
After

In particular, I think the thread should see a magic_number of 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?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 15:46):

fatlotus added the bug label to Issue #12219.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 15:47):

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:

main.wat.txt

Steps to Reproduce

Expected Results

$ iwasm --max-threads=2 main.wat
thread_data=0x113b8
Running on a thread. tid=1, arg=0x113b8, magic_number=1
After

Actual 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
After

In particular, I think the thread should see a magic_number of 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?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 15:49):

bjorn3 commented on issue #12219:

Did you compile for the wasm32-wasip1 or wasm32-wasip1-threads target?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 15:51):

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 start function might be reinitializing main memory when starting the second Instance for the new thread.)

view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 15:52):

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 start function might be reinitializing main memory when starting the second Instance for the new thread.)

view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 16:22):

bjorn3 commented on issue #12219:

Try linking with -Wl,--import-memory,--export-memory

view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 19:30):

fatlotus commented on issue #12219:

Yep, that does the trick!

I wonder if that logic at the end of add_to_linker was 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.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 20:43):

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:

main.wat.txt

Steps to Reproduce

Expected Results

$ iwasm --max-threads=2 main.wat
thread_data=0x113b8
Running on a thread. tid=1, arg=0x113b8, magic_number=1
After

Actual 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
After

In particular, I think the thread should see a magic_number of 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?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 25 2025 at 20:44):

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:

main.wat.txt

Steps to Reproduce

Expected Results

$ iwasm --max-threads=2 main.wat
thread_data=0x113b8
Running on a thread. tid=1, arg=0x113b8, magic_number=1
After

Actual 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
After

In particular, I think the thread should see a magic_number of 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?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2026 at 15:11):

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:

main.wat.txt

Steps to Reproduce

Expected Results

$ iwasm --max-threads=2 main.wat
thread_data=0x113b8
Running on a thread. tid=1, arg=0x113b8, magic_number=1
After

Actual 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
After

In particular, I think the thread should see a magic_number of 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?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 05 2026 at 15:11):

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 -Sthreads requires you to import a memory which means we couldn't ever turn -Sthreads on-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