Skip to content

perf(runtime): #6759 Phase C3a — stable shape identity: owned-grow migration + GC rekey#6801

Merged
proggeramlug merged 4 commits into
mainfrom
perf/6798-c3a-stable-shape-identity
Jul 22, 2026
Merged

perf(runtime): #6759 Phase C3a — stable shape identity: owned-grow migration + GC rekey#6801
proggeramlug merged 4 commits into
mainfrom
perf/6798-c3a-stable-shape-identity

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Phase C3a of #6759 / #6798 (design: #6798 comment): stable shape identity, runtime-only. Stacked on #6800 (Phase C2).

Problem

ArrayHeader stores elements inline, so every append growth reallocates an owned keys array — and every consumer keyed on the keys address treats that as a brand-new shape. For the C1 shape records specifically, a wide object's slot map was orphaned at the old address on each capacity doubling and rebuilt O(key_count) at the new one. (Small shapes ≤64 keys ride the shared-transition system with stable per-shape addresses; it's exactly the wide regime — the one C1's records exist for — that churned.)

What changed

  • Shape records gain a stable shape_id: u32 (monotonic, 1-based, allocated at record birth). Not yet consumed — it's the identity C3b re-keys FIELD_CACHE/typed_feedback exactness on, and C3c makes header-resident (parent_class_id slot for class_id == 0 objects, per the design).
  • Owned-grow migration: the four owned-append sites in js_object_set_field_by_name's slow paths call shape_keys_grown(prev, new) when js_array_push reallocated a NON-GC_FLAG_SHAPE_SHARED keys array — the record (slot map, indexed_len, shape_id) moves to the new address instead of being dropped. A shared array's fork deliberately does NOT migrate: the old address still describes the siblings' live shape; the clone is a genuine transition edge.
  • GC rekey scanner (scan_shape_table_rekey_mut): when evacuation moves a live keys array, the shape record follows it (metadata-rewrite phase only; the records hold no heap references and mark nothing — same pattern as the descriptor-table owner rekey).
  • Safety net unchanged: records stay accelerators — every hit re-validates key bytes against the live array, so a wrong or stale migration can't produce wrong results, only a wasted rebuild.

Validation

  • Full cargo test -p perry-runtime --lib -- --test-threads=1 green (1445 passed), including two new tests: owned-grow migration preserves the record + shape_id; copied-minor move rekeys the record to the to-space address.
  • Gap suite vs node --experimental-strip-types on this tip (C2+C3a stacked) — results in PR comment.

Runtime-only; no codegen, header, or semantic change.

Summary by CodeRabbit

  • Performance

    • Improved runtime handling of object shapes as owned key arrays grow, preserving stable shape-slot mappings across reallocations.
    • Added monotonic shape identity to maintain consistent shape identity during growth and evacuation.
  • Reliability

    • Updated GC metadata processing so shape records are rekeyed correctly when keys array addresses move, keeping metadata consistent.
  • Tests

    • Added Phase C3a audit tests covering shape-table death/move correctness, rekeying to-space, and stable shape identity during owned-key growth.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f3de2b9-c349-4a79-bb49-f4de1ac51203

📥 Commits

Reviewing files that changed from the base of the PR and between f2c5148 and 182695f.

📒 Files selected for processing (5)
  • changelog.d/6801-stable-shape-identity-phase-c3a.md
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/tests/dead_owner_side_tables.rs
  • crates/perry-runtime/src/object/field_set_by_name.rs
  • crates/perry-runtime/src/object/shapes.rs

📝 Walkthrough

Walkthrough

Shape records now receive monotonic stable IDs and migrate with their keys arrays during owned growth and GC evacuation. Shape-table scanning is registered with GC, and tests cover both relocation paths.

Changes

Stable shape identity

Layer / File(s) Summary
Shape identity and rekeying primitives
crates/perry-runtime/src/object/shapes.rs
Shape records gain stable IDs, owned-growth migration, GC rekeying, and test helpers.
Owned keys growth integration
crates/perry-runtime/src/object/field_set_by_name.rs, crates/perry-runtime/src/gc/mod.rs, changelog.d/...
Owned keys-array growth paths migrate shape records, and the GC registers the metadata rekey scanner.
Growth and evacuation validation
crates/perry-runtime/src/gc/tests/dead_owner_side_tables.rs
Tests verify shape-record migration and stable IDs across owned growth and copied-minor evacuation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ObjectSetter
  participant ShapeTable
  participant GarbageCollector
  ObjectSetter->>ShapeTable: migrate record after owned keys growth
  ShapeTable-->>ObjectSetter: retain shape_id
  GarbageCollector->>ShapeTable: rekey metadata after keys evacuation
  ShapeTable-->>GarbageCollector: retain shape_id at new address
Loading

Possibly related issues

  • PerryTS/perry issue 6804 — This change provides stable shape IDs and rekeying for related code-generation and PIC behavior.
  • PerryTS/perry issue 6798 — This change directly implements stable Shape identity and rekeying for the tracked shape-tree work.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title is specific, concise, and accurately summarizes the main runtime change: stable shape identity with owned-grow migration and GC rekeying.
Description check ✅ Passed The description covers the summary, concrete changes, related issues, and validation; only the optional screenshot and checkbox checklist are not fully filled out.
✨ 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 perf/6798-c3a-stable-shape-identity

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.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Gap suite vs node --experimental-strip-types on this branch's tip (C2+C3a stacked): 98.2% parity — 386 pass / 7 mismatch / 0 compile-fail / 0 crashed / 0 skipped; gap gate OK (all 7 mismatches are the pre-existing triaged set on main). Report: test-parity/reports/parity_report_20260722_193115.json.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Base automatically changed from perf/6798-c2-shape-attrs to main July 22, 2026 19:43

@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-runtime/src/object/shapes.rs`:
- Around line 58-61: Update ShapeRegistry::alloc_shape_id so the shape counter
cannot wrap and reuse an existing ID after u32::MAX. Use a wider non-wrapping
counter, preferably u64, and update the associated shape ID storage/types as
needed; otherwise fail explicitly before allocation would reuse an ID. Preserve
monotonic, unique IDs for all successful allocations.
🪄 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: 11e820aa-7185-43fe-8a05-6866d2e5667f

📥 Commits

Reviewing files that changed from the base of the PR and between f1b2f28 and f2c5148.

📒 Files selected for processing (5)
  • changelog.d/6801-stable-shape-identity-phase-c3a.md
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/tests/dead_owner_side_tables.rs
  • crates/perry-runtime/src/object/field_set_by_name.rs
  • crates/perry-runtime/src/object/shapes.rs

Comment on lines +58 to +61
fn alloc_shape_id(&self) -> u32 {
let id = self.next_id.get();
self.next_id.set(id.wrapping_add(1).max(1));
id

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

Prevent shape_id reuse on counter wrap.

After u32::MAX, this wraps and allocates 1 again. That breaks the promised monotonic, unique shape identity and would alias future identity-keyed consumers. Use a non-wrapping wider ID (preferably u64) or fail explicitly before reuse.

Proposed fix
-    shape_id: u32,
+    shape_id: u64,
...
-    next_id: std::cell::Cell<u32>,
+    next_id: std::cell::Cell<u64>,
...
-    fn alloc_shape_id(&self) -> u32 {
+    fn alloc_shape_id(&self) -> u64 {
         let id = self.next_id.get();
-        self.next_id.set(id.wrapping_add(1).max(1));
+        self.next_id
+            .set(id.checked_add(1).expect("shape id space exhausted"));
         id
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn alloc_shape_id(&self) -> u32 {
let id = self.next_id.get();
self.next_id.set(id.wrapping_add(1).max(1));
id
fn alloc_shape_id(&self) -> u64 {
let id = self.next_id.get();
self.next_id
.set(id.checked_add(1).expect("shape id space exhausted"));
id
}
🤖 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-runtime/src/object/shapes.rs` around lines 58 - 61, Update
ShapeRegistry::alloc_shape_id so the shape counter cannot wrap and reuse an
existing ID after u32::MAX. Use a wider non-wrapping counter, preferably u64,
and update the associated shape ID storage/types as needed; otherwise fail
explicitly before allocation would reuse an ID. Preserve monotonic, unique IDs
for all successful allocations.

@proggeramlug
proggeramlug force-pushed the perf/6798-c3a-stable-shape-identity branch from f2c5148 to 182695f Compare July 22, 2026 19:44
@proggeramlug
proggeramlug merged commit a1128df into main Jul 22, 2026
27 of 29 checks passed
@proggeramlug
proggeramlug deleted the perf/6798-c3a-stable-shape-identity branch July 22, 2026 19:44
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Legitimate against this PR's snapshot — and already resolved on main: #6803 replaced this per-thread Cell<u32> allocator entirely with a process-global AtomicU32 in a dedicated range (0x8000_0000..0xC000_0000) that parks at exhaustion (every post-exhaustion call re-stores the end sentinel and returns 0 = "no id", which disables the acceleration but can never wrap back into the valid range or reuse an id). Global uniqueness was also required for cross-thread correctness (the worker serializer replays parent_class_id verbatim), which is why the fix went further than widening the counter.

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.

1 participant