<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/page.cgi?id=bugzilla.dtd">

<bugzilla version="5.0.4.1"
          urlbase="https://bugs.webkit.org/"
          
          maintainer="admin@webkit.org"
>

    <bug>
          <bug_id>311568</bug_id>
          
          <creation_ts>2026-04-06 09:15:12 -0700</creation_ts>
          <short_desc>Atomics.wait &quot;not-equal&quot; return path missing memory fence — stale reads with 3+ workers</short_desc>
          <delta_ts>2026-04-06 17:54:33 -0700</delta_ts>
          <reporter_accessible>1</reporter_accessible>
          <cclist_accessible>1</cclist_accessible>
          <classification_id>1</classification_id>
          <classification>Unclassified</classification>
          <product>WebKit</product>
          <component>JavaScriptCore</component>
          <version>WebKit Nightly Build</version>
          <rep_platform>Unspecified</rep_platform>
          <op_sys>Unspecified</op_sys>
          <bug_status>RESOLVED</bug_status>
          <resolution>INVALID</resolution>
          
          
          <bug_file_loc></bug_file_loc>
          <status_whiteboard></status_whiteboard>
          <keywords></keywords>
          <priority>P2</priority>
          <bug_severity>Normal</bug_severity>
          <target_milestone>---</target_milestone>
          
          
          <everconfirmed>1</everconfirmed>
          <reporter>lostit1278</reporter>
          <assigned_to name="Nobody">webkit-unassigned</assigned_to>
          <cc>syg</cc>
    
    <cc>ysuzuki</cc>
          

      

      

      

          <comment_sort_order>oldest_to_newest</comment_sort_order>  
          <long_desc isprivate="0" >
    <commentid>2197256</commentid>
    <comment_count>0</comment_count>
    <who name="">lostit1278</who>
    <bug_when>2026-04-06 09:15:12 -0700</bug_when>
    <thetext>SUMMARY

When Atomics.wait returns &quot;not-equal&quot; (because the watched value has already changed before the call), JavaScriptCore does not emit a full sequential-consistency memory fence. Workers that take the &quot;not-equal&quot; fast path read stale values from SharedArrayBuffer — values written by other workers before the barrier are invisible.

This is not JSC-specific. All three major JavaScript engines are affected: V8 (Chromium), SpiderMonkey (Firefox), and JavaScriptCore (Safari). Three independent engines failing identically confirms this is a spec-level ambiguity in the ECMAScript memory model. V8 has progressively fixed the fence in recent versions; SpiderMonkey and JavaScriptCore have not.

STEPS TO REPRODUCE

1. Open https://lostbeard.github.io/v8-atomics-wait-bug/ in Safari
2. Click &quot;Run All Tests&quot;
3. Observe Test 2 fails with stale reads

Source: https://github.com/LostBeard/v8-atomics-wait-bug

WHAT THE TEST DOES

Three workers synchronize using a standard generation-counting barrier with Atomics.wait/Atomics.notify. Each iteration: workers write a unique value to their slot, enter the barrier, then read all other workers&apos; slots and verify values match.

Three tests isolate the bug:
- Test 1 (2 workers, wait/notify): PASS — 0 stale reads
- Test 2 (3 workers, wait/notify): FAIL — stale reads detected
- Test 3 (3 workers, spin/Atomics.load): PASS — 0 stale reads

EXPECTED BEHAVIOR

After Atomics.wait returns — regardless of return value (&quot;ok&quot;, &quot;not-equal&quot;, &quot;timed-out&quot;) — all prior stores from all agents that happened-before the event that caused the return should be visible.

ACTUAL BEHAVIOR

When Atomics.wait returns &quot;not-equal&quot;, stores from other workers that preceded the generation bump are not visible. Workers read stale values.

JAVASCRIPTCORE TEST RESULTS (all via BrowserStack)

Safari 18 / macOS Sequoia:
- Test 1 (2W wait/notify): PASS — 0 / 200,000 stale reads (0%)
- Test 2 (3W wait/notify): FAIL — 1,625 / 15,000 stale reads (10.8%)
- Test 3 (3W spin): PASS — 0 / 18,000 stale reads (0%)

Safari 17 / macOS Sonoma:
- Test 1 (2W wait/notify): PASS — 0 / 200,000 stale reads (0%)
- Test 2 (3W wait/notify): FAIL — 1,526 / 3,000 stale reads (50.9%)
- Test 3 (3W spin): PASS — 0 / 9,000 stale reads (0%)

Safari 26 / macOS Tahoe:
- Test 1 (2W wait/notify): PASS — 0 / 200,000 stale reads (0%)
- Test 2 (3W wait/notify): FAIL — 784 / 3,000 stale reads (26.1%)
- Test 3 (3W spin): PASS — 0 / 9,000 stale reads (0%)

Safari iOS 18 / iPhone 16 (ARM):
- Test 1 (2W wait/notify): PASS — 0 / 200,000 stale reads (0%)
- Test 2 (3W wait/notify): FAIL — 638 / 3,000 stale reads (21.3%)
- Test 3 (3W spin): PASS — 0 / 9,000 stale reads (0%)

Safari iOS 16 / iPhone 14 (ARM):
- Test 1 (2W wait/notify): PASS — 0 / 200,000 stale reads (0%)
- Test 2 (3W wait/notify): FAIL — 634 / 3,000 stale reads (21.1%)
- Test 3 (3W spin): PASS — 0 / 9,000 stale reads (0%)

JSC fails consistently across all tested platforms — macOS Sequoia, Sonoma, Tahoe, iOS 18, iOS 16. Error rates range from 10.8% to 50.9% with no improvement trend across JSC versions.

On the same macOS Tahoe BrowserStack host, V8 (Chrome/Edge 146) passes with 0 stale reads across 10 runs, while JSC (Safari 26) fails at 26.1% — proving this is engine-specific, not hardware-related.

WORKAROUND

Replacing Atomics.wait with a pure spin on Atomics.load fixes the issue:
  while (Atomics.load(view, genIdx) === myGen) {}
Every Atomics.load is seq_cst — when it observes the new generation, the total order guarantees all prior stores are visible.

CROSS-ENGINE RESULTS

All three major engines affected:
- V8 12.4 (Node.js 22.14), x86-64 Windows: ~66%
- V8 14.6 (Chrome 146), x86-64 Windows: 10.5%
- V8 14.6 (Chrome 146), macOS Tahoe: 0% (fixed)
- SpiderMonkey (Firefox 148), x86-64 Windows: 63.2%
- SpiderMonkey (Firefox 149), macOS Tahoe: 10.3%
- JSC (Safari 18), macOS Sequoia: 10.8%
- JSC (Safari 17), macOS Sonoma: 50.9%
- JSC (Safari 26), macOS Tahoe: 26.1%
- JSC (Safari iOS 18), ARM iPhone 16: 21.3%
- JSC (Safari iOS 16), ARM iPhone 14: 21.1%
- Android Chrome (3 ARM SoCs): 14.5%-48.4% — fails 2-worker test on ARM

SPEC REFERENCES

- ECMAScript Section 25.4.12 (Atomics.wait): https://tc39.es/ecma262/#sec-atomics.wait
- ECMAScript Section 29 (Memory Model): https://tc39.es/ecma262/#sec-memory-model
- WebAssembly Threads (memory.atomic.wait32): https://webassembly.github.io/threads/core/exec/instructions.html

RELATED BUGS

- Chromium: https://issues.chromium.org/issues/495679735
- Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=2029633
- Reproducer: https://github.com/LostBeard/v8-atomics-wait-bug
- Live demo: https://lostbeard.github.io/v8-atomics-wait-bug/

Cross-browser testing powered by BrowserStack (https://www.browserstack.com).</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2197421</commentid>
    <comment_count>1</comment_count>
    <who name="Shu-yu Guo">syg</who>
    <bug_when>2026-04-06 16:42:40 -0700</bug_when>
    <thetext>Our Atomics.wait implementation correctly implements a seq-cst load. The bug is in the reproducer (see my comment on the spec repo). JSC is behaving correctly.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2197442</commentid>
    <comment_count>2</comment_count>
    <who name="">lostit1278</who>
    <bug_when>2026-04-06 17:54:33 -0700</bug_when>
    <thetext>Shu-yu Guo already closed this as INVALID, and he was right.

The bug was in our barrier implementation - a single Atomics.wait without a loop, vulnerable to spurious cross-barrier wakeups. The corrected barrier (with a while loop) produces 0 stale reads on Safari 26/macOS Tahoe and all other tested platforms.

Apologies for the false report. Full details at https://github.com/tc39/ecma262/issues/3800.</thetext>
  </long_desc>
      
      

    </bug>

</bugzilla>