Skip to content

Merge train 183r: #10218 - #10221

Merged
proggeramlug merged 4 commits into
mainfrom
train183r
Sep 13, 2026
Merged

proggeramlug merged 4 commits into
mainfrom
train183r

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Merge train 183r: lands #10218 (one inline hit and one exit for the dynamic obj[i] read) at ce3708d7ca, plus one census commit and the workspace version bump to 0.5.1560.

The two PR commits were cherry-picked onto 5d3bf86823 without conflicts.

Added commit lint(census): drop the dynamic index read's retired header-size callsite (#10218): #10218 moved the shape-carried subclass IC tower out of inline_dyn_typed_array.rs, deleting that file's one object_header_size_bytes call. scripts/shape_descriptor_census_baseline.json still expected it, both in the exact callsite multiset and in the codegen_object_header_size_sites summary (43 → 42). That was #10218's only CI difference from main. The replacement read lives in js_packed_arraylike_index_get, which is compiled per target and so takes the target's header size.

Validation (macOS arm64)

Run on the train minus the census commit, a JSON-only difference:

  • scripts/run_lint_gates.sh: 79 of 83 pass. The only failure beyond main's three was the census step fixed above, which now passes (python3 scripts/shape_descriptor_census.py: rc 0).
  • raw_handle_debt.py (both invocations), gc_runtime_root_holders.py, fmt, file size: pass. check_thread_locals.py: only main's regex/perex_owner.rs.
  • cargo test --release: perry-codegen 2029 passed / 0 failed (all targets). perry-runtime 3775 passed / 1 failed; the failure is gc::tests::heap_generation::a_free_or_move_outside_every_scope_is_caught_in_debug_builds, which is already on main from perf(regex): resume JS-level searches on non-ASCII strings from the previous call's position #10205 (noted there).
  • Full gap suite (scripts/run_gap_tests.sh, 779 tests): 768 pass, 10 parity fail, 1 crash. The gated set is identical to train 181b's full run: main's known four plus 9592_child_timeout_threads, where the Node oracle fails on this host.
  • Dynamic-index probe: every typed-array kind including BigInt64/BigUint64/Float16, subarray views at offsets, out-of-range and negative indices, a plain array with holes and mixed values, an extends Array subclass, an array-like object, and 300 rounds of lazy JSON arrays read by index under allocation churn. Output is byte-identical to Node plainly, and under PERRY_GC_SCHEDULE_SEED 5, 183 and 10218 at rate 0.3 with forced evacuation, verification and from-space protection (555–564 copying minors), which exercises the GC_FLAG_FORWARDED guard perf(codegen): one inline hit and one exit for the dynamic obj[i] read #10218 added for movable lazy arrays.
  • GC stress (seeded, rate 0.5, ALLOC_KB=0, forced evacuation, verification, from-space protection) on json_lazy_indexed_cache, json_lazy_growth_alias, json_record_loop_clone and json_owned_tape: all match Node.

CI attribution (#10218 run 34771184597 vs main's run at b5a82cfeae)

The same failing check/warnings/cargo-test steps and errors, the same native_stack failure, the same four gated gap regressions with identical output, and the same gc-stress failure. lint differs only by the census step fixed here.

Review notes

  • The kept inline arms are unchanged: the header/brand checks, the packed dense-array bounds, element and hole path, and the elements-backed probe. A codegen test pins the exact ordered block list and a single dispatcher call.
  • The typed-array ladder now keys on element width. Each width loads its lane once and derives signed, unsigned or float with non-trapping selects (shl/ashr for signed, bitcast i32 → float for Float32Array). Width 8 serves only Float64Array inline.
  • js_packed_arraylike_index_get now performs the lazy-JSON probe itself, only for an unforwarded GC_TYPE_LAZY_ARRAY header. Nothing between the header read and the CannotCollect probe can collect, and a forwarded receiver falls through to js_array_get_f64.

Summary by CodeRabbit

  • Performance

    • Improved performance for dynamic indexed reads across ordinary arrays, typed arrays, array subclasses, and lazy JSON arrays.
    • Reduced generated code and runtime dispatch overhead for common access patterns.
    • Added optimized handling for typed-array element widths and lazy JSON array lookups.
  • Bug Fixes

    • Improved coverage and reliability for invalid indices, guarded typed-array views, boxed values, BigInt, Float16, and varied array-like receivers.
  • Documentation

    • Updated the documented release version to 0.5.1560 and expanded dynamic index-read documentation.

Ralph Küpper added 4 commits September 13, 2026 19:19
The guarded element read for a dynamically-typed receiver emitted ~50 basic
blocks and ~343 pre-RS4GC instructions per `a[i]`: eight typed-array
element-kind arms behind a seven-block kind dispatch, the whole shape-carried
Array-subclass IC tower (identity, dense-tail family token, spilled `length`,
spilled elements), the elements-backed subclass probe, the lazy-JSON-array
probe, four runtime calls and six `js_number_coerce` arms. On
`prettier/plugins/flow.mjs` that tower is 55% of all emitted IR across 10,778
sites, for a program that neither constructs a typed array nor subclasses
`Array`.

The site now keeps four guarded arms and one out-of-line call, 20 blocks and
173 instructions:

  * the receiver tag / heap-band and canonical-index checks plus the managed
    `GcHeader` load, and the packed ordinary `GC_TYPE_ARRAY` arm — byte
    identical to before;
  * the typed-array arm, collapsed onto the four ELEMENT WIDTHS the header
    already stores instead of the nine element kinds. `tav.w4` resolves
    `Int32Array`/`Uint32Array`/`Float32Array` from ONE load with two `select`s;
  * the elements-backed Array-subclass probe (`ObjectMeta.elements`) — byte
    identical;
  * `js_packed_arraylike_index_get`, the same call the old `arrlike.ic.miss`
    block made, as the single exit for everything else.

Every removed arm was an acceleration of a decision that helper already makes,
and it is still handed the site's own cache slot, so neither the answer nor the
primed cache words move. The one arm it did NOT already make, #10114's
lazy-JSON-array probe, moved into the helper — a `JSON.parse` result still
skips the `js_array_get_f64` -> `lazy_get` chain without every read site
paying three blocks for the proof.

The shape-carried IC tower (15 of the 50 blocks) is removable because it cannot
hit in the shipped configuration: its hit needs a primed layout cache, and
`build_dense_layout` is reached only when `elements_of(obj)` is null, which the
default elements store makes false for every Array subclass.

Measured on the OpenCode corpus (5 interleaved rounds, quiet host):
prettier-flow `.text` -12.67% (38,113,543 -> 33,284,398), babel-parser -0.68%,
babel-types-validators unchanged; `.perry_gcmap` within +-0.14%; O0-fallback
units unchanged. No workload regressed: every shared row is within -0.43% ..
+0.07% retired instructions and -1.1% .. +0.2% peak RSS, while a dynamically
typed `Float64Array` read loop is -9.98% instructions / -40.6% walltime, a
plain `number[]` -40.03% / -72.0%, an `Array`-subclass -12.10% / -22.9% and a
`Uint8Array` -4.79% / -4.8%.

(cherry picked from commit 3d79647)
(cherry picked from commit ce3708d)
@proggeramlug
proggeramlug merged commit bb9aa5a into main Sep 13, 2026
21 of 23 checks passed
@proggeramlug
proggeramlug deleted the train183r branch September 13, 2026 20:20
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: da352355-e723-4601-9e82-48ee517ac22d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d3bf86 and 7b4e60f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • CLAUDE.md
  • Cargo.toml
  • changelog.d/10218-dynamic-index-get-one-exit.md
  • crates/perry-codegen/src/block.rs
  • crates/perry-codegen/src/codegen/index_method_clone_tests.rs
  • crates/perry-codegen/src/expr/index_get/inline_dyn_typed_array.rs
  • crates/perry-codegen/src/expr/index_get_claim_tests.rs
  • crates/perry-codegen/src/runtime_decls/strings.rs
  • crates/perry-runtime/src/array/index_get_exit_tests.rs
  • crates/perry-runtime/src/array/mod.rs
  • crates/perry-runtime/src/array/subclass_packed_index.rs
  • crates/perry-runtime/src/json_tape.rs
  • scripts/shape_descriptor_census_baseline.json

📝 Walkthrough

Walkthrough

Dynamic indexed reads now use a smaller inline control-flow shape. Ordinary arrays, width-based typed arrays, and elements-backed subclasses remain inline. Other cases use one runtime exit, which adds lazy-JSON probing. Code-generation and runtime tests validate the new behavior.

Changes

Dynamic index read optimization

Layer / File(s) Summary
Inline dynamic index-read code generation
crates/perry-codegen/src/block.rs, crates/perry-codegen/src/expr/index_get/inline_dyn_typed_array.rs, crates/perry-codegen/src/runtime_decls/strings.rs, scripts/shape_descriptor_census_baseline.json
The inline path keeps ordinary-array, width-based typed-array, and elements-backed subclass reads. Other cases use one js_packed_arraylike_index_get exit.
Shared runtime exit handling
crates/perry-runtime/src/array/subclass_packed_index.rs, crates/perry-runtime/src/json_tape.rs, crates/perry-runtime/src/array/mod.rs
The dispatcher probes non-forwarded lazy JSON arrays before existing fallback paths. The probe is re-exported and the test module is registered.
Code-generation contract validation
crates/perry-codegen/src/expr/index_get_claim_tests.rs, crates/perry-codegen/src/codegen/index_method_clone_tests.rs
Assertions cover the 21-block shape, one exit call, collapsed typed-array handling, receiver probes, clone behavior, and number coercion.
Runtime exit differential tests
crates/perry-runtime/src/array/index_get_exit_tests.rs
Tests compare exit results with generic dynamic access across typed arrays, arrays, lazy JSON arrays, subclasses, objects, invalid receivers, boxed values, and coercion.
Version and release documentation
CLAUDE.md, Cargo.toml, changelog.d/10218-dynamic-index-get-one-exit.md
The documented and workspace versions advance from 0.5.1559 to 0.5.1560. The changelog records the indexed-read changes and measurements.

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

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant DynamicRead
  participant InlineCodegen
  participant ArraylikeExit
  participant LazyJsonProbe
  DynamicRead->>InlineCodegen: lower obj[i]
  InlineCodegen->>ArraylikeExit: call on guarded miss
  ArraylikeExit->>LazyJsonProbe: probe non-forwarded lazy array
  LazyJsonProbe-->>ArraylikeExit: value or TAG_HOLE
  ArraylikeExit-->>DynamicRead: return indexed value
Loading
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch train183r

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.

1 participant