Skip to content

perf(runtime): born-old children for a producer's fresh old-born container (split) - #12613

Merged
proggeramlug merged 1 commit into
mainfrom
perf/born-old-producer-children
Oct 14, 2026
Merged

proggeramlug merged 1 commit into
mainfrom
perf/born-old-producer-children

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

What

When a runtime producer fills a fresh container it owns and that container was born old, the children it allocates for it are now born old too, and its stores into that container skip the old→young remembering. Old→old needs no remembered slot.

  • gc/producer_birth.rs: ProducerChildBirth::for_fresh_container(addr) decides once per producer call from the container's own birth generation (classify_heap_generation == Old). There's no cache, no side table and no per-element test. fresh_old_container_store_barrier keeps only the incremental mark shade.
  • string_storage_alloc_for(birth, cap): Old takes the same birth a large object takes (arena_alloc_gc_old_born_tenured: old pages + GC_FLAG_TENURED).
  • First producer: String.prototype.split on a non-empty delimiter (split_by_delimiter). The empty-delimiter path builds its parts through the shared constructors, which stay young, so it keeps the full barrier.

Correctness

  • Marking: old births read the live birth colour after any collection the allocation itself runs (gc_birth_extra_flags + gc_note_black_birth), so a part born during a budgeted cycle is born MARKED (allocate-black). During a sweep, birth flags are clear and the old free list is withheld (Random heap corruption with the incremental old-gen collector on: package-manager installs fail ~10–40%; PERRY_GC_INCREMENTAL=0 passes 48/48 #11842), which is the same rule promotions and large objects already follow. The store also keeps the mark shade.
  • Escape: the reduced barrier applies only to stores into the producer's fresh result, before it is returned. A result that is born young and later promoted mid-fill was decided Young, so it keeps the full barrier.
  • Moving GC: minors never evacuate old pages. Old-page defrag (full collections only) rewrites every reference, including the producer's rooted handles.

Tests (gc/tests/born_old_producer.rs)

  • A split whose result is born old gets old, TENURED parts and adds no remembered slot. Every part survives a minor and a full collection.
  • A 64-part split keeps a young result with young parts.
  • A split during an active budgeted mark (MarkPropagation; the test asserts the cycle is still active after the split) births every part MARKED. All parts read back after the cycle completes and after a later full collection.
  • Sabotage: with the birth mark cleared, the born-marked assertion fails. The survival part alone did not fail under that sabotage, because the born-black result's seed re-traces the filled array after the split.

Results (qb6, base = a86b06b)

base head Δ
words split phase (ms, wall n=5 med) 841 392 −53.4%
words map phase (ms) 573 410 −28.5%
words phase total (ms) 2179 1576 −27.7%
words wall (ms) 2220 1640 −26.1%
words max RSS (KB) 570256 522320 −8.4%
words instructions:u (n=3) 31.948 G 21.926 G −31.4%
zodwork 5000 instr:u 9.1883 G 9.1879 G −0.004%
tscwork 1 instr:u 4.30826 G 4.30826 G −0.000%
fastify inject instr:u 13.1836 G 13.1814 G −0.016%
.text +448 B (runtime only)

Node 26.8.1 on the same words program: split 358 ms, map 378 ms.

Gates run locally

  • scripts/gc_call_effects/regen.sh linux-x86_64 --check: identical to the archives.
  • Native gc-root-dominance (statepoints, moving-only), both the corpus and the dependency-scale corpus: 0 hazards (0 unrooted, 0 stale), 40/40 seeded violations caught.
  • gc_repsel_matrix.sh --arms pr (129 files × 7 arms): 696 PASS / 200 UNVER / 7 FAIL. All 7 FAILs are repsel_element_group_numeric, which fails the same way on base (TypeError ... reading 'y').
  • Debug cargo test -p perry-runtime --lib gc:: string:: array:: --test-threads=1: 2460 pass / 4 fail. All 4 fail the same way on base.
  • Parity against node: words plus 15 driver programs match, except root-survival, which differs on base too.

When a runtime producer fills a fresh container it owns and that
container was born old, the children it allocates for it are now born
old too, and its stores into the container skip the old->young
remembering (old->old owes the remembered set nothing).

The decision is made once per producer call from the container's own
birth generation (`ProducerChildBirth::for_fresh_container`): no cache,
no side table, no per-element test. Old children take the same birth a
large object takes (`arena_alloc_gc_old_born_tenured`), so they inherit
allocate-black during a mark cycle, the sweep-window rules, and
non-movement by the minor collector. The store keeps the incremental
mark shade. Only stores into the producer's own fresh container use the
reduced barrier; every other store is unchanged.

First producer: String.prototype.split on a non-empty delimiter. A
5M-part split built a 40 MB result born old and stored 5M young parts
into it, all old->young; every minor then re-scanned, copied and
promoted them.

Tests: an old-born split result gets old, TENURED parts and adds no
remembered slot; a small split keeps young parts; a split during an
active budgeted mark (MarkPropagation) births every part marked, and
all parts survive that cycle and a later full collection.
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Review in 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: 01c4345f-8705-4b78-b929-44c6c4e48af3
📥 Commits

Reviewing files that changed from the base of the PR and between a86b06b and f9b7139.

📒 Files selected for processing: 6
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/producer_birth.rs
  • crates/perry-runtime/src/gc/tests/born_old_producer.rs
  • crates/perry-runtime/src/gc/tests/mod.rs
  • crates/perry-runtime/src/string/mod.rs
  • crates/perry-runtime/src/string/split.rs
 ____________________________________________
< Double tap everything. Kill the bugs dead. >
 --------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( 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
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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
proggeramlug marked this pull request as ready for review October 14, 2026 15:25
@proggeramlug
proggeramlug merged commit eecf79d into main Oct 14, 2026
92 of 101 checks passed
@proggeramlug
proggeramlug deleted the perf/born-old-producer-children branch October 14, 2026 15:26
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