# Session 2026-10-22 — corpus triage, libc, SYSTEM, proc types, sized storage, SysClock, small fixes Branch **`master`**. Continues `summary_session-2026-10-19.md`; the only plan source was its "Remaining" list (corpus `LANG` triage). ## Numbers | gate | session start | end | | --- | --- | --- | | suite (`compiler/run_tests.sh`) | 249/249 | **259/259** | | fixpoint (`bootstrap/fixpoint.sh`) | OK (3,882,337 B) | **OK (4,028,447 B)** | | corpus (`tools/v3-corpus/corpus.sh`) | 467/612 | **511/612, 0 crashes** | Reproduce: ```sh cd compiler && ./build.sh && ./run_tests.sh # 259/259 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh tools/v3-corpus/corpus.sh # 511 OK, 0 crash ``` ## Triage method The 118 `LANG` entries were compiled one at a time (a single-file version of `corpus.sh`), and each first diagnostic was read against its source line. They fall into these buckets: | bucket | count | verdict | | --- | --- | --- | | imports `libc` but uses a libc function as a value (`printf`/`malloc`) | ~48 | libc prototypes were missing; **fixed below** (the rest fail later for other reasons) | | declaration order (type/const/var used before its declaration) | ~25 | correctly rejected (`docs/gaps/`); not a target | | gm2 extensions (`ASM`, `''` NUL char, `666B`, absolute `VAR [addr]`) | ~7 | not a target unless chosen | | ISO features actually missing | ~20 | `PACKEDSET`, exceptions/`UNIQUE`, `__FUNCTION__`, `PROC` as a result type; sized SYSTEM types **fixed below** | | **conforming** clusters: arrays, subranges, ordinal `FOR`, empty string, libc prototypes, SYSTEM sized types, procedure types, SysClock, small fixes | 44 | **fixed this session** | ## What landed ### PIM/ISO multi-index arrays — `compiler/src/M2.atg` `ARRAY [0..1], [0..2] OF T` is the standard spelling (17 corpus files use it; none use V3's single-bracket comma list). `ArrayType` now parses one bracket group per dimension (`{ "," "[" ... "]" }`) and appends every group's bounds before the element type; nesting stays inside-out, so the layout matches `ARRAY [0..1] OF ARRAY [0..2] OF T`. The old `ARRAY [0..1, 0..2] OF` form still works. Corpus **+6**: `arraybool`, `assignment2`, `foo`, `recordarray2`, `tinyarray4/5`. Regression `t_multidim`. ### Subranges keep their ordinal base — `SymTab`, grammar A subrange was always `FSub` with no base descriptor, so `ClassOf` returned `ClInt` for `['A'..'Z']` and `[red..blue]`: assigning a `CHAR`/enum literal (or comparing with one) failed. `SymTab.NewSubB` stores the base when both bounds have the same type; `ClassOf`, `SubBounds`, `SetSpan`/`BaseSpanOk` and the set-span check now follow it. `Assignable`/`EqCheck`/`OrdCheck` accept a base value against a subrange of the same `CHAR`/`BOOLEAN`/enum base (two different enums stay incompatible). Corpus **+3**: `subrange16`, `subrange17`, `enum3`. Regression `t_subrange`. Assignment range checks remain (future) — V3 is already lenient for the integer family. ### `FOR` over ordinals + typed `MIN`/`MAX` of sets — grammar `FOR` accepted only the integer family. It now accepts any ordinal control variable (bounds must be assignment-compatible with it), which covers enums and `CHAR`. `MIN`/`MAX` of a `SET` now yields the set's base type instead of `INTEGER`, so `FOR i := MIN(S) TO MAX(S)` binds (the bound was previously `INTEGER` against an enum `i`). Corpus **+3**: `forloop2`, `smallset4`, `minmax`. ### ISO empty string literal — `SymTab` + grammar + `Lower` ISO makes the empty string literal (`""` / `''`) length 0 and compatible with `CHAR`, where its value is the terminator. V3 typed it as an ordinary string, so `ch := ""`, `ch = ""`, `CASE ch OF ""`, `FOR ch := "" TO …`, a `CHAR` formal actual, and `RETURN ""` either failed (`incompatible assignment`/`comparison`, `invalid Fact`) or stored a descriptor address. `SymTab.NewEmptyStr` marks the literal's type (`tlo = -2`); it is still `ClStr`, so `ARRAY OF CHAR` contexts keep working (`s := ""`, `s = ""`, `t_strempty`). `Assignable`/`EqCheck` accept it against `CHAR`, and `Lower` substitutes the value `0` when the context is a character (assignment, relation, `CASE` label, `FOR` bound, value actual, `RETURN`). Corpus **+6**: `arraychar`, `arraychar2`, `arraychar3`, `nulcharcase`, `nulcharvar`, `forloopnulchar`. Regression `t_emptychar`. ### `libc` prototypes — `SymTab` + `Lower` + grammar `FROM libc IMPORT P` used to declare every name as a permissive no-result varargs external, so `printf`/`malloc`/`write` in an expression were `invalid call`. The known C functions now get real signatures from a built-in table (`DeclareCProc`/`CProto`): `printf`/`snprintf` are varargs functions with an `ARRAY OF CHAR` format, the rest have typed formals/results (`exit` none, `write`/ `read`/`open`/`close`/`lseek`, `malloc`, `memcpy`/`memset`/`strncpy`, `system`/`getenv`, `atof`/`strtod`). `Lower.NoteCProc` copies the signature into its proc table. Discarding the result of a call in statement position is now allowed **only for foreign C varargs** (`printf`/`snprintf`) — the documented compatibility carve-out. An ordinary Modula-2 function used as a statement is still `233` (`t_bad_funstmt`). Corpus **+6**: `procedure2`, `tinyparam`, `tinyparam2`, `sizes`, `variant9`, `varient` (`except5` now stops on ISO exceptions). Regression `t_libc2`. ### ISO procedure-type syntax — `M2.atg` `PROCEDURE` types accepted `;`-separated sections and the GNU type-only `(T1, T2)` shorthand, but not the ISO/PIM forms used by the corpus: - comma-separated unnamed parameter types with a keyword/`VAR` start: `PROCEDURE(ARRAY OF CHAR, ARRAY OF CHAR)` and `PROCEDURE (CHAR, VAR scanClass, VAR scanState)`; - `PROC` as a result type (`PROCEDURE ... : PROC`), since `PROC` is a keyword token and the result position only took `TypeIdent`. The parameter list now parses a comma as either another name or a new unnamed type entry; if a type entry starts, the collected names are resolved as types (`CHAR`, a declared type, or the GNU shorthand). The name-group behaviour (`a, b : T`) is unchanged. `PROC` in a result position maps to the predeclared procedure-value type. Corpus **+4**: `typeonly.def`, `testrecursive.def`, `procconv`, `procconv2`. Regression `t_proctype2`. ### True 8-bit `BYTE`/`SBYTE` — `SymTab` + `QbeGen` + `Lower` `SYSTEM.BYTE` had been an alias for the 32-bit `CARDINAL` (a phase-1 shortcut: only `w` integer values existed), so `SIZE(BYTE)` was 4 and `ARRAY OF BYTE` was not byte-granular. `BYTE`/`SBYTE` (and `WORD8`/ `CARDINAL8`/`INTEGER8`) are now distinct 8-bit storage types: `TypeSize`/`ElemBytes` 1, `loadub`/`loadsb`/`storeb` for scalars, parameters, record fields and array elements, `{ b 0 }` for globals and static data, and arithmetic promotes to `INTEGER`/`CARDINAL`. The `gap09` scalar→`ARRAY OF BYTE` wrapper now copies one byte for a `BYTE` actual; actual arrays are passed as real byte buffers. `WORD16`/`WORD32` (and the 16-bit named types) still map to the 32-bit word model, and `COMPLEX*` remains a `REAL` placeholder. Regression `t_byte` (SIZE, wrapping, signed loads, record/array fields, `ARRAY OF BYTE`, `SET OF BYTE`, `LONGINT`/`FLOAT`/`VAL`). Corpus unchanged at 503 (no file depended on the old 32-bit `BYTE`). ### True 16-bit `WORD16`/`CARDINAL16`/`INTEGER16` — same technique The 16-bit names were aliases of the 32-bit family. They now use dedicated forms/classes (signed/unsigned) with `SIZE` 2, `loaduh`/`loadsh`/`storeh` in scalars, parameters, record fields and array elements, `h 0` data for globals/static aggregates, and arithmetic promoting to `INTEGER`/`CARDINAL` (stores truncate to 16 bits). `SET OF WORD16`/`SET OF INTEGER16` work (65536-element span, the documented set limit). `WORD`/`WORD32` remain the 32-bit word. Regression `t_word16`. ### ISO `SysClock` — `stdlib/sysclock.def` / `.mod` The stdlib stub only had PIM-style `Time`/`Clock`, so `testclock` and `testclock2` failed on `CanGetClock`/`DateTime`/`GetClock`. `SysClock` now implements the ISO 10514-1 interface: `maxSecondParts`, `Month`/ `Day`/`Hour`/`Min`/`Sec`/`Fraction`/`UTCDiff`, `DateTime`, `CanGetClock`, `CanSetClock`, `IsValidDateTime`, `GetClock`, `SetClock`. `GetClock` converts the shim's epoch seconds to a UTC civil date (Hinnant's algorithm; `zone = 0`, no DST); `SetClock` is a no-op with `CanSetClock = FALSE`. `Time`/`Clock` remain as extras. Corpus **+2**: `testclock`, `testclock2`. Regression `TSysClock`. ### Small conformance items — grammar + `Lower` Five corpus gaps plus one incidental: - one-character string literals concatenate (`"W" + "o"`): both are `CHAR`-typed immediates, so the fold builds the quoted 2-character literal and the AST stays literal; longer `CONST`s then fold through the existing string rules; - ordinal relations of constants fold in `Expr` (needed by `ORD(a > b)` in a `CONST`); - `HIGH`/`LEN` of a string `CONST` use `QbeGen.StrLitLen` (the pooled literal text), in both the parser's constant folding and `Lower`; - `INC`/`DEC` accept any ordinal (`CASE`/enum control), not just the integer family; - a leading `|` before the first `CASE` alternative is tolerated; - `__FUNCTION__` is a predefined literal folded to the current procedure name (module name at unit level; `unitName` is now set for program modules too). Corpus **+6** (`constbool4`, `hexstring`, `function`, `enum2`, `testcase`, plus one incidental). Regression `t_smallgaps`. ## Commits Three tagged states, each green on all gates: | commit | tag | scope | | --- | --- | --- | | `aa4f49b` | `v3-corpus-triage` | multi-index arrays, typed subranges, ordinal `FOR` (+12) | | `f87bcc9` | `v3-empty-string` | ISO empty string as `CHAR` (+6) | | `c02fd41` | `v3-libc-prototypes` | typed libc prototypes, `printf` varargs (+6) | | `713650f` | `v3-system-types` | ISO SYSTEM sized types + qualified `SYSTEM` (+8) | | `f236600` | `v3-proc-types` | ISO procedure-type syntax (+4) | | `7621f16` | `v3-byte` | true 8-bit `BYTE`/`SBYTE` storage | | `a5d0f16` | `v3-word16` | true 16-bit `WORD16`/`CARDINAL16`/`INTEGER16` | | `4d05afe` | `v3-sysclock` | ISO `SysClock` API | | `87e8605` | `v3-smallfix` | small conformance items (+6) | ## Lessons - A corpus "bucket" can be misleading until each file's **first** diagnostic is read: 48 files import `libc`, but most stop earlier on declaration order, exceptions or sized types. Only 6 were blocked by the missing prototypes. - The `printf` decision was a policy choice, not a technical one: the permissive external already made statement calls work. Option 3 (function + allow discarding the result only for foreign C varargs) keeps `t_bad_funstmt` and unlocks all 59 `printf` files. - `docs/gaps/README.md` is still the place to record *why* a case is rejected; the new entries state the fix and the remaining carve-out. ### ISO SYSTEM sized types + qualified `SYSTEM` — `SymTab` + grammar The hard-coded `SYSTEM` module was not registered, so `SYSTEM.CARDINAL8` (qualified type access) was undeclared even though `FROM SYSTEM IMPORT CARDINAL8` worked. `InitSystemModule` now registers a pseudo-module with its own scope and copies every predef type and constant into it; `TypeIdent`'s `QualType` and `DesignTail`'s `MaterializeAlias` then resolve `SYSTEM.X` normally. `FROM SYSTEM IMPORT` stays permissive (its names are global predefs or keywords). Added the ISO/gm2 names: `WORD8/16/32/64`, `BITSET8/16/32`, `REAL32/64/128`, `COMPLEX32/64/128`, `CSIZE_T`/`CSSIZE_T`/`COFF_T`, and `BITSPERLOC`/`LOCSPERWORD`/`LOCSPERBYTE`. At this step V3 still had a 32-bit word model, so the narrow integer/word names shared `INTEGER`/`CARDINAL`; later in the session `BYTE`/`SYSTEM` 8-bit names and `WORD16`/`CARDINAL16`/`INTEGER16` became true 8/16-bit storage (see below). `COMPLEX*` remains a `REAL` placeholder. Corpus **+8**: `compsize`, `int8field`, `returnrecord`, `printsize`, `testgeneric`, `testgeneric2/3/4`. Regression `t_system`. ## Remaining (for a next session) 1. **A real `libc.def`** — the built-in prototype table covers the common functions; a definition module (or `DEFINITION MODULE FOR "C"`) would let users extend it. Not needed for the corpus now. 2. **`PACKEDSET`**, ISO exceptions/`UNIQUE` — each one or two files; larger or gm2-flavoured. 3. **Runtime subrange range checks** — the remaining conformance difference from this session's subrange work. 4. `STATUS.md` "what remains" is the live list. ## Publication - Branch `master`; feature tags `v3-corpus-triage`, `v3-empty-string`, `v3-libc-prototypes`, `v3-system-types`, `v3-proc-types`, `v3-byte`, `v3-word16`, `v3-sysclock`, `v3-smallfix`. - `v3-session-2026-10-22` is a **moving** session tag: it was advanced after publication, so the remote rejects updating it without `--force`. The feature tags are immutable checkpoints. ```sh git push origin master v3-smallfix # only if you want the moving tag advanced on the remote too: git push --force origin refs/tags/v3-session-2026-10-22 ``` ## Stop point Session stopped here by request. Final gates: suite **259/259**, fixpoint **OK (4,028,447 bytes)**, corpus **511/612, 0 crashes**. Feature tag `v3-smallfix`; the session summary is tagged `v3-session-2026-10-22-end`. The next-session plan is `docs/handoff-2026-10-22-next.md`.