Skip to content

codegen: RS4GC expands a single bundled module factory 21x (413 MB IR, 1.5M gc.relocates) — blocks #8034 as a CI gate #8132

Description

@proggeramlug

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

  1. 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.
  2. Split the monolith. Outline so no single function carries 8k safepoints. Bounds the product directly.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions