Spec M23 — dynamic limits: best-effort sizing with a tolerance, no hard caps
Owner's direction (2026-09-03): limits must be dynamic. mc computes a best-effort estimate of what
the build needs, reserves that times 1 + tolerance, and grows if the estimate was short. The only
knob is tolerance, a float in [0, 1] in mc.toml. A checker reports how good the estimate was
and may, with consent, adjust the tolerance. No per-table caps anywhere in the self-hosted mc.
Problem #
Every compiler table is a static array sized by a MAX* constant; programs that outgrow one die
with too many X, and the architect had to bump five constants by hand after M15.
Design (all in src/*.mc; stage0 keeps static tables and only compiles src/mc.mc) #
- Growable tables. Every
MAX*-sized array insrc/becomes an arena block that doubles on demand (grow(ptr, count, cap, elem_size)inarena.mc, the patternnodes_growalready uses). TheMAX*constants disappear fromsrc/(they remain in stage0 only). Growth never changes output order (tables keep insertion order), so determinism is untouched. -
Best-effort estimate. Two sources, combined by taking the larger:
- Static, before parsing: from the byte size of the entry file plus every include it can see
(
#include "x"and<name>resolved eagerly by a cheap pre-scan; the bundle sizes are known):tokens ~ bytes / 5,nodes ~ tokens * 3/5,strings ~ count of '"' / 2,functions ~ count of ") {",defines ~ count of "#define",symbols ~ functions + globals + strings,heap ~ sum of (count * record size) + section buffers ~ 2 x bytes. Coefficients live in one table and are calibrated againstsrc/mc.mcandexamples/api(documented with the measured values). - Remembered, from the previous build of the same project:
mc buildwritesbuild/.mc-usage.toml(per table: used count; arena high-water) and the next build pre-sizes from it. Deterministic output does not depend on it (only capacities do).
- Static, before parsing: from the byte size of the entry file plus every include it can see
(
tolerance([limits] tolerance = 0.25, default0.25, float in[0, 1]): pre-reserveestimate * (1 + tolerance)for every table and for the arena (mmapat startup; the staticheap[]is the fallback whenmcruns without a TOML).tolerance = 0reserves exactly the estimate;1doubles it. Values outside[0, 1]are refused withfile:line:col. Floats are parsed bytoml.mcas decimal text and stored as basis points (i64, 0..10000) — the language has no floating point and does not need it here.mc limits [DIR](alsomc build --limits): after the build, one line per table: estimate, reserved, used, growth events, and a verdict:ok(no growth),grew(the estimate was short — the build still succeeded),tight(used > 90% of reserved). Exit 0 / 3 (grewortight) / 1 (build failed). Deterministic output for CI.mc build --fix-limits: with the developer's consent (the flag), if any tablegrew, raisestolerancein the project'smc.tomlto the smallest value in[0, 1]that would have avoided growth (rounded up to 0.05), rewriting only the[limits]section byte for byte otherwise; if1.0is not enough, says so (the static estimate is the culprit) and refreshes the remembered usage instead. Never writes without the flag.- Seed guard.
make checkgainscheck-limits:mc limitsonsrc/mc.mccompares the seed's fixedMAX*constants (read fromstage0/mc.h/*.c) with the actual usage and fails when any is above 90% — the early warning the architect lacked at M15.
Acceptance #
- A generated program with 5 000 functions and 5 000 string literals builds with no TOML change;
mc limitsshowsgrewfor the static estimate on the first build andokon the second (remembered usage).make checkobjects and golden unchanged for the corpus except one rewrite (arena change). tolerance = 0onexamples/api: build succeeds (growth),mc limitsexit 3;--fix-limitsrewrites only[limits](diff shows nothing else), nextmc limitsexit 0.tolerance = 1.5->mc.toml:L:C: tolerance must be between 0 and 1.check-limitsinmake checkreports the seed headroom for functions, strings, defines, tokens.- Docs:
docs/build.md§ limits (estimate table with measured coefficients);docs/determinism.mdnote that capacity never influences output.