Spec M2 — locals, parameters, if/else, loop/break N/continue, calls, extern, local arrays, memory
Prerequisite: M1 closed and reviewed. Read docs/plan.md, docs/specs/M1.md and the current code.
Scope: everything a function needs to compute and call other functions. Out (M3): globals,
string literals, #include, #define, arena, __cstring/__data/__bss.
Parser #
- New statements:
type name = expr;,type name;,type name[CONST];(N_VAR: name, type, a=init or 0, val=number of elements or 0),name = expr;(N_ASSIGN: name, a=expr),if (expr) stmt [else stmt](N_IF a,b,c),loop stmt(N_LOOP a),break;/break N;(N_BREAK val=N, default 1),continue;(N_CONTINUE),return [expr];, block,expr;. - New top level:
extern type name(params);(N_EXTERN) and functions with parameterstype name(type a, type b, ...)— max. 8 params, error if exceeded. - New expressions:
&name(N_ADDR, unary-precedence&prefix), callname(args)already parsed in M1 (N_CALL: name, a=list of args). - Scoping: nested blocks may shadow; resolution goes from innermost outward. Implement with a flat
stack of locals
{name, type, offset, nelem}plus a size mark per block.
Codegen #
- Frame: each local/param takes an 8-byte slot (arrays:
nelem * widthrounded to 16); negative offsets from x29, assigned in declaration order; no slot reuse.frame = round16(slots + spills).sub sp≤ 4095 or error. Compute the frame BEFORE encoding but AFTER walking the whole function (counting locals from nested blocks). - Params: the prologue writes
x0..x7to their slots (str xN, [x29, #-off]), without otherwise touchingx0..x7. - Registers by depth (from the plan): depth 0..6 →
x9..x15. Spill: depth ≥ 7 goes to a spill slot in the frame; operations with a spilled operand load it intox16/x17. Track the function's maximum depth to size the spill area. - Call: evaluate args at depths
cur..cur+n-1; savex9..x(9+cur-1)(the live ones) to per-depth dedicated save slots;mov x0..x(n-1)from the args;bl _name(relocR_BRANCH26, pcrel=1, len=2 viareloc_add;sym_reffor externs, a defined symbol for functions in the file);mov x(9+cur), x0; restore the live ones. Functions called before being defined: register ALL signatures (N_FUNC/N_EXTERN) before generating any bodies. Checks: arity; call to an unknown name = error. - Intrinsics (recognized by name in N_CALL, not symbols):
ld8/ld16/ld32/ld64(p)→ldrb w/ldrh w/ldr w/ldr x(zero-extend);st8/st16/st32/st64(p, v)→strb/strh/str w/str x. Return types: u8/u16/u32/u64; st* is void. - Locals: read via
ldrwith the type's width (zero-extend); write viastrwith that width; an array as a value =sub xd, x29, #off(address, type uptr).&name= the same address. - Control flow:
ifviacbz;loopwith start/end labels; a loop stack forbreak N(N > depth = error) andcontinue;return→x0+bto the epilogue. - Assigning to an array or an unknown name = error. Type of
namein an expression = declared type. - Keep
--dump-asmcovering everything (str/ldr with offsets, bl with symbol, labels).
Tests (all with // expect-exit and/or // expect-stdout) #
010-locals.mc: locals, assignment, block shadowing → 42.011-loop.mc: sum 1..n withloop/break/continue→ 55 (n=10).012-call.mc: recursivefib(10)→ 55; function with 8 params summing → known exit.013-putnum.mc:extern i64 write(i64 fd, uptr buf, i64 n);+putnum(local array u8[24],st8,/,%) printingfib(24)→ stdout46368\n(the\nwritten withst8(buf+i, 10)), exit 0.014-ldst.mc:u8 b[16];st32(b, 0x11223344); st16(b+8, 0xBEEF);andreturn ld8(b+1) + ld16(b+8) ...with the result doubled to 42; zero-extend test (ld8of 0xff = 255).015-break-n.mc: two nested loops withbreak 2→ 42.016-spill.mc: an expression with depth ≥ 9 (e.g. a call with 8 args that are themselves calls) → known result.
Acceptance #
make stage0 && make stage0-san && make budget && make test green; --dump-asm deterministic.
Report lines per file and the total.