Skip to content

Atomics followup - #237

Merged
andreaTP merged 4 commits into
bytecodealliance:mainfrom
andreaTP:atomics-followup
Oct 14, 2026
Merged

andreaTP merged 4 commits into
bytecodealliance:mainfrom
andreaTP:atomics-followup

Conversation

@andreaTP

Copy link
Copy Markdown
Contributor

@andreas-karlsson I went after some findings after your last PR, I'll wait for your feedback here 🙏

- ByteBufferMemory.waitOn reads the expected value with atomicReadInt/Long,
  so the check is ordered with atomic stores that now lock the containing
  8-byte word rather than the exact address (matches ByteArrayMemory and
  the Redline memories).
- Non-shared memories skip locking again and don't allocate the wait state
  map: they can't be accessed by more than one wasm thread, and this is
  what the Redline memories already do.
- ByteArrayMemory only goes lock-free when short volatile get/set are also
  supported, so short atomic reads/writes can't end up locked while
  everything else is lock-free. The per-size volatile flags are implied by
  the full atomic checks and are dropped.
Locked atomics used the wait state map as their lock table, so every
atomic boxed the word address, went through ConcurrentHashMap
computeIfAbsent and left an entry behind. That is every atomic on a
shared ByteBufferMemory (the default memory) and, on JDK 22+, on a shared
ByteArrayMemory.

Atomics now lock one of 256 stripes picked from the 8-byte word with
Fibonacci hashing, so per-thread data at power-of-two strides doesn't
pile onto one stripe. The wait state map only holds wait queues. Lock
order is still wait state -> stripe (waitOn reads the value atomically)
and atomics never take a wait state, so this can't deadlock.

atomicAddInt throughput on a shared memory, 4 threads, ops/us
(map -> stripes, local JMH, laptop, noisy):

                        own word         own 1024 words
  JDK 21 ByteBuffer     76 -> 124        39 -> 133
  JDK 25 ByteBuffer     54 -> 143        45 -> 129
  JDK 25 ByteArray      58 -> 145        58 -> 145

Single-threaded and all-threads-on-one-word are unchanged within noise.
@andreas-karlsson

Copy link
Copy Markdown
Collaborator

@andreaTP will have a look by eod.

@andreas-karlsson andreas-karlsson left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the new WordLocks solution.

My only concern is silently dropping atomicity on unshared memories. With the striped implementation it shouldn't cost much, and even single-threaded it can be very useful, for instance to swap in a new configuration from the host. The spec allows it: "All atomic memory accesses can be performed on both shared and unshared linear memories." It doesn't cover visibility to the host, though it does anticipate host interaction with unshared memory (notify on unshared memory has zero waiters "unless the host has created such a waiter"). Either way, silently dropping atomicity doesn't seem like a good choice to me.

Atomics are valid on non-shared memories and the host can touch them from
another thread, so don't skip locking there (as bytecodealliance#233 intended). Every
memory now owns its word locks; the wait state map stays shared-only.

Fallback atomics on a non-shared memory (ByteBufferMemory on any JDK,
ByteArrayMemory on JDK 22+) now cost an uncontended lock, about what a
shared memory pays: single-threaded atomicAddInt went from ~2 ns (no-op
lock, elided by C2) to ~19 ns on a laptop. ByteArrayMemory on JDK 11-21
keeps using hardware atomics.

New tests: concurrent adds on non-shared memories (fail before this
change on ByteBufferMemory, and on ByteArrayMemory on JDK 22+), and
wait/notify mixed with atomics on the same 8-byte word.
@andreaTP

Copy link
Copy Markdown
Contributor Author

My only concern is silently dropping atomicity on unshared memories.

Last commit should have restored that, to be clear, I get your reasoning and the motivations, at the same time, Threads is still a Wasm proposal, so we might expect changes to the spec, I'd favor having a consistent implementation for "shared" memories, but I'm not (yet?) aiming for feature parity of non-shared memories.

@andreaTP
andreaTP merged commit 0329569 into bytecodealliance:main Oct 14, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants