Skip to content

Merge train 180: #10177, #10186 - #10200

Merged
proggeramlug merged 5 commits into
mainfrom
train180
Sep 13, 2026
Merged

Merge train 180: #10177, #10186#10200
proggeramlug merged 5 commits into
mainfrom
train180

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Merge train 180: lands #10177 (birth a large JSON.stringify result in the arena when the young generation already holds a document; closes #10169) at head 9115323f84 and #10186 (raise the machine-pipeline budget on x86-64, keep 100k elsewhere) at head 739c208c71, plus the workspace version bump to 0.5.1553.

The four PR commits were cherry-picked onto fc736cbf8a (main after the #10176, #10181 and #10183 landings) without conflicts.

Validation (macOS arm64)

Full validation first ran on the same two PRs over 5cec2fbbc9:

  • scripts/run_lint_gates.sh: 80 of 83 pass. The 3 failures are the same steps as main: public benchmark evidence freshness, API docs drift, and -D warnings stopping on main's global_this_webassembly.rs dead code (register_extern_wrapper, registered_extern_handle, wasm_memory_descriptor_maximum).
  • cargo test --release: perry-runtime 3728 passed / 0 failed, perry-codegen 1997 / 0
  • Gap stringify 8/8. test_gap_json 36 pass, 1 fail, json_lazy_defineproperty_index, which is parity_fail in gap_snapshot.json (JSON.parse lazy array: Object.defineProperty index accessor is bypassed by reads #10097).
  • perf(gc): birth a large JSON result in the arena when the young generation already holds a document #10177's route, parse-then-stringify: a 5.7 MB record document parsed and stringified 12 times, plus a stringify-only loop over one retained parse. Output matches Node plainly (377 MiB peak RSS) and under PERRY_GC_SCHEDULE_SEED 3 and 180 at rate 0.2 with forced evacuation, verification and from-space protection (399–417 copying minors).

Confirmation on this exact tree, rebased because #10183 added string allocators next to json_output_storage_alloc:

CI attribution (vs main's run at b5a82cfeae)

Review notes

Summary by CodeRabbit

  • Performance

    • Improved garbage-collection efficiency for large JSON parse-and-stringify workloads, reducing CPU usage and peak memory in benchmarked scenarios.
    • Increased the optimized compilation budget on x86-64, reducing fallback to slower code generation for larger workloads.
    • Expanded fallback diagnostics to report all affected functions and impacted function counts.
  • Documentation

    • Updated the changelog with performance details and platform-specific compilation behavior.
  • Release

    • Version updated to 0.5.1553.

Ralph Küpper added 5 commits September 13, 2026 14:09
…ion already holds it

JSON results at or above 512 KB are malloc-tracked so that the next minor
can reclaim a discarded result without a whole-old-heap trace. That trade
inverts when the young generation already holds at least as many bytes as
the leaf, which is the shape of a freshly parsed document the caller is
about to stringify: a non-empty malloc registry forbids the untraced
in-place promotion, so the next minor traces the whole tree (55 ms for a
20 MB document, 52% of the roundtrip's wall time) to reclaim one leaf.

Such a leaf is now born old in the arena instead. The tree promotes
untraced, and the leaf is reclaimed by the old-reclaim full that has to
mark the tree anyway. Results allocated while the young generation is
small keep the malloc path, and only tracked leaves charge malloc-output
debt.

(cherry picked from commit 2b4751b)
(cherry picked from commit 9115323)
…here

The O0 machine-pipeline fallback is selected per function but applied per
module, so every ordinary function sharing a unit with one extreme
generated function is demoted with it. Measured on @babel/parser unit 0
(LLVM 22, x86-64, -Os IR pipeline): the demotion costs its 282 ordinary
siblings 2.06 MiB of extra machine code (1.382 -> 3.441 MiB, 168 of 282
functions change) and the offending 227k-instruction closure another
2.0 MB (241 KB -> 2,254 KB), to save 6 s of llc time.

LLVM has no per-function escape: optnone reaches instruction selection
and the optional machine passes but not LiveIntervals or the greedy
allocator, so an optnone-demoted giant still took 9.5 s of the 10.0 s
and more memory than the undemoted arm.

So raise the ceiling from 100k to 600k instructions -- but only on
x86_64, which is where every affordability measurement was made: above
the whole measured population (60 of the 61 OpenCode-corpus functions
past the old ceiling are under 600k; the largest measured end to end,
mime types/other.ts at 522,756 instructions, emits optimized in 704 s at
2.26 GB against 393 s at 2.34 GB demoted, for 13.72 MB of .text against
36.97 MB). aarch64/arm64 and every other unmeasured target keep 100k:
both pathological observations (a 100,152-instruction function past
~10 GiB RSS, a 277k one over 16 minutes in LiveIntervals/regalloc) are
arm64, are current rather than stale, and sit inside the 600k band. The
ceiling follows the target the unit is emitted for, not the host, so a
cross-compile to arm64 keeps arm64's ceiling.

Also report every over-budget function instead of only the widest, and
name in the message how many functions the unit demotes along with it,
so a build names the whole set that has to shrink.

PERRY_LL_FAST_EMIT_MAX_INSTRS keeps its semantics on every target:
=100000 restores the old ceiling exactly, =0 disables the fallback.

(cherry picked from commit 140ad1f)
(cherry picked from commit 739c208)
@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: 54b0daa7-2b3e-4ef3-b562-e9fe3c64a665

📥 Commits

Reviewing files that changed from the base of the PR and between fc736cb and 521b5d6.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • CLAUDE.md
  • Cargo.toml
  • changelog.d/10177-json-leaf-arena-route.md
  • changelog.d/10186-fast-emit-budget-x86-64.md
  • crates/perry-codegen/src/inprocess.rs
  • crates/perry-codegen/src/inprocess/optimize_emit.rs
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/policy.rs
  • crates/perry-runtime/src/gc/promote_in_place.rs
  • crates/perry-runtime/src/gc/tests/mod.rs
  • crates/perry-runtime/src/gc/tests/young_leaf_route.rs
  • crates/perry-runtime/src/json/mod.rs
  • crates/perry-runtime/src/json/stringify_flat.rs
  • crates/perry-runtime/src/json/stringify_record_output.rs
  • crates/perry-runtime/src/json/stringify_string_tests.rs
  • crates/perry-runtime/src/string/json_construction.rs
  • crates/perry-runtime/src/string/mod.rs
  • scripts/gc_runtime_root_holders.json

📝 Walkthrough

Walkthrough

The pull request routes qualifying large JSON leaves through arena allocation, updates GC trigger handling, and expands validation. It also adds target-specific fast-emission budgets, reports all over-budget functions, updates pipeline tests, and bumps the project version.

Changes

JSON leaf GC routing

Layer / File(s) Summary
GC state and trigger policy
crates/perry-runtime/src/gc/*
The GC tracks old-born JSON leaves, exposes young-generation measurements, and gives one nursery scavenge priority when the young generation is unmeasured and its cap is due.
Allocator routing and accounting
crates/perry-runtime/src/string/*, crates/perry-runtime/src/json/*
Large JSON leaves can use arena storage based on young-generation state. Allocation callers now receive malloc-tracking state and record completion accounting only for malloc-tracked output.
GC routing tests and audit updates
crates/perry-runtime/src/gc/tests/*, crates/perry-runtime/src/gc/mod.rs, crates/perry-runtime/src/gc/promote_in_place.rs, scripts/gc_runtime_root_holders.json
Tests cover allocation routing, survival states, and one-time trigger priority. GC exports and root-holder audit data support the new state and test controls.

Fast-emission budget handling

Layer / File(s) Summary
Target-specific budget selection
crates/perry-codegen/src/inprocess.rs, crates/perry-codegen/src/inprocess/optimize_emit.rs
x86-64 targets now default to a 600,000-instruction budget. Other unmeasured targets retain 100,000. Explicit budget settings continue to use the parser.
Fallback collection and emission wiring
crates/perry-codegen/src/inprocess.rs, crates/perry-codegen/src/inprocess/optimize_emit.rs
Fast-emission fallback records now include every over-budget function, sorted widest first, and include the number of defined functions. A non-empty fallback list selects the O0 machine pipeline.
Budget and pipeline validation
crates/perry-codegen/src/inprocess/optimize_emit.rs
Tests cover target defaults, diagnostics, shipped-pipeline statistics, and assembly changes for ordinary sibling functions when a unit falls back.

Release metadata

Layer / File(s) Summary
Version and changelog updates
CLAUDE.md, Cargo.toml, changelog.d/*
The project version changes from 0.5.1552 to 0.5.1553. Changelog entries document the JSON GC routing and fast-emission budget changes.

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

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant JSONStringifier
  participant JSONStorageAllocator
  participant GC
  participant GCTrigger
  JSONStringifier->>JSONStorageAllocator: allocate JSON output
  JSONStorageAllocator->>GC: inspect young-generation state
  GC->>JSONStorageAllocator: choose arena or malloc storage
  JSONStorageAllocator->>GC: record old-born leaf when arena storage is used
  GCTrigger->>GC: evaluate the next budgeted trigger
  GC->>GCTrigger: return nursery scavenge priority or existing trigger
Loading
sequenceDiagram
  participant Codegen
  participant TargetBudget
  participant FallbackAnalysis
  participant MachineEmitter
  Codegen->>TargetBudget: resolve budget for effective target
  Codegen->>FallbackAnalysis: inspect module functions
  FallbackAnalysis->>Codegen: return sorted fallback records
  Codegen->>MachineEmitter: select O0 pipeline when fallback records exist
Loading

Suggested reviewers: jdalton

✨ 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 train180

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.

JSON roundtrip at scale: every minor traces the whole live tree because the large stringify result is malloc-tracked (untraced promotion vetoed)

1 participant