You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf(codegen): Coop's self-contained Next.js route takes 116 min to compile on 0.5.1519 (~8 min on 0.5.1512) — one LLVM unit thread in TailCallElim's alloca walk #8883
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:
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
(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.
Correction: not a regression between the pins — the control run says so
I re-ran the identical fixture on the same (still loaded) box with Perry 0.5.1516 (3885ba491), the last build the fixture loads on:
Perry
compile wall time
hot thread
0.5.151941e8479a5
6,973,947 ms (116 min)
AllocaDerivedValueTracker::walk → SmallPtrSet::insert_imp_big, one perry-llvm-unit thread, ~100 CPU-min before the emit phase
0.5.15163885ba491
5,526,141 ms (92 min)
same frames, same shape: ~85 CPU-min on one thread at 0.3 GB RSS, then the 3.4 GB parallel emit
So the 1512→1519 framing in the title is wrong and I'm withdrawing it: whatever produced the "~8 minutes on a quiet M1" figure in PerryTS/coop#12, it is not reproducible for this fixture on either compiler here, and the difference between the two pins (24 min) is within what a load average swinging between 10 and 67 explains.
What stands, and is the actual report: one function in this bundle holds the entire build for ~1.5 CPU-hours inside TailCallElim's alloca-escape walk, on both compilers. That is a pathological-input problem in the default function-simplification pipeline, not a change in Perry — but it is Perry's pipeline choice for functions of this width, and PERRY_CODEGEN_UNIT_TIMINGS (#8586) is the instrument that will name the function. Suggest retitling along the lines of "TailCallElim's alloca walk is quadratic on the widest Next.js route function" — I'll leave that to you rather than edit history under people's feet.
Activity
proggeramlug commentedon Aug 27, 2026
Correction: not a regression between the pins — the control run says so
I re-ran the identical fixture on the same (still loaded) box with Perry
0.5.1516(3885ba491), the last build the fixture loads on:0.5.151941e8479a5AllocaDerivedValueTracker::walk→SmallPtrSet::insert_imp_big, oneperry-llvm-unitthread, ~100 CPU-min before the emit phase0.5.15163885ba491So the 1512→1519 framing in the title is wrong and I'm withdrawing it: whatever produced the "~8 minutes on a quiet M1" figure in PerryTS/coop#12, it is not reproducible for this fixture on either compiler here, and the difference between the two pins (24 min) is within what a load average swinging between 10 and 67 explains.
What stands, and is the actual report: one function in this bundle holds the entire build for ~1.5 CPU-hours inside TailCallElim's alloca-escape walk, on both compilers. That is a pathological-input problem in the default function-simplification pipeline, not a change in Perry — but it is Perry's pipeline choice for functions of this width, and
PERRY_CODEGEN_UNIT_TIMINGS(#8586) is the instrument that will name the function. Suggest retitling along the lines of "TailCallElim's alloca walk is quadratic on the widest Next.js route function" — I'll leave that to you rather than edit history under people's feet.batch: land PerryTS#8892, PerryTS#8893, PerryTS#8894 (PerryTS#8898)