# Session summary — SET CONST + builtin CONST folding 2026-10-18 Branch **`resolve-check`**. Suite **239/239**; fixpoint **OK (3,812,771 bytes)**; corpus **405/612 compile-OK, 0 crashes**. Tags: `v3-set-constants`, `v3-const-builtin-fold`, `v3-const-conv-fold`, `v3-proc-addr`, `v3-proc-val`, `v3-real64`, `v3-shift-import`, `v3-foreign-module`, `v3-m2-conformance`, `v3-strict-decl-order`, `v3-convert`, `v3-set-word`, `v3-enum-import`, `v3-ordl`, `v3-gap-examples`. ## Conformance note This project follows **Modula-2** (declaration before use; the only forward reference is a `POINTER`'s base type), not gm2's leniencies. A `v3-array-index-fwd` change (forward array-index types) was **reverted** for that reason: `record7`/`complextypes` use a type before its declaration and are correctly rejected again (corpus 400 → 398). Every remaining case, classified (standard / gm2-leniency / V3-bug / limit), is catalogued with a reproducer in `docs/gaps/`. ## What this session did Landed the two features recorded in `docs/analysis_gaps.md` §9 by the previous corpus sweep — **set constants** (static masks) and **builtin calls in `CONST` initialisers** — then went further on `CONST` initialisers, procedure values, SYSTEM sized reals, builtin imports, and ISO foreign modules. (A forward-type-reference change was added and then reverted for conformance; see the note at the top.) Corpus **382 → 388** (+16 files, 0 crashes). ```mod CONST Digits = CharSet{"0".."9"}; (* char range *) Signs = CharSet{'+', '-'}; NumCh = Signs + Digits; (* union of two set constants *) Warm = CS{Red, Blue}; (* enum base *) ``` New suite tests: `t_setconst` (run), `t_bad_setconst` (reject), `t_constbuiltin` (run). ## Why it was reverted before, and the fix A set literal is emitted as a **runtime temp** (`NewSetTemp`/`SetZero`/ `SetBit`), so a set `CONST` had no static value; accepting the declaration produced a `230` rather than a miscompile (see the previous sweep's finding). Sets are also *addresses* of memory words, so the constant must be a real `data` symbol. The fix computes the **bit mask at parse time** — where the element values are known and validated — and has `Lower` re-emit it as a pooled static descriptor: - `Lower.SetConstMaskOf` (exported) folds a set expression to `(mask, words, lo)`: set literals (`NkSetLit`/`NkBraceLit`), char / integer / named-CONST elements, `a..b` ranges, and `+`/`-`/`*`/`/` over two foldable set operands (union / difference / intersection / symdiff), plus `+ - * DIV MOD` on element ordinals. - `ConstDecl` (grammar) calls it for `ClSet`; on success it records the mask (`Lower.NoteSetMask`) and lets the constant be an aggregate; on failure it stays a clear `230`. - `Lower.LowerDecls` looks the mask up by name and calls the new `QbeGen.PoolSet`, which registers `data $setN = { w mask0, ... }` and returns its address. `FlushSets` emits the pool in `EndModule`. - The grammar's `Design` and `Lower`'s `LowerDesign` now treat a `ClSet` `KindConst` as an aggregate: its value *is* the descriptor address (so `c1 := Digits`, `c IN Digits` work). Bound: masks are folded up to **16 words (512 bits)**; a wider `SET CONST` stays a `230` (`MaxCFoldW`). The corpus set constants are ≤ 8 words. ## Builtin CONST folding `CAP`/`ABS`/`ODD` set the runtime sentinel `"@"`, so a `CONST` such as `A = CAP(VAL(CHAR, 65))` (`testcap2`) was a `230`. They now fold over an immediate operand in the grammar (`Fact`): `CAP` uppercases a CHAR, `ABS` takes the magnitude, `ODD` yields `0`/`1`. This only feeds the `ConstDecl` `IsImm` check (Lower re-lowers from the AST, so runtime code is unchanged). `MIN`/`MAX` of a set type already folded via `TypeBounds`. A `BOOLEAN` `CONST` other than `TRUE`/`FALSE` was also rejected at the designator — the grammar's `Design` class list was missing `ClBool` (Lower already had it); added. Corpus **387 → 389**. ## CONST conversions `VAL` between representations used to leave a runtime temp, so `CONST r = VAL(REAL, MAX(INTEGER))` (`constcast`) and `CONST a = VAL(LONGCARD, MAX(LONGINT))` (`maxlongint`) were `230`. The grammar now folds an immediate operand for the representation-changing conversions: `INT`/`LONGINT`→`REAL` (and `FLOAT`, via `NormReal`), `INT`↔`LONGINT` (identity on an immediate), and identical-class `LONGINT`→`LONGINT`. Corpus **389 → 391**. ## Forward array-index type (REVERTED) `TYPE a = ARRAY idx OF T; idx = [-2..2]` (and `ARRAY sub OF …` inside a record/pointer, `record7`/`complextypes`) was briefly accepted by deferring the array bounds (`SymTab.DeferArrayIdx`/`FixupArrayIdxs`, `tFwd`). **Reverted**: this is use-before-declaration, which strict Modula-2 forbids (only a `POINTER`'s base type may forward-reference). V3 rejects `record7`/`complextypes` again. Corpus **391 → 393** is undone (see the conformance note above). ## Procedure values `ADR(foo)` returned `0` (Lower's `ADR` handled only vars/params), while `VAL(ADDRESS, foo)` gave the code address, so `procadr` compiled but failed at run time. `Lower`'s `ADR` now emits the code address for a procedure (`LowerProcAddr`). The grammar's `VAL` conversion was also missing procedure-value classes: added `PROC`↔`PROC`, `PROC`↔`PTR`. Then `Assignable` rejected `p := foo` (a procedure value into a `PROC` variable, mapped onto `ADDRESS`): added `PROC`↔`PTR` assignability. `procconv` now passes and runs. Corpus **393 → 396**; suite tests `t_procaddr`, `t_procval`. ## SYSTEM sized reals `REAL32`/`REAL64` (SYSTEM) were undeclared; mapped onto V3's `REAL` (`dReal`), so `MIN(REAL64)`/`MAX(REAL64)` fold. Corpus **396 → 397**; suite test `t_real64`. ## Builtin imports; SHIFT fault `FROM SYSTEM IMPORT SHIFT` (and `ROTATE`/`CAST`/…) failed: builtins are reserved tokens, so `ImpName` rejected them. The import list now accepts the builtins (accepted and ignored — the builtin applies regardless). That exposed a real **miscompile**: `SHIFT` by an out-of-range amount (negative, or ≥ the span) addressed a wild word and `loadw`'d from it (bits were discarded only at the store). The word index is now clamped to 0 when the destination bit is out of range, so the load is safe and the store is still a no-op. Corpus **397 → 399** (`shift`, `shift2`); suite test `t_shiftneg`. ## ISO foreign modules `DEFINITION/IMPLEMENTATION MODULE FOR "C" M` and `EXPORT UNQUALIFIED` (ISO foreign-module syntax) are now parsed; `v.def` → 0 errors. Corpus **399 → 400**; suite session test `CForeignProg`. ## Conformance: strict declaration order (Track A, first cut) `TypeIdent` no longer invents a forward placeholder for an undeclared type: a new `allowFwdType` flag is set **only** while parsing a `POINTER`'s base type (Modula-2's single forward-reference exception), so module-level forward type/`VAR` references, forward types inside a `TYPE` block, and forward `SET OF ` are now `201`. The compiler's own sources (and runtime/stdlib) do not rely on it — fixpoint stays green. `t_fwdtype`/`t_fwdset` became reject tests; `t_ptrfwd` locks the legal exception. Corpus **398 → 388**: the 10 files dropped (`arrayptr`, `arrayptr6`, `constructor1`, `constructor3`, `param3`, `record14.def`–`record16.def`, `tinyset6`, `tinyset7`) were gm2 leniency. Forward references to module-level **variables**/**procedures** from a body (the two-phase parse) remain — a separate, larger item. ## Showcase24: multi-file, generics + classes + libraries `showcase24` (test suite) is an eleven-file session: a generic `GStack(T)` (instantiated `IntStack`), a two-parameter `GPair(K,V)` (instantiated `PairIC`), a `Shape/Circle/Square` class hierarchy driven by VIRTUAL dispatch through base-class pointers, a local helper library (`Show24Util`) over `Strings`/`Conversions`/`Math`, and `TextIO`. It prints `showcase24 total=42` and exits 42. It surfaced a **generics limitation**: instantiating one generic twice with arguments from different families (`GStack(INTEGER)` + `GStack(CHAR)`) fails on the second one; `INTEGER`+`CARDINAL` passes only because the shared binding still type-checks. Recorded in `docs/analysis_gaps.md` §10. The showcase therefore uses one `GStack` instantiation plus the 2-parameter `GPair`. ## Track B: `ORDL` de-reserved `ORDL` is a gm2/FileIO helper, not a Modula-2 built-in; V3 reserved it, so `PROCEDURE ORDL` (`fileio.def/mod`) was rejected. It is now an ordinary identifier: the scanner frame takes `FROM FileIO IMPORT ORDL` (Coco/R's generated CONTEXT handling calls `ORDL`), `runtime/syslib/ FileIO` exports it, and `t_compat` drops its ORDL probe. (`ORD`, `CHR`, `VAL`, … remain keyword-style — de-reserving the *standard* built-ins is a larger parser refactor.) Corpus **402 → 405**; suite test `t_builtinname`; `docs/gaps/gap13` retired. ## Track B: enum literals travel with their type Modula-2 exports/imports a type's **enumeration literals** with the type. `SymTab` now keeps an enum-literal registry; `EnterEnumLits` makes a type's literals visible when the type is declared (as an alias) or imported (`FROM M IMPORT T`), so `M.lit`, `MRe.lit` (through a re-export) and unqualified `lit` (FROM-import) all work. This also required fixing **`QbeGen.LoadDesignator`**, which returned `0` for any qualified const (an enum literal has no data symbol) — a latent bug for qualified constants generally. `imp*` pass; corpus **392 → 402** (with the harness fix below); suite session test `EEnumProg`. The corpus **harness** (`tools/v3-corpus/corpus.sh`) now resolves **transitive** imports — it used to copy only a base file's direct imports, so `impb.def`'s `impc` was missing and 9 of the 10 `imp*` files could not be measured. This is more accurate and is the method from here. ## Track B: CONVERT `CONVERT(T, x)` (ISO SYSTEM, a `VAL` sibling) is now accepted — the grammar's `VAL` production takes `( "VAL" | "CONVERT" )`, so it reuses `VAL`'s conversion rules; `procconv2` passes and runs. Corpus **388 → 389**; suite test `t_convert`, `docs/gaps/gap07` retired. ## Track B: set↔word VAL/CAST `VAL`/`CAST` between a set and its machine word are now accepted: set→ `CARDINAL` loads word 0 of the set, and `CARDINAL`→set builds a zeroed one-word set temp holding the value (the grammar's `VAL` production also takes `CAST`). `constset`, `constset2`, `constset3` pass; corpus **389 → 392**; suite test `t_setword`, `docs/gaps/gap08` retired. `packed` still needs `__FILE__`/`__LINE__` (`gap22`). ## Remaining genuine gaps (Track B) - **`gap13` builtins as identifiers** — a real V3 bug, but the builtins are Coco/R keyword tokens; fixing it means de-reserving them (a parser refactor), *not* a one-liner. - **`gap08` set↔word `CAST`/`VAL`** (BITSET as a machine word). - **`gap15` enumeration literals with an imported type** (ISO import/export through re-exports). - `gap16`/`gap17` set-span limits. ## Not fixed (recorded) - **`setconst3.mod`** still fails: its `VAR mem: ARRAY [0..CARDINAL( addressLines)]` names a `CONST` declared *after* the `VAR` (a forward CONST reference), which is a separate gap. Its `SET CONST` itself now parses. - Forward *type* references in a `CONST` conversion (`subrange4`/`7`) and as a `VAR` type before its TYPE block (`subrange11`, `shift3`/`4`); `procconv2` (`CONVERT`); `packed` (`CAST` set↔int); `aochar`/ `arrayofbyte`/`bytearray` (a scalar actual into `ARRAY OF BYTE`); `testparam2`/`3` (`VAR` actual of a different int/real type); `tinyproc2`/`tinyproc4` (bare procedure value as a statement); variant-record empty arms / `ELSE` (`varient2`/`3`/`8`, `tsize`/`2`); `builtin`/`fileio` (a builtin name as a procedure name, `__BUILTIN__`); the `imp*` qualified-import family (partly a corpus harness transitive-`.def` gap). ## Files `compiler/src/Lower.{def,mod}` (evaluator + `PoolSet` call + `ADR(proc)`), `compiler/src/QbeGen.{def,mod}` (`PoolSet`/`FlushSets`/pool state; `SetShift` clamp), `compiler/src/SymTab.{def,mod}` (proc-value assignability; sized reals), `compiler/src/M2.atg` (`ConstDecl`, `Design`, `CAP`/`ABS`/`ODD` folds, `VAL` conversion folds, proc-value conversions, builtin imports, foreign modules), `compiler/tests/t_setconst.mod`, `t_bad_setconst.mod`, `t_constbuiltin.mod`, `t_constreal.mod`, `t_constlong.mod`, `t_procaddr.mod`, `t_procval.mod`, `t_real64.mod`, `t_shiftneg.mod`, `t_ptrfwd.mod`, `t_fwdtype.mod`, `t_fwdset.mod`, `c_foreign.def`, `c_foreign.mod`, `c_foreign_prog.mod`, `compiler/run_tests.sh`. ## Resume ```sh cd .../m2compiler-V3 git checkout resolve-check cd compiler && ./build.sh && ./run_tests.sh # 239/239 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh M2FLAGS="" ./.scratch/corpusx.sh # 405 OK, 0 crash ```