ソースを参照

docs: summary of the array-of-record inline length-word fix

Eric Streit 3 日 前
親
コミット
782a991000
1 ファイル変更、73 行追加、0 行削除
  1. 73 0
      docs/summary_arrrec-field-fix.md

+ 73 - 0
docs/summary_arrrec-field-fix.md

@@ -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
+```