Bug 313005

Summary: [JSC] ASSERTION FAILED: a.tmp1.tmpIndex(bank) != b.tmp1.tmpIndex(bank) in AirAllocateRegistersByGreedy.cpp buildCoalescingGroups
Product: WebKit Reporter: 2576361468
Component: JavaScriptCoreAssignee: Dan Hecht <dan.hecht>
Status: RESOLVED FIXED    
Severity: Normal CC: 2576361468, syg, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Mac (Intel)   
OS: macOS 14   
Attachments:
Description Flags
test js file none

2576361468
Reported 2026-04-22 10:27:25 PDT
Created attachment 479243 [details] test js file **Summary** The Greedy register allocator's `buildCoalescingGroups` function crashes with an assertion failure due to duplicate Move entries sharing the same (cost, tmp0, tmp1) triple. **Steps to Reproduce** Save the following as `test.js` and run with a debug or ASan-enabled JSC: ``` let buf = new ArrayBuffer(64); let ta = new Float64Array(buf); for (let i = 0; i < 8; i++) ta[i] = i + 0.5; function iterateTA(a) { let sum = 0; for (let v of a) sum += v; return sum; } for (let i = 0; i < 1e4; i++) iterateTA(ta); let newBuf = buf.transfer(); try { iterateTA(ta); } catch(e) { print(e); } ``` Command: ``` jsc test.js ``` No special flags needed — the function gets FTL-compiled through the warmup loop. **Expected Result** The program prints a TypeError (from iterating a detached TypedArray) and exits normally. **Actual Result** Assertion failure in `AirAllocateRegistersByGreedy.cpp` line 1843, followed by SIGILL (exit code 132): ``` ASSERTION FAILED: a.tmp1.tmpIndex(bank) != b.tmp1.tmpIndex(bank) AirAllocateRegistersByGreedy.cpp(1843) : auto JSC::B3::Air::Greedy::GreedyAllocator::buildCoalescingGroups(...) ``` **Analysis** In `buildCoalescingGroups<bank>()` (lines 1838-1845), the sort comparator for Move objects assumes no two Move entries share the same (cost, tmp0, tmp1) triple: ```cpp std::ranges::sort(moves, [](auto& a, auto& b) { if (a.cost != b.cost) return a.cost > b.cost; if (a.tmp0.tmpIndex(bank) != b.tmp0.tmpIndex(bank)) return a.tmp0.tmpIndex(bank) < b.tmp0.tmpIndex(bank); ASSERT(a.tmp1.tmpIndex(bank) != b.tmp1.tmpIndex(bank)); // FAILS return a.tmp1.tmpIndex(bank) < b.tmp1.tmpIndex(bank); }); ``` The Move list is built at lines 1828-1835 by iterating each Tmp's `coalescables` list. The deduplication filter (`tmp < with.tmp` at line 1833) prevents reversed pairs but does not prevent the same (tmp0, tmp1) pair from appearing multiple times when they share the same coalescing cost from multiple coalescing opportunities. I note that the coalescing loop at lines 1847-1855 has an "Already grouped" check that safely skips duplicate entries, so in Release builds (where the ASSERT is compiled out) the duplicates are harmless — the comparator returns `false` for equal elements which satisfies strict weak ordering. The impact is limited to debug/ASan assertion crashes. Multiple JS patterns trigger this — TypedArray for-of + detach (above), Proxy iteration, Map.forEach with clear — suggesting it is a general issue in the Move list construction, not specific to any particular JS construct. **Introduced By** I believe this was introduced by commit f8b9ab1a0bf (2026-03-17): "[JSC] Rewrite of the greedy register allocator's coalescing strategy" (Bug 309992 / rdar://172621751). **Suggested Fix** Either remove the ASSERT (the comparator is already correct for equal elements), or deduplicate the `moves` vector before sorting. **Environment** - WebKit main branch @ HEAD (tested 2026-04-22) - macOS 14, x86_64 - ASan build (JSCOnly/Release with -fsanitize=address)
Attachments
test js file (3.33 KB, application/x-javascript)
2026-04-22 10:27 PDT, 2576361468
no flags
Radar WebKit Bug Importer
Comment 1 2026-04-22 17:11:23 PDT
Shu-yu Guo
Comment 2 2026-04-22 17:51:28 PDT
I'm having trouble reproducing this on Apple Silicon at 311814@main. What commit did you test? Could you provide your exact build configuration?
Shu-yu Guo
Comment 3 2026-04-22 18:06:04 PDT
I also can't reproduce on x64 using Rosetta.
2576361468
Comment 4 2026-04-22 20:11:50 PDT
Thank you for looking into this, and sorry for the trouble reproducing. https://drive.google.com/file/d/1mPMCG0sAmEm29ivc9emYhMQoGRTcDecr/view?usp=drive_link ## Exact build info **Source:** WebKit main @ **311660@main** (commit `2fa13e45b258`) **Host:** macOS 14.1.1 (23B81), native Intel x86_64 (Core i5-1038NG7) — *not* Apple Silicon, *not* Rosetta. **Toolchain:** - Apple clang 15.0.0 (clang-1500.0.40.1) from CommandLineTools (no Xcode.app) - CMake 3.23.3 (MacPorts), Ninja 1.11.0 **Build steps:** ```bash # 1. Enable ASan configuration Tools/Scripts/set-webkit-configuration --asan --release # 2. Build Tools/Scripts/build-webkit --jsc-only --release ``` This produces a JSCOnly Release build with ASan and assertions enabled. The relevant CMake cache values: ``` CMAKE_BUILD_TYPE = Release PORT = JSCOnly DEVELOPER_MODE = ON ENABLE_ADDRESS_SANITIZER = ON (via set-webkit-configuration --asan) ENABLE_ASSERTS = AUTO (resolves to ON for ASan/developer builds) ENABLE_JIT / DFG / FTL = ON ENABLE_C_LOOP = OFF USE_SYSTEM_MALLOC = OFF CMAKE_CXX_FLAGS_RELEASE = -O1 -g -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls CMAKE_EXE_LINKER_FLAGS = -Wl,-no_compact_unwind -fsanitize=address CMAKE_OSX_SYSROOT = MacOSX14.0.sdk ``` I can attach the full CMakeCache.txt (1614 lines) if that would help. **Run command:** ```bash DYLD_FRAMEWORK_PATH=jsc-asan-x86_64-311660/lib jsc-asan-x86_64-311660/bin/jsc test.js ``` No special env vars or JSC flags needed beyond the framework path. The test case is the 14-line reproducer from the original report. **Output (deterministic, 5/5 runs):** ``` ASSERTION FAILED: a.tmp1.tmpIndex(bank) != b.tmp1.tmpIndex(bank) AirAllocateRegistersByGreedy.cpp(1843) : ...buildCoalescingGroups(...) ``` Exit code 132 (SIGILL from CRASH() after assertion failure). ## Why it likely doesn't reproduce on Apple Silicon or Rosetta I think this is a **native x86_64-only** issue, for two related reasons: 1. **ARM64 vs x86_64 register sets produce different B3 IR.** x86_64 has fewer general-purpose registers (16 vs 31), creating more register pressure. This means more spills, more moves, and more coalescing opportunities in Air — increasing the chance that two Move entries end up with identical `(cost, tmp0, tmp1)` triples, which is what triggers the assertion. On ARM64 the same JS function may simply never produce a duplicate triple because there's less coalescing pressure. 2. **Rosetta alters tier-up behavior.** Even with an x86_64 binary, Rosetta's translation overhead changes execution timing, GC behavior, and profiling heuristics. This can shift when (or whether) FTL compilation fires, and with what profiling data — producing different B3 IR that doesn't hit the duplicate condition. In some cases Rosetta may cause FTL to not fire at all for this function, in which case `buildCoalescingGroups` is never reached. To reproduce, I believe you would need a **native x86_64** build on an actual Intel Mac (or an x86_64 CI bot), built with `set-webkit-configuration --asan && build-webkit --jsc-only --release`. If that's not practical, I completely understand and am happy to close this. The issue is assertion-only (the comparator is correct for equal elements under strict weak ordering), so it does not affect Release builds without assertions. Let me know how you'd like to proceed.
Dan Hecht
Comment 5 2026-04-23 08:56:08 PDT
Dan Hecht
Comment 6 2026-04-23 09:11:41 PDT
> but does not prevent the same (tmp0, tmp1) pair from appearing multiple times when they share the same coalescing cost from multiple coalescing opportunities. The coalescables lists never have duplicates to begin with, see Coalescables::add(). So this is not a valid explanation. See the pull request for a possible explanation.
EWS
Comment 7 2026-04-23 11:12:42 PDT
Committed 311871@main (51024b1e8ca6): <https://commits.webkit.org/311871@main> Reviewed commits have been landed. Closing PR #63431 and removing active labels.
Note You need to log in before you can comment on or make changes to this bug.