summary_set-constants-2026-10-18.md 6.1 KB

Session summary — SET CONST + builtin CONST folding 2026-10-18

Branch resolve-check. Suite 231/231; fixpoint OK (3,841,894 bytes); corpus 396/612 compile-OK, 0 crashes.

Tags: v3-set-constants, v3-const-builtin-fold, v3-const-conv-fold, v3-array-index-fwd, v3-proc-addr, v3-proc-val.

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, forward type references, and procedure values. Corpus 382 → 396 (+14 files, 0 crashes).

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

TYPE a = ARRAY idx OF T; idx = [-2..2] (and ARRAY sub OF … inside a record/pointer, record7/complextypes) was a 230: the index alias is unresolved when the array is built. SymTab now marks forward aliases (tFwd, set by ForwardType) and defers such arrays with placeholder bounds (DeferArrayIdx); EndTypeBlock patches their [lo..hi] (FixupArrayIdxs) once the alias resolves. Generic type formals are not marked, so they are unaffected. Corpus 391 → 393; suite test t_arrfwd.

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.

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); minmaxreal3 (SYSTEM REAL64); procconv2 (CONVERT); tinyproc2/tinyproc4 (bare procedure value as a statement); 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), compiler/src/SymTab.{def,mod} (forward array-index deferral; proc-value assignability), compiler/src/M2.atg (ConstDecl, Design, CAP/ABS/ODD folds, VAL conversion folds, array-index defer, proc-value conversions), compiler/tests/t_setconst.mod, t_bad_setconst.mod, t_constbuiltin.mod, t_constreal.mod, t_constlong.mod, t_arrfwd.mod, t_procaddr.mod, t_procval.mod, compiler/run_tests.sh.

Resume

cd .../m2compiler-V3
git checkout resolve-check
cd compiler && ./build.sh && ./run_tests.sh   # 231/231
cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
M2FLAGS="" ./.scratch/corpusx.sh              # 396 OK, 0 crash