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:

  1. Transliteration, not a rewrite. One .mc file per .c file, 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).
  2. Core only. No #rule, while, struct, prelude. loop { if (!c) break; ... } by hand.
  3. Always use accessors. Every C "struct" becomes a layout of #define FIELD_X off + x_field(p) / set_x_field(p, v). Zero ld64(p + 16) outside of accessors.

Layouts (flat data in the arena; all fields 8 bytes unless noted) #

C.mcfields
TokenTOK_*id, start(uptr), len, val, line — 40 B
TokEntTE_*text(uptr), len, word, id — 32 B
NodeND_*kind, op, type, val, name(uptr), a, b, c, d, next, line — 88 B
InfixEnt/PrefixEntIN_*/PR_*tok, prec, right, tmpl / tok, tmpl
InsINS_*op, rd, rn, rm, imm, label, sym — 56 B
SectionSEC_*seg(16 B inline), sect(16 B inline), flags, align, data(Buf inline: p,len,cap), zsize, rel(uptr), nrel, relcap
SymbolSYM_*name(uptr), sect, value, global — 32 B
RelocREL_*off, sym, type, pcrel, len — 40 B
BufBUF_*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) #

  1. arena.mc + test: writes a file and reads it back → identical.
  2. macho.mc + src/m0.mc: reproduces M0 (the .o for movz x0,#42; ret), and the .o must be byte for byte identical to what build/mc0 generates for tests/001-return42.mc. This is the first real cross-check, and it closes out the writer before any front end exists.
  3. ast.mc + parse.mc (with lex.mc): --dump-ast for every tests/*.mc identical to mc0's (scripts/check-ast.sh, modeled on check-lex.sh).
  4. gen_arm64.mc: --dump-asm identical to mc0's for every test (scripts/check-asm.sh).
  5. main.mc: .o identical (cmp) to mc0's for every test (scripts/check-obj.sh).
  6. make test with build/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).

Edit this page