TRVMterm-rewriting VM
slot 0x00 · results

Correctness first. Then cost.

The same bytes — one interaction-calculus term — pushed through six independent TRVM runtimes written in five languages, on 48 workloads. The first question is never how fast. It is whether they agree, because a runtime that is fast and wrong is worth nothing, and a system whose whole premise is replayable evidence has to be identical across implementations before its numbers mean anything.

They agree. Every number below is downstream of that.

48 × 6
workloads × runtimes
Multiplication to Ackermann, in explicitly linear Church encoding.
48 / 48
byte-identical normal forms
Zero conformance disagreements across the IC32 family — and zero disagreements on interaction counts either, which is stronger than the contract requires.
478
interactions for 224 negations
Optimal sharing, unforced: 16,777,216 units of semantics in 478 rewrites.
1.006× vs 129×
engine swap vs boundary fix
On the real application workload, a 3.2× faster reducer is worth almost nothing. Where the time actually goes is further down.
slot 0x01 · how to read this page

Three quantities that are not interchangeable.

Most benchmark pages collapse these into one bar chart. They behave completely differently, and mixing them is how benchmarks lie without anyone intending to.

quantitystatuswhat it is good for
normal form normative. Every IC32-model runtime must produce identical bytes; a difference is a conformance bug, full stop. Portability. If the WASM build and the C build disagree, nothing else on this page matters.
interactions Machine-independent work. Not normative — a different strategy may legitimately take a different count — so it is reported and cross-checked, never asserted. Comparing algorithms and encodings. Survives being run on someone else's hardware.
wall time Machine-dependent, and the easiest of the three to measure wrongly. Budgeting. Ratios port between machines; absolute numbers do not.

Correctness is checked against arithmetic, not against another runtime

Ground truth for every workload is computed in ordinary Python integers. All six runtimes can be wrong together and it still shows. Backends are discovered, never assumed: an unbuilt one is reported as skipped with the missing path named, never silently counted as a pass.

Four methodology rules, each of which changed a result when it was added

rulewhat it prevents
minimum, not meanLaunch noise is one-sided — preemption and page faults only ever make a run slower — so the fastest observed run is both the best estimate and the most stable.
startup is subtractedA 34 ms interpreter launch swamps a 0.05 ms reduction, and the table ends up ranking execve instead of the runtime.
only large reductions are rankedA rate is trustworthy only when the reduction is large compared to the startup subtracted from it. Skipping this inverts the result: on small workloads Zig appears to beat C purely as an artifact of subtracting Zig's 34 ms baseline.
measure rep-major, not cell-majorRunning all reps of one cell, then all reps of the next, gives each cell a different 30-second window of a machine whose load is not stationary. A contention episode then lands inside one cell and becomes a difference between cells. This produced a fully convincing, entirely false "2.2× engine gap at one width only" — see the corrections ledger.
slot 0x02 · conformance

Do they agree?

Every row is one workload. Every cell is one runtime's result on it. This table is generated from the recorded run — bench/results.json — not typed by hand.

completed · normal form matches ground truth did not complete — reason shown blank = out of that runtime's documented range

What the two disagreement columns mean

Five of the six runtimes implement the same model — the IC32 typed-node floating-duplicator machine — in C, Zig, Mojo, WebAssembly and Python. Across all 48 workloads they produce byte-identical normal forms and identical interaction counts wherever they both ran. Agreement on counts is stronger than conformance requires and indicates they are running the same reduction strategy, not merely landing on the same answer.

ic_ref is deliberately a different model — a simple non-floating core kept as an independent check — so it reaches the same normal forms by a different path and its interaction counts differ on 21 workloads. That is expected and is the point of having it. The one thing it must never do is disagree on an answer.

the failure mode worth naming

ic_ref fails silently on tetration, and silence is the worst failure mode there is. On nested exponentiation it does not hang and does not error — it returns a residual superposition such as (s (s &81{a,b})) for 2^^3, which reads back as s2 instead of s16. A caller counting s gets a plausible, wrong number. This is outside the fragment IC_REF.md scopes it to, so it is a model limit rather than a conformance bug — but it is an argument for returning a stuck-term error rather than a readable normal form, and it is the reason this page leads with agreement instead of speed.

Ackermann marks the edge of the safe fragment

ack(m,n) for m ≤ 1 reduces correctly on all six runtimes. At m ≥ 2 the iteration duplicates a term that itself contains a duplicator, two independent duplicators end up sharing a label, and reduction diverges — on all six runtimes identically. It is kept as a workload precisely to check that they agree on diverging. That is an encoding limit, not a runtime defect.

slot 0x03 · throughput

How fast, and why the honest answer needs two tables.

Throughput on this workload is memory-latency bound, not compute bound — pointer-chasing over a large heap. Compiler flags barely move it: C at -O3 -march=native -flto measures 55.9 M interactions/s against 54.3 M/s at -O2, inside noise.

Geometric mean over the throughput tier

runtimeinteractions / secvs beststartuplanguage
ic3234.9 M1.0×0.45 msC
ic32z30.9 M1.1×34 msZig
ic32m15.8 M2.2×48 msMojo
ic_ref0.47 M75×29 msPython — reference core
ic_float0.15 M235×13 msPython — model oracle
ic32.wasmunranked20 msWebAssembly — cannot reach the scale

C's 34.9 M/s sits inside the 22–34 M/s that IC32_RUNTIME.md claims, measured independently. The two Python runtimes are not competitors — one is the executable specification, the other the model oracle. Their job is to be readable and to disagree loudly.

correction · do not read C-beats-Zig off that table

The confidence filter that keeps a rate honest also leaves each backend with a different subset of workloads in its geometric mean — Zig's 34 ms baseline means only the largest workloads qualify for it. Fit wall time against interactions across sizes instead: the slope is marginal throughput and the intercept is startup, which cancels the noisy baseline exactly. Slope-fitted over 5 trials, interleaved, min of 7: C 56.1 M/s, Zig 57.8 M/s median, ranges fully overlapping. They are statistically indistinguishable. An early single fit said "Zig 25% faster"; that was noise. Never trust one fit.

ic32 swings 5× against itself depending on the workload

Which is why a single headline throughput number for any interaction-net runtime should be treated as a category error. These are all the same binary:

workload familyM interactions / seccharacter
cnot chain30.3duplicator-heavy, one reused label — worst case
dup chain21.3value-nested duplicators
WRL fold, w=2085.513.4 MB heap image, outgrows cache
WRL fold, w=16113.2real application workload
WRL fold, w=8153.22.2 MB image, cache-resident
body-nested dup157.3best case

Benchmarking TRVM only on the cnot family understates it by roughly 4×. Quoting only the WRL fold overstates it by the same margin. This page quotes the family alongside every rate for that reason.

slot 0x04 · startup and reach

Runtimes differ far more in reach than in speed.

The 100× startup gap is a fixable defect, not a language difference

All three native runtimes reserve the same 16M-slot, 128 MB heap. What they do with it differs, and that difference is the entire gap:

runtimeallocationstartup
ic32 (C)calloc() — kernel lazy zero pages, untouched0.45 ms
ic32 (Zig)gpa.alloc + @memset(heap,0) — eagerly writes all 128 MB34 ms
ic32 (Mojo)List[UInt64](.., fill=0) — eagerly writes all 128 MB48 ms

A workload touching a few thousand slots pays to zero 128 MB it never reads. This is invisible in interactions/sec and dominant for short reductions — which is most of them. Neither @memset nor fill=0 is required for correctness; the runtimes bump-allocate and initialise slots on use.

Proven, not asserted: a Zig variant replacing @memset with an anonymous mmap (kernel zero pages, lazy) drops startup from 29.2 ms to 0.4 ms and then matches or beats C at every size, bit-identically — same normal forms and same interaction counts on exp, fib, fact, parity, tetration and dup.

Maximum readback depth

Found by doubling exp 2^k until the runtime stops being able to read the answer back:

The WASM ceiling reproduces the limitation IC32_WASM.md documents: recursive normal/whnf overflow V8's call stack, and -Wl,-z,stack-size cannot lift it because that sets the linear-memory shadow stack, not V8's. This is the single biggest gap between the WASM build and the native ones — bit-identical where it completes, but it completes 33 of 48 workloads.

open defect · two ceilings, one diagnosed

At roughly 106 interactions on a default 8 MB stack, ic32 segfaults — an unguarded C stack overflow in the reduction recursion, not the heap and not the printer (it crashes identically with readback disabled, and completes correctly under ulimit -s 65536). That is distinct from ic32's clean FATAL: heap overflow at 1.5×107 interactions against the 16M-slot heap. The stack limit is the more serious of the two because it is silent, and it is reachable by ordinary programs. It is the same recursive structure V8 truncates at 8,192 in the WASM build — one shape, three symptoms.

slot 0x05 · optimal sharing

16.7 million negations in 478 rewrites.

Parity of 2N, computed as NOT applied 2N times. Interactions grow linearly in N while the semantics grows exponentially:

2Nnegationsinteractions20N − 2
24167878
28256158158
2124,096238238
21665,536318318
2201,048,576398398
22416,777,216478478

This is the optimal-λ-evaluation speedup and it falls out of the runtime unforced — no memo table, no cache, no rewrite rules added. The sharing is genuine rather than degenerate: odd exponents return FALSE (315, 39, 511) and even return TRUE. Parity of 511 = 48,828,125 negations takes 681 interactions.

where the win actually lives — and where it evaporates

Optimal sharing pays only when the answer is small but the naive path is long. Parity of 2256 is 5,118 interactions in 1.1 ms. But exp 2^N, whose answer has size 2N, costs ∝2N: N=20 is 2,097,400 interactions, N=21 is 4,194,565, and N=22 dies with FATAL: heap overflow. No encoding trick beats an answer that big.

It is also not a TRVM differentiator. It is a property of the encoding, and any optimal-sharing engine given the same encoding reproduces the same number — HVM4 does, exactly. See the next section, and the correction where this page previously claimed otherwise.

slot 0x06 · head-to-head

Against another interaction-calculus engine.

Comparing runtimes across surface syntaxes is where benchmark fraud usually lives, by accident. So a translator for the pure-IC subset of HVM4's syntax carries the same bytes to both engines, and doubles as a portability classifier: a program is in the shared fragment if and only if it translates. Anything using native u32, constructors, pattern-matching lambdas or string sugar fails translation and is reported as out-of-fragment, never silently skipped — an early version truncated 1 + 2 + 3 + 4 to 1 and scored it a pass.

Given the same term, the two engines agree exactly

Of HVM4's own 217-program test corpus, 36 are in the shared fragment and 29 run on both: 23 produce alpha-equivalent normal forms, 6 differ only in how a residual superposition is read back, and 0 genuinely disagree — with the interaction count identical on every single one. That extends across four orders of magnitude:

cnot_NnegationsHVM4TRVM ic32
N = 124,09657,39657,396exact
N = 1665,536917,572917,572exact
N = 18262,1443,670,0923,670,092exact

On identical work, HVM4 is ~3× faster per interaction

Because the counts match exactly, wall time on this family is a pure engine comparison — nothing about encoding or strategy is folded in:

engineM interactions / sstartupfit R²fitted over
HVM467.0134 ms0.9997N = 18..22
TRVM ic3223.4~0 ms0.9996N = 14..19

Each engine is fitted only over sizes where its own reduction dominates its own startup. Fitting both over the same sizes reports HVM4 at 754 M/s, because its 115 ms launch exceeds the entire reduction at every size TRVM can reach — pure launch noise presented as a throughput number. The harness prints R² and a reduce/startup ratio and flags any fit that fails them. TRVM's startup remains ~200× better, so it wins outright on short reductions, which is most of them.

And then total wall time inverts, by 1,600×

On a value-nested duplicator chain at N=100,000 — identical 300,000 interactions on both engines:

NinteractionsHVM4 wallic32 wall
25,00075,0002,003 ms8.9 ms
50,000150,0009,821 ms17.6 ms
100,000300,00036,075 ms32.8 ms
200,000600,000132,559 ms64.1 ms

HVM4's parser is quadratic; its reducer is not. Parked in an unused definition so it is never reduced, parse alone at N=100,000 costs 55 s while HVM4's own reported reduce time stays flat at ~0.15 s. Root-caused to count_uses in src/hvm.c, which is called ~2N times and pops O(N) each — exactly quadratic, 4.00× per doubling.

the same function is also a memory-safety defect

That worklist is a fixed malloc(sizeof(Term) * 1024) with no bounds check on the push. An ASan build turns HVM4's vague double free or corruption into a precise heap-buffer-overflow, WRITE of size 8, 0 bytes after an 8192-byte region, at parse time. It is why HVM4 cannot ingest the real WRL step term at all (which needs a worklist of 1,958) — so re-hosting TRVM's application workload on HVM is not a slower option, it is not an option. One bug, two symptoms: the overflow and the quadratic parse. Reported upstream as the buffer, not as the "342 duplicators" that happen to cross it.

Caveats specific to this comparison: HVM4's default run stops at a residual term with unresolved duplicators while TRVM's normal always performs the full readback, so the harness runs both modes and reports each — comparing plain HVM4 output to TRVM compares two different questions. The 6 "superposition" rows are a readback-contract difference, not a disagreement. One engine, one machine, one build. Ratios port, absolute numbers do not.

slot 0x07 · the benchmark that decided something

Everything above measures the engine. This one measures the product.

The application is a WallRiderLang world fold — the workload the Forge bench actually runs: a compiled step term applied to a config and a state, once per epoch, for as many epochs as the scenario has. It is the only benchmark here that answers a question anyone was going to spend money on: where does the time go, and what is worth optimising?

Where one epoch goes today

21 real epochs at 16-bit rotor width, C, median of 7 reps:

today — the step term is re-parsed every epoch206.96 ms / epoch
parse 205.3 ms · 99.2%reduce 1.33 ms · 0.6%
parse the step term once, restore a heap snapshot per epoch1.61 ms / epoch
restore 0.27 msparse 0.009 msreduce 1.32 ms · 83%

Bars are to scale within each row, not across them. The reduce component is identical in both modes — 27.83 ms vs 27.80 ms over 21 epochs — which is the check that the change did no semantic work.

So what is a faster engine worth here?

changems / epochspeedupwhat it optimises
today206.96
a 3.2× faster reducer205.71.006×the 0.6%
parse once + snapshot restore1.61129×the 99.2%

This is the finding. The entire HVM4 speed advantage, applied perfectly and for free, would move this product by 0.6%. The boundary — re-serialising and re-parsing an 8.67 MB term that is byte-identical on every epoch — is 99.2% of the bill, and fixing it is a change to the process boundary, not to the calculus. No new identity construct, no new runtime construct, no sem- address moves.

Report all three speedups, or none of them

A snapshot has to be built before it can be restored, so the honest number depends on the question. All three come from the same shipped run:

widthsteady stateamortized over 21 epochsepoch 1 alonebreakeven
w = 863.0×13.2×0.78×2 epochs
w = 16128.9×16.4×0.89×2 epochs
w = 20147.8×18.9×1.01×1 epoch

Quoting only the steady-state number is the overclaim; quoting only the amortized number understates it by 7.5×; and on the very first epoch the change is a small loss. A harness that adds the one-time parse to one mode and omits it from the other silently compares two different quantities — that mistake is in the ledger.

Why it costs that much in the first place

With no native numbers in the calculus, a w-bit arithmetic operation lowers to λ-terms costing O(w²) interactions. Fitted from w=8 and w=20 and checked at w=12 and w=16:

itrs/epoch = 593.0 * w² + 1738 max error 0.18% w= 8 39,689 itrs step term 2.20 MB w=12 87,285 itrs step term 5.08 MB w=16 153,723 itrs step term 9.17 MB w=20 238,929 itrs step term 14.46 MB

The constant 1,738 is everything that is not bit-blasted arithmetic — 1.1% of the work at w=8, 0.7% at w=20. So roughly 99% of all interactions are simulated integer arithmetic, and a 32-bit rotor would cost 4× a 16-bit one.

That is the price of a seven-tag calculusVAR LAM APP ERA SUP DP0 DP1, no numbers, no constructors, no primitives — and it is a deliberate trade, not an oversight. Nothing being primitive is exactly what makes a sealed semantic address complete: the artifact fully determines behaviour, and a pure-Python reference implementation can provably agree with a C one. Make + an opcode and you are trusting C's +; the artifact no longer covers the semantics. The finding is not that the trade was wrong. It is that the bill had never been read.

The fix ports — proven in a second language, not asserted

"This ports to any flat-heap backend in ~40 lines" was an assertion holding up a 129× decision, so it was measured. A Zig port of the same change, written against a runtime developed separately:

CZiggap
heap image bytes, w=168,666,8648,666,864identical
normal form, all widthsbyte-identical across all four engine × mode cellsidentical
parse, ms/epoch (before)205.3108.190%
steady state, ms/epoch (after)1.611.4412%

Two parsers written independently in different languages build the same size heap image — the snapshot is not a C trick. And the convergence is the portability evidence: before the change the two languages differ by 90% because the work is parsing; after it they differ by 12% because the work is reduction, and both kernels do the same interactions. The change moves the workload out of the part that varies by language into the part that does not — which is precisely what makes Zig, Mojo and WebAssembly viable backends rather than curiosities.

Corollary worth stating plainly: a speedup ratio is a property of the baseline you escape. Zig's speedup is roughly half of C's at every width — not because the change works less well in Zig, but because Zig's parser was already twice as fast. Report the absolute steady-state cost, where the two languages agree to within 12%, and treat the ratio as commentary.

slot 0x08 · practical reading

What these numbers mean if you are actually going to use this.

Benchmarks are only useful attached to a decision. Here are the decisions these support, and the ones they refuse to support.

Ship the runtime to a browser

The WASM build is bit-identical to the C build wherever it completes, which is what makes a client-side demo evidence rather than decoration. Budget around the one hard limit: normal forms nested deeper than 8,192 hit V8's call-depth cap and cannot be read back, so 33 of 48 workloads complete. Keep the answers shallow, or keep them on a server.

safe · with a stated depth budget
Embed it in a CLI or an editor loop

Startup is 0.45 ms for the C build against 115 ms for HVM4 — a ~200× gap that decides every short reduction, and short reductions are most of them. If you invoke the runtime per keystroke, per file save, or per test case, startup is your benchmark and throughput is a footnote. Prefer C or a lazily-allocated build; Zig and Mojo currently pay 34–48 ms to zero a heap they never read.

the strongest result on this page
Fold a long-running world, epoch after epoch

Do not shell out per epoch. 99.2% of a naive epoch is re-parsing a term that did not change, and a persistent process with a pre-parsed heap image is worth 129× where a 3.2× faster engine is worth 1.006×. Breakeven is 1–2 epochs, so the only case where it loses is a single-shot run.

129× · steady state, w=16
Run many independent replicas

Trajectories, scenarios and replicas are embarrassingly parallel and need no sharing. But measure before you scale: the fold is memory-bandwidth bound, and replica-parallel throughput saturates at 3.7–3.9× on a 12-core machine regardless of heap size. The parallelism axis is real; it is just narrower than core count suggests.

3.8× ceiling · measured 1 → 24 replicas
Port the runtime to another language

Five implementations already agree byte-for-byte, so the contract is real and testable: term on stdin, normal form on stdout, with the conformance vectors as the gate. Two independently written parsers produce the same heap image size, and after the boundary fix two languages agree on cost to within 12%. This is the least risky thing on the list.

5 languages · 0 disagreements
Expect optimal sharing to be your speedup

It usually will not be. Sharing collapses exponential work only when the answer stays small — parity of 2256 in 5,118 interactions. When the answer itself is large the cost tracks the answer, and heap overflow arrives on schedule. It is also not a differentiator: another optimal engine given the same encoding produces the identical count.

honest limit · not a selling point
Reach for a GPU

Interaction-net reduction is uncoalesced pointer chasing over a large heap — the GPU's worst access pattern — and the available intra-epoch parallelism is the simulated arithmetic, which native numerics would delete rather than accelerate. They are substitutes, not complements. Note also that this page's earlier parallelism estimate was withdrawn outright, so treat any headroom figure here as unmeasured.

not supported by current evidence
Re-host on HVM4

Attractive on paper — native u32 and ~3× per interaction. In practice HVM4 cannot parse the real step term at all (fixed 1024-entry worklist, heap-buffer-overflow at parse time), its parser is quadratic, its startup is 115 ms against 0.5 ms, it has no second implementation to check against, and the identity spine needs a frozen calculus under our control. Measured, then ruled out.

ruled out on evidence, not preference
slot 0x09 · corrections and retractions

Every number here that was published and then withdrawn.

A benchmark page with no corrections section is either very young or not being checked. These were all found by re-measuring claims that had already been written down — several of them were flattering, which is exactly why they survived first review.

withdrawn
"478 vs 234 million — TRVM achieves a sharing collapse HVM4 misses"
was: a 490,000× advantage. is: no advantage; both engines produce both numbers.

It compared two different programs: TRVM's Church-exponentiation parity workload against HVM4's N-level doubling chain. Given the same encoding each engine reproduces the other's number exactly — HVM4 also does 224 parity in 478 interactions, and both take ~2N on the doubling chain. The collapse is a property of the encoding. This is why the whole HVM4 comparison was rebuilt around a translator that carries the same bytes to both engines.

withdrawn
Span and available-parallelism table
was: ~15 rounds per epoch, flat in w; parallelism ~40w². is: withdrawn entirely.

The third instrumentation bug in that measurement: the sampling threshold was file-scope while the interaction counter reset every epoch, so after a heavy epoch left the threshold high, the next heavy epoch never crossed it — its work counted, its samples did not. The workload is bimodal, so this deleted samples from exactly the epochs carrying the parallelism. The repaired estimator converges only at w=8, where it reads ~124 rounds and ~320 parallelism against the published 17.2 and 2,306. The argument that native numerics and GPU are substitutes survives, because it rests on interaction counts rather than span — but its headroom figure does not, and the honest reading is now more sceptical of GPU, not less.

corrected
Boundary-fix speedup: "~145×"
was: 145×. is: 129× steady state at w=16, 16.4× amortized over 21 epochs, 0.89× on epoch 1.

Both harnesses added the one-time step parse to the fold mode and omitted it from the re-parse mode, so the reported per-epoch figure meant "amortized" in one mode and "steady-state" in the other. Comparing them directly gives 17×, understating by 7.5×; 145× was the right quantity measured once. It is also width-dependent. All three numbers are now published together, computed inside each repetition before summarizing — because median(a+b+c) is not median(a)+median(b)+median(c) and these components have very different spreads.

corrected
"C is faster than Zig" — and then "Zig is 25% faster than C"
was: 34.9 vs 30.9 M/s. is: 56.1 vs 57.8 M/s slope-fitted; statistically indistinguishable.

The geometric-mean table's confidence filter leaves each backend with a different subset of workloads, so the two means are not like-for-like. The slope method — fit wall time against interactions across sizes, take the slope as throughput and the intercept as startup — cancels the baseline exactly. The opposite claim came from trusting a single fit. The entire remaining difference is Zig's 24.6 ms fixed startup cost, and removing it makes the two bit-identical and equal in speed.

measurement defect
A 2.2× engine gap that did not exist
was: zig_fold at w=16 reading 2.98 ms/epoch against C's 1.33. is: an artifact of measurement order.

The runner measured cell-major — all repetitions of one cell, then the next — so each cell sampled a different 30-second window of a machine whose load is not stationary, and a contention episode became a difference between cells rather than noise within them. The symptom was specific enough to be convincing: the gap appeared at one width only, while w=8 and w=20 tracked C exactly. There is no mechanism that makes a port slow at one width. Fixed by interleaving rep-major.

measurement defect
A busy machine moved the headline in the flattering direction
was: 223.5× at w=16 on a contended run. is: 129× quiet — while the fold side moved only 2.6%.

Because the workload is memory-bandwidth bound, contention lands almost entirely on the re-parse baseline. A busy machine therefore does not merely add noise, it inflates the ratio. The runner now refuses to start above a 1-minute load average of 1.5, and records load average and the busiest processes at both ends of every run. Second independent argument for reporting the absolute steady-state cost and treating the ratio as commentary.

evidence defect
Standard output and standard error came from different process invocations
was: a published error file next to a correct result file and a manifest saying it passed.

All three statements were true — about three different executions. Re-running that command gives a clean exit and a matching hash, so it was never a real failure, which is worse: the raw evidence was capable of contradicting the headline claim without anyone noticing. Now one subprocess call produces stdout, stderr, exit code and timings together; they are asserted against each other and published by atomic rename or not at all. The published unit is the invocation, not the cell. Verified against that exact failure — truncating an input aborts the run and publishes nothing, because a fail-closed script that has never been seen to fail is untested.

corrected
Quoting one engine rate for all workloads
was: "ic32 does 23.4 M interactions/s". is: 21–157 M/s depending entirely on the workload.

23.4 M/s came from a slope fit on the cnot family, which is ic32's worst case. Measured in-process on the actual application workload it does 104–130 M/s, falling to 53.5 M/s at w=20 when the 14 MB image outgrows cache. Per-interaction rate is strongly workload- and locality-dependent, so the "3× faster" ratio is unmeasured on the application workload and must not be quoted for it.

slot 0x0A · reproduce

Run it yourself.

The conformance suite discovers whichever backends are built and names the ones that are not. Nothing on this page requires trusting the table.

# the six-runtime suite — correctness gate, throughput tier, depth ceiling python3 bench/bench.py # full suite, ~6 min python3 bench/bench.py --quick # small sizes python3 bench/bench.py --json bench/results.json # regenerate the conformance matrix on this page from that run python3 site/build_benchmarks.py # head-to-head against HVM4 — same bytes, both engines clang -O2 -o /tmp/hvm4 $HVM4_SRC/src/hvm.c # build outside its repo python3 bench/hvm4_families.py /tmp/cnot # the two scaling families python3 bench/hvm4_corpus.py # HVM4's own 217 tests, both engines python3 bench/hvm4_throughput.py # identical counts, so wall time is the engine

Provenance

machine AMD Ryzen AI 9 HX 370 · 12 physical / 24 threads · 24 MB L3 os Linux 6.18.6 x86_64 toolchain gcc 15.2.1 · zig 0.15.2 · clang -O2 conformance run application run 20260727T175133Z-1528358 · 12 cells × 7 reps · rep-major · all assertions passed

Standing caveats. Single machine, single run; absolute times are not portable, ratios are. runtime/js/swarm.js is excluded — it is a distributed multi-worker harness, not a term-on-stdin reducer, so it does not share the contract. The throughput tier includes readback in wall time, since not every backend has a quiet mode; it is paid by all of them equally. The application figures come from an out-of-tree prototype: they measure the process boundary, and no identity or runtime construct was changed to obtain them.