Skip to content

codegen: emit the receiver-unknown numeric index tiers once per dynamic site - #8940

Closed
proggeramlug wants to merge 1 commit into
PerryTS:mainfrom
proggeramlug:perf/claimed-key-single-lattice
Closed

codegen: emit the receiver-unknown numeric index tiers once per dynamic site#8940
proggeramlug wants to merge 1 commit into
PerryTS:mainfrom
proggeramlug:perf/claimed-key-single-lattice

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Rebased onto main after #8931 merged; one commit.

What

arr[key] on an erased-Array receiver with a dynamic key (packed[sparse[x]] in wolf-ecs SparseSet.has, a[b[i]] in general) lowers through the canonical-i32 split. Since the brand arm (#8890's v90), every integral key below 2³¹ — canonical double or INT32-boxed — is served in the canonical arm's aidx.claimed.other branch, which emits the receiver-unknown numeric tiers (inline typed-array read tav.*, dense Array-subclass arrlike.ic.*, complete dispatcher) once. The runtime-key arm (lower_claimable_array_string_key_get) still carried its own copy of that lattice from the v83 dynamic-key work (aidxkey.intaidxkey.int.exact), which after v90 only ever ran for integral keys in [2³¹, 2³²). Its other caller passes static string/symbol keys, where the numeric path is dead.

This PR drops that copy: those keys take the complete js_array_get_index_or_string route (which handles every index). Same semantics; ~11 KB of IR less per dynamic site.

Why it matters

Diagnosing why SparseSet.has$pshape is still a bl inside addComponent$pshape (campaign item #4): an env-gated trace of the pre-statepoint inline admission showed the one-statement clone has$pshape$idx_u31 at 45,936 bytes of lowered IR against the 16 KiB budget (GUARDED_SPECIALIZATION_PREINLINE_MAX_IR_BYTES) — which is why the earlier 32 KiB experiment (v82b) could never fire. Byte split: arrlike.ic 13.2 KB, tav 9.4 KB, guarded arr 6.7 KB, aidx 5.7 KB, plen 5.1 KB — i.e. the dynamic-key site alone was ~30 KB because the two arms each emitted the same lattice. This is the first of the size reductions; the clone is still above budget afterwards (the .length IC lattice and the two guarded this.sparse[x] tiers are next), so no inlining change is claimed here — only smaller code at every such site.

Verification (local, per the campaign rule)

  • The v83 test (any_typed_dynamic_key_takes_the_numeric_tiers_when_it_is_an_array_index) now asserts exactly one tav.get.brand and one arrlike.ic.family_token block definition per site, no aidxkey.int block, and the SSO-string and complete-route calls still present. index_get_claim_tests: 12/12.
  • Crate suites (perry-codegen, perry-transform, perry-hir) and the lint gates (-D warnings, file size, GC store inventory, raw-handle debt, shape census, local-binding audit, addr-class audit) are running now; I'll confirm in a comment together with the Mac-mini paired screens (2 s + 50 ms windows, 11 pairs, both wolf-ecs benchmarks) and the traced clone sizes before/after.

https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ

Summary by CodeRabbit

  • Performance

    • Reduced generated code for dynamic array access, improving code size in affected methods.
    • Eliminated redundant runtime dispatch for certain numeric array keys without changing behavior.
  • Bug Fixes

    • Preserved correct handling for canonical numeric keys, string keys, and non-index keys.
    • Maintained complete fallback behavior for dynamic keys that are not recognized as array indexes.
  • Tests

    • Updated coverage to verify streamlined dispatch and correct handling across supported key types.

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b509ba28-aa9e-43d0-91d5-050ca5ab07d7

📥 Commits

Reviewing files that changed from the base of the PR and between 0b78ea5 and 9e49235.

📒 Files selected for processing (3)
  • changelog.d/0000-claimed-key-single-lattice.md
  • crates/perry-codegen/src/expr/index_get.rs
  • crates/perry-codegen/src/expr/index_get_claim_tests.rs

Included review availability: Your plan provides up to 8 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The compiler removes the duplicate receiver-unknown numeric dispatch for dynamic keys on erased arrays. Non-SSO keys now use js_array_get_index_or_string, while tests verify single-lattice emission and updated key classifications.

Changes

Claimed array key dispatch

Layer / File(s) Summary
Update claimable-array lowering
crates/perry-codegen/src/expr/index_get.rs
The non-SSO branch now uses the generic array index-or-string fallback. The merge no longer includes the removed inline numeric result.
Validate single-lattice emission
crates/perry-codegen/src/expr/index_get_claim_tests.rs, changelog.d/0000-claimed-key-single-lattice.md
Tests verify updated classifications, one tav.* and arrlike.ic.* lattice per dynamic site, and the aidxkey.sso fallback route. The changelog records the change.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 9e492

This localized codegen change removes duplicate numeric-tier emission while preserving the complete fallback behavior; no actionable merge-blocking risk remains at the current head beyond normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately summarizes the main code generation change: emitting receiver-unknown numeric index tiers once per dynamic site.
Description check ✅ Passed The description clearly explains the change, motivation, semantic impact, generated-IR reduction, affected code paths, and verification status. It does not use the template headings or include the che…
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (1 skipped: 1 …
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.
Full details: Description check

Explanation

The description clearly explains the change, motivation, semantic impact, generated-IR reduction, affected code paths, and verification status. It does not use the template headings or include the checklist and related-issue section, but it is sufficiently complete and directly relevant.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (1 skipped: 1 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Local gates on 4ae1c462c: cargo test -p perry-codegen -p perry-transform -p perry-hir 2500 passed / 0 failed; RUSTFLAGS=-D warnings cargo check --workspace --all-targets (host-compatible scope) clean; file size OK; GC store-site inventory passed; raw-handle debt unchanged (967, no ceilings raised); shape census OK; local-binding audit OK; addr-class audit passed. Changelog fragment included.

#8931 has merged since this was opened — rebasing onto main next so only this PR's commit remains; Mac-mini paired screens and the before/after traced clone sizes to follow.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Traced lowered-IR sizes (PERRY_PREINLINE_TRACE, a local diagnostic patch not in this PR) for the wolf-ecs add/remove tail, before → after this change; the pre-statepoint budget is 16 KiB, so nothing newly flips to admitted=true here — this is the size reduction only:

function before after Δ
ECS.addComponent$pshape$generic 244,484 166,985 −31.7%
ECS.removeComponent$pshape$generic 244,087 166,576 −31.8%
ECS.updatePending$pshape 266,519 189,026 −29.1%
SparseSet.has$pshape$generic 102,314 63,572 −37.9%
SparseSet.remove$pshape$generic 68,312 42,935 −37.1%
SparseSet.has (u31 clone) 56,272 43,712 −22.3%
SparseSet.has$pshape (u31 clone) 45,936 33,583 −26.9%
ECS.destroyEntity$pshape$generic 52,510 40,177 −23.5%

21 other traced clones unchanged (no dynamic-key site). Machine-code effect and the Mac-mini screens follow once the build here finishes.

…ic site

`lower_claimable_array_string_key_get` — the canonical-i32 split's
runtime-key arm for erased-Array receivers — still carried its own copy of
the inline typed-array + dense-subclass `arrlike.ic` + dispatcher lattice
from the v83 dynamic-key work. Since the brand arm (`aidx.claimed.other`)
every integral key below 2^31, canonical or INT32-boxed, is served by the
canonical arm's single copy, so the runtime copy only ever ran for integral
keys in [2^31, 2^32) — at ~11 KB of IR per site (wolf-ecs `SparseSet.has`'s
proven-this u31 clone was 46.7 KB for one statement, 22 KB of it these two
lattices, against a 16 KiB pre-statepoint inline budget). Its other caller
passes static string/symbol keys, where the numeric path is dead.

Those keys now take the complete `js_array_get_index_or_string` route,
which handles every index. The v83 test asserts exactly one inline
typed-array tier and one dense-subclass tier per site, and no
`aidxkey.int` block.

Claude-Session: https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ
@proggeramlug
proggeramlug force-pushed the perf/claimed-key-single-lattice branch from 4ae1c46 to 9e49235 Compare August 28, 2026 09:55
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Rebased onto main (9e492352d, one commit) after #8931 merged. Re-verified on the rebased head: RUSTFLAGS=-D warnings cargo check --workspace --all-targets clean; cargo test -p perry-codegen -p perry-transform -p perry-hir 2504 passed / 0 failed. Mac-mini screens are running; posting them when done.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Mac-mini paired screens (11 alternating pairs, taskpolicy -t 0 -l 0), #8931 head (v95) → this PR (v98):

window add/remove entity cycle
2 s 0.4057 → 0.4059 (+0.03%, 4/11) 0.3365 → 0.3378 (+0.39%, 0/11)
50 ms 0.4050 → 0.4052 (+0.02%, 5/11) 0.3364 → 0.3377 (+0.34%, 1/11)

So: no time win — add/remove is noise-flat and the entity cycle is consistently ~0.4% slower (deltas 0.14…0.71 with one 1.53), most plausibly code layout after the hot has bodies shrank. The IR/machine-code reduction is real (table above) but by the campaign's retain rule (both benchmarks 11/11 better) this does not qualify on its own. Converting to draft: it stays open only because the follow-up single-statement pre-inline budget experiment (v99) needs the smaller clone to be admitted at all; if that experiment doesn't win, I'll close this.

@proggeramlug
proggeramlug marked this pull request as draft August 28, 2026 10:03
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Follow-up experiment (v99: a 40 KiB pre-statepoint inline budget for one-statement bodies, which this PR's smaller has$pshape clone makes reachable) on the Mac, this PR (v98) → v99: add/remove −0.36% / −0.43% (11/11 in 2 s and 50 ms), entity cycle −0.22% / −0.30% (10/11 both). Net of the two changes vs main: add/remove ≈ −0.3%, entity ≈ +0.2% — nothing. The traces explain it: the hot add$pshape/remove$pshape clones already contain has$pshape$idx (index clones are always pre-statepoint-inlined), so what the budget admits is only the $generic fallback wrappers, and the dead lattices removed here cost compile size, not time.

Closing this PR: the change is correct and smaller, but it does not pay by the campaign's retain rule, and I'd rather not land layout-neutral code churn while three ECS PRs are in flight. The branch stays (perf/claimed-key-single-lattice, 9e492352d) in case a later change needs the freed budget.

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