fix(codegen,runtime): entry-initialize boxed slots — eliminate undef box-pointer operands (#4926)#4931
Merged
Merged
Conversation
…box-pointer operands (#4926) The boxed-var Stmt::Let path hoisted the slot alloca to the entry block but stored the box pointer only where the Let executes. Any boxed read/write on a path that skips the Let (switch fallthrough, sibling- branch capture) loaded an uninitialized slot; LLVM folded the load to undef and regalloc substituted whatever register was live — under typed-feedback density that was a read-only guard-string constant, the source of the #4898 SIGBUS. - codegen: entry-init every boxed slot (Stmt::Let + PreallocateBoxes) with TAG_UNDEFINED, mirroring the non-boxed path. Skipped-init paths now read a defined sentinel that the runtime rejects deterministically. - runtime: js_box_get on an unregistered pointer returns undefined (the JS read-before-initialization value; Perry has no TDZ) instead of bare NaN. TAG_UNDEFINED is a quiet-NaN bit pattern, so numeric consumers are unchanged. - e2e test: switch-case skipped-init boxed read/write must be deterministic undefined. Verified: react-reconciler stray js_box_set operand collapses from arbitrary __TEXT garbage to the sentinel; 225-test gap sweep has byte-identical failure sets vs baseline (zero regressions); runtime suite green (1 pre-existing date failure, on main too).
This was referenced Jun 10, 2026
fix(cjs_wrap): flat-emit module.exports = <Class> that closes over a top-level binding (#4933)
#4947
Merged
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 #4926 — the deferred source bug behind the #4898 react-reconciler SIGBUS (runtime gate shipped in #4902).
Root cause
The boxed-var
Stmt::Letpath hoisted the slot alloca to the entry block (so it dominates all uses), but the store of the box pointer into that slot only runs where theLetexecutes. Any boxed read/write on a control-flow path that skips the Let — switch fallthrough into a later case, sibling-branch closure capture — loaded an uninitialized slot. LLVM folds that load toundef, and regalloc substitutes whatever register happens to be live. Under typed-feedback (#854) density (react-reconciler emits ~2500register_sitecalls in one minified function), that live value was the read-only"object_get_by_name_guard"cstring pointer →js_box_setwrote into__TEXT.__cstring→ SIGBUS.PreallocateBoxes(#569) had the same non-dominating-store shape when nested insideIf/Try/Labeledbodies.Fix
stmt/let_stmt.rs,stmt/mod.rs): entry-initialize every boxed slot withTAG_UNDEFINEDviaentry_allocas_push_store— exactly what the non-boxed Let path already did. Skipped-init paths now read a defined sentinel that even the structural checks alone reject deterministically; no more undef, no more regalloc roulette.box.rs):js_box_geton an unregistered pointer returnsundefinedinstead of bareNaN— the JS value of a read-before-initialization variable (Perry has no TDZ).TAG_UNDEFINEDis a quiet-NaN bit pattern, so numeric consumers behave exactly as before;typeof/null-checks now seeundefined.crates/perry/tests/issue_4926_boxed_slot_skipped_init.rs): switch-case skipped-init boxed read/write must be deterministicundefined, taken paths unchanged.Verification
letcaptured+mutated by a closure, entered at the later case): pre-fixPERRY_DEBUG=1showedjs_box_get: invalid box pointer 0x1(undef materialized as garbage) andtypeof xprintednumber; post-fix the pointer is exactly the sentinel0x7ffc000000000001andtypeof xprintsundefined.rr_min.tsxfrom react-reconciler 0.33 init crashes under Perry — SIGBUS on import (standalone) / 'undefined is not iterable' in createReconciler (in-ink); blocks all custom React renderers #4898): the strayjs_box_setoperand collapses from arbitrary__TEXTgarbage (0x1016e9360) to the deterministic sentinel — proving the rr stray call was this exact mechanism and is now defined behavior end to end.createReconciler({...full hostconfig})still builds a working reconciler.test_gap_*sweep vsnode --experimental-strip-types— failure sets byte-identical between baseline (clean main) and fixed builds (190 pass / 35 pre-existing, the only output diffs being ASLR addresses and thread IDs in panic backtraces). Fullperry-runtimesuite green single-threaded (1013 passed; the 1 failure,date::tests::test_full_year_setters_revive_invalid_date_only, fails on main too).cargo fmtclean; all touched files under the 2000-line gate.No version bump / CHANGELOG entry per maintainer convention (folded in at merge).