Our situation is a bit complicated because WASIX implements most of an operating system that runs WebAssembly code, including threads and processes.
Regarding the happens-before/happens-after relationship, it's the same as normal Rust. You can use normal loads and stores and they're seen whenever (yay, race conditions) or you can use atomics and the updates have a well defined ordering.
Each process gets its own address space (SharedArrayBuffer) that all its threads have access to. If they try to observe reads/writes from multiple threads without using the proper WebAssembly atomic operations then that's a race condition and their problem. Same as if it was x86 code.
How exactly are you doing these non-atomic operations?
The only way one worker can communicate with or observe the effects of another worker is via either postMessage() and modifying a shared part of the WebAssembly instance's linear memory (i.e. via static variables).
Rust forces you to make sure all variables inside a static are Sync, which means any accesses need to go through some sort of synchronisation mechanism (e.g. atomics or a mutex), which gives us that well-defined happens-before/happens-after relationship.
If you are trying to observe the effects of two workers without using a synchronisation mechanism, that's UB according to Rust's threading model and SharesArrayBuffer is irrelevant.
Yep, that's exactly it. My responses are for if you use a single SharedArrayBuffer as the linear memory for multiple WebAssembly instances running on different workers. Hence the talk of UB and synchronisation mechanisms.
I think this article from Lin Clark answers the question you're asking:
Basically, if you want to avoid data races then you should use operations from the Atomics namespace when using the same SharedArrayBuffer from multiple workers.
the shared data block referenced by the two SharedArrayBuffer objects is the same data block, and a side effect to the block in one agent will eventually become visible in the other agent.
Shared memory can be created and updated simultaneously in workers or the main thread. Depending on the system (the CPU, the OS, the Browser) it can take a while until the change is propagated to all contexts. To synchronize, atomic operations are needed.
To which he replied:
...ensuring (amongst other things) that CPU L1d caches are up-to-date, etc.?
Yes, that was the intent of that language. The writes to the memory should happen-before the postMessage and the receive-message should happen-before the reads.
... That's also a "synchronization edge"?
Yes, same argument. The writes happen-before the wake, and the wakeup of the wait happens-before the reads.
All of this is by intent so as to allow data to be written and read with cheap unsynchronized writes and reads, and then for the (comparatively expensive) synchronization to ensure proper observability.
There appears to be something going on with:
you can do arbitrary (non atomic) writes/reads, and the act of postmessage/onmessage causes a synchronization barrier.