Skip to content

fix(gc): root builtin.rs's constructor arguments across each other's lowering (#6986) - #7719

Merged
proggeramlug merged 3 commits into
mainfrom
gc/6986-builtin-ctor-roots
Aug 9, 2026
Merged

fix(gc): root builtin.rs's constructor arguments across each other's lowering (#6986)#7719
proggeramlug merged 3 commits into
mainfrom
gc/6986-builtin-ctor-roots

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #6986. #7699 fixed the three non-class branches of lower_new_impl_inner (lower_call/new.rs) named in the issue and explicitly left lower_call/builtin.rs's arms open, with the note "builtin.rs's ~22 arms stay open, with the inventory on the issue."

An arm-by-arm audit found 30 match arms (not the rough "~22" estimate — the issue's own author said "I did not audit each one") that lower args[0] then args[1] (then, for several, discard the rest for side effects) with plain lower_expr and no rooting decision at all: Utf8Stream, EvalError/URIError, Uint8Array, the typed-array-view family, DataView, RegExp, EventEmitter, EventEmitterAsyncResource, SocketAddress, BroadcastChannel, Event, CustomEvent, DOMException, Console, StringDecoder, the Readable/Writable/Duplex/Transform/PassThrough family, LRUCache, DatabaseSync, StatementSync, Session, RateLimiterMemory, CronJob, AsyncResource, SuppressedError, WeakMap, WeakSet, TextDecoderStream, CompressionStream/DecompressionStream, ReadableStreamBYOBReader, CountQueuingStrategy/ByteLengthQueuingStrategy.

WeakMap/WeakSet are a variant of the same bug rather than the textbook shape: the iterable argument was lowered (eagerly, via .map(lower_expr)), then js_weakmap_new/js_weakset_new — an unconditional allocation — ran, and only then was the iterable's now-possibly-stale register read back out.

What changed

lower_builtin_new now takes the caller's RootedGroup (threaded in from lower_new_impl_inner, which already opens one per #6969/#7699 — both of lower_builtin_new's call sites in new.rs pass it through). Three small helpers do the adoption, following #7699's own stated discipline exactly — "adopt as the value is produced, never after the fact; rooting a finished list publishes an already-dangling argument 0, which is worse than not rooting at all":

  • adopt_optional_arg — the one-operand-at-a-time primitive: lowers args[idx] if present, rooted across everything from args[idx+1..].
  • adopt_leading_arg_discard_rest — the single-leading-argument-plus-discard-loop shape (>12 arms).
  • adopt_two_leading_args_discard_rest — the two-leading-arguments-plus-discard-loop shape (Event, CustomEvent, DOMException, Console, TextDecoderStream).

CronJob needed a bespoke ordering rather than the generic helpers: its cronTime argument's raw-pointer derivation (js_get_string_pointer_unified) can itself allocate (SSO materialize), so it has to run before onTick/start are re-read from their slots, not after — otherwise the fix would trade one unrooted register for another.

Explicitly out of scope, and why: the extract_options_fields-based arms (Response, Request, Blob, File, Headers, ReadableStream, WritableStream, TransformStream) share the same underlying hazard — an earlier field's lowered value can sit unrooted across a later field's — but their per-property-match loop over a dynamic Vec<(String, Expr)> is a structurally different shape from the fixed args[0]/args[1]/args[2] sequence. Reusing these three helpers there isn't a good fit; it needs its own audit.

Verification

Unit tests (crates/perry-codegen/src/temp_root_coverage/builtin_ctor.rs, so they run in the per-PR --lib --bins gate, not the nightly-only tests/*.rs tier): six tests covering all three helper shapes plus the WeakMap variant, through perry_codegen::testing::temp_slots's codegen-contract assertions (same infrastructure #6969/#6983 used). Sabotage-confirmed: copied onto a clean origin/main checkout, the five positive assertions fail against the pre-fix code (%r1 is never stored into a rooted slot, etc.); the paired negative gate (RegExp with two non-allocating arguments) passes on both, so the positives aren't vacuously satisfied by a compiler that roots everything.

Static checker (scripts/gc_root_dominance_check.py), both lowerings:

  • The existing corpus (scripts/gc_root_dominance_corpus.sh, test_gap_gc_*/test_gap_new*/etc.) reads 0 violations before and after — it contains no source that constructs any of these built-ins, so it can't show a reduction either way.
  • A scoped probe built from ordinary TS object/array-literal arguments also read 0 both ways — not because the fix is a no-op, but because Phase-3 closed-shape synthesis and the array-literal inline-bump-allocator diamond each bind their own shadow slot for unrelated reasons, which happens to also cover a plain constructor argument end-to-end. This is a real gap in what a naive probe can show, not a claim of full checker coverage.
  • Switching to the shape the project's own reproducers use elsewhere (fresh(k) / "x" + churn(N), matching gc: constructor arguments (new C(a, b)) are not precise roots across the instance allocation #6969's own new Function(fresh(0), "return " + churn(N))) found the checker's actual reach: on new DataView(fresh(1), "o"+churn(N), "l"+churn(N)) and new SuppressedError(fresh(3), "y"+churn(N), "z"+churn(N)), the middle argument's own producing call (js_string_concat_value) is itself in ALLOC_RE, and the checker reports it stale.
    • --stale-registers (shadow, PERRY_RS4GC=0): 2 → 0.
    • --statepoints (native, PERRY_RS4GC=1 + production rewrite-statepoints-for-gc): 2 unrooted → 0.
    • The 2-argument arms in the same probe (RegExp, EventEmitter) read 0 both ways in this probe because their first argument comes from a plain user-function call (fresh(k)), which ALLOC_RE — by design, it matches js_* runtime symbols — doesn't recognize as a source. Their fix is the identical shape and is covered by the unit tests above.

cargo test -p perry-codegen --no-fail-fast (full suite including the nightly-only tests/*.rs tier, which per-PR CI does not run): 6 pre-existing failures, identical on a clean origin/main baseline built in a separate worktree — large_local_array_push_inbounds_store_emits_precise_slot_barrier, proven_buffer_and_typed_array_reads_are_numeric_operands, reassigned_typed_array_store_records_runtime_fallback, integer_modulo::i32_counter_mod_unsafe_or_nonliteral_divisors_keep_frem, typed_f64_receiver_method_clone_raw_loads_after_composed_guards, integer_arithmetic_array_push_omits_inbounds_layout_note_and_barrier (tracked in #7708). No new failures. --lib --bins (the per-PR gate): 803 passed, 0 failed (797 pre-existing + 6 new).

cargo fmt --all -- --check and cargo clippy -p perry-codegen --lib --bins --no-deps are both clean on the touched files.

Test plan

  • cargo test -p perry-codegen --lib --bins --no-fail-fast — 803 passed
  • cargo test -p perry-codegen --no-fail-fast (full suite) — same 6 pre-existing failures as clean origin/main, no new ones
  • New sabotage-confirmed unit tests in temp_root_coverage::builtin_ctor
  • scripts/gc_root_dominance_check.py before/after on a scoped probe, both lowerings (2→0)
  • cargo fmt --all -- --check
  • cargo clippy -p perry-codegen --lib --bins --no-deps

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability when creating built-in objects whose arguments may trigger memory collection.
    • Preserved previously evaluated constructor arguments across allocations, including collections, typed data, regular expressions, streams, and asynchronous resources.
    • Fixed initialization handling for weak collections to prevent values from being lost or improperly collected.
  • Tests

    • Added coverage for constructor argument preservation across supported lowering modes and allocation scenarios.

proggeramlug pushed a commit that referenced this pull request Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@proggeramlug, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e02833f4-7d27-4109-806d-f76ea7aa9432

📥 Commits

Reviewing files that changed from the base of the PR and between 96c2553 and 9589f94.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • CLAUDE.md
  • Cargo.toml
📝 Walkthrough

Walkthrough

Built-in constructor lowering now adopts constructor arguments into the active RootedGroup before later lowering or allocation. The change covers standard and specialized constructors, including WeakMap, WeakSet, and CronJob, with targeted temporary-root coverage tests.

Changes

Built-in constructor rooting

Layer / File(s) Summary
RootedGroup wiring and helpers
crates/perry-codegen/src/lower_call/builtin.rs, crates/perry-codegen/src/lower_call/new.rs
Built-in constructor lowering receives the active RootedGroup. Shared helpers adopt and reread leading constructor arguments.
Standard constructor argument migration
crates/perry-codegen/src/lower_call/builtin.rs
Typed-array, DataView, RegExp, event, stream, encoding, and related constructors preserve arguments across later expression lowering.
Special constructor paths
crates/perry-codegen/src/lower_call/builtin.rs
DatabaseSync, StatementSync, Session, CronJob, SuppressedError, WeakMap, and WeakSet use rooted arguments across sequential evaluation and allocation.
Root coverage and changelog
crates/perry-codegen/src/temp_root_coverage/*, changelog.d/7719-builtin-ctor-roots.md
Tests cover RegExp, EventEmitter, DataView, SuppressedError, and WeakMap. The changelog records the fix and validation results.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ExprNew
  participant lower_builtin_new
  participant RootedGroup
  participant RuntimeConstructor
  ExprNew->>lower_builtin_new: Lower built-in constructor
  lower_builtin_new->>RootedGroup: Adopt evaluated arguments
  lower_builtin_new->>lower_builtin_new: Lower later arguments and side effects
  lower_builtin_new->>RootedGroup: Reread preserved arguments
  lower_builtin_new->>RuntimeConstructor: Invoke constructor with rooted values
Loading

Possibly related issues

Possibly related PRs

  • PerryTS/perry#7651 — Provides the RootedGroup infrastructure extended here for built-in constructor arguments.
  • PerryTS/perry#7670 — Directly modifies built-in constructor lowering and RootedGroup-based argument rooting.
  • PerryTS/perry#7699 — Addresses constructor argument rooting in lower_call/new.rs, which this change extends to built-in constructors.

Suggested reviewers: thehypnoo, jdalton

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: rooting built-in constructor arguments across their lowering.
Description check ✅ Passed The description covers the summary, changes, related issue, test plan, scope limits, verification results, and known pre-existing failures.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gc/6986-builtin-ctor-roots

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-codegen/src/lower_call/builtin.rs`:
- Around line 230-239: Update the affected constructor arms in
crates/perry-codegen/src/lower_call/builtin.rs:230-239, 264-273, and 298-312 to
lower and discard args[3..] after the optional length; at 332-340, 633-642,
655-664, 677-686, and 851-860, lower and discard args[2..] after the supported
flags, options, or arg1; and at 899-913, lower and discard args[3..] after the
message. Ensure every extra argument is evaluated for side effects while
preserving the existing supported-argument lowering.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: effc7429-9d58-4f7e-8278-f6b3725f284c

📥 Commits

Reviewing files that changed from the base of the PR and between e732f82 and 96c2553.

📒 Files selected for processing (5)
  • changelog.d/7719-builtin-ctor-roots.md
  • crates/perry-codegen/src/lower_call/builtin.rs
  • crates/perry-codegen/src/lower_call/new.rs
  • crates/perry-codegen/src/temp_root_coverage/builtin_ctor.rs
  • crates/perry-codegen/src/temp_root_coverage/mod.rs

Comment on lines +230 to +239
let source_collects = rooting::any_operand_may_collect(ctx, args[1..].iter());
let source_idx = group.lower(ctx, &args[0], source_collects)?;
let offset_collects = rooting::any_operand_may_collect(ctx, args[2..].iter());
let offset_idx = group.lower(ctx, &args[1], offset_collects)?;
let length_idx = adopt_optional_arg(ctx, args, 2, group)?;
let source = group.reread(ctx, source_idx)?;
let offset_box = group.reread(ctx, offset_idx)?;
let length_box = match length_idx {
Some(i) => group.reread(ctx, i)?,
None => double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Evaluate ignored constructor arguments.

These arms lower only their supported arguments. They do not lower later arguments for side effects. JavaScript evaluates every argument before a constructor call. Add a discard loop after the last supported argument in each arm.

  • crates/perry-codegen/src/lower_call/builtin.rs#L230-L239: Lower args[3..] after the optional length.
  • crates/perry-codegen/src/lower_call/builtin.rs#L264-L273: Lower args[3..] after the optional length.
  • crates/perry-codegen/src/lower_call/builtin.rs#L298-L312: Lower args[3..] after the optional length.
  • crates/perry-codegen/src/lower_call/builtin.rs#L332-L340: Lower args[2..] after flags.
  • crates/perry-codegen/src/lower_call/builtin.rs#L633-L642: Lower args[2..] after options.
  • crates/perry-codegen/src/lower_call/builtin.rs#L655-L664: Lower args[2..] after arg1.
  • crates/perry-codegen/src/lower_call/builtin.rs#L677-L686: Lower args[2..] after arg1.
  • crates/perry-codegen/src/lower_call/builtin.rs#L851-L860: Lower args[2..] after options.
  • crates/perry-codegen/src/lower_call/builtin.rs#L899-L913: Lower args[3..] after message.
📍 Affects 1 file
  • crates/perry-codegen/src/lower_call/builtin.rs#L230-L239 (this comment)
  • crates/perry-codegen/src/lower_call/builtin.rs#L264-L273
  • crates/perry-codegen/src/lower_call/builtin.rs#L298-L312
  • crates/perry-codegen/src/lower_call/builtin.rs#L332-L340
  • crates/perry-codegen/src/lower_call/builtin.rs#L633-L642
  • crates/perry-codegen/src/lower_call/builtin.rs#L655-L664
  • crates/perry-codegen/src/lower_call/builtin.rs#L677-L686
  • crates/perry-codegen/src/lower_call/builtin.rs#L851-L860
  • crates/perry-codegen/src/lower_call/builtin.rs#L899-L913
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/lower_call/builtin.rs` around lines 230 - 239,
Update the affected constructor arms in
crates/perry-codegen/src/lower_call/builtin.rs:230-239, 264-273, and 298-312 to
lower and discard args[3..] after the optional length; at 332-340, 633-642,
655-664, 677-686, and 851-860, lower and discard args[2..] after the supported
flags, options, or arg1; and at 899-913, lower and discard args[3..] after the
message. Ensure every extra argument is evaluated for side effects while
preserving the existing supported-argument lowering.

Ralph Küpper added 3 commits August 9, 2026 18:52
…lowering (#6986)

30 arms of lower_call/builtin.rs's lower_builtin_new lowered args[0] then
args[1] (then, for several, discarded the rest for side effects) with plain
lower_expr and no rooting decision — the same #6969 shape #7699 fixed in
lower_new.rs's three non-class branches, left open by that PR for this file.
WeakMap/WeakSet are a variant: the iterable was lowered, then js_weakmap_new
(an unconditional allocation) ran, and only then was the iterable's
now-possibly-stale register read.

lower_builtin_new now takes the caller's RootedGroup (threaded in from
lower_new_impl_inner, which already opens one per #6969/#7699) and three
helpers adopt each operand into it as it is produced, never after the fact.
CronJob needed a bespoke ordering: its raw-pointer derivation can itself
allocate, so it has to run before the other two operands are re-read.

Left out of scope: the extract_options_fields-based arms (Response, Request,
Blob, File, Headers, ReadableStream, WritableStream, TransformStream) share
the hazard but are a structurally different shape.
@proggeramlug
proggeramlug force-pushed the gc/6986-builtin-ctor-roots branch from 96c2553 to 9589f94 Compare August 9, 2026 16:52
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Merging as v0.5.1416

30 arms, not ~22 — the issue's own author flagged that estimate as unaudited, and it was low. Fixed by threading the caller's RootedGroup into lower_builtin_new from both lower_new_impl_inner call sites, plus three helpers that follow #7699's discipline exactly (adopt as produced, never after the fact — adopting late is how the store ends up after a call that allocates, which is #7192's shape). CronJob and WeakMap/WeakSet needed bespoke handling.

The honest part, and the reason I'm confident in this

The static checker showed 0/0 both ways on the existing gate corpus — because that corpus contains no source exercising these constructors, so it cannot show a reduction. A naive TS-literal probe also read 0/0, and the report explains why rather than shrugging: Phase-3 object-literal synthesis and the array-literal inline-bump allocator each bind their own unrelated shadow slot that happens to also cover a plain constructor argument end-to-end.

Only on switching to the project's own reproducer shape (fresh(k) / "x"+churn(N), matching #6969's repro) did the checker reach the hazard: --stale-registers (shadow) 2 → 0, and --statepoints (native, with the production rewrite-statepoints-for-gc rewrite) 2 unrooted → 0.

That sequence is worth keeping. A checker reporting 0/0 is the single easiest way to conclude "no bug here", and three separate reasons for a false zero showed up before the real signal did.

Primary verification is therefore 6 sabotage-confirmed unit tests in temp_root_coverage/builtin_ctor.rs — deliberately placed where per-PR --lib can see them, since crates/*/tests/*.rs is nightly/tag-only. Five positive assertions fail against pre-fix code and pass after; the paired negative gate passes on both, so it isn't a one-directional test.

cargo test -p perry-codegen --no-fail-fast: the same 6 pre-existing failures on this branch and on a freshly built clean origin/main (#7708) — no new ones. --lib --bins: 803/803. Lint 19/19.

Named, not silently skipped

The extract_options_fields arms — Response, Request, Blob, File, Headers, ReadableStream, WritableStream, TransformStream — share the hazard but use a structurally different per-field-loop shape, and are called out in both the PR body and the changelog. That's the right way to leave scope behind: an unfixed hazard that is written down is tractable; one that is quietly out of scope is not.

@proggeramlug
proggeramlug merged commit 72ef47a into main Aug 9, 2026
1 of 16 checks passed
@proggeramlug
proggeramlug deleted the gc/6986-builtin-ctor-roots branch August 9, 2026 16:59
proggeramlug pushed a commit that referenced this pull request Aug 9, 2026
…receiver hazard (#7664)

scripts/gc_root_dominance_check.py: the native/--statepoints chain treated a
phi as unconditionally transparent, so one tainted incoming edge blanket-
tainted the phi's result and a downstream use was checked against ANY CFG
path between source and use (between_blocks is deliberately path-insensitive,
sound for an ordinary register but not for a phi, whose dynamic value depends
on which edge was actually taken). All four reported unmasked hits were the
same &&/|| short-circuit join: the tainted edge never crosses a safepoint,
the OTHER edge does, and the checker reported that.

_cast_closure gains phi_all_edges: a phi joins `chain` only once every
incoming edge is independently in it. That closes the false positive and
deliberately excludes the case of a single tainted edge with its own
intervening safepoint before its predecessor's terminator; _phi_edge_hazard
covers that separately, checking each edge's own window. Two new self-test
fixtures (phi_safe_edge / phi_hazard_edge) pin both directions, each verified
against a sabotaged copy of the checker to confirm it can still fail.

lower_call/property_get/static_dispatch.rs: (Lexer as any).lex(...) reads a
module-global receiver, then held it raw across arg-bundling logic that can
allocate (a rest-param bundle always allocates; an object-literal argument
can too) before implicit_this_save/js_static_this_arm_value read the stale
copy -- the same #6969/#6986 shape #7719 just fixed in lower_call/builtin.rs,
here on the receiver. Wrapped it in RootedGroup::adopt/reread.

Re-verified against the current corpus: the checker fix eliminates exactly
the four phi false positives with nothing else changing. The static-dispatch
fix was not yet re-verified against a fresh corpus run after this rebase
(disk pressure and box load made prior corpus runs unreliable) -- see the PR
description for exactly what is and isn't confirmed.
proggeramlug pushed a commit that referenced this pull request Aug 9, 2026
…7664)

Re-verifying the checker fix found 9 real+false hits, not the 8 the prior
snapshot recorded -- test_gap_static_method_value_name_collision joined the
population after #7691 without the budget being re-measured. Of the 9: 4 were
the checker's own phi-edge false positives (fixed in the prior commit), 3
were unrooted:global (2 already fixed upstream by #7719, 1 fixed in the prior
commit's static_dispatch.rs change), and 2 are unrooted:capture -- real,
diagnosed, and tracked as this budget's referent rather than rushed.

Measured on the native corpus, both arms of --moving-only, stale still 0.
proggeramlug added a commit that referenced this pull request Aug 9, 2026
…--max-unrooted to 2 (#7664) (#7724)

* gc: fix the phi-edge checker false positives and the static-dispatch receiver hazard (#7664)

scripts/gc_root_dominance_check.py: the native/--statepoints chain treated a
phi as unconditionally transparent, so one tainted incoming edge blanket-
tainted the phi's result and a downstream use was checked against ANY CFG
path between source and use (between_blocks is deliberately path-insensitive,
sound for an ordinary register but not for a phi, whose dynamic value depends
on which edge was actually taken). All four reported unmasked hits were the
same &&/|| short-circuit join: the tainted edge never crosses a safepoint,
the OTHER edge does, and the checker reported that.

_cast_closure gains phi_all_edges: a phi joins `chain` only once every
incoming edge is independently in it. That closes the false positive and
deliberately excludes the case of a single tainted edge with its own
intervening safepoint before its predecessor's terminator; _phi_edge_hazard
covers that separately, checking each edge's own window. Two new self-test
fixtures (phi_safe_edge / phi_hazard_edge) pin both directions, each verified
against a sabotaged copy of the checker to confirm it can still fail.

lower_call/property_get/static_dispatch.rs: (Lexer as any).lex(...) reads a
module-global receiver, then held it raw across arg-bundling logic that can
allocate (a rest-param bundle always allocates; an object-literal argument
can too) before implicit_this_save/js_static_this_arm_value read the stale
copy -- the same #6969/#6986 shape #7719 just fixed in lower_call/builtin.rs,
here on the receiver. Wrapped it in RootedGroup::adopt/reread.

Re-verified against the current corpus: the checker fix eliminates exactly
the four phi false positives with nothing else changing. The static-dispatch
fix was not yet re-verified against a fresh corpus run after this rebase
(disk pressure and box load made prior corpus runs unreliable) -- see the PR
description for exactly what is and isn't confirmed.

* gate(gc): lower gc-root-dominance-statepoints' --max-unrooted to 2 (#7664)

Re-verifying the checker fix found 9 real+false hits, not the 8 the prior
snapshot recorded -- test_gap_static_method_value_name_collision joined the
population after #7691 without the budget being re-measured. Of the 9: 4 were
the checker's own phi-edge false positives (fixed in the prior commit), 3
were unrooted:global (2 already fixed upstream by #7719, 1 fixed in the prior
commit's static_dispatch.rs change), and 2 are unrooted:capture -- real,
diagnosed, and tracked as this budget's referent rather than rushed.

Measured on the native corpus, both arms of --moving-only, stale still 0.

* chore: key the changelog fragment on PR #7724

* chore: point the budget referent at the split-out #7725

* chore: bump version to 0.5.1420

Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix

* style: cargo fmt

Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix

---------

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gc: constructor arguments on lower_new's non-class branches (Readline / imported ctor / new Function) and builtin.rs are still not precise roots

1 participant