summary_ast-stageA.md 3.7 KB

Session summary — 2026-10-02: AST Stage A + array-of-record fix

Suite 178/178; self-hosting fixpoint OK (2,900,782 bytes). Tag v3-ast-stageA.

This is the first step of the AST / two-phase frontend refactor (option 1): make it possible to build a syntax tree while parsing and lower it in a later pass, so forward references (variables, procedures, typed constants, CONST constructors) resolve naturally and the text-patching machinery can be deleted.

1. Stage A — the AST module (compiler/src/AST.def/.mod)

An arena-backed node store, referenced by an integer index (Node), mirroring SymTab.TypeIndex (no pointers in the interface, no GC, bounded memory — self-hosting-friendly).

  • NodeRec = kind, op, ty, nch, child[0..MaxChild-1] — one flat record array; every construct is expressed with a kind + up to 8 children (the armornick/CocoR AST.java pattern, flattened).
  • Node kinds for units, declarations, type constructors, statements and expressions (NkUnit, NkVarDecl, NkProcDecl, NkArray, NkAssign, NkIf, NkWhile, NkBinExpr, NkSelector, …).
  • Operator and selector codes (OpAdd…OpIn, SelField/SelIndex/ SelDeref) in the node's op.
  • An interned text pool (AddTxt/Txt/TxtLen) for identifiers and literal spellings.
  • Constructors MakeNode/MakeLeaf/MakeBin/MakeUn, accessors (Kind/Op/Ty/NChild/Child/AddChild/SetChild), and a debug Dump.

Nothing in the grammar calls it yet — this is purely additive; the suite and fixpoint are unaffected. tests/t_ast.mod builds a small tree (a := b + 2, an unary node, a block) and walks it: exit 42.

2. Bug found and fixed — array-of-record element stride

QbeGen.ElemSize computed an array element's stride from its class, and ClRecord/ClClass/ClSet fell through to 4. So

TYPE R = RECORD kind, op : INTEGER; child : ARRAY [0..7] OF INTEGER END;
VAR a : ARRAY [0..3] OF R;

used stride 4 instead of 48: consecutive elements overlapped, and a[i].op / a[i].child[0] read neighbouring elements' fields (and the array descriptor's count). A single record, and a 2-element array, happened to work; 4 elements did not.

Fix: record/class/set elements use SymTab.TypeSize(elemT) (the inline footprint). Repro that now passes: rec3.mod (exit 42); previously rc=1.

This is what blocked the AST self-test (which stores child : ARRAY [0..7] OF Node in a record), and it is a real user-facing bug for any array of records with inline array fields.

3. Two false alarms (recorded so they are not re-chased)

  • “Nested calls mis-order arguments” — not a bug. rc is the process exit status modulo 256, so a test encoding 1234 observed 210, 1274 observed 250. A proper equality check (P(1,2,3,4) → rc=42) confirmed the call and codegen are correct.
  • wrapc/time wrappers — unaffected.

4. Next (Stage B, option 2)

Convert declarations to two-phase: parse all CONST/TYPE/VAR/ PROCEDURE headings into the symbol table before lowering any body. That removes the forward-variable text-patching (FwdPatchAll / FixLoadClass / FixStoreClass) without a full AST, and is the stepping stone to Stage C (statements/expressions → AST → lowering).

Branch-first discipline: after every increment run fixpoint.sh and run_tests.sh; revert the increment if either fails.

Resume

cd .../m2compiler-V3
./bootstrap/fixpoint.sh                       # FIXPOINT OK
cd compiler && ./build.sh && ./run_tests.sh   # 178/178

Files

compiler/src/AST.def, compiler/src/AST.mod, compiler/src/QbeGen.mod (ElemSize), compiler/tests/t_ast.mod, compiler/run_tests.sh, this doc.