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.
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/.
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).
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).
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.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.
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.
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.
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).
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.
REAL32/REAL64 (SYSTEM) were undeclared; mapped onto V3's REAL
(dReal), so MIN(REAL64)/MAX(REAL64) fold. Corpus 396 → 397;
suite test t_real64.
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.
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.
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 <type> 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 (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.
ORDL de-reservedORDL 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.
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.
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.
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).
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.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.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).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.
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