2026-09-11, late evening. A ledger of timings and known issues on the Roc side, rough where it says rough, kept so the next sweep can be targeted instead of waited for. Update in place.
roc-lang/nightlies publishes a release build of the new compiler every day
(the tutorial in the tree says so; I had looked only at roc-lang/roc's own
releases, which are the old compiler). With nightly-2026-09-11-793f9d8:
| what | debug build | nightly |
|---|---|---|
roc check, 4,000 literals |
31 s | 0.35 s, and linear |
| the 54-unit sweep | 5 m 27 s | 27 s, two at a time |
roc build SafariApp.roc --target=wasm32 |
2 m | 14 s |
| the frame | 15.5 ms | 15 ms, same code |
So the quadratic-checker finding is a debug-build fact and no longer on our
path; the debug build stays for working on the compiler itself. The
nightly is also stricter: == on a type variable needs a where clause,
which the emitter now writes, and the gate now refuses a compile error even
when the output matches, because roc compiles a type error into a crash at
its site and runs the rest. safari/retest.sh emits every unit, diffs
against the tracked safari/roc/, and runs only what changed.
The box is 8 GB, two cores, and the Roc compiler is a DEBUG build, which is roughly ten times slower than a release build would be at everything it does. That factor sits under every compile number below.
| what | time | notes |
|---|---|---|
roc compile + run of a small spec (Echo platform) |
~3 s | mostly compiler start-up and checking the modules |
safari/emitted.sh, all 54 units |
~5 min | was 42 min before the stills were baked as strings |
| SafariSpec alone | ~50 s | roc run evaluates the ride at compile time |
| CatStillsSpec alone | ~7 s | was 8 min as literals |
| rocemit on one unit | well under 1 s | debug rocemit; release is faster |
| irdump whole over 54 units (the wire check) | ~1 min | |
cargo build of rocemit, debug |
~10 s incremental | release build minutes; never in a fix loop |
roc build SafariApp.roc --target=wasm32 |
45 s to 1 m 40 s | grew with the accumulator rewrite's extra definitions |
zig build of the wasm host |
4 s | |
| one frame in the browser module, before the rewrite | 110 ms | ride_frame 46, blit_expand 61, pack 3 |
| one frame after the accumulator rewrite | 28 ms | ride_frame 34 in isolation, blit_expand 0.7, pack 4 |
advance (the physics step) |
0.07 ms | |
| Roc's eval test suite | 47 min | 2086 tests, two processes |
| Roc compiler, debug build from clean | ~10 min | |
| Roc compiler, ReleaseFast | fails | the final LLVM link reaches 5.4 GB resident and has been killed each time; one unattended retry pending, to run ALONE |
native FrameBench (Echo platform, --opt=speed) |
820 ms per frame | not representative: the native Echo allocator dominates; use it for names, not numbers |
Fifty-four units at about five to six seconds each, plus SafariSpec's fifty. Each unit pays the compiler's start-up, the check of every module it imports, and the run. Two cores are idle half the time because the sweep is serial.
Check.zig:34522,
unverified, unreported, Steve's call. Worked around: any constant of 256
or more literal leaves is baked as a string by bake_stills.py.x & f rest copies everything
below at every level: 2,430 elements cost 1.7 s, 4,860 cost 4.7 s. The
emitter now recognises the right-fold shape and emits an accumulator
loop, with List.append per element; List.concat onto the accumulator
is fifty times slower per element and goes quadratic for short literals.
Fifty-seven safari definitions take the rewrite. Two recursive concat
definitions remain (kept_of, hull_insert), both small.roc run evaluates top-level constants at compile time. SafariSpec's
ride is built and stepped by the compile-time evaluator, which is why it
costs fifty seconds where the wasm module steps in microseconds._name); "condition known at compile time" in Cat.roc:133 is not
avoidable from the IR and is harmless.roc__proc_NNNN. Stage probes in the app and micro
benchmarks in ~/build/roc-apps/probe/ are what work.args has no program name at index 0. My first
benchmarks read args[1] as the mode and measured the default branch for
half an hour. Read the output, not only the clock.--opt=speed and the dev backend gave the same frame time for the
wasm module, which is suspicious and unexplained; the wasm path may go
through LLVM either way.Three moves, in the order I'd take them.
emitted.sh, after the running sweep ends (never edit a running
bash script).roc is a
few hundred MB; xargs -P2 halves the wall clock to about 2.5 minutes,
with the chapter-identity check done after, not during.safari/roc/ is tracked, so after a
rocemit change the modules that changed are git status, and the units
to re-run are the specs that import them. A safari/retest.sh that
computes that set and runs it would make most fix loops a coffee break
or less. Until it exists, emitted.sh <Spec> runs one unit, and the
five stills units and SafariSpec are the usual suspects.Beyond those, the release build of roc is the multiplier on everything, and the one-line checker fix is what makes the debug build tolerable if the release build never links on this box.