summary.md 5.0 KB

Test2 — SimpleMod2 with QBE backend — session summary

Scalar-subset Modula-2 compiler: SimpleQ.atg (Coco/R) + SymTab (type checking, reused from Test1) + new QbeGen backend emitting QBE SSA to gen_qbe/<Module>.ssa, assembled with qbe and linked with cc. All work lives in Test2/, binary Test2/SimpleQ.

1. Scope (user choice: scalar subset + full SSA→run pipeline)

Full SimpleMod2 syntax parses and type-checks with Test1's exact codes (200/201/202, 210–224). Only scalar data is lowered:

  • INTEGER / CARDINAL / SHORTINT / LONGINT / BOOLEAN / CHAR / enumerations → QBE w; REAL / LONGREAL → QBE d (widened).
  • Everything else parses but gets error 230 ("qbe backend: construct not supported in scalar subset"): ARRAY / RECORD / SET / POINTER variables, string variables, WITH, IN, set literals, non-literal CONST expressions and BY steps, EXIT outside LOOP, imported names used as values.

Test convention (the language has no I/O): a global VAR ExitCode : INTEGER makes generated main return its value; otherwise the program returns 0.

2. Files

  • SimpleQ.atg (856 lines) — grammar; single-pass QBE emission. Every expression synthesizes a QBE operand (immediate like 42, -5, d_3.14 or fresh %tN); statements emit jumps/labels. Code emits only on type-correct subtrees, so backend 230s never cascade with type errors.
  • QbeGen.def/.mod (120/448 lines) — SSA writer: data globals, export function w $main, temporaries (%tN), labels (@LN), ceqw/cnew/csltw/cslew/csgtw/csgew + ceqd/cned/cltd/cled/cgtd/cged, swtof for INTEGER→REAL assignment, loop-exit stack, CASE as compare-chains, NoQbe guard (single 230 for WITH bodies).
  • SymTab.def/.mod — Test1 copy + 2 accessors (PendCount, PendName) so VAR-list storage is emitted before FixPending.
  • FileIO, scanner.frm, parser.frm, compiler.frm (+230 text), compiler.gpm — copied from Test1.
  • build_qbe.sh — regenerates (CRFRAMES=. ../CR -m -C SimpleQ.atg), compiles with gm2 -fiso, links via the two-phase module-list workaround, then runs the suite: SimpleQ → qbe → cc → execute, checking exit codes; negatives must be rejected with the right message.
  • tests/ — 14 positive + 6 negative .mod sources (with .LST listings); gen_qbe/ — backend output (.ssa, plus .s, binaries and empty .qbeerr/.ccerr logs from the last run).

3. Test results — 20/20 (verified 2026-09-14)

test exit covers
q_minimal 0 empty body, default ret 0
q_arith 7 + - * DIV MOD, precedence, parens
q_const 15 CONST, unary minus, hex 0AH
q_bool 42 AND/OR/NOT, comparisons
q_if 20 IF/ELSIF/ELSE, empty-IF fallthrough
q_case 103 labels, ranges, ELSE, expr selector
q_while 55 WHILE sum 1..10
q_repeat 55 REPEAT..UNTIL
q_loop 7 LOOP + EXIT
q_for 85 FOR, BY -2 negative step
q_real 31 REAL arith, int→real assign, real CONST (runtime init)
q_char 97 CHAR literals/comparison
q_enum 1 enum ordinals, CASE on enum
q_full 74 combined demo (FOR/MOD/WHILE/REPEAT/LOOP/CASE)
q_bad_array rej/230 ARRAY var + indexing
q_bad_with rej/230 RECORD var + WITH (single 230)
q_bad_set rej/230 SET var + set literal
q_bad_exit rej/230 EXIT outside LOOP
q_bad_const rej/230 non-literal CONST (B = A + 1)
q_bad_asn rej/210 REAL→INTEGER (type parity with Test1)

Ad-hoc edge probe also verified: unary-minus temps (-y, -r), empty FOR (5 TO 1 skipped), boolean CASE, 10H, nested LOOP/EXIT (inner EXIT targets inner loop), AND-ed WHILE-style conditions.

4. Bugs found and fixed this session

  1. IntStr/NegFold wrote the string terminator one past the end (double Len() evaluation) → random garbage bytes in .ssa.
  2. LabelList took body/next labels as value params, so NewLabel results were lost → garbage labels. Made them VAR.
  3. LoopStat missed the back-edge jmp → loop body ran once.
  4. This qbe accepts only integer literals in data (d 0 ok, d 1.5 rejected) → non-zero REAL consts emit d 0 plus a runtime copy flushed at function start (BeginBody).

5. Known limits

  • No mixed INTEGER/REAL arithmetic (Test1 rule; r*2.0 + x is a 211 error — convert via assignment first).
  • DIV/MOD truncate toward zero (C/QBE semantics, not Modula-2 floor).
  • Assignment targets emit a dead load before the store (valid QBE, minor inefficiency from always-loading designators).
  • Multi-dimensional arrays check every index but track one element type (inherited Test1 rule); unsupported anyway (230).

6. Rebuild / run

  • Full suite: ./build_qbe.sh (from Test2/).
  • Single file: ./SimpleQ tests/<file>.mod (listing next to the source, SSA in gen_qbe/), then qbe -o gen_qbe/<M>.s gen_qbe/<M>.ssa && cc gen_qbe/<M>.s -o gen_qbe/<M> && gen_qbe/<M>; echo $?.
  • Requires: CR binary (repo root, ./build.sh), gm2 -fiso, qbe, cc.