|
|
@@ -0,0 +1,73 @@
|
|
|
+# Array-of-record inline length words — fix and run-time init (2026-10-08)
|
|
|
+
|
|
|
+Branch **`ast-stage-c`**. Tags **`v3-fix-arrrecfield`** (code),
|
|
|
+**`v3-arrrecfield-complete`** (this summary).
|
|
|
+
|
|
|
+## The bug
|
|
|
+
|
|
|
+A record's array field is stored **inline** as a descriptor
|
|
|
+(`{ l <len>, data }`), so its length word must be initialised wherever
|
|
|
+the record lives. For a **standalone** record `RecItems` does this, and
|
|
|
+for a **fixed** top-level array `ArrData` does. But in **static
|
|
|
+array-of-record** data the elements were emitted as one compact zero
|
|
|
+blob (`z n*size`), so each element's embedded length word stayed 0.
|
|
|
+Passing such a field as an **open-array actual** made the callee read
|
|
|
+length 0 and trap (`docs/wip/arrrec-field-openarray.mod`, rc 134;
|
|
|
+`docs/summary_l3_selfhost_fix.md`). V3-built compilers miscompiled this;
|
|
|
+Lower's own tables avoided it by using flat arrays.
|
|
|
+
|
|
|
+## The fix — two paths
|
|
|
+
|
|
|
+**1. Small record arrays: inline expansion** (`2ebf662`).
|
|
|
+`QbeGen.ArrBodyItems` / `ArrData` expand record/class elements per
|
|
|
+element so each inline array field's length word is in the static data.
|
|
|
+The predicate was generalised from `HasNestedArray` (only
|
|
|
+`ARRAY OF ARRAY`) to **`HasArrayField`** (any array field, recursively).
|
|
|
+
|
|
|
+**2. Large record arrays: run-time init** (`0ee6029`).
|
|
|
+Expanding a huge table would blow the 4 MiB image — `AST.arena` is
|
|
|
+`ARRAY [0..262143] OF NodeRec` (262144 records; even an interleaved
|
|
|
+`l <len>, z <n>` form needs ~2.6 MB of text). So arrays whose element
|
|
|
+data exceeds a budget stay compact and their length words are set by a
|
|
|
+generated init loop:
|
|
|
+
|
|
|
+- `QbeGen.RecArrayExpanded(n, t)` centralises the budget
|
|
|
+ (`n * TypeSize(elem) <= 131072`).
|
|
|
+- `QbeGen.RecArrayNeedsInit(n, t)` — array of record/class that
|
|
|
+ (recursively) has array fields and is too big to expand.
|
|
|
+- `QbeGen.DeclRecTemplate(varName, rec)` emits
|
|
|
+ `data $<mod>_<var>_tmpl = { <one record with counts set> }` (plus any
|
|
|
+ nested sub-descriptors).
|
|
|
+- `QbeGen.InitRecArray(varName, arrT)` emits the loop that copies the
|
|
|
+ template over each element: element `i` is at `base + 8 + i*stride`.
|
|
|
+- `Lower.EmitVarNames` records module-level globals that need it
|
|
|
+ (declaring the template alongside their data); `Lower.EmitCountInits`
|
|
|
+ runs the loops in the unit's init — **emitting an init even for a unit
|
|
|
+ with no BEGIN body**, since `main` calls every registered init.
|
|
|
+
|
|
|
+Together the two paths cover all sizes; the latent 0-count bug is gone.
|
|
|
+
|
|
|
+## Gotchas
|
|
|
+
|
|
|
+- **QBE temps are single-assignment**, so the loop counter must live in
|
|
|
+ a **stack slot** (`Slot4`/`LoadW`/`StoreW`); using a temp made the
|
|
|
+ condition reload the initial `0` and loop forever.
|
|
|
+- `Op3` takes a 5th `isReal` argument.
|
|
|
+- The loop must run **before** the unit's body, and an init must exist
|
|
|
+ even when the unit has none.
|
|
|
+
|
|
|
+## Verification
|
|
|
+
|
|
|
+- `docs/wip/arrrec-field-openarray.mod` → exit 0 (was rc 134).
|
|
|
+- `t_arrrecfld.mod` (small, inline path) → 42; `t_arrrecbig.mod`
|
|
|
+ (5000 records, run-time path) → 42.
|
|
|
+- **suite 183/183**; **FIXPOINT OK (3,388,344 bytes)** in ~7.6 s — the
|
|
|
+ compiler's own `AST.arena` is now initialised by the generated loop at
|
|
|
+ each startup (cheap: one `memcpy`-per-element loop).
|
|
|
+
|
|
|
+## Commits
|
|
|
+
|
|
|
+```
|
|
|
+0ee6029 fix: run-time init of large array-of-record inline length words
|
|
|
+2ebf662 fix: init inline array-field length in array-of-record static data
|
|
|
+```
|