bench: mono-vs-vdisp micro-benchmark + revised Decision 11 rationale

Hypothesis-driven measurement of "did monomorphisation actually
buy us performance?" on a 100M-iter LCG hot loop, AILang mono'd
code vs. four C reference variants (direct-inlinable, direct-
noinline, indirect-monomorphic, indirect-polymorphic). Zen 3,
clang -O2, median-of-15.

Headline: H1 supported, but the mechanism is inlining, not
dispatch shape. AILang mono = hand-C direct (1.000x). Indirect-
monomorphic = direct-noinline (1.000x) — saturating branch
predictor makes the indirect-call cost vanish on this hardware.
Inlining is the actual 3.31x win; polymorphic indirect adds
another 21% predictor-miss penalty.

DESIGN.md Decision 11 gains a rationale paragraph reframing mono
as inlining-enabler rather than indirect-call-eliminator, with
explicit pointer to the bench. JOURNAL entry records the full
methodology, ratios, limitations, and the side-effect mono-pass
env.globals-seeding bug surfaced while building the AILang fixture
(separate RED-first debug iter to follow).
This commit is contained in:
2026-05-10 01:03:21 +02:00
parent 105ad3fe80
commit 4cacfcbdac
8 changed files with 626 additions and 0 deletions
+17
View File
@@ -1606,6 +1606,23 @@ After this pass, the IR contains no class machinery — only ordinary
monomorphic functions and direct calls. Codegen sees no difference
between a hand-written `show_int` and a synthesised `show__Int`.
**Why mono, not virtual dispatch (the empirically-grounded version).**
The original rationale implicitly argued that mono saves a per-call
indirect-jump cost. The 2026-05-10 mono-vs-vdisp micro-benchmark
(`bench/mono_dispatch.py`, JOURNAL entry of the same date) refutes
that specific claim: on a saturating branch predictor with a
monomorphic indirect target, indirect dispatch is free relative to
the same-shape non-inlined direct call (1.000x on Zen 3 / clang -O2).
The correct rationale is one level up: **mono makes the call target
visible to the optimiser, unlocking inlining and downstream loop
transformations that virtual dispatch prevents in principle.** The
measured end-to-end win on a tight LCG hot loop is 3.31x vs
non-inlinable direct call and 4.00x vs polymorphic vdisp (4 distinct
targets cycling). On larger callee bodies or cold call sites the
inlining win shrinks toward zero, but the architectural claim — "mono
enables optimisations vdisp forbids" — holds across the spectrum,
while the older "saves an indirect call" framing does not.
The separator is `__` rather than `#` or `@` because `#` and `@`
are invalid in LLVM IR global identifiers (the IR verifier rejects
them inside `@ail_<module>_<def>` mangled names). `__` is legal in