Summary
RS4GC expands one Perry-emitted function by 21x, producing 1.5 million relocations. This is not a bug in the pass — it is doing exactly what the input demands. The input is the problem: Perry emits a bundled webpack module factory as a single function carrying thousands of safepoints and thousands of live GC pointers.
RS4GC's output scales as safepoints x live-values, so a monolith of that shape is inherently explosive.
Measurement
Input: next@16.3.0's bundled jsonwebtoken (dist/compiled/jsonwebtoken/index.js, sha256 056c2ddd…a6b9), compiled to an app-only dylib.
The offending function, perry_closure_jsonwebtoken_index_js__227:
| property |
value |
| size |
4.93 MB |
| basic blocks |
8,471 |
| lines |
101,601 |
| call sites (safepoints) |
8,044 |
| allocas |
1,015 |
addrspace(1) GC pointers |
13,369 |
RS4GC on the containing unit (measured with stock opt at LLVM 22.1.4, -mcpu=apple-m1):
pre-RS4GC IR: 19 MB
post-RS4GC IR: 413 MB <- 21x
statepoints: 21,774
gc.relocate: 1,502,654 <- mean 83 operands/statepoint, max 426
Why it matters
Compile time on that single module, same input and same compiler, differing only in opt level applied to RS4GC's output:
| arm |
result |
-O0 (PERRY_LL_SIZE_OPT=0) |
2m46.5s, 40 MB dylib |
-Os (current default for this unit) |
>100 minutes, stopped without finishing |
This blocks #8040 structurally, not just cosmetically. That tracker's definition of done requires ten cold process starts, two verifier runs each, and a 100-iteration 20-way concurrent batch — none of which is reachable at hours-per-compile. The #8034 fixture cannot serve as a CI gate at this cost regardless of correctness.
It was previously masked: the compile crashed (#8121) before the slowness could be observed. With that fixed, this is what's left.
Directions
- Reduce values live across safepoints. ~83 relocations per statepoint is the underlying inefficiency, and shrinking it helps every program, not just this one. Likely levers: tighter liveness, sinking/rematerialization, or not modelling every value as a GC pointer where a proof exists.
- Split the monolith. Outline so no single function carries 8k safepoints. Bounds the product directly.
- Opt-level threshold (separate, bounded — being handled apart from this issue).
oversized_opt_flag routes a unit to -O0 when its widest function exceeds ll_o0_threshold_bytes() (6 MB). This function is 4.93 MB and misses the cutoff. Retuning is a compile-time/code-size tradeoff needing its own measurement; it treats the symptom, not the cause — RS4GC still emits 413 MB of IR either way.
(1) and (2) change how Perry lowers every closure, so both want gap-suite and perf-corpus measurement before landing.
Refs #8040, #8121.
Summary
RS4GC expands one Perry-emitted function by 21x, producing 1.5 million relocations. This is not a bug in the pass — it is doing exactly what the input demands. The input is the problem: Perry emits a bundled webpack module factory as a single function carrying thousands of safepoints and thousands of live GC pointers.
RS4GC's output scales as
safepoints x live-values, so a monolith of that shape is inherently explosive.Measurement
Input:
next@16.3.0's bundledjsonwebtoken(dist/compiled/jsonwebtoken/index.js, sha256056c2ddd…a6b9), compiled to an app-only dylib.The offending function,
perry_closure_jsonwebtoken_index_js__227:addrspace(1)GC pointersRS4GC on the containing unit (measured with stock
optat LLVM 22.1.4,-mcpu=apple-m1):Why it matters
Compile time on that single module, same input and same compiler, differing only in opt level applied to RS4GC's output:
-O0(PERRY_LL_SIZE_OPT=0)-Os(current default for this unit)This blocks #8040 structurally, not just cosmetically. That tracker's definition of done requires ten cold process starts, two verifier runs each, and a 100-iteration 20-way concurrent batch — none of which is reachable at hours-per-compile. The #8034 fixture cannot serve as a CI gate at this cost regardless of correctness.
It was previously masked: the compile crashed (#8121) before the slowness could be observed. With that fixed, this is what's left.
Directions
oversized_opt_flagroutes a unit to-O0when its widest function exceedsll_o0_threshold_bytes()(6 MB). This function is 4.93 MB and misses the cutoff. Retuning is a compile-time/code-size tradeoff needing its own measurement; it treats the symptom, not the cause — RS4GC still emits 413 MB of IR either way.(1) and (2) change how Perry lowers every closure, so both want gap-suite and perf-corpus measurement before landing.
Refs #8040, #8121.