Research / Compiler, backends and verification

P51 · empirical · draft

Auditing Generated Code Against Hand-Written Code on Load-Independent Metrics: Fuel, Allocated Bytes, Allocs/op, Microtask Turns and Element Copies

shipped evidence empirical Compiler, backends and verification

State of the work. Six per-backend audit harnesses and their before/after counts are on main with tests pinning most fixes; there is no uniform cross-backend corpus, no timing validation and one correctness defect found is still open.

This is a draft, not peer reviewed. Repository references link to revl at commit 45bec431f.

Pitch

Counting things that do not depend on machine load (wasmtime fuel, bytes allocated per op, allocs/op, microtask turns, elements copied) let one team audit six emitters against hand-written code in one audit cycle on a machine running a dozen other jobs, and the counts found a quadratic accumulation in four of six backends (Python, TypeScript, Go, Rust), a double-evaluation correctness bug and an 85% dead prelude that timing-based audits would have missed or misattributed.

Abstract

Compiler back ends are normally audited with wall-clock benchmarks, which are unreliable on shared machines and blind to the cause of a gap. We report a per-backend audit of the code a single compiler (revl) emits for six targets (wasm, Java, TypeScript, Python, Go, Rust), compared against code a competent developer would write by hand for the same semantics. Every default metric is a count whose value does not depend on machine load (the JVM figure is allocated bytes, which do depend on JIT state): wasmtime fuel for wasm, thread-allocated bytes per operation on a JVM, allocs/op and B/op from Go's testing.B, microtask turns and Promise allocations for TypeScript, bytecode, frame and element-copy counts for Python, and counting-allocator allocation counts for Rust. The audits found, among others: persistent push/set/remove lowered as full copies in Python, TypeScript, Go and Rust (for example 500500 element copies against 1000 at n=1000 in Python, 4274103 B against 8192 B at n=1000 in Go, 729599 allocations against 2425 for hand-written Rust self-append); a string lift costing 19 fuel per byte on wasm that became 0 after a zero-copy change; 82 to 86 percent of the emitted wasm core module removed by pruning unreachable functions on the audit's three probes; and a semantic defect, ?? evaluating its left operand twice in Python, found by counting effects. We also report what the counts refuted: a static count of Java allocation sites predicted the wrong winner (221224 B against 286760 B), and a Python opcode counter reports the largest defect at 0.96x because a list copy is one bytecode. We close with the places where the method did not hold: one Rust self-host measurement used wall-clock, and one mirror defect in Go was found but not fixed.

Problem and claim

Existing compiler-output audits either time a benchmark suite or inspect emitted text. Timing is unusable when other jobs share the machine (the audit host ran about a dozen concurrent jobs), and it says nothing about why a gap exists. Inspecting text finds shapes but cannot say whether a shape costs anything: the Java audit's static count of construction sites reversed when measured.

The claim is narrow and checkable. For each backend there is a metric that (a) is a deterministic count, (b) is blind to host load, and (c) separates the emitter's choice from the language's cost. Using it, an emitter can be compared against a hand-written yardstick that keeps the emitted structure and changes one choice at a time. The same discipline requires negative controls: shapes measured and found already at hand-written cost are written down so they are not re-audited.

We do not claim the counts replace timing in general. We claim they are sufficient to find and fix the defect classes below, and we document the cases where a count is the wrong instrument.

Contributions

  1. A per-backend catalogue of load-independent metrics and the harness that computes each (bench/codegen/{wasm,java,typescript,python,go,rust}), each refusing to report a number when the hand-written side stops agreeing with the emitted side (wasm run.py variant gate, Go Test* functions, TypeScript STALE, Rust agreement assertion).
  2. A catalogue of emitter defects found by counting, with before/after ratios: quadratic accumulation (concat, push, set, remove, template fold), unneeded async colour, zero-copy lift, unconditional helper preludes, per-call code-point materialisation, boxing of Opt/Result.
  3. A semantic bug found by counting effects: lookup(k) ?? 0 invoked the extern twice on Python, and the mirror defect (right operand evaluated eagerly) exists on Go.
  4. A profile-before-fix decomposition for Rust (clone-on-append about 85 percent of the gap, charAt about 2.7 percent, build profile ruled out) that redirected a fix that a plausible intuition had gotten wrong (the 28 percent lexer regression).
  5. Negative results, including a case where a static count and an executed byte count disagree in sign, and a list of declined optimisations with the measured reason.
  6. An account of where the load-independent method failed or was bypassed.

Evidence on main

Claimpath:line or testStatusNote
Wasm audit measured in fuel, no timingsdocs/v2.0-roadmap.md:1707; bench/codegen/wasm/README.md:1-12shippedHarness plus results.json committed
Wasm string lift 19 fuel/byte to 0, guarded by allocator floordocs/v2.0-roadmap.md:1708; backends/wasm/canonical.py:835 ($__canon_lift_str)shipped"echo 19.000 to 0.000 fuel/byte, flat 49 fuel" (roadmap line 1708)
82 to 86 percent of emitted wasm core removed by pruning unreachable functions; reachability sweep declines on ref.func, call_indirect, table, elembackends/wasm/emit.py:7154 (prune_unreachable_funcs), :7036 (_UNSWEEPABLE); selfhost/emit_wasm.rvl:2348 (prune_preamble)shippedecho 3483 to 503 B in the audit (docs/v2.0-roadmap.md:1709); the "85 percent" figure is a three-probe figure
Arena reclaim gated by a soundness checkbackends/wasm/canonical.py:959 (_arena_safe); backends/wasm/test_heap_reclaim_432e.py; backends/wasm/test_heap_growth_432e.pyshipped
Java audit by thread allocated bytes, 15-sample minimum, openjdk 26.0.2docs/v2.0-roadmap.md:1718, :1731; bench/codegen/java/README.md:1-30shippedHeader was corrected after a macOS java stub misled the probe
Java String.format to concat chain, 508968 vs 40040 B/opdocs/v2.0-roadmap.md:1731; backends/java/emit.py:4542 (_format_java)shipped
Java static count reversed by execution: 221224 vs 286760 B/op, ratio 0.77docs/v2.0-roadmap.md:1723, :1731; bench/codegen/java/cases/list_pushshippedClosed as a negative result
Java router, config constants, stdlib gatingbackends/java/emit.py:8823, :3865, :1804shipped
TS async colour per function, excess turns to 0 for sync armsbackends/typescript/emit.py:3540 (_renders_await), :3636; bench/codegen/typescript/results/counts.txt (match-sync-arms)shippedcounts.txt still lists 2.0 turns / 4.0 promises excess per op (24 turns, 48 promise allocations in total) for match-in-async-fn, where one arm genuinely suspends
TS string helper memo, n=1600: 5121600 to 1600 code pointsbackends/typescript/emit.py:4687; backends/typescript/tests/str_scan_memo.test.ts; docs/v2.0-roadmap.md:1745 (before values); counts.txt string-scan table (after values only)shippedcounts.txt alone does not show the before column
TS destructive push via frontend unique markerbackends/typescript/emit.py:3798; backends/typescript/tests/unique_writes.test.tsshipped
Python audit with opcode, frame, tracemalloc and element-copy counters; opcode counter blind to the main defect (0.96x)bench/codegen/python/README.md; docs/v2.0-roadmap.md:1754shipped
Python ?? double evaluation fixeddocs/v2.0-roadmap.md:1761; tests/test_71_codegen_perf_findings.pyshipped"200 extern invocations per 100 uses"
Go ?? mirror defect: right operand eagerbackends/go/emit.py:6895-6900 (revlOptOr(left, right)); backends/go/emit.py:10669; docs/v2.0-roadmap.md:1768; backends/go/test_emit_go.py:636shipped as defect, not fixedThe test pins the eager spelling. Still open at 45bec431f as far as could be found
Go audit via testing.B allocs/op, B/op; all eight findings landeddocs/roadmap-archive/13-the-embeddable-compiler-revl-as-a.md:220; bench/codegen/go/README.mdshipped10x input gives 101x bytes for push, 84x for map set
Go shared ownership analysis; scan loops; string accumulatorssrc/revl/ownership.py; backends/go/emit.py:7898 (_v3_str_accumulators), :8082 (_v3_scan_loops)shippedUse of src/revl/ownership.py by the Go emitter was not checked
Rust audit under counting allocator, agreement assertedbench/codegen/rust/README.md; docs/v2.0-roadmap.md:1787; bench/codegen/rust/census.pyshippedThe README has no census numbers; the roadmap's census percentage is not reproduced in the cited sources and is treated as unverified
Rust self-append through concat: 729599 allocations against 2425 hand-writtendocs/v2.0-roadmap.md:1791; backends/rust/emit.py:8498, :8629shippedFixed 2026-09-05 (7831b8c5); the fixed count is not stated in the cited sources. The fix is in _v3_self_append_inplace (emit.py:5049, per the roadmap), not at :8498 or :8629
Rust accepted residual with measured reasontests/test_116_rust_perf_accepted.pyshipped
Rust lexer decomposition and the fix sequence 17x to 1.37x slower than CPythondocs/bench-selfhost.md:244, :310-347, :433-455; docs/roadmap-archive/12-post-v2-0-horizon-strategy-review.md:567-571shippedSee the limits: this is wall-clock

Numbers that exist

Paper outline

  1. Introduction: the shared-machine problem, the claim, the defect classes.
  2. Method: choosing a count per backend, the yardstick rule (change one choice), agreement gates, STALE reporting.
  3. Per-backend metrics and harnesses (table).
  4. Findings: accumulation, async colour, boundary copies, dead preludes, boxing, string materialisation.
  5. A correctness bug found by counting effects (Python and Go ??).
  6. Negative results and measurements that reversed a prediction (Java sites vs bytes; Python opcode counter; Rust profile before fix).
  7. Where the method did not hold (wall-clock use in the Rust self-host table, JIT effects on Java counts, unfixed Go defect).
  8. Related work and discussion.

Threats to validity and limits

Fact-check

Claims in this draft were checked against revl main at commit 45bec431f: 50 checked, 11 corrected.

Cite this draft

@misc{revlp51,
  author       = {Thomas Moussajee},
  title        = {Auditing Generated Code Against Hand-Written Code on Load-Independent Metrics: Fuel, Allocated Bytes, Allocs/op, Microtask Turns and Element Copies},
  year         = {2026},
  howpublished = {\url{https://inso1337.github.io/revl/research/P51.html}},
  note         = {Research draft, not peer reviewed. Written against revl commit 45bec431f. Licensed CC BY 4.0.}
}

Text licensed CC BY 4.0, author Thomas Moussajee.

← All research drafts