WebAssembly / WebAssembly/wasi-libc
pthread should use atomic load/store for access of contended fields
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 1k
- Forks
- 251
- Avg merge
- 7h 15m
- Merged PRs (30d)
- 3
Description
Sorry if this is the wrong repo for this issue, I guess it is more related to LLVM but I am finding it through analysis of wasi-libc and WebAssembly threads proposal specifically, and perhaps the change should be scoped to just libc.
I have been working on threads support for wazero, and for example run tests with this simple use of pthread mutex
https://github.com/tetratelabs/wazero/blob/main/experimental/testdata/pthread.c
One cool thing about wazero being a Go runtime is that the Go race detector can be applied. When trying to run that code concurrently from multiple host threads, each with an instantiated wasm module accessing an imported shared memory (the code in main does not reflect this yet, I am trying in https://github.com/tetratelabs/wazero/compare/main...anuraaga:wazero:mod-per-routine?expand=1), races are shown. It's hard to find the actual C code that causes a race, but through some pattern matching, I found for example this code during locking
trylock is a CAS while r = m->_m_lock is a load of a volatile field. I see the compiled Wasm uses i32.atomic.rmw.cmpxchg for the CAS, but just i32.load, not an atomic load for the volatile read. I can't say I'm an expert on this subject, but my guess is that a non-atomic load is used because on normal hardware, a 32-bit load is atomic vs other atomic instructions, there's no such thing as a non-atomic load per se. However, WebAssembly can be run in many environments, and in the case of wazero's interpreter mode (compiler mode has not been implemented yet and would likely not exhibit this race), CAS operations are not implemented with atomic instructions (Go does not provide the APIs to do so for all the instructions in the thread proposal) and uses locks instead.
atomic.load locks
https://github.com/tetratelabs/wazero/blob/main/internal/engine/interpreter/interpreter.go#L3974
normal load doesn't lock and will race with any cmpxchg operation
https://github.com/tetratelabs/wazero/blob/main/internal/engine/interpreter/interpreter.go#L755
Because wasm32-wasi-threads will be executed on arbitrary machines, does it make sense to use atomic.load for volatile read instead of normal one? While I understand that C volatile is not supposed to be a guarantee of atomicity, I wonder if in practice code like in musl does expect it. Or perhaps wasi-libc's fork of musl needs to be made more precise by explicitly specifying atomic load, because it cannot assume a fixed set of supported architectures like musl does. Sorry if this is completely off the mark.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with musl/src/thread/pthread_mutex_timedlock.c at the cited load, then compare the referenced wazero interpreter implementations for cmpxchg, atomic.load, and normal load. Determine whether wasi-libc needs an explicit atomic access for this path and identify the relevant thread tests; done means the behavior and scope are agreed and covered by a reproducible test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, wasm
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100