Skip to content

chore: land #10846 + #10842 + #10843 (v0.5.1629) - #10875

Merged
proggeramlug merged 16 commits into
mainfrom
train-10846-10842-10843
Sep 21, 2026
Merged

proggeramlug merged 16 commits into
mainfrom
train-10846-10842-10843

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Lands #10846, #10842 and #10843 together as v0.5.1629. They touch the same property-read path and could not be separated.

Expedited at the owner's request: merged on the gate set plus targeted tests rather than a full train sweep.

Contents

source change
#10846 fix(#10827): an explicitly ended prototype chain ends the READ too
#10842 perf(runtime): one validity word replaces the per-hop chain walk; one flags word replaces two registry probes
#10843 perf(codegen): the generic own-property read is tag test → shape compare → load

Integration work this train had to do

The ic_miss.rs conflict. #10843's kind-byte commit re-inserts hook A and let mut inherited_declined at the old call site, but #10842 has hoisted both above the async-resource probe. Taking that hunk verbatim compiles fine and silently produces a second inherited_read_cache_lookup per read plus a shadowed binding. Resolved by keeping #10842's structure and taking only the commit's real contribution — the single validated try_read_gc_header that replaces three later header reads. Asserted afterwards: exactly one inherited_declined declaration and one hook-A lookup survive.

object/mod.rs 2030 → 1979 — the ObjectMeta::flags region (5 constants plus the bit map, 57 lines) moved to object/meta_flags.rs. The map is what a reader needs in order to add a bit, and the "BIT 7 IS THE LAST FREE BIT" warning is only useful beside the constants it constrains.

prototype_chain.rs handle-floor 3 vs baseline 2. #10846's chain walk copied a local top16 == 0 && bits > 0x10000 idiom — a hand-typed handle-band floor, the shape #6321 fixed. Routed through addr_class::is_above_handle_band rather than ratcheting the baseline.

gc_store_site_inventory.py reported a comparison as a store. (*meta).elements == 0 is a read; four of the scanner's five store regexes matched = without excluding ==, while RUST_POINTER_FIELD_STORE_RE two lines below already had the (?!=) guard. Fixed the scanner rather than adding a GC_STORE_AUDIT marker, which would document a store that does not exist, or an allowlist entry, which would leave it over-matching every other reader of those six fields. Proven still non-vacuous: a real unmarked store planted at the same line is caught (exit 1), and the tree is clean with it removed.

Two -D warnings failures CI's warnings job would have rejected: an unnecessary unsafe in inherited_read_cache_tests.rs, and non_snake_case on #10846's a_null_ENDED_interior_prototype_... test (renamed; emphasis moved into a comment — the repo's #[allow(non_snake_case)] precedent is for JS-API thunks, not test names).

#10843's census retarget rides along: it asserts rule 3 is closed rather than requiring the is_plain_kind instruction that the GC-header-load removal deletes. Verified to discriminate by reopening rule 3 on a kind chosen independently of its own self-test (GC_TYPE_BUFFER) — exit 1 naming the kind, exit 0 restored.

Evidence

Twelve gates green: check_file_size, raw_handle_debt, gc_store_site_inventory, addr_class_inventory, shape_descriptor_census, gc_runtime_root_holders, string_payload_access_inventory, gc_rekeyed_key_tables, native_result_ledger, check_test_registration, cargo fmt --all -- --check, and cargo check -p perry-runtime --all-targets under -D warnings.

Tests: inherited_read_cache 31 pass, proto_validity 10, prototype_chain 11. The object:: suite is 487 pass / 1 fail, and that one failure — transition_fast_rejects_object_prototype_even_with_a_cached_edgereproduces identically on main with zero train commits, so it is not this train's. It is an ordering dependency introduced by v0.5.1627's resolve_prototype_addr change: the test passes inside the full suite and fails in isolation, and is tracked separately.

Not run, and stated rather than implied: the full release unit suites, the compiler-output suites, repsel_census, and the gap sweep.

perry-bot and others added 16 commits September 21, 2026 10:17
`Object.setPrototypeOf(o, null)` says there is nothing above `o` any more.
Perry's property reads walked up anyway and answered from the prototype `o`
was BORN with, while `"a" in o` on the same object correctly answered false.
The object contradicted itself:

    const P = { a: 1 };
    const o = Object.create(P);
    Object.setPrototypeOf(o, null);
    o.a          // node: undefined      perry: 1
    "a" in o     // node: false          perry: false
    Object.getPrototypeOf(o)  // node: null   perry: null

Perry bakes class ids at allocation time, so every "the own-key scan missed,
what does this object inherit?" path in the runtime ends at the receiver's
CLASS surface — its vtable, its declaration prototype, or `Object.prototype`.
That fallback is right for an object whose chain was never touched. There was
no test for whether the chain had been ENDED, so it ran for those too.

The issue reports one case. There are ten, in three families, and they are one
root cause:

  * the receiver's own chain ended — on an `Object.create(P)` result, on a
    `new C()` instance, on a declared-class instance, by `setPrototypeOf` or
    by `__proto__ = null`; data reads and method reads both (they resolve
    through different paths, and both were wrong);
  * a prototype INSIDE the chain ended, the receiver never touched:
    `Object.setPrototypeOf(K.prototype, null)` and then `new K().toString`.
    Neither the receiver's guard nor the holder's can see that — the statement
    is one hop above the receiver and one below the answer;
  * the recorded prototype is itself an object with no prototype
    (`setPrototypeOf(o, Object.create(null))`). The sharpest row: `o.a` kept
    answering from the prototype `o` was born with, which is no longer in its
    chain at all.

`Object.create(null)` was already right, because a birth with no prototype has
its own header bit (`OBJ_FLAG_NULL_PROTO`, #1175) and the fallback tested it.
This is the same answer, asked of the whole chain instead of one cell.

## The fix

One predicate, `prototype_chain::prototype_chain_ends_in_explicit_null`, and
two gates.

The predicate walks the chain a READ walks — per-instance record first, then
the hop's class link, with the same declared-prototype-before-synthetic
precedence `native_get::try_data_get_bytes` uses, so it cannot answer about a
hop a read never visits. A chain ends explicitly at a recorded `TAG_NULL` or a
cell born with `OBJ_FLAG_NULL_PROTO`; a hop that merely has no record is an
ordinary object standing on the class default, which is exactly the case the
fallback exists for, and the walk answers false there.

  * `prototype_override::inherited_field_if_overridden` now distinguishes its
    two kinds of miss. A miss on a chain that ends in an explicit null is the
    final answer and it is `undefined`. Every other miss still returns `None`
    and defers, which is what #9244 requires: the arms below it are not only
    the class vtable, they are also everything Perry SYNTHESIZES rather than
    stores on a real prototype (a plain function's `.prototype`, the
    boxed-wrapper builtins, the iterator helpers), and swallowing those made
    them unreachable.
  * `accessors::ordinary_object_prototype_property_value` asked only whether
    the RECEIVER was born without a prototype. It now asks the chain.

Cost is paid only on a MISS. An ordinary receiver answers false from one
absent meta record plus one header bit. Measured on a loop whose body is three
property misses (an `Object.create` receiver, a class instance, a plain
object): 34040.8 instructions per iteration before, 33663.9 after — no
regression, and the miss path is so expensive already (~11 k instructions per
miss) that this is invisible inside it. `class_prototype_object` and
`class_decl_prototype_object` are pure registry reads, so the walk allocates
nothing and cannot re-enter.

## Tests

`test-files/test_parity_explicit_null_prototype.ts` — 20 rows against node,
each printing the READ, the `in` and `getPrototypeOf` together, because the
bug's signature is the object contradicting ITSELF. A fixture that printed
only the read could be "fixed" by making `in` wrong too; this one cannot be.
It includes the four rows that were always correct (`Object.create(null)`,
an own key on an ended prototype, a chain ended and then restored) so a
future fix that reaches for the class surface again cannot regress them
unnoticed.

Must-fail proof: compiled with the unfixed compiler, **10 of the 20 rows
differ from node**; compiled with this one, 0 differ. The two binaries `cmp`
different.

Five unit tests on the predicate itself: an ended chain, an ended INTERIOR
prototype, a hop born without a prototype, an untouched object (which must
stay false — that is the common case and the whole fallback depends on it),
and a recorded prototype cycle, which must terminate.

`cargo test -p perry-runtime -- --test-threads=1`: 4113 passed, 0 failed.

(cherry picked from commit 6d3ff25)
… one flags word replaces two registry probes

Two stages of the same change, in one commit because the second cannot compile
without the first: both facts live in the same word, and that word had to move
before either was safe.

## Stage (i): the per-hop walk becomes one compare

#10834 re-proves a cached inherited read with one ShapeId compare per hop, up
to four dependent loads through prototype objects that are usually cold. It is
proportional to the DEPTH of the chain, and it is a LOOP, so the hit can only
ever live behind a call: an emitted property-read site cannot branch on a
variable number of compares.

The root cause is that a mutation of an object somebody INHERITS from is
invisible to the objects below it. This is V8's prototype validity cell,
collapsed to one global counter (`object::proto_validity`):

  * An object is MARKED (`OBJECT_META_FLAG_IS_PROTOTYPE`) by the
    `[[Prototype]]` install funnel, and the read cache REFUSES to record a hop
    that is not already marked.
  * Every shape-word CHANGE on a marked object bumps the counter, hooked at
    `stamp_object_shape_id_with_carrier_note` — the runtime's single
    structural-mutation publication funnel, which its own header already names
    as such.
  * `prop_plan_epoch_bump` and `class_lookup_surface_gen_bump` bump the same
    word, so it also stands for everything the semantic property epoch stands
    for, and for a re-registered class prototype object.

A plain value store to an existing key deliberately does not invalidate: an
entry records (holder, slot) and LOADS the value on every hit.

## Stage (ii): two registry probes become one bit

A cached hit asked `is_arguments_object` (14.0 instructions) and
`is_process_env_ptr` (5.0), both address-keyed registry probes, on every read.
`OBJECT_META_FLAG_EXOTIC_READ_RECEIVER` is a per-object summary of both, set
inside each registry's single writer in the same breath as the insert, and it
sits in a word the hit path already loads — beside `elements`, which folds in
too. Each probe keeps a `debug_assert` that a registry hit implies the flag, so
an insert that skips the mark fails those suites loudly.

Decisively for the next stage: an emitted read sequence could not have called
either probe at all.

## The word these flags live in, and the one they do not

Both started in `GcHeader::_reserved`, on the claim in that file's `OBJ_FLAG_*`
block that bits 12..13 were "the last free bits". **They are not free.**
`gc/layout.rs` owns 12 (`GC_OBJ_TYPED_LAYOUT_INTACT`), 13
(`GC_LAYOUT_ALL_POINTERS`) and 14..15 (`GC_LAYOUT_STATE_MASK`) in a separate
constant namespace in a different file, and `set_layout_state` CLEARS bit 13 on
every layout-state change. A mark placed there is not merely shared, it is
silently ERASED — so the reader answers `false` for an object the writer
marked, the invalidation never fires, and a cached entry returns a stale value.

Found by measuring: claiming bit 12 regressed the `Object.create` fixtures from
383 to 1533 instructions per read, because those receivers all carry
`GC_OBJ_TYPED_LAYOUT_INTACT`. That regression is what sent me to read
`gc/layout.rs` and find bit 13 under the mark this PR had already shipped.

Both facts now live in `ObjectMeta::flags` bits 5 and 6, verified against
#8690's reservation comment and every reader in the tree. It is the better
home, not a worse one: the hit path already loads `meta`, so the facts cost
the hit nothing, and nothing in the layout machinery can reach them.

This commit also installs the complete `_reserved` bit map — both namespaces,
one table — on `OBJ_FLAG_RESERVED_BIT_MAP_SEE_DOC` in `gc/types.rs`, and points
`gc/layout.rs` at it. #8690 hit the same trap and left its warning in
`ObjectMeta::flags`' doc comment, which is not a file anyone reads when
spending a header bit.

## The polarity, and why the cache still marks

The install funnel marks; the cache refuses an unmarked hop. When the prime
meets one it marks it and ABANDONS the walk without recording anything —
marking allocates a meta record, which can move `obj`, `next` and every address
in `hops` — and the next read of that pair primes normally.

That keeps the invariant that matters absolutely (no entry is ever recorded
through a hop that was not already marked before the walk began) while making
coverage self-healing: an install route the funnel misses costs one declined
read, not a permanent loss. The refusal is not remembered, because marking
bumps no validity and a negative entry would decline the pair for the life of
the process.

`class_prototype_object_root_store` looked like the place to mark the
`Object.create` route and SIGSEGVs the suite: it holds a bare `proto_ptr` that
it re-uses for an address-index rekey and a write barrier, so a mark that
allocates leaves both stale. Any mark that allocates must be the last thing its
caller does with the pointer.

## Measured

`perf stat -x, -e instructions:u`, min of 3, fitted 200 k -> 5 M, two trees
whose binaries `cmp` different, output identical to node on every row.
Inheritance = fixture minus its own-read twin; the twins are unchanged to the
instruction (`own1` 130.00, `ownm` 303.00, `ownpoly` 218.25).

| fixture | #10834 | this | node | bun |
|---|---|---|---|---|
| 1-level `Object.create` | 278 | **226** | 2.1 | 0.4 |
| 3-level chain | 296 | **226** | 0.1 | 0.8 |
| class prototype | 286 | **236** | 0.0 | 0.5 |
| method through the prototype | 282 | **232** | 0.7 | 0.6 |
| 4 receiver shapes, one prototype | 370 | **319** | 4.0 | 0.4 |

Still depth-independent: a three-level chain costs exactly what a one-level one
costs, because the guard no longer has a length. That is the property the
emitted sequence needs.

Key-add churn with a live entry: 420 -> 368. `own1` 130.00 before and after.

Over-invalidation of the GLOBAL counter, measured: a fixture that structurally
mutates an UNRELATED marked prototype once per 64 inherited reads costs 710.91
per iteration against 710.92 for one that mutates the prototype being read.
#10834's semantic-epoch check was already global, so the only event class this
makes global that was not is a plain key add on an object used as a prototype.
One invalidation costs one re-prime: 1494 - 356 = 1138 instructions.

## Tests

`cargo test -p perry-runtime -- --test-threads=1`: 4136 passed, 0 failed.

The coverage test is the one worth reading. It builds a receiver six ways —
`setPrototypeOf` on a literal, `Object.create`, a class-default link, a
class-evaluation link, two hops, and a key added to the prototype after the
receiver exists — and asserts each read is SERVED BY THE CACHE, because a cache
that declines everything returns exactly the values the chain walk would and is
invisible in a program's output. Its first version shared ONE prototype across
all six styles, so five of them were marked by the first and passed vacuously;
giving each style its own prototype turned it red immediately. Each style now
gets a fresh prototype.

Plus: an unmarked prototype is refused rather than cached; the three
invalidation controls from #10842's first revision (mark/hook, class-surface
bump, semantic-epoch fold); and the 27 cache tests, the two GC root tests and
the differential fixture against node.

Hit evidence, from compiled programs with enough misses elsewhere to make
`PERRY_IC_DIAG` dump: `hits=25811149 primes=1 declines=1` for a one-level chain
and `hits=26087239 primes=1 declines=3` for a three-level one — exactly one
mark-and-abandon per hop, then steady hits.

(cherry picked from commit e3fdbb8)
…c-resource registry

`get_field_ic_miss_impl` asked `is_async_resource_handle(obj)` before the
inherited-read cache could answer. Once that registry's latch is armed — which
anything creating one `AsyncResource` does, for the life of the process — the
probe is a thread-local registry lookup costing 16.0 instructions per call
(callgrind, `--separate-callers=1`), and it ran on EVERY inherited read whether
or not the cache could serve it.

The lookup moves above it. It cannot be confused by an async resource handle:
those are `Box::into_raw` native allocations outside the GC arena, so their
word at payload +4 is the high half of a small counter rather than a live
ShapeId, `object_shape_stamp` answers 0, and the lookup returns `Unknown` about
ten instructions later without dereferencing anything further. Nothing that was
below the probe moves, and the async-resource dispatch itself is unchanged.

Measured (`perf stat -x, -e instructions:u`, min of 3, fitted 200 k -> 5 M,
two trees, binaries `cmp` different, output identical to node on every row):

| fixture | before | after |
|---|---|---|
| 1-level `Object.create` | 356 | **334** |
| 3-level chain | 356 | **334** |
| class prototype | 366 | **344** |
| method through the prototype | 535 | **513** |
| 4 shapes, one prototype | 537.75 | **515.75** |
| key-add churn, live entry | 368 | **346** |

-22 on every inherited row and **0.00 on every own-read row** — `own1` 130.00,
`ownm` 303.00, `ownpoly` 218.25, all unchanged to the instruction, because an
own read reached the probe before this change and reaches it after.

This also documents, at the one read that depends on it, why the identity load
at payload +0/+4 is safe on the three pointer-tagged values that #10828's rule
3 does NOT cover — `SymbolHeader`, `AsyncHookHandle` and `AsyncResourceHandle`
are `Box::into_raw` allocations outside the GC kind table. Two are safe by
construction (`registered` is 0 or 1; `index`'s high half is zero). The third,
`AsyncResourceHandle.ids.async_id`, is safe only by MAGNITUDE — its high half
is zero until a process creates 2^32 async resources — which is the same class
of argument #10824 refused for buffer capacities. It is not load-bearing:
`is_shape_id`'s range test rejects the word either way, and an emitted guard
keeps that protection for free because a site's expected ShapeId is always in
[0x8000_0000, 0xC000_0000). Anyone dropping that range test would be resting on
the magnitude argument, and should say so.

`cargo test -p perry-runtime -- --test-threads=1`: 4136 passed, 0 failed.

(cherry picked from commit 38ddd83)
…ty read

Every descriptor install, per-key removal and bulk clear on an ordinary
object transitions its ShapeId (#10824), and the prime side already refuses
a descriptor-bearing receiver, so the ShapeId compare subsumes the
OBJ_FLAG_HAS_DESCRIPTORS test. The GC-kind test becomes a single obj_type
byte compare on every target; the packed kind+descriptor i32 word and the
native-endian i16 reserved-halfword pair are gone with the flag.

(cherry picked from commit 58faf25)
#10828 closed rule 3: for any POINTER-tagged value that passes the tag test,
the u32 at payload +4 equals a live object ShapeId only if the cell is a
GC_TYPE_OBJECT carrying that shape. The ShapeId compare therefore proves the
kind, and the header word at receiver-8 is no longer loaded on the way to
the slot. pic.recv_hdr is gone; the compact-word load moves into pic.token.
The one key that still reads the kind byte is .size, only to serve a native
Map/Set from its own arm; a non-collection receiver takes the ShapeId
compare like every other key.

(cherry picked from commit bf79b37)
…d's shape-gated hit

#10826 made every successful delete a shape transition: the receiver's +4
word always changes and, when the keys array is owned, the predecessor id
is retired. A compact word primed before a delete therefore cannot match
after it, and a ShapeId hit proves the slot it names is live. Every inline
slot is born TAG_UNDEFINED, so nothing but a delete ever writes a hole into
one. The hit block now ends in the slot load and a branch to the merge;
pic.hit.deleted is gone. The hole stays in the slot: the way path, the
spill arm and every keys-array walker keep treating it as absent.

(cherry picked from commit 5eb9d1e)
…ip the closure/buffer/typed-array ladder for ordinary objects

On a read site that sees many shapes every read is a miss, and the miss
handler walked the closure probe (magic read + accessor side table), the
buffer registry and the typed-array registry for every heap receiver
before reaching the object path: 33% of such a read (measured by #10833),
against 3% for the key scan. Those kinds are distinct GC kinds
(shape_rule3.rs classifies all 21), so one validated header read decides
the ladder; a GC_TYPE_OBJECT goes straight to the object path, which now
also takes its kind, descriptor and forwarding bits from that same read
instead of three more. The elements-backed Array-subclass probe is gated
on the key first. The full-outline twin (pic_outlined_mru_hit) mirrors
the three emitted guard removals, and its hit is gated on the exact
POINTER tag like the inline one.

Two latent defects found by #10833:
- js_in_operator_presence_ic resolved its cache before deciding whether it
  could arm, leaving word 0 at zero for a prototype-chain true; the emitted
  guard then matched any unstamped receiver (parent_class_id 0), so
  "k" in {} answered true at such a site. The cache is now resolved with
  word 0 pre-seeded to IN_PRESENCE_UNARMED (1 << 32) before publication
  (pic_slot_resolve_init).
- keys_find_slot_by_bytes_resolved scanned forward, contradicting #10595's
  most-derived-wins rule that its two twins already follow; it scans
  back-to-front now and is pinned by the #10595 test.

test-files/test_parity_read_guards.ts is the differential probe for all
three guard removals against node.

(cherry picked from commit 4a3b92c)
… the shape word from memory

For every key but .length the tag test is the exact POINTER test, so the
handle is bits ^ POINTER_TAG rather than bits & POINTER_MASK: the same
value on the pointer path, and the value the tag test is computed from, so
the mov $0x30/bzhi pair after the compare is gone. The hot ShapeId load
now has a single use (the compare): pic.token.miss re-reads the word through
an atomic load that GVN will not merge, which lets isel fold the hot load
into the compare itself.

(cherry picked from commit 31a2471)
An absent-key miss falls through to js_object_get_field_by_name, whose own
dispatch still probes the registries for an object receiver (eight probes,
measured); that ladder is not this change's, so the test asserts the count
only across the own-key miss, which is the read the skip was measured on.

(cherry picked from commit 2792581)
…er-primed edge, before calling out

A read whose key lives on the prototype chain is never an own slot on the
receiver's shape, so a site that only reads such a key never resolves its
per-site cache, and every read of it reaches pic.token.ways with `present`
false. That edge used to go straight to the exit, where the read paid the
slow entry's prologue and dispatch (79 of an inherited read's 204
instructions, measured by the inherited-reads lane) just to reach the same
lookup inside get_field_ic_miss_impl. It now asks
js_inherited_read_cache_hit_f64(masked receiver, interned key) first
(#10834/#10842's cache); TAG_HOLE is its decline sentinel, so the answer is
one compare with the served edge as the true edge, and a decline continues
to the one exit exactly as before. Nothing primes from emitted code. The
call is a pure state read: declared in runtime_decls, a leaf in
gc_call_effects and root_reload, and in the dominance checker's
NONCOLLECTING set.

Placement, measured: asking on every path into the exit charged each
own-key miss a declining probe (+88 per read on a 64-shape site, +89 on a
spill read). The never-primed edge is the one only an inherited-only site
takes, so every other path is unchanged to the instruction. Typed-feedback
builds keep the old edge, so their record edges stay byte-identical.

The full-outline twin deliberately does not get the hook: it is already
inside the runtime, and its miss handler asks the same cache first.

(cherry picked from commit 2101c18)
…urvives

The census required `cond_br(&is_plain_kind, &tok_label, &cold_label)` in the
generic read PIC -- the `obj_type == GC_TYPE_OBJECT` fence in front of the
ShapeId compare. #10843 deletes it, and the census refused the tree.

It was right to refuse and the removal is right. #10828 closed rule 3 (no
pointer-tagged non-object cell holds a ShapeId-range value at payload +4), so
a successful shape compare proves the kind by itself and the header load is
redundant. Under that object model, asserting the branch asserts nothing about
safety.

Assert the licence instead: no row of `RULE3_KINDS` in
`object/shape_rule3.rs` carries `Rule3Word::RangeReachable`. Stronger on the
axis that can regress -- the branch could only notice its own deletion, this
notices a NEW kind whose +4 word can alias a live ShapeId, and names it.

Guarded against a vacuous pass: the kind set is re-derived from `gc/types.rs`
and must be fully classified, stale rows are red, and both the
`RangeReachable` variant and the runtime test that walks the table must still
exist. Two sabotage arms: reopen rule 3 on `DateCell`, and delete its row.

(cherry picked from commit d721436b47c5a78abe3fe9e13290f0bff90741d3)
- object/mod.rs 2030 -> 1979 via an ObjectMeta::flags split (meta_flags.rs)
- prototype_chain.rs's new hand-typed handle floor routed through
  addr_class::is_above_handle_band rather than ratcheting the baseline
- two -D warnings failures: an unnecessary unsafe, and non_snake_case on
  #10846's test name (renamed; emphasis moved to a comment)
Four of five store regexes matched = without excluding ==, so
(*meta).elements == 0 was reported as a raw heap pointer field store.
RUST_POINTER_FIELD_STORE_RE two lines below already had the (?!=) guard.

Not silenced with a marker: GC_STORE_AUDIT on a comparison would document a
store that does not exist. Verified still non-vacuous -- a planted real store
at the same line is caught (exit 1) and the tree is clean without it.
@proggeramlug
proggeramlug merged commit 89dd494 into main Sep 21, 2026
22 of 23 checks passed
@proggeramlug
proggeramlug deleted the train-10846-10842-10843 branch September 21, 2026 09:12
@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 84a33458-ccfa-42db-b19e-763a6718be2f

📥 Commits

Reviewing files that changed from the base of the PR and between 47ade47 and e4eb52b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (37)
  • CLAUDE.md
  • Cargo.toml
  • changelog.d/10842-gc-store-scanner-false-positive.md
  • changelog.d/10843-census-rule3-invariant.md
  • crates/perry-codegen/src/expr/in_presence_ic.rs
  • crates/perry-codegen/src/expr/property_get/generic_dispatch.rs
  • crates/perry-codegen/src/expr/property_get/tests.rs
  • crates/perry-codegen/src/gc_call_effects.rs
  • crates/perry-codegen/src/root_reload.rs
  • crates/perry-codegen/src/runtime_decls/objects.rs
  • crates/perry-runtime/src/gc/layout.rs
  • crates/perry-runtime/src/gc/types.rs
  • crates/perry-runtime/src/object/arguments.rs
  • crates/perry-runtime/src/object/class_registry/dispatch.rs
  • crates/perry-runtime/src/object/class_registry/state.rs
  • crates/perry-runtime/src/object/field_get_set.rs
  • crates/perry-runtime/src/object/field_get_set/accessors.rs
  • crates/perry-runtime/src/object/field_get_set/has_property_ic.rs
  • crates/perry-runtime/src/object/field_get_set/ic_miss.rs
  • crates/perry-runtime/src/object/field_get_set/ic_slot.rs
  • crates/perry-runtime/src/object/field_get_set/prototype_override.rs
  • crates/perry-runtime/src/object/inherited_read_cache.rs
  • crates/perry-runtime/src/object/inherited_read_cache_tests.rs
  • crates/perry-runtime/src/object/keys_lookup.rs
  • crates/perry-runtime/src/object/meta_flags.rs
  • crates/perry-runtime/src/object/mod.rs
  • crates/perry-runtime/src/object/prop_plan.rs
  • crates/perry-runtime/src/object/proto_validity.rs
  • crates/perry-runtime/src/object/proto_validity_tests.rs
  • crates/perry-runtime/src/object/prototype_chain.rs
  • crates/perry-runtime/src/object/shapes.rs
  • crates/perry-runtime/src/process/env_misc.rs
  • scripts/gc_root_dominance_check.py
  • scripts/gc_store_site_inventory.py
  • scripts/shape_descriptor_census.py
  • test-files/test_parity_explicit_null_prototype.ts
  • test-files/test_parity_read_guards.ts
 ___________________________________________________________________________________________________________________________________________________________________________
< You talkin' to me? You talkin' to me? Then who the hell else are you talking...you talking to me? Well, I'm the only one here. Who do the bugs think they're hiding from? >
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

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.

2 participants