perf(repsel): open the array-element escape for Ptr<Shape> (#7034 §3) - #7149
Conversation
9d4647b to
ecd4b6c
Compare
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (20)
🚧 Files skipped from review as they are similar to previous changes (14)
📝 WalkthroughWalkthroughAdds Phase 3b analysis for pointer-shaped objects stored in proven arrays. Integrates element provenance with pointer-shape promotion, return validation, reporting, census checks, regression coverage, and Zig benchmark cache configuration. ChangesPtr Shape Array-Element Analysis
Zig Cache Isolation
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
Suggested labels: Sequence Diagram(s)sequenceDiagram
participant HIRFacts
participant ElementShapeFacts
participant PtrShape
participant ReturnShape
participant RepselCensus
HIRFacts->>ElementShapeFacts: collect proven array-element facts
HIRFacts->>PtrShape: pass ElementShapeFacts
PtrShape->>PtrShape: promote indexed-read locals and validate contained pushes
PtrShape->>ReturnShape: validate promoted return locals
RepselCensus->>PtrShape: measure selected and consumed promotions
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
f1061cf to
9cbfe9f
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
crates/perry-codegen/src/collectors/ptr_shape_elements_tests.rs (2)
190-200: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider passing the test's own class list into
facts_for.
facts_foralways builds the module from a pristineclass_c(). Two tests then analyze a different class set:mixed_element_classes_deny_the_arrayaddsDonly to theclassesmap, andan_inadmissible_element_class_denies_the_arrayadds the getter only to theclassesmap. The assertions still hold, because both denials come from theclassesmap. The module dispatch facts, however, are computed over a class set that does not exist in the fixture. If a future barrier or admissibility rule reads the module instead of the map, these tests would pass for the wrong reason.A small change keeps the two views consistent: let
promoteandelementsforward theirclassesvalues into the HIR module.🤖 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/collectors/ptr_shape_elements_tests.rs` around lines 190 - 200, Update the test helpers facts_for, promote, and elements so facts_for accepts the test’s class list and constructs its HIR from that list, while promote and elements forward their classes values when computing ModuleDispatchFacts. Keep the existing baseline class_c() and assertions intact while ensuring the classes map and module use the same fixture classes.
683-697: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for the array alias path.
The collector resolves array aliases through
collect_alias_edgesand closes over them in a fixpoint loop. The module comment inptr_shape_elements.rsstates that this exists because thefor…ofdesugar bindsconst __arr_N = <the iterable>before the loop. No test in this file reads or pushes through an alias, so the alias fixpoint and the alias poisoning conditions (boxed_vars,module_globals,let_counts != 1) have no red set. Thefor…ofread form named at line 470 is therefore only covered in its non-alias shape.Add two cases: one positive, where
const b = athen a bounded read ofb[i]promotes; one negative, where the alias is re-declared and the read is denied.🤖 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/collectors/ptr_shape_elements_tests.rs` around lines 683 - 697, Extend the tests around promote and the existing alias helpers with two array-alias cases: verify that a single declaration assigning alias b from array a, followed by a bounded read of b[i], promotes the relevant producer/reader; then verify that re-declaring b causes the aliased read to remain unpromoted. Cover the alias-resolution fixpoint and poisoning condition through the existing boxed_vars, module_globals, or let_counts behavior without changing collector implementation.crates/perry-codegen/src/collectors/ptr_shape_elements.rs (1)
890-953: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse the existing
walk_stmtshelper instead of a second traversal.The inner
walkfunction repeats the traversal that the freewalk_stmtsat lines 839-882 already implements. Two traversals must stay identical, because one issues the facts and the other issues the seeds. If one gains a statement arm and the other does not,ptr_shape.rsseeds a candidate set that disagrees withelement_reads.♻️ Proposed refactor
pub(super) fn element_read_seeds( stmts: &[Stmt], element_facts: &ElementShapeFacts, ) -> Vec<(u32, String)> { let mut out = Vec::new(); if element_facts.is_empty() { return out; } - fn walk(stmts: &[Stmt], facts: &ElementShapeFacts, out: &mut Vec<(u32, String)>) { - // … 50 lines of duplicated statement traversal … - } - walk(stmts, element_facts, &mut out); + walk_stmts(stmts, &mut |s| { + if let Stmt::Let { + id, + init: Some(Expr::IndexGet { .. }), + .. + } = s + { + if let Some(class_name) = element_facts.element_read_class(*id) { + out.push((*id, class_name.to_string())); + } + } + }); out }🤖 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/collectors/ptr_shape_elements.rs` around lines 890 - 953, Update element_read_seeds to reuse the existing walk_stmts traversal helper instead of defining its local walk function. Preserve the existing IndexGet filtering and seed collection by supplying a callback or equivalent visitor logic to walk_stmts, ensuring traversal coverage remains identical to element_reads.
🤖 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 @.github/workflows/test.yml:
- Around line 1275-1286: Update the repository branch-protection configuration
to mark the repsel-census status check as required. Ensure the existing
array-element liveness assertion in the repsel-census workflow cannot be
bypassed when it fails.
In `@changelog.d/7149-ptr-shape-array-element-escape.md`:
- Line 116: Fix the Markdown syntax in the changelog sentence beginning with
“#7139+this-change” so it is treated as paragraph text rather than a heading;
reflow the sentence or escape the hash while preserving its meaning.
In `@crates/perry-codegen/src/collectors/ptr_shape_elements.rs`:
- Around line 727-742: Update the Expr::ArrayPush handling to disqualify the
pushed local when an Expr::LocalGet value resolves to a tracked root, while
preserving PushValue::Local for producer locals. Ensure this disqualification
occurs before the existing early return, since LocalGet values are intentionally
not passed to walk_expr.
- Around line 692-725: Track property names written through element accesses in
ArrayWalk for each array root instead of discarding property in PropertySet and
ignoring it in PropertyUpdate. After class_name is resolved in
collect_element_shape_facts, reject the root when any recorded property is not a
declared chain field for that class; preserve existing root disqualification for
direct array writes and admissibility for valid element accesses.
In `@crates/perry-codegen/src/collectors/ptr_shape.rs`:
- Around line 540-562: Update the group-integrity removal logic around
element_facts.group_members so that when any member is dropped, it also removes
every tracked alias whose root resolves to that dropped member, not just the
reported member IDs. Apply report::deny_local to each removed tracked ID,
preserving the existing all-or-nothing behavior for the group.
---
Nitpick comments:
In `@crates/perry-codegen/src/collectors/ptr_shape_elements_tests.rs`:
- Around line 190-200: Update the test helpers facts_for, promote, and elements
so facts_for accepts the test’s class list and constructs its HIR from that
list, while promote and elements forward their classes values when computing
ModuleDispatchFacts. Keep the existing baseline class_c() and assertions intact
while ensuring the classes map and module use the same fixture classes.
- Around line 683-697: Extend the tests around promote and the existing alias
helpers with two array-alias cases: verify that a single declaration assigning
alias b from array a, followed by a bounded read of b[i], promotes the relevant
producer/reader; then verify that re-declaring b causes the aliased read to
remain unpromoted. Cover the alias-resolution fixpoint and poisoning condition
through the existing boxed_vars, module_globals, or let_counts behavior without
changing collector implementation.
In `@crates/perry-codegen/src/collectors/ptr_shape_elements.rs`:
- Around line 890-953: Update element_read_seeds to reuse the existing
walk_stmts traversal helper instead of defining its local walk function.
Preserve the existing IndexGet filtering and seed collection by supplying a
callback or equivalent visitor logic to walk_stmts, ensuring traversal coverage
remains identical to element_reads.
🪄 Autofix (Beta)
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: c984e34e-d07a-491f-a45b-c3eacdf21238
📒 Files selected for processing (16)
.github/workflows/test.ymlbenchmarks/repsel_census/baseline.jsonbenchmarks/repsel_census/fixtures/fixture_ptr_shape_elements.tschangelog.d/7149-ptr-shape-array-element-escape.mdcrates/perry-codegen/src/collectors/hir_facts.rscrates/perry-codegen/src/collectors/mod.rscrates/perry-codegen/src/collectors/ptr_shape.rscrates/perry-codegen/src/collectors/ptr_shape_elements.rscrates/perry-codegen/src/collectors/ptr_shape_elements_tests.rscrates/perry-codegen/src/collectors/ptr_shape_opt_report_tests.rscrates/perry-codegen/src/collectors/ptr_shape_report.rscrates/perry-codegen/src/collectors/ptr_shape_returns.rscrates/perry-codegen/src/collectors/ptr_shape_returns_tests.rsscripts/compiler_output_harness/repsel_census.pytest-files/test_gap_repsel_ptr_shape_elements.tstest-parity/gc_repsel_corpus.txt
397e09b to
c1e4ce7
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
crates/perry-codegen/src/collectors/ptr_shape_elements_tests.rs (2)
891-910: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename the getter so it does not shadow a declared field.
class_c()declaresxas a field at line 39. This test then adds a getter also namedx. The resulting class shape is self-contradictory and does not match any real Perry class. The guard under test ischain_admissible, which rejects the class for having any getter, so the test still isolates the intended guard. A distinct getter name removes the ambiguity.♻️ Optional: use a getter name that is not a declared field
let mut c = class_c(); c.getters = vec![( - "x".to_string(), + "computed".to_string(), perry_hir::Function { id: 50, - name: "x".to_string(), + name: "computed".to_string(),🤖 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/collectors/ptr_shape_elements_tests.rs` around lines 891 - 910, Rename the getter entry in the test setup from "x" to a distinct property name not declared by class_c(), while preserving the getter’s implementation and the chain_admissible assertion that rejects any getter.
190-223: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider passing the test's own classes into
facts_for.
facts_foralways pushes a pristineclass_c()into the HIR module.promoteandelementsthen callfacts_for(Vec::new(), Vec::new()). So the module used forcollect_module_dispatch_factscan differ from theclassesmap used by the collector.Two tests rely on this:
mixed_element_classes_deny_the_arrayregistersDonly in theclassesmap, andan_inadmissible_element_class_denies_the_arrayadds a getter toConly in theclassesmap. Both tests assert denial today, so the divergence does not create a false pass now. If a future change makesModuleDispatchFactsdepend on getters or on the full class set, the fixtures will stop modelling the intended input.A small change keeps the two views in sync.
♻️ Optional: derive dispatch facts from the same class set
-fn elements(stmts: &[Stmt], classes: &HashMap<String, &Class>) -> ElementShapeFacts { - elements_with(stmts, classes, &facts_for(Vec::new(), Vec::new())) +fn facts_of(classes: &HashMap<String, &Class>) -> ModuleDispatchFacts { + let mut hir = Module::new("t"); + hir.classes = classes.values().map(|c| (*c).clone()).collect(); + super::super::collect_module_dispatch_facts(&hir) +} + +fn elements(stmts: &[Stmt], classes: &HashMap<String, &Class>) -> ElementShapeFacts { + elements_with(stmts, classes, &facts_of(classes)) }🤖 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/collectors/ptr_shape_elements_tests.rs` around lines 190 - 223, Update the test helpers facts_for, elements, and promote so dispatch facts are built from the same classes map used by the collectors, rather than always using a standalone class_c() fixture. Preserve the existing function inputs and ensure both mixed_element_classes_deny_the_array and an_inadmissible_element_class_denies_the_array model identical class sets across ModuleDispatchFacts and collector inputs.
🤖 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/collectors/ptr_shape_elements_tests.rs`:
- Line 882: Update ElementShapeFacts::is_empty to check arrays, array_roots,
pushed, and element_reads, returning true only when all fact collections are
empty; preserve its use as the gate for fact consumers.
---
Nitpick comments:
In `@crates/perry-codegen/src/collectors/ptr_shape_elements_tests.rs`:
- Around line 891-910: Rename the getter entry in the test setup from "x" to a
distinct property name not declared by class_c(), while preserving the getter’s
implementation and the chain_admissible assertion that rejects any getter.
- Around line 190-223: Update the test helpers facts_for, elements, and promote
so dispatch facts are built from the same classes map used by the collectors,
rather than always using a standalone class_c() fixture. Preserve the existing
function inputs and ensure both mixed_element_classes_deny_the_array and
an_inadmissible_element_class_denies_the_array model identical class sets across
ModuleDispatchFacts and collector inputs.
🪄 Autofix (Beta)
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: 42d43d44-f3f4-4f43-a00f-580122e3d7a8
📒 Files selected for processing (17)
.github/workflows/test.ymlbenchmarks/repsel_census/baseline.jsonbenchmarks/repsel_census/fixtures/fixture_ptr_shape_elements.tschangelog.d/7149-ptr-shape-array-element-escape.mdcrates/perry-codegen/src/collectors/hir_facts.rscrates/perry-codegen/src/collectors/mod.rscrates/perry-codegen/src/collectors/ptr_shape.rscrates/perry-codegen/src/collectors/ptr_shape_elements.rscrates/perry-codegen/src/collectors/ptr_shape_elements_tests.rscrates/perry-codegen/src/collectors/ptr_shape_opt_report_tests.rscrates/perry-codegen/src/collectors/ptr_shape_report.rscrates/perry-codegen/src/collectors/ptr_shape_returns.rscrates/perry-codegen/src/collectors/ptr_shape_returns_tests.rscrates/perry-codegen/src/linker.rsscripts/compiler_output_harness/repsel_census.pytest-files/test_gap_repsel_ptr_shape_elements.tstest-parity/gc_repsel_corpus.txt
🚧 Files skipped from review as they are similar to previous changes (14)
- crates/perry-codegen/src/collectors/hir_facts.rs
- crates/perry-codegen/src/collectors/ptr_shape_returns.rs
- crates/perry-codegen/src/collectors/ptr_shape_opt_report_tests.rs
- crates/perry-codegen/src/collectors/ptr_shape_returns_tests.rs
- scripts/compiler_output_harness/repsel_census.py
- crates/perry-codegen/src/collectors/mod.rs
- test-parity/gc_repsel_corpus.txt
- .github/workflows/test.yml
- benchmarks/repsel_census/baseline.json
- test-files/test_gap_repsel_ptr_shape_elements.ts
- crates/perry-codegen/src/collectors/ptr_shape_report.rs
- crates/perry-codegen/src/collectors/ptr_shape.rs
- benchmarks/repsel_census/fixtures/fixture_ptr_shape_elements.ts
- crates/perry-codegen/src/collectors/ptr_shape_elements.rs
816a5a3 to
5a7e2d3
Compare
`Ptr<Shape>` rule 2 disqualifies a local at any escape. #7034 §4 opened `return`; this opens `array/object element` for the array half — `rows.push(row)` and `for (const r of rows) r.field`. A `return` is a terminator, so no use of the local can follow it. An element escape is not: the object stays reachable through the array for the rest of its life. The containment region therefore widens from one local to one local array and everything derived from it, with the array's own uses bounded exactly as an object local's are (`collectors/ptr_shape_elements.rs`, five conjuncts E1-E5, each with its own red set). E5 — an element read is licensed only under `for (let i = 0; i < A.length; i++)` — is the whole difference between this pass and a wrong one: without it `A[i]` can be `undefined`, and a guard-free fixed-offset load masks a NaN-boxed `undefined` into a wild pointer. Measured on a build-then-consume kernel: 0 -> 3 promotions, all consumed, and the promoted body loses all 7 `js_typed_feedback_class_field_get_guard`, all 7 `js_typed_feedback_record_fallback_call` and all 7 `js_object_get_field_by_name_f64` calls (72 -> 51 `js_*`, 1426 -> 1076 IR lines) with every GC-related call count identical. `batch.ts` is unchanged, and dependency JS gains ~nothing: over 180 real CJS modules the element position accounts for 1 of 746 denials. Both findings, and the rule-1 wall they point at instead, are in the changelog fragment.
5a7e2d3 to
6a3b5bf
Compare
Review round addressed — all 6 inline findings + all 4 nitpicksEvery reproducer was added as a test before any fix, so each finding had to prove itself red first. Result on the unfixed HEAD:
Nitpicks — all four taken, because three are the "test passes for the wrong reason" shape and one is the "two traversals drift" shape, both of which have bitten this campaign:
Re-validation (rebased onto
|
|
Matrix re-run against the fixed compiler is in: |
Ptr<Shape>rule 2 disqualifies a local at any escape. #7034 §4 openedreturn; this opensarray/object elementfor the array half —rows.push(row)andfor (const r of rows) r.field.returnwas easy because a return is a terminator: no use of the local canfollow it, so every access the pass licensed had already run while the object
was unaliased. An element escape is not. The object stays reachable through the
array for the rest of its life, so the containment region had to widen from
one local to one local array and everything derived from it, and the
array's own uses had to be bounded exactly as an object local's are.
The rule (
collectors/ptr_shape_elements.rs)A region-local
Ais an element-shape-proven array of classCwhen allof:
Let { mutable: false, init: Array([]) },not boxed, not a module global. The literal must be empty: a non-empty
one can carry elisions, whose slots read back as
undefined.ArrayPushofnew C(...),inline or via a local bound by one
Let { init: New { C } }and pushedexactly once. No other mutator at all — no
pop/shift/splice/unshift/copyWithin, noIndexSet, nolengthwrite. That is what makesAdense and monomorphic for its whole lifetime..lengthread, orreturn A(repsel row 1: scoping Ptr<Shape> beyond locals — measured, promotion is 0 on the motivating workload; build --opt-report (#6952) first #7034 §4's terminator exemption, unchanged).Call argument, closure capture, reassignment, container element, unrecognised
array method: all still disqualify.
Cpasses the samechain_admissiblegate rule 1applies to a
new C(...)local, and the rule-5 module barrier is clear.A[i]is licensed only whereiis the inductionvariable of an enclosing
for (let i = 0; i < A.length; i++), writtennowhere else in the region. This conjunct is the whole difference between
this pass and a wrong one. Without it
A[i]can beundefined, and aguard-free fixed-offset load masks a NaN-boxed
undefinedinto a wildpointer. E3 admits no mutator that can shrink
Aand E2 makes it dense, so0 <= i < A.lengthat the read meansA[i]is an own element of classC.for (const r of A)desugars to exactly the E5 shape(
lower/stmt_loops.rs::lazy_or_index_elem), so the iterator form is covered bythe indexed proof rather than by a second one.
Then two halves in
ptr_shape.rs:A.push(row)stops disqualifyingrow, andconst r = A[i]at a licensed site is rule-1 provenance ofnew-strength.Group integrity. Every member of an element group — the pushed producers
plus the element-read locals — references objects the other members also
reach. One member failing rule 2 (
r.extra = 1, a closure capture, an opaquecall) can transition the shape the others read guard-free, so the group is
all-or-nothing:
collect_shape_proven_ptr_localsdrops every member when anyone fails. No fixpoint is needed — dropping never admits a member.
numeric_fieldsis not claimed for group members. The numeric proof is anexhaustive-reachable-store proof that containment makes possible because no
alias exists; a group has aliases by construction, and a sibling's
r.score = "s"(a declared field, so rule 2 permits it) downgrades the slot'sraw-f64 layout. Same stand-down as
proven_this.rsandptr_shape_returns.rs.The shape proof alone still retires the whole guard diamond.
What it buys, measured
On a build-then-consume kernel (40 000 records, produced with a local, then
read back both ways) the promoted function goes 0 → 3 selected, 3 consumed,
and its emitted IR loses:
js_typed_feedback_class_field_get_guardjs_typed_feedback_record_fallback_calljs_object_get_field_by_name_f64js_*callsEvery other
js_*call count is identical, including all ofjs_shadow_slot_bind(5),js_write_barrier_root_nanbox(5),js_write_barrier_slot(1),js_array_push_f64(2),js_gc_loop_safepoint(4) and the inline incremental-mark barrier sites (5). Nothing but guard
machinery went away.
GC contract, verified in emitted IR
js_shadow_slot_bindin the entry block (slots 3 and4 of the probe).
TaPtr's callee-side no-bind shortcut is not copied —GC_TYPE_OBJECTis movable (fix(gc): typed-array constructor sources are precise roots (#6981) #6990, gc: default-on evacuating young-gen scavenge (moving minor at precise safepoints) #7019).load double, ptr %rN→and POINTER_MASK→inttoptr→gep +header→gep index→load. Thefor…oflocal is reloaded 3× (3 field reads) andthe indexed local 4× (4 access sites); nothing is cached across a safepoint.
mark-barrier check and
js_write_barrier_root_nanbox.lowering, and
js_array_push_f64counts are identical between arms.ptr_shape_get_number.plain/.coercepair — the2-instruction plain-finite check with a cold arm — because the group claims
no numeric fields.
What this does NOT reach, and the measurements that say so
batch.tsis unchanged: 2 selected / 1 consumed, identical tomain. Bothof its element denials fail for reasons outside this rule:
buildRows'sconst rows = []; …; return rowsnever reaches the analysis —the interprocedural deforestation pass (
perry-transform/src/deforest)has already rewritten it into a
__deforest_outparameter, and a parameterarray has no provenance. That transform fires on exactly the
const a = []; …push…; return aproducer shape this rule targets, which is areal coverage hole rather than an incidental one.
summarize'sbyBucketis passed asrows.reduce(…)'s seed — a callargument, so the array escapes (repsel row 1: scoping Ptr<Shape> beyond locals — measured, promotion is 0 on the motivating workload; build --opt-report (#6952) first #7034 §1 territory).
Dependency JS gets essentially nothing. #7139 reported that ~103 candidates
its CJS barrier exemption freed were "immediately re-denied by rule 2", and
this position was picked on the assumption that those were element escapes.
They are not. Over 180 real
__esModuleCJS modules fromreal-apps/scriptc/node_modules, compiled by a #7139-only arm and a#7139+this-change arm (both 180/180), the 746
Ptr<Shape>candidates deny as:Both arms are identical on every line. The rule-2 bare references are all
Perry's own
__cjs_modulewrapper local, and the 506 rule-1 denials break downas constructor argument 182, statement 162, call argument 84, return 64, array
element 8, initializer 6. The wall in dependency JS is rule 1 (allocations
never bound to a local), not containment.
Validation
cargo test -p perry-codegen --lib: 429 passed (18 new).deleted in turn, the suite re-run, and the failing tests recorded: push
exemption, in-bounds read, both GC rooting obligations, group integrity,
single-push, empty-literal seed,
constarray binding, shrinking mutators,indexed store, class agreement, index write count, bare array reference,
closure capture,
.lengthreceiver. Control green in all 16 runs. Twoweaknesses it caught and fixed:
a_local_pushed_into_two_arrays_is_not_exemptpassed on
HashMapiteration order (now asserts the facts directly), and theclosure-capture test denied through the body walk rather than the capture
list.
fixture_ptr_shape_elementswith floorsptr-shape 3 / ptr-shape-consumed 3; no existing floor moved; thePERRY_PTR_SHAPE_LOCALS=0sabotage step inrepsel-censusnow asserts theelement fixture goes to zero too — without that, the whole analysis could
stop issuing facts and every counter in the job would be unchanged.
test_gap_repsel_ptr_shape_elements.ts, registered intest-parity/gc_repsel_corpus.txt, byte-exact against Node 26.5.1: both readforms, 200 allocations between two field reads of the same element local,
NaN/±Infinity/-0 written through one group member and read through another,
and four arrays that must not be proven.
Follow-ups filed from this work
buildRows).A[i].fieldreads are admitted by E3 but not promoted (consumptionkeys on
Expr::LocalGet).let, non-empty array literals, andsort/reverse/sliceon a proven array are all conservative refusals.never bound to a local.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests