Repository navigation
fix(jsc): initialize VM entry and deferred-scope storage - #24
Merged
steipete merged 3 commits intoOct 9, 2026
Merged
Conversation
Clear new entry records at their generic and fixed-arity creation sites so unused callee-save slots and alignment padding cannot retain stale JavaScript cells during conservative collection. Preserve active entry records and existing unwind and OSR spill handling. Add a Linux x64 native regression covering all nine entry paths, poisoned scratch storage, locator purity, nested entry preservation, and JavaScript exception handling. The unpatched engine fails each poisoned-entry case; the fixed engine passes each case.
Represent deferred entry scopes with two initialized pointer fields and preserve lazy entry, reset, global-object updates, and nested ownership. Add native poisoned-storage and real microtask lifecycle coverage.
steipete
marked this pull request as ready for review
October 9, 2026 18:07
This was referenced Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A canceled catalog joiner’s reason can remain alive because otherwise dead values survive in native stack storage scanned conservatively by JavaScriptCore. This change removes two observed sources: newly reserved VM entry scratch/padding, and the flag/padding word in deferred microtask entry scopes. Native regressions and matched workload measurements support the engine change. The qualification follow-up now uses the merged collected-realm fixture from openclaw/bun#151; changed-input CI now passes at
66ca2eb28e99d4568da9104c8404ebd7be9a6489.Initialize scratch storage only when creating a VM entry record. Existing records retain legitimate unwind and OSR state. Represent deferred
VMEntryScopestate with two initialized pointers, replacing bothstd::optional<VMEntryScope>locals. Lazy entry, global-object updates, reset, re-entry, nested ownership, and teardown boundaries remain intact; no heap allocation is added.The original collector was observed accepting a reason pointer from entry-record scratch storage. After entry initialization, a second failing collection had zeroed entry scratch but accepted an interior pointer formed from an optional's one-byte engaged flag and seven stale padding bytes in
VM::drainMicrotasks. The heap path was lexical environment → lexical environment → captured signal → AbortSignal → reason. Exact unwind information, native disassembly, scanned bytes and accepted-root arrays establish the current storage owner. The historical writers of the observed stale values are not identified. Post-assertion WeakRef and Error.actual edges were excluded from this ownership conclusion.The same failure mode and whole-word-state remedy are documented by @robobun in oven-sh/WebKit#694 (reviewed at
2a67b8290ff3e2075ad570e634afd7372ac45d01), with the related investigation in oven-sh/bun#43217. This fork adapts that remedy inVMEntryScopeitself: its nullable VM pointer carries engagement, avoiding a separate optional wrapper and clearing inactive fields. The tradeoff is that the existing scope type now supports an inactive state; the lifecycle regression exercises that boundary. Failure rates supplement the storage regression and production disassembly, since rebuilding alone can move stale stack words.Native Linux x64 proof:
testVMEntryRecordexercises actual entry stubs.testVMEntryScopeuses the same full-word assertions with a default-OFFTEST_VM_ENTRY_SCOPE_OPTIONAL_CONTROLadapter for the historical representation. Its lifecycle checks run through the real checkpoint implementations. Optimized production disassembly confirms full 16-byte initialization of both deferred scopes; the representation test alone is not used to infer production code generation.The original OpenClaw sample, at
d7b523ece4fdf1a9bd637675e993c50cce25fbf0, observed 0/50 standalone failures on each of Node 24.21.0, Bun 42bd and bf099; full target-shard failures were 0/10 on 42bd and 7/10 on bf099. A fixed-order three-case bisect observed 0/3 at native 42bd, 0/3 at ff038, 1/3 at the 9e640 tree, 1/3 at the 9141 tree and 3/3 at published bf099. Those small samples and different endpoint build styles do not establish a first-bad Bun commit or attribute ownership to the compilation cache.Entry initialization alone was insufficient: a separate matched native bf099 comparison failed 1/5 with the original engine and 3/5 with entry initialization. All ten target censuses completed, without unrelated failures. The subsequent predeclared ten-pair comparison completed: entry-only failed 2/10; combined failed 0/10. All twenty target censuses completed without unrelated failures; every combined run also completed the native tail. Each invocation restored identical all-passing Vitest cache bytes. Every outcome is retained; no failing test was retried to green. These finite counts supplement the native storage proof and do not establish that all possible conservative retention is eliminated.
Independent P2 review of the complete change is scoped-clean. The entry-only change passed 122 standard-mode cases across nine relevant engine stress files. The combined candidate passed 260 standard-mode cases across 19 files. The predeclared 15-cell async microbenchmark measured medians of 0.554588s (original), 0.575084s (entry-only), and 0.571732s (combined): a 3.09% cost versus original in this benchmark, not a general throughput claim. This PR stays unmerged; no engine artifact publication or consumer pin change is included.
The matched real-workload cost study found no regression meeting its predeclared >1% and beyond-noise trigger: Gateway CPU/reply changed -0.57% (95% paired interval -2.98% to +1.89%; five eligible pairs), and unit-fast wall time changed -0.31% (-1.22% to +0.61%; six pairs). The earlier +3.09% async microbenchmark result remains valid for that benchmark. These finite measurements do not prove universal or ±1% equivalence.
The original engine CI run, at
34de44cfc51d1ee0ef8da3db97f871a9d9b9952e, passed engine stress, module tests, macOS compilation and native Windows ARM64 qualification, but failed the paired Linux Bun qualifier: the FinalizationRegistry fixture expected zero callbacks and observed five. That red result is retained. Its exact Bun executables were not uploaded, so no historical-binary replay is claimed.The failure is classified as the fixture-precondition intermittent also reported by @robobun in oven-sh/bun#44544 on builds without this engine change.
JSCTaskScheduler::cancelWorkOfDeadRealmscancels tickets only for unmarked realms; the old fixture did not establish that the dropped realms were unmarked. Removing stale conservative roots can make objects die earlier, not keep a dead realm marked.openclaw/bun#151, merged as
4c8accdd045b84c942fe447348a31a7cba04cde4, witnesses collection and requires zero callbacks for every collected realm, at least one collected realm, and no callbacks before the collection. Its debug positive passes; disabling cancellation produces an executable-UUID-matched ASAN failure in FinalizationRegistry cleanup. Fork Linux/macOS native CI and independent P2 review pass. The adjacent Atomics assertion is unchanged.The qualifier uses pinned Bun sources, so this PR carries that exact merged fixture diff into both paired arms and the feature-sync qualifier. Local reconstruction proves every fixture block is byte-identical to the merged source. The compatibility-only pinned-tree contract stays intact. The engine code remains identical to the previously reviewed head. This is a changed-input qualification, not a retry-to-green; the old result remains visible.
PR and artifact workflows now retain all four qualified Linux Bun executables and their hashes in
linux-bun-executablesfor three days, including binaries available when qualification fails. VM test-file hashes and the applied fixture patch hash accompany the existing proof logs. The diagnostic executables do not become published WebKit release assets. Existing source pins, test selection, assertions, timeouts, heap limits, worker counts and publication gates are preserved.Follow-up validation: 25 release-integrity tests, four JSC-result verifier tests, actionlint, shell syntax and whitespace checks pass. Independent P2 review of the follow-up is scoped-clean. The single changed-input engine CI run and native ARM64 regressions pass at
66ca2eb28e99d4568da9104c8404ebd7be9a6489. Linux paired baseline and candidate each pass all 46 result entries; feature-sync passes all 85. The unchanged compatibility startup and three memory-release tests pass. Both native Linux architectures pass 1,779 engine stress and 1,639 module cases; macOS compilation and native Windows ARM64 startup, DFG, FFI and five accounting modes also pass. Windows uses PR mergee508e74a95fc8a7b696c45c68d3f9475b73ad7c6, whose tree exactly equals the reviewed head.The inherited runner retains its known epoll setup errors and completes unfinished files through its existing fallback: zero failed files and 26 unfinished files in each paired arm, zero failed and 55 unfinished in feature-sync. Every selected result completes successfully. No workflow or failed-test rerun was requested, and no assertion or limit was changed to obtain green.
The Linux executable artifact was independently downloaded and verified: four x86-64 ELF executables match their qualification hashes, the VM test-file and fixture-patch hashes match the reviewed reconstruction, and the artifact expires on 2026-10-12. The baseline executable is byte-identical to the original CI baseline hash; the new candidate is a separate binary and no historical candidate replay is claimed. Copies of all four executables are also retained locally. The original failed run remains part of the evidence. No engine merge, publication or consumer pin change is included in this step.