Spec M6 — src/mc.mc: the self-hosted compiler · Spec M7 — fixed point
Prerequisite: M5 closed (stage0 is the executable specification; freeze its behavior before starting). This is the milestone that dominates the schedule. Golden rules:
- Transliteration, not a rewrite. One
.mcfile per.cfile, same functions, same names, same order, same I/O shape. Diverging on purpose is forbidden — the only freedom is what the language itself forces (no structs, no typed pointers). - Core only. No
#rule,while,struct, prelude.loop { if (!c) break; ... }by hand. - Always use accessors. Every C "struct" becomes a layout of
#define FIELD_X off+x_field(p)/set_x_field(p, v). Zerold64(p + 16)outside of accessors.
Layouts (flat data in the arena; all fields 8 bytes unless noted) #
| C | .mc | fields |
|---|---|---|
Token | TOK_* | id, start(uptr), len, val, line — 40 B |
TokEnt | TE_* | text(uptr), len, word, id — 32 B |
Node | ND_* | kind, op, type, val, name(uptr), a, b, c, d, next, line — 88 B |
InfixEnt/PrefixEnt | IN_*/PR_* | tok, prec, right, tmpl / tok, tmpl |
Ins | INS_* | op, rd, rn, rm, imm, label, sym — 56 B |
Section | SEC_* | seg(16 B inline), sect(16 B inline), flags, align, data(Buf inline: p,len,cap), zsize, rel(uptr), nrel, relcap |
Symbol | SYM_* | name(uptr), sect, value, global — 32 B |
Reloc | REL_* | off, sym, type, pcrel, len — 40 B |
Buf | BUF_* | p(uptr), len, cap — 24 B |
Record arrays = base + index * size. C strings = uptr to NUL-terminated bytes in the arena.
Files #
src/arena.mc (xalloc, buf_, out_, die, read_file, write_file, cstrlen, str_eq, mem_eq),
src/lex.mc (already exists from M4; adjust to the layout), src/ast.mc, src/parse.mc,
src/gen_arm64.mc, src/macho.mc, src/main.mc (driver, same CLI: mc [--dump-*] in.mc [-o out.o]).
src/mc.mc = #include of all of them in dependency order. The arena is u8 heap[HEAP_SIZE] in bss.
Delivery strategy (verifiable slices, in this order) #
arena.mc+ test: writes a file and reads it back → identical.macho.mc+src/m0.mc: reproduces M0 (the.oformovz x0,#42; ret), and the.omust be byte for byte identical to whatbuild/mc0generates fortests/001-return42.mc. This is the first real cross-check, and it closes out the writer before any front end exists.ast.mc+parse.mc(withlex.mc):--dump-astfor everytests/*.mcidentical tomc0's (scripts/check-ast.sh, modeled oncheck-lex.sh).gen_arm64.mc:--dump-asmidentical tomc0's for every test (scripts/check-asm.sh).main.mc:.oidentical (cmp) tomc0's for every test (scripts/check-obj.sh).make testwithbuild/mc1(=mc0 src/mc.mc, linked) passing the same suite.
M7 — fixed point #
scripts/bootstrap.sh:
build/mc0 src/mc.mc -o build/mc1.o && scripts/link.sh build/mc1 build/mc1.o
build/mc1 src/mc.mc -o build/mc2.o && scripts/link.sh build/mc2 build/mc2.o
build/mc2 src/mc.mc -o build/mc3.o
cmp build/mc2.o build/mc3.o # the criterion
shasum -a 256 build/mc2.o | diff - tests/golden/mc2.sha256 # (create it the first time)
make bootstrap calls the script. mc1.o vs mc2.o may differ (different compilers);
mc2.o == mc3.o is what proves it. Divergence → diff <(build/mc1 --dump-asm src/mc.mc)
<(build/mc2 --dump-asm src/mc.mc) and bisect by function.
Acceptance #
make bootstrap green; scripts/test.sh build/mc2 green; golden recorded. Report the size of
src/*.mc. Report the time for mc1 src/mc.mc (expected: well under 1 s).