Skip to content

batch: land #8872, #8875, #8877 - #8878

Merged
proggeramlug merged 22 commits into
mainfrom
merge/batch-8872-8877
Aug 27, 2026
Merged

batch: land #8872, #8875, #8877#8878
proggeramlug merged 22 commits into
mainfrom
merge/batch-8872-8877

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Batch landing of three reviewed PRs, validated once as a single merged tree. No fixes needed — all three gate-clean as submitted.

PR
#8872 perf: remove cross-module ECS dispatch and argument-bundle overhead
#8875 perf(descriptors): index descriptors by owner instead of scanning
#8877 fix(runtime): match Node fs.readFile prototype (clears the r22 parity blocker)

Audit of #8875

These tables are process-global maps keyed by (owner_address, key), so an added secondary index in the same key domain is worth checking carefully.

The new indexes stay consistent with the primary maps at every mutation point. Traced all of them: add (:771, :992, :1051-1052, :1084), remove (:790, :1011), object destruction (:1210-1212), and owner transfer (:1259-1260). transfer_descriptor_owner moves both primary maps and calls owner_index_transfer on both new indexes. No desync path found.

It also fixes a pre-existing correctness bug. Its own comment records it: a freshly grown array has a null meta, so owner_may_have_descriptor_entries answered false authoritatively, and Object.keys / getOwnPropertyDescriptor silently lost every accessor the array had before growth. That gate sat in front of the old full-table scan too, so the scan never ran for the new owner either. Carrying the per-object Bloom summary across the transfer is a real fix, not just an indexing change.

One question flagged, not blocking. transfer_descriptor_owner is called from array growth (array/push_pop.rs:199), not from any GC move path — so owner-address keying relies on descriptor-owning objects not being relocated. That is pre-existing: #8875 adds an index in the same key domain rather than introducing the exposure. But it is the #8393 shape (a side table keyed by a raw heap address going stale after a copying minor), and I could not locate the mechanism that guarantees it. Worth confirming whether such objects are pinned; if they are not, both the old and new tables share the exposure.

Validation (merged tree)

Summary by CodeRabbit

  • Performance

    • Improved ECS workloads through faster cross-module execution and reduced call overhead.
    • Accelerated array indexing, appends, iteration callbacks, collection size reads, and short string comparisons.
    • Improved Object.keys and for…in performance, especially when unrelated objects have descriptors.
  • Bug Fixes

    • Preserved array accessors during growth and garbage collection.
    • Preserved Map key/value types in for…of destructuring.
    • Aligned callback-style native exports such as fs.readFile with expected .prototype behavior.
    • Improved array layout and garbage-collection bookkeeping correctness.

Ralph Küpper and others added 22 commits August 26, 2026 23:09
…ry entry

Three hot paths answered "what does THIS owner have?" by walking every
descriptor in the process and filtering on the owner address:

  * js_object_keys' array branch, twice (enumeration.rs) — a full
    property_descriptors walk per enumeration, just to decide whether a
    per-index enumerable check was needed;
  * accessor_descriptor_keys_for_obj, on the own-keys path;
  * transfer_descriptor_owner, on every ArrayHeader growth;
  * scan_descriptor_roots_mut, on EVERY GC cycle — so since the moving
    young-gen scavenge became default (#7019) this was a per-collection
    tax proportional to the whole program's descriptor count rather than
    to what actually moved.

Profiling `claude -p` put 46.6% of main-thread samples in
shapes/descriptors, with a HashMap Keys iteration the single hottest
self-time entry by 4x over anything else.

DescriptorTables now carries attr_keys_by_owner / accessor_keys_by_owner
mirroring the two (owner, key) maps, so each of those becomes a lookup.
The maps stay authoritative; the index is a mirror, and the tests assert
that invariant directly (index == what a full scan would return) across
install, redefine, delete, bulk-clear and owner transfer, because the
failure mode of a mirror is silent drift, not a crash.

Also fixes a pre-existing correctness bug the new tests caught:
transfer_descriptor_owner moved descriptors to the new address but never
carried the per-object Bloom summary. A freshly grown array has a null
meta, for which owner_may_have_descriptor_entries answers false
AUTHORITATIVELY — so after an array grew, Object.keys and
getOwnPropertyDescriptor silently lost every accessor it had. That was
equally true before this change: the gate sat in front of the old scan,
so the scan never ran for the new owner.
The `lint` job failed on four gates that the PR's own changes tripped:

- changelog: add the `changelog.d/8872-*` fragment for the crates/ changes.
- file size: `array/indexing.rs` reached 2,024 lines after the resolved-store
  work; move the transactional `js_array_numeric_range_add*` kernel (a block
  with no raw-handle or address-classification debt, so no per-module ratchet
  ceiling moves) into `array/numeric_range.rs`.
- local-binding-type audit: classify the synthetic `arguments.length` marker
  read in `property_get.rs::lower` (runtime-validated: the marker type exists
  only in direct-call-only clones whose caller materialized the count).
- GC store-site inventory: register `store_array_slot_resolved` as a
  chain-verified discharge helper for the three BARRIERED markers that now
  lean on it, mark its own resolved slot write, and pin the second `apush`
  codegen marker (the unconditional element store inside
  `emit_dynamic_pointer_push_store`, barriered by the same stem) with the
  self-test tree updated to match.

Every step of the lint job was replayed locally, including the raw-handle and
unrooted-local ratchets against the merge base d354443.

Claude-Session: https://claude.ai/code/session_01FUvFrRNZyc5qknBiJbYbby
@proggeramlug
proggeramlug merged commit 59a4c9a into main Aug 27, 2026
19 of 20 checks passed
@proggeramlug
proggeramlug deleted the merge/batch-8872-8877 branch August 27, 2026 10:13
@coderabbitai

coderabbitai Bot commented Aug 27, 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: 530c97fb-116a-468a-b3cd-332849b39780

📥 Commits

Reviewing files that changed from the base of the PR and between 41e8479 and a2560b5.

📒 Files selected for processing (69)
  • changelog.d/8872-ecs-cross-module-dispatch.md
  • changelog.d/8875-descriptor-owner-index.md
  • changelog.d/8877-release-r23-node-function-prototype.md
  • crates/perry-codegen/src/codegen/arguments.rs
  • crates/perry-codegen/src/codegen/closure.rs
  • crates/perry-codegen/src/codegen/entry.rs
  • crates/perry-codegen/src/codegen/function.rs
  • crates/perry-codegen/src/codegen/method.rs
  • crates/perry-codegen/src/codegen/method_registry.rs
  • crates/perry-codegen/src/codegen/mod.rs
  • crates/perry-codegen/src/codegen/opts.rs
  • crates/perry-codegen/src/codegen/ordinary_method_artifacts.rs
  • crates/perry-codegen/src/collectors/hot_callees.rs
  • crates/perry-codegen/src/expr/array_callback_shape_tests.rs
  • crates/perry-codegen/src/expr/array_push.rs
  • crates/perry-codegen/src/expr/array_push_guard_tests.rs
  • crates/perry-codegen/src/expr/class_method_arguments_object_tests.rs
  • crates/perry-codegen/src/expr/compare.rs
  • crates/perry-codegen/src/expr/compare_tests.rs
  • crates/perry-codegen/src/expr/index_get.rs
  • crates/perry-codegen/src/expr/index_get_claim_tests.rs
  • crates/perry-codegen/src/expr/logical_collections.rs
  • crates/perry-codegen/src/expr/mod.rs
  • crates/perry-codegen/src/expr/property_get.rs
  • crates/perry-codegen/src/expr/property_get/generic_dispatch.rs
  • crates/perry-codegen/src/expr/property_get/tests.rs
  • crates/perry-codegen/src/expr/readonly_collection_tests.rs
  • crates/perry-codegen/src/lib.rs
  • crates/perry-codegen/src/lower_call/alloc_hot_tests.rs
  • crates/perry-codegen/src/lower_call/method_override.rs
  • crates/perry-codegen/src/lower_call/property_get/dynamic_dispatch.rs
  • crates/perry-codegen/src/lower_call/typed_shape_bake_tests.rs
  • crates/perry-codegen/src/runtime_decls/strings_part2.rs
  • crates/perry-codegen/tests/typed_feedback.rs
  • crates/perry-hir/src/lower/collection_view_tests.rs
  • crates/perry-hir/src/lower_decl/body_stmt.rs
  • crates/perry-runtime/src/array/element_shape.rs
  • crates/perry-runtime/src/array/element_shape_matrix_tests.rs
  • crates/perry-runtime/src/array/element_shape_tests.rs
  • crates/perry-runtime/src/array/header.rs
  • crates/perry-runtime/src/array/header_gc_slots.rs
  • crates/perry-runtime/src/array/indexing.rs
  • crates/perry-runtime/src/array/iter_methods.rs
  • crates/perry-runtime/src/array/mod.rs
  • crates/perry-runtime/src/array/numeric_range.rs
  • crates/perry-runtime/src/array/push_pop.rs
  • crates/perry-runtime/src/array/tests.rs
  • crates/perry-runtime/src/array/typed_array_receiver_tests.rs
  • crates/perry-runtime/src/gc/layout.rs
  • crates/perry-runtime/src/gc/tests/copying/all_pointer_elements_7469.rs
  • crates/perry-runtime/src/gc/tests/layout_trace/element_shape.rs
  • crates/perry-runtime/src/object/descriptor_state.rs
  • crates/perry-runtime/src/object/field_get_set/enumeration.rs
  • crates/perry-runtime/src/object/mod.rs
  • crates/perry-runtime/src/object/native_module/constructor_exports.rs
  • crates/perry-transform/src/aggregate_scalar.rs
  • crates/perry-transform/src/inline/call_inliner.rs
  • crates/perry-transform/src/inline/cross_module.rs
  • crates/perry-transform/src/inline/mod.rs
  • crates/perry-transform/src/lib.rs
  • crates/perry/src/commands/compile/collect_modules.rs
  • crates/perry/src/commands/compile/collect_modules/finish.rs
  • crates/perry/src/commands/compile/object_cache.rs
  • crates/perry/src/commands/compile/object_cache/object_cache_tests.rs
  • crates/perry/src/commands/compile/run_pipeline.rs
  • crates/perry/tests/issue_5268_native_ctor_prototype_undefined.rs
  • scripts/gc_store_site_inventory.py
  • scripts/local_binding_type_allowlist.json
  • test-files/test_issue_5268_native_ctor_prototype_undefined.ts

📝 Walkthrough

Walkthrough

This PR adds cross-module function inlining, scalar arguments.length dispatch, specialized array and collection lowering, demand-driven array proofs, resolved array storage, descriptor owner indexes, Map type preservation, and Node-compatible callback export prototypes.

Changes

Compiler and code generation

Layer / File(s) Summary
Cross-module function inlining and scalar rewriting
crates/perry-transform/..., crates/perry/src/commands/compile/...
The compiler gathers safe exported function graphs, localizes them into destination modules, propagates re-exports, inlines record-returning calls, and scalarizes eligible return records.
Arguments-length direct ABI
crates/perry-codegen/src/codegen/..., crates/perry-codegen/src/lower_call/..., crates/perry/src/commands/compile/...
Eligible synthetic arguments.length methods receive scalar-count clones. Dispatch, imports, metadata, cache keys, and regression tests support the additive ABI.
Specialized lowering paths
crates/perry-codegen/src/expr/..., crates/perry-codegen/src/collectors/...
The code generator adds captureless Array.some, canonical dynamic array indexes, inline Map/Set sizes, three-byte string comparisons, tiny allocation admission, and guarded pointer append bookkeeping.
Map iteration typing
crates/perry-hir/src/lower_decl/body_stmt.rs, crates/perry-hir/src/lower/collection_view_tests.rs
Map key and value types now remain available through method-body for-of destructuring and specialized array calls.

Array runtime

Layer / File(s) Summary
Resolved array storage and numeric range kernel
crates/perry-runtime/src/array/indexing.rs, crates/perry-runtime/src/array/header_gc_slots.rs, crates/perry-runtime/src/array/numeric_range.rs, crates/perry-runtime/src/array/push_pop.rs
Array operations reuse resolved header flags and use a fused barrier-aware slot store. Numeric range helpers move into a dedicated module.
Array shape, iteration, and GC layout
crates/perry-runtime/src/array/element_shape.rs, crates/perry-runtime/src/array/iter_methods.rs, crates/perry-runtime/src/gc/layout.rs, crates/perry-runtime/src/gc/tests/...
Element-shape proofs become demand-driven. Rooted array access and captureless iteration gain fast paths. Empty arrays can promote to all-pointer layout after a pointer append.

Descriptor and native export behavior

Layer / File(s) Summary
Descriptor owner indexing
crates/perry-runtime/src/object/descriptor_state.rs, crates/perry-runtime/src/object/field_get_set/enumeration.rs
Descriptor tables maintain owner-key indexes across installation, deletion, transfer, pruning, and GC rekeying. Enumeration uses owner-scoped lookups.
Callback export prototypes
crates/perry-runtime/src/object/native_module/constructor_exports.rs, crates/perry/tests/..., test-files/...
fs.readFile is classified and tested as exposing a non-null prototype object.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • PerryTS/perry#8313: Directly relates to descriptor metadata and owner-based key handling.

Suggested reviewers: thehypnoo, jdalton

✨ 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 merge/batch-8872-8877

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 pushed a commit to proggeramlug/perry that referenced this pull request Aug 27, 2026
…-mutation

Second merge round after the PerryTS#8872/PerryTS#8875/PerryTS#8877 batch landed. Conflicts and
their resolutions:

- codegen/method.rs: keep this branch's guarded-falsy/index/pshape-arg clone
  handling and add main's `!arguments_length_clone` exclusions.
- expr/property_get.rs: keep both the Symbol-then-named-field IC dispatch
  (ours) and main's synthetic `arguments.length` fast path.
- property_get/generic_dispatch.rs: main's native Map/Set `size` split ahead
  of the object PIC, with this branch's `is_object_kind` naming.
- lower_call/method_override.rs: `direct_call_fn` (main, argument-length
  clone) is consulted first, then the pshape+index clone (ours); the two are
  mutually exclusive by construction.
- array/element_shape.rs: adopt main's demand-driven proofs (no eager
  `establish` on the first store) inside this branch's
  `note_element_store_with_bit` / `_resolved_flags` split; the now-unused
  `element_identity_of_bits` goes with it, and the renamed
  `pushes_do_not_create_an_unrequested_element_shape_proof` test replaces the
  eager-establishment one.
- array/header_gc_slots.rs + mod.rs: keep both resolved-head store helpers
  (`note_array_slot_resolved_flags` ours, `store_array_slot_resolved` main).
- array/push_pop.rs: `js_array_push_f64_resolved` now stores through main's
  `store_array_slot_resolved`.
- array/indexing.rs: the strict setter keeps this branch's dense fast path
  first, then main's resolved-head strict path; main moved the numeric-range
  helpers into `array/numeric_range.rs` (byte-identical bodies), so the
  in-file copies and their keepalive anchors are dropped; main's fused
  strict store in `js_array_set_index_or_string_strict` is ported into
  `indexing_keyed.rs`.
- expr/index_get_claim_tests.rs: union of imports/constants and both test
  sets (main's canonical-i32 split tier and this branch's `Any`-key tier
  are complementary arms).
- lower_call/property_get/dynamic_dispatch.rs grew past the 2,000-line gate;
  the tower-of-pshape routing moved to `dynamic_dispatch_tower.rs`.

Verified locally: fmt; perry-codegen and perry-runtime lib + test targets
build warning-free; both suites green; file-size, GC store-site, addr-class,
raw-handle, shape-descriptor census, binding and architecture audits pass.

Claude-Session: https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ
proggeramlug pushed a commit to proggeramlug/perry that referenced this pull request Aug 27, 2026
…ses densely

After merging main (PerryTS#8878 / PerryTS#8872's canonical-i32 read split), a declared-array
receiver with a non-static key takes the guarded plain-array tier first. On an
object-backed `class X extends Array` receiver (wolf-ecs `Archetype`,
`packed[sparse[x]]` in SparseSet.has/remove) that guard always misses and
`js_typed_feedback_array_index_get_fallback_boxed`'s GC_TYPE_OBJECT arm
stringified every index into a by-name lookup (from_utf8 + string alloc +
reflection ladder per read): both wolf-ecs benchmarks regressed ~2.2x.

The fallback now asks `array_subclass_fast_index_get` for a canonical
(plain or INT32-boxed) non-negative index before its registry probes and the
by-name path; receivers without a dense proof keep the established route.

Mac mini 11-pair screen vs the pre-merge build: add/remove +0.5%, entity-cycle
-1.2% (from +126% / +121%); semantics probe byte-identical to Node.

Claude-Session: https://claude.ai/code/session_019WVcWKmYsUBnnFB7nBgbBJ
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