# V3 step 7 — syslib port: analysis + enablers (partial, 2026-09-22) Goal: remove gm2-only imports from the compiler sources (`FileIO` on `SysIO`/`Storage`). Attempting it exposed a fundamental bootstrap conflict; this step lands the enablers, the measuring gate, and the analysis that fixes the plan. Suite 102/102. ## Delivered - **Value open-array formals** (`PROCEDURE P(a : ARRAY OF T)`). Previously 230; now supported (passed as a descriptor address, no copy — same representation as `VAR`). Needed because the compiler's own sources use value open arrays pervasively (`WriteString(s : ARRAY OF CHAR)`, etc.). `Assignable` gained fixed-1D-array → open-formal compatibility. Tests: `t_valopen` → 15, `t_openformal` promoted to a run test. - **syslib gate** in `run_tests.sh`: fails if `SymTab`/`QbeGen`/ driver/parser/scanner import `Environment`, `FileSysOp`, `TextIO`, `RawIO`, `WholeIO`, `IOChan`, `ProgramArgs`. They do not. Only `FileIO` remains (excluded, pending). ## The bootstrap conflict (why the port is not just mechanical) `FileIO` is compiled twice, by two different compilers with two different ABIs: 1. **Stage 1 — gm2.** gm2's `ARRAY OF CHAR` is a **NUL-terminated** C buffer; foreign procedures are declared `DEFINITION MODULE FOR "C" … EXPORT UNQUALIFIED name; …` (verified against the gm2 testsuite `examples/callingC`). 2. **Stage 2 — V3.** `ARRAY OF CHAR` is our **length-prefixed descriptor** (`LONGCARD count + bytes`), and foreign procedures use `PROCEDURE … ; EXTERNAL;` (gm2 rejects that spelling). So one `FileIO` source cannot pass strings to one shim: gm2 hands the shim a `char*` (NUL-terminated), V3 hands it a descriptor pointer. The `runtime/syslib` shim's string functions (`m2write`, `m2puts`, …) read the V3 descriptor layout, so they work for V3 programs but not for gm2-compiled `FileIO`. ## Chosen resolution (for the next pass) Make the **foreign-call boundary NUL-terminated for both**: - V3 codegen materialises a NUL-terminated copy when an `ARRAY OF CHAR` actual is passed to an `EXTERNAL` procedure (internal strings stay length-prefixed — only the boundary copy gets a NUL). - The shim's string functions switch to `strlen`-based (no count header), so gm2 and V3 both work. - A single `runtime/syslib/SysShim.def` (gm2 `FOR "C"` / `UNQUALIFIED`) binds the raw shim, and **V3 gains support for `DEFINITION MODULE FOR "C"` + `EXPORT UNQUALIFIED`** (its foreign dialect), so the same `SysShim.def` compiles under both. - Then `FileIO` is rewritten over `SysShim` (+ `Storage`), and the gate is extended to cover it. ## Also identified - Corrected `EXTERNAL` FFI spelling for gm2 is `DEFINITION MODULE FOR "C"`; our `PROCEDURE …; EXTERNAL;` is V3-only. This is the concrete grammar addition V3 needs next. - Pointer-sized (`l`) handles, already the convention (step 5.4), apply to any host resource. ## Files `compiler/src/M2.atg` (value open arrays), `compiler/src/SymTab.mod` (assignability), `compiler/run_tests.sh` (gate), `t_valopen`, `t_openformal`. Build/`FileIO` unchanged (restored gm2-hosted).