# Next session — plan **Branch state (2026-10-19):** work is on `master`, tree clean. **Baseline:** suite **249/249**, fixpoint **OK (3,882,337 bytes)**, corpus **467/612, 0 crashes**. **Policy:** follow **Modula-2**, not gm2 degradation — declaration before use (only a `POINTER`'s base may forward-reference). In `docs/gaps/`, the `decl-order` and `gm2-ext` classes are **not targets** except where gm2 compatibility was explicitly chosen (`gap22`, `gap09`). --- ## P0 — generics: two instantiations of one generic ✅ DONE Fixed on 2026-10-19. The bug was **not** `EnterTypeParamBound` (each instance re-binds `T` correctly). It was in **Lowering**: after `EndUnit`, `Lower` re-emitted each unit in `globScope`, so `Lower.MaterializeGlobals` copied the unit's types into `globScope`; a generic instance leaked its `NodeRec` there, and the *second* instance's `POINTER TO NodeRec` picked it up via `ForwardType` (a forward pointer base made it visible; direct records did not). See `docs/analysis_gaps.md` §10 for the full trail. - `SymTab.EnterLowerScope`/`LeaveLowerScope` + `Lower` re-emits each unit in its own module scope (also around the `ExitCode` probe in `Lower.EndSession`). - `Lower.ReenterImports` re-materialises a nested module's FROM-imports in the enclosing scope (the nested scope is popped before lowering). - Regression: `compiler/tests/g_twice{,i,c,p}` (`GTwiceProg`, 42). *Follow-up:* `showcase24` can now use two instantiations of one generic (simplify it, or add a demo) — optional. ## P1 — quick corpus wins ✅ DONE Chosen with the user despite the `gm2-ext` class (compatibility mode). - **`gap22` `__FILE__`/`__LINE__`** — done (`v3-file-line`): scanner allows a leading `_`, both names are reserved literals folded to the file-name string / line-number integer. **+3** corpus. - **`gap09` scalar → `ARRAY OF BYTE`** — done (`v3-bytebuf`): a parse-time marker on `ARRAY OF BYTE` open formals, accepted for scalar/pointer/string actuals; a scalar is wrapped in a stack descriptor `[count=SIZE(actual)][copy]`. **+3** corpus. See the caveats in `docs/gaps/README.md` (writes reach the copy; string `HIGH` is one less than gm2; `BYTE` is still 32-bit). ## P2 — structural gm2 extensions ✅ DONE (2026-10-19) - **`gap10`** same-size scalar `VAR` actual (`VarParamOk`). `testparam3` still fails (needs a 4-byte `SHORTREAL`; V3 reals are all 8 bytes). - **`gap11`** bare proc-value statement + PIM `PROC` type keyword. - **`gap12`** variant empty arms, `ELSE`, comma labels, untagged inner `CASE`, nested variant `CASE`. - **`gap18`** set-typed `CASE` labels (word-wise `CmpSet`). Regression tests: `t_varsame`, `t_bareproc`, `t_variantext`, `t_setcase`. Corpus **+16**. Still open: **`gap13` remainder** (de-reserve the standard builtins — contextual-keyword parser refactor; no corpus exemplar); **`gap14` (`__BUILTIN__`) — DONE** (see the follow-ups below). ## P3 — strict declaration order in bodies ✅ DONE (2026-10-19) Deferred bodies are now order-aware: `SymTab` assigns each module-level symbol a declaration sequence; `DeferBody` records the enclosing procedure's sequence and `ReenterDeferred` arms a window during which `Lookup` reports a module-level **variable, constant or type** declared after the procedure as undeclared. Parser state (`sym`/`errDist`) moved into the grammar's section so the parser itself obeys the rule. Scope note: forward **procedure** calls from a body stay allowed — the generated parser's mutually recursive productions rely on it, and forbidding it would need FORWARDs that the gm2 bootstrap link rejects. `FORWARD` works for the conforming case (`t_fwdok`). Tests: `t_fwdvar`/`t_fwdsel`/`t_fwdprop` now *expected to fail* (undeclared); `t_fwdproc`/`t_fwdok` run. Corpus **427 → 421** — 6 gm2-leniency files (`block`, `blockindirect`, `nestedfor`, …) are now correctly rejected. The drop is intended conformance, not a regression. ## P4 — latent correctness ✅ DONE (2026-10-19) Root-caused: **not size-sensitive, not a codegen bug.** `ASTDUMP` printed the literal `"Parsed correctly"`, and the fixpoint's M2make gate was `grep -q Parsed` — so the gate passed while the compiler session was reporting `Incorrect source`. Removing the dump unmasked *real* errors (Lower's forward procedure calls). Already fixed in `a3ef897`; verified the gate now fails on errors. See `docs/summary_l3_selfhost_fix.md`. **Latent item — fixed:** the *record array field as an open-array actual* bug (inline length word lost in static array-of-record data) was resolved by the hybrid `QbeGen.ArrBodyItems`/`InitRecArray` fix; covered by `t_arrrecfld`/`t_arrrecbig`/`t_nestarr` (all green). No open codegen case remains. ## Next: robustness ✅ first pass (2026-10-19) Table-limit overflows are now loud (`SemError(230)`) instead of silent: the two-phase span table (silently dropped a body's codegen — regression `t_manyproc`), the label/`GOTO` tables, the 32-actual AST buffer, and `SymTab.MaxTypes` (`TypesOverflow`). Remaining: error recovery and message polish, and the library holes (`docs/wip/`). ## Library holes ✅ first slice (2026-10-19) - `stdlib/chanconsts` + `stdlib/streamfile` (ISO 10514-1), layered on `IOChan`. Regression `t_streamfile` (open/write/read/close + flag handling, exit 42). - This surfaced and fixed a real compiler bug: a **set CONST exported from a definition module** was pooled as `$setN` while clients referenced `_` → undefined symbol. Module-level set consts are now emitted under their global name (`QbeGen.PoolSetNamed`; `Lower` uses `SymTab.GlobalRef`), verified cross-unit by `t_streamfile` (`StreamFile.write`) and a `ChanConsts` link test. Also fixed `Lower.EvalOrdConst` to fold a **qualified enum literal** (`M.lit`) in set constants (a *different* blocker for the same file). - Remaining library work: the rest of the device/IO framework (`docs/wip/`). ## Library holes ✅ second slice (2026-10-19) - **Harness fidelity**: `tools/v3-corpus/corpus.sh` now defaults `V3_LIBS` to V3's own shipped libraries (`stdlib`, `runtime/syslib`) and only treats `SYSTEM`/`libc` as built-ins-without-`.def`. A corpus file importing `Strings`/`IOChan`/`StrIO`/… now resolves against the real `.def` instead of being mislabelled `LIB` (427 → 447). - **PIM/gm2 I/O family**: added `NumberIO`, `RealInOut`, `FpuIO` (`StrIO`/`InOut` already shipped; a new `m2writefix` C formatter backs the fixed-point writers). Regression `t_pimio` (exit 42). 447 → 467. - Still missing: gm2 runtime modules (`M2RTS`, `Builtins`, `Processes`, `Preemptive`, `M2Printf`, …). **Baseline:** suite **249/249**, fixpoint **OK (3,882,337 bytes)**, corpus **467/612, 0 crashes**. ## P5 — housekeeping ✅ DONE (2026-10-19) - `docs/INDEX.md` rewritten as the entry point (start-here, load-bearing list, this session). - 45 unreferenced historical files moved to `docs/archive/` (`docs/archive/README.md`); referenced ones stay in `docs/`. - (`resolve-check` → `master`: **done**.) ## Corpus-feature follow-ups - **`gap14` `__BUILTIN__` / `__ATTRIBUTE__` pragmas — DONE** (2026-10-19): an optional declaration pragma after `PROCEDURE` is parsed and ignored (`builtin.def`, `builtin.mod` now compile). - **`gap13` remainder — documented, deferred**: the corpus case (`ORDL`) is already fixed; the standard built-ins (~24 reserved words) as identifiers need a contextual-keyword rewrite of `Fact`/`ArgList`/statements (Coco/R is LL(1)) with **no corpus exemplar**; a declaration-site-only half-measure would bind calls to the built-in. See `docs/gaps/README.md`. --- ## Gates for every step `compiler/build.sh` → `compiler/run_tests.sh` (must stay 100%) → `bootstrap/fixpoint.sh` (**byte-identical**) → `tools/v3-corpus/corpus.sh` (record OK/CRASH + note) → update `docs/gaps/` + `STATUS`/`README` → commit with a `v3-*` tag. ## Resume ```sh cd .../m2compiler-V3 git checkout master cd compiler && ./build.sh && ./run_tests.sh # 249/249 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh tools/v3-corpus/corpus.sh # 467 OK, 0 crash ``` **Recommendation:** P1 (two bounded wins) next, then reserve a separate session for P4.