# Inline record array fields (self-hosting layout fix) Tag `v3-inline-array-fields`. Main suite 111/111; full compiler source set still compiles under V3 and links. ## Why V3 stored a record's array fields as **8-byte pointers to static descriptors**, initialised only for static records. Heap records (`ALLOCATE`/`NEW`) never initialised them, so `SymTab.RawEnter`'s `Assign(node^.name, …)` dereferenced garbage → the self-compiled compiler crashed at startup. Standard Modula-2 (and gm2) store record array fields **inline**, so V3 now does too. ## Changes - **SymTab layout**: a fixed-array field occupies its full inline object (`ArrObjSize = 8 + count*elemBytes`), not 8. `TypeSizeD`'s record branch and `ComputeOffsets` use `FieldSize`; new `ArrObjSize`/`FieldSize`; `ElemBytes` moved earlier. - **QbeGen data**: `ArrBodyItems` (inline descriptor contents: `l , z ` or `l , l $sub_i, …`) + `ArrData` (wraps it and emits nested `$name_i`). `RecItems` emits array fields inline; `RecStatics` only emits sub-descriptors for *nested* array fields. - **Field access**: `Design`'s `.field` now passes the field *address* for array fields (was `ElemLoad` of the pointer). - **CopyRecord**: array fields copied via `CopyArray(fieldAddr, …)`. - **InitHeap**: a record's inline array field is initialised in place (header = count), no separate allocation. - **SymTab**: `ALLOCATE(x, TSIZE(T))` → `NEW(x)` (typed allocation runs `InitHeap`). `FROM Storage IMPORT ALLOCATE` kept for gm2's `NEW`. ## Remaining blocker (the string model) Running the self-compiled compiler still aborts, but now in a reproducible, understood place: the compiler's string helpers scan an argument for a trailing `NUL` (`SymTab.Assign`, `Equal`, `StrLen`, …), while V3 strings are **count + bytes with no terminator** and trap when indexed past the count. Minimal repro: ```modula2 PROCEDURE Copy (VAR d: ARRAY OF CHAR; s: ARRAY OF CHAR); BEGIN ... WHILE (i < HIGH(d)) AND (s[i] # CHR(0)) DO ... END ... END Copy; Copy(p^.n, "hi"); (* s[2] out of bounds -> abort *) ``` gm2 strings are NUL-terminated so the sources work there. Options: 1. **NUL-terminate V3 CHAR arrays** (reserve a terminator slot, allow reading index = count, emit `z (n+1)`, literals `… , b 0`). Keeps the sources unchanged; broad but mechanical. 2. Rewrite every NUL-scanning helper in the compiler sources to be length-based (must stay gm2-compatible) — larger. Then: the rest of the bootstrap (cross-stage `FileIO`, `fixpoint.sh`). ## Files `compiler/src/{M2.atg,SymTab.mod,QbeGen.mod,M2.lst}`.