Summary
Compiling Coop's Next.js fixture — the production next build --webpack App Route with server chunk splitting disabled, so the route is one self-contained bundle (~15–21 MB of IR per unit, per PerryTS/coop#12) — took 6,973,947 ms (116 min) wall on Perry 0.5.1519 (41e8479a5), with two earlier attempts killed by 1800 s and 5400 s budgets. PerryTS/coop#12 measured the same fixture at roughly 8 minutes on a quiet M1 on 0.5.1512 (fc6b3378).
Caveat up front: this host was not quiet (load average 25–56 on 10 cores, from unrelated Perry builds), and an M1 thread pushed onto efficiency cores burns more CPU-seconds per unit of work. So the wall-clock ratio overstates it. The reason I think it is still a regression rather than contention: the single hot thread accumulated >100 CPU-minutes inside one analysis, which no contention model turns into 8 wall-minutes of total work. It needs a quiet-machine confirmation, which I could not give it today.
Where the time goes
sample on the compiler process, twice 30 minutes apart, same picture both times. Four workers (perry-module-0/1, perry-llvm-unit-0/1); three parked on channels, one — perry-llvm-unit-1 — running:
llvm::SmallPtrSetImplBase::insert_imp_big(void const*) 3507 / 2024 samples
(anonymous namespace)::AllocaDerivedValueTracker::walk(llvm::Value*) 108 / 47
llvm::CallBase::getCaptureInfo · llvm::CallBase::onlyReadsMemory · llvm::AttributeList::getMemoryEffects
called from the CGSCC pipeline (llvm::LazyCallGraph::SCC, CGSCCUpdateResult, AnalysisManager<LazyCallGraph::SCC> frames present). AllocaDerivedValueTracker is TailCallElim's alloca-escape walk (Transforms/Scalar/TailRecursionElimination.cpp): for every alloca it walks every derived use into a SmallPtrSet, so on a function with a very large alloca count × use count it is effectively quadratic. RSS stayed at 0.2–0.3 GB for the ~100 minutes this thread was alone, then jumped to 3.9 GB when the remaining units ran in parallel — i.e. one function held the whole build.
Perry does not use LLVM coroutines and names no coroutine or TRE pass itself; this is the default function-simplification pipeline meeting a function of that width. Per-unit stats (PERRY_CODEGEN_UNIT_TIMINGS) were not enabled on this run — they would name the function; #8586 added exactly that instrument for this situation.
What changed between the pins
crates/perry-codegen/src/native_emit.rs moved 646+/128− between fc6b3378 and 41e8479a5 (#8586, #8602, #8657, #8696, #8700 among them), and 72 codegen commits landed in the range, including the perf(codegen) series of 2026-08-24/25. I have not bisected. #8586's notes say #8421's contract — every function optimized at the requested level — stands, so nothing is demoted; if a change in that range made the widest function wider (more allocas, more uses), TRE's walk is where it would show first.
Reproduction
PerryTS/coop main at 3e7180e, then scripts/build-perry-libraries.sh, cargo build --release -p coop-daemon -p coop-worker, and
PERRY_CODEGEN_UNIT_TIMINGS=1 COOP_NEXT_COMPILE_TIMEOUT=10800 scripts/prepare-next-benchmark.sh
(the env override is in PerryTS/coop#17). The daemon writes the compiler's stderr only on failure, so for the unit stats run perry compile directly on the staged deployment with the same argv the daemon logs under compiling deployment … perry=….
Filed alongside the 0.5.1519 init regression on the same fixture; the two together are what currently keeps #8546 from being reproduced on main.
Summary
Compiling Coop's Next.js fixture — the production
next build --webpackApp Route with server chunk splitting disabled, so the route is one self-contained bundle (~15–21 MB of IR per unit, per PerryTS/coop#12) — took 6,973,947 ms (116 min) wall on Perry0.5.1519(41e8479a5), with two earlier attempts killed by 1800 s and 5400 s budgets. PerryTS/coop#12 measured the same fixture at roughly 8 minutes on a quiet M1 on0.5.1512(fc6b3378).Caveat up front: this host was not quiet (load average 25–56 on 10 cores, from unrelated Perry builds), and an M1 thread pushed onto efficiency cores burns more CPU-seconds per unit of work. So the wall-clock ratio overstates it. The reason I think it is still a regression rather than contention: the single hot thread accumulated >100 CPU-minutes inside one analysis, which no contention model turns into 8 wall-minutes of total work. It needs a quiet-machine confirmation, which I could not give it today.
Where the time goes
sampleon the compiler process, twice 30 minutes apart, same picture both times. Four workers (perry-module-0/1,perry-llvm-unit-0/1); three parked on channels, one —perry-llvm-unit-1— running:called from the CGSCC pipeline (
llvm::LazyCallGraph::SCC,CGSCCUpdateResult,AnalysisManager<LazyCallGraph::SCC>frames present).AllocaDerivedValueTrackeris TailCallElim's alloca-escape walk (Transforms/Scalar/TailRecursionElimination.cpp): for every alloca it walks every derived use into aSmallPtrSet, so on a function with a very large alloca count × use count it is effectively quadratic. RSS stayed at 0.2–0.3 GB for the ~100 minutes this thread was alone, then jumped to 3.9 GB when the remaining units ran in parallel — i.e. one function held the whole build.Perry does not use LLVM coroutines and names no coroutine or TRE pass itself; this is the default function-simplification pipeline meeting a function of that width. Per-unit stats (
PERRY_CODEGEN_UNIT_TIMINGS) were not enabled on this run — they would name the function; #8586 added exactly that instrument for this situation.What changed between the pins
crates/perry-codegen/src/native_emit.rsmoved 646+/128− betweenfc6b3378and41e8479a5(#8586, #8602, #8657, #8696, #8700 among them), and 72 codegen commits landed in the range, including the perf(codegen) series of 2026-08-24/25. I have not bisected. #8586's notes say #8421's contract — every function optimized at the requested level — stands, so nothing is demoted; if a change in that range made the widest function wider (more allocas, more uses), TRE's walk is where it would show first.Reproduction
PerryTS/coop
mainat3e7180e, thenscripts/build-perry-libraries.sh,cargo build --release -p coop-daemon -p coop-worker, and(the env override is in PerryTS/coop#17). The daemon writes the compiler's stderr only on failure, so for the unit stats run
perry compiledirectly on the staged deployment with the same argv the daemon logs undercompiling deployment … perry=….Filed alongside the 0.5.1519 init regression on the same fixture; the two together are what currently keeps #8546 from being reproduced on
main.