fix(gc): scope inline-arena resync to the Eden arena (#1824) - #2134
Merged
Merged
Conversation
`Arena::alloc`'s block-reuse forward-scan calls `resync_inline_to_current`, which mirrors the thread-global `INLINE_STATE` used by the codegen inline bump-allocator. `INLINE_STATE` is only meaningful for the general nursery-Eden arena, but the same `Arena::alloc` body backs the old-gen, survivor, and longlived arenas. When a large-object `await` allocation (e.g. a big JSON `response.json()` payload, which is born in the old-gen) forward-scanned to reuse an earlier old-gen block, it repointed `INLINE_STATE` at that old-gen block. The next general-Eden `arena_alloc` then wrote the foreign block's offset into the live Eden block, rewinding the bump pointer so a fresh string allocation landed on top of a still-live, suspended async-step closure. When that closure was later resumed by the microtask runner, its overwritten function pointer (now string bytes) was jumped to → SIGSEGV. This matches the #1824 report exactly: a garbage "function pointer" in `js_promise_run_microtasks` / the resumed await continuation, reproducible with no GC cycle (`full_gc=0`/`minor_gc=0`) and surviving every GC escape hatch (`PERRY_GEN_GC=0`, `PERRY_GEN_GC_EVACUATE=0`, `PERRY_WRITE_BARRIERS=0`) — it is an allocator-state corruption, not a collection/rooting bug. Fix: `resync_inline_to_current` no-ops unless `self.space == NurseryEden`. The Eden forward-scan path is unchanged; only the non-Eden arenas stop clobbering `INLINE_STATE`. Regression test reproduces the old-gen forward-scan reuse and asserts the Eden `INLINE_STATE` is left intact (fails before the fix, passes after).
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.
Fixes #1824.
Root cause — allocator-state corruption, not GC
The reporter's symptom — a garbage "function pointer" in the resumed async-step continuation, reproducible with no GC cycle and surviving every GC escape hatch (
PERRY_GEN_GC=0,PERRY_GEN_GC_EVACUATE=0,PERRY_WRITE_BARRIERS=0) — is not a GC/rooting bug and not codegen spill/restore. It is corruption of the codegen inline bump-allocator'sINLINE_STATEby a non-Eden allocation:Arena::alloc's block-reuse forward-scan callsresync_inline_to_current, which mirrors the thread-globalINLINE_STATE.INLINE_STATEis only meaningful for the general nursery-Eden arena, but the sameArena::allocbody backsOLD_ARENA/ survivors / longlived.awaitresult (e.g.await response.json()of a big payload) is born in the old-gen viaarena_alloc_gc's large-object path. When that old-gen allocation forward-scans to reuse an earlier old-gen block,resync_inline_to_currentrepointsINLINE_STATEat a non-Eden block.arena_allocthen writes that foreign block's offset into the live Eden block, rewinding the Eden bump pointer.func_ptr(now string bytes) is jumped to → SIGSEGV.This exactly reproduces the report:
full_gc=0/minor_gc=0(no collection), ASCII-bytes-as-pointer inlldb(e.g.0x343030302f303036="600/0004"from the JSON URL strings), and the crash surviving every GC mode.Fix
resync_inline_to_currentno-ops unlessself.space == HeapSpace::NurseryEden. The Eden forward-scan path is unchanged; only the non-Eden arenas stop clobberingINLINE_STATE. 16 lines including the explanatory comment.Validation
fetch/jsonin a loop feeding a long-lived accumulator + a retainedBIG) that SIGSEGV'd deterministically with the reported signature.PERRY_GEN_GC=0— the mode the reporter tested where the crash persisted) runs the repro 3/3 clean to "ALL DONE".arena::(31) andgc::(289) unit tests still green.old_arena_block_reuse_does_not_repoint_eden_inline_stateexercises anOLD_ARENAblock-reuse forward-scan and asserts the EdenINLINE_STATEis left intact. Fails before the fix, passes after.cargo fmt --all -- --checkclean.Follow-up I noticed while verifying (not this PR's scope)
The aggressive repro (8000-element retained allocation + 5000-element JSON payloads) still SIGSEGVs under default generational GC only after this fix — a garbage promise pointer
0x65('e' string byte) injs_promise_rejectfrom the microtask runner, requiring ≥2 copying-GC cycles. It does not reproduce underPERRY_GEN_GC=0and not with a smaller payload. So that is a separate copying-GC promise-chain rooting issue under heavy large-object pressure (#1597 family), distinct from the reported crash. I can file it as its own issue if useful.