瀏覽代碼

fix: nested-array record fields in array-of-record data

A record element inside an array was emitted as a zero blob, so a nested
ARRAY OF ARRAY field's sub-descriptors were never emitted and its
descriptor pointers were null -> segfault on arr[i].field[j][k].

QbeGen.ArrBodyItems now expands array-of-record elements per element
(nested EmitRec) when the record recursively has a nested-array field,
and ArrData emits the matching per-element sub-descriptors (nested
EmitRecStatics).  Records without nested-array fields keep the compact
zero blob, so AST.arena (65536 NodeRec) does not balloon past sessBuf.

Regression tests/t_nestarr.mod (was docs/wip/nested-array-field-bug.mod),
exit 42.  Suite 192/192; FIXPOINT OK (stage2 == stage3, 3,322,779 bytes).
Eric Streit 6 天之前
父節點
當前提交
8a67c75882
共有 5 個文件被更改,包括 204 次插入 和 11 次删除
  1. 1 0
      compiler/run_tests.sh
  2. 136 4
      compiler/src/QbeGen.mod
  3. 50 0
      compiler/tests/t_nestarr.mod
  4. 11 2
      docs/summary_lower_l4_l5.md
  5. 6 5
      docs/wip/nested-array-field-bug.mod

+ 1 - 0
compiler/run_tests.sh

@@ -92,6 +92,7 @@ expect_run t_unicode.mod 31
 expect_run t_charbounds.mod 3
 expect_run t_record.mod 160
 expect_run t_recarr.mod 42
+expect_run t_nestarr.mod 42
 expect_run t_with.mod 114
 expect_run t_set.mod 131
 expect_run t_setchar.mod 77

+ 136 - 4
compiler/src/QbeGen.mod

@@ -2019,18 +2019,88 @@ PROCEDURE Trap;
   END Trap;
 
 (* ---------------- records: flat blobs, pointer fields ---------------- *)
-(* Scalars/sets inline, array fields as 8-byte pointers to static
-   descriptors, nested records inline. Static offsets throughout. *)
+(* Scalars/sets inline, array fields as inline descriptors (8-byte length
+   word + data), nested records inline. Static offsets throughout. *)
+
+PROCEDURE HasNestedArray (t: SymTab.TypeIndex): BOOLEAN;
+(* TRUE when record type t (recursively) has an ARRAY OF ARRAY field,
+   i.e. a field whose element is itself an array.  Only such record
+   arrays need per-element expansion (their nested sub-descriptors must
+   be emitted); plain record arrays stay a compact zero blob. *)
+  VAR i: CARDINAL; fn: SymTab.Name; ft, et: SymTab.TypeIndex; cls: INTEGER;
+  BEGIN
+    i := 0;
+    WHILE i < SymTab.FieldCount(t) DO
+      SymTab.FieldName(t, i, fn);
+      ft := SymTab.FieldType(t, fn);
+      cls := SymTab.ClassOf(ft);
+      IF cls = SymTab.ClArray THEN
+        et := SymTab.ArrayElem(ft);
+        IF SymTab.ClassOf(et) = SymTab.ClArray THEN RETURN TRUE END
+      ELSIF (cls = SymTab.ClRecord) OR (cls = SymTab.ClClass) THEN
+        IF HasNestedArray(ft) THEN RETURN TRUE END
+      END;
+      INC(i)
+    END;
+    RETURN FALSE
+  END HasNestedArray;
 
 PROCEDURE ArrBodyItems (prefix: ARRAY OF CHAR; t: INTEGER);
 (* Inline contents of an array descriptor (no "data $name = {" wrapper
    and no closing brace): "l <n>[, <elem>...]".  Nested levels are
-   referenced as $prefix_i (emitted by ArrData). *)
+   referenced as $prefix_i (emitted by ArrData).  Record/class elements
+   with nested-array fields are expanded per element (EmitRec) so their
+   sub-descriptors are referenced; others stay a compact zero blob. *)
    VAR n, i: CARDINAL;
      elem: SymTab.TypeIndex;
      ecls: INTEGER;
      esz: CARDINAL;
-     bv: QVal;
+     bv, sub: QVal;
+     first: BOOLEAN;
+
+   (* One inline record: mirrors RecItems (same field order and comma
+      handling), but array fields recurse into the enclosing
+      ArrBodyItems.  Nested here so no FORWARD declaration is needed. *)
+   PROCEDURE EmitRec (rt: SymTab.TypeIndex; rpre: ARRAY OF CHAR;
+                      VAR first: BOOLEAN);
+     VAR i, k, w: CARDINAL;
+       fn: SymTab.Name;
+       ft: SymTab.TypeIndex;
+       cls: INTEGER;
+       rsub: QVal;
+
+     PROCEDURE Sep;
+       BEGIN
+         IF first THEN first := FALSE ELSE W(", ") END
+       END Sep;
+
+     BEGIN
+       i := 0;
+       WHILE i < SymTab.FieldCount(rt) DO
+         SymTab.FieldName(rt, i, fn);
+         ft := SymTab.FieldType(rt, fn);
+         cls := SymTab.ClassOf(ft);
+         IF cls = SymTab.ClReal THEN Sep; W("d 0")
+         ELSIF cls = SymTab.ClChar THEN Sep; W("b 0")
+         ELSIF cls = SymTab.ClArray THEN
+           Sep;
+           Cpy(rsub, rpre); App(rsub, "_"); App(rsub, fn);
+           ArrBodyItems(rsub, ft)
+         ELSIF cls = SymTab.ClSet THEN
+           w := SymTab.SetWords(ft);
+           IF w = 0 THEN w := 1 END;
+           Sep; W("w 0");
+           k := 1;
+           WHILE k < w DO W(", w 0"); INC(k) END
+         ELSIF (cls = SymTab.ClRecord) OR (cls = SymTab.ClClass) THEN
+           Cpy(rsub, rpre); App(rsub, "_"); App(rsub, fn);
+           EmitRec(ft, rsub, first)
+         ELSE Sep; W("w 0")
+         END;
+         INC(i)
+       END
+     END EmitRec;
+
    BEGIN
     n := SymTab.ArrayLen(t);
     elem := SymTab.ArrayElem(t);
@@ -2046,6 +2116,17 @@ PROCEDURE ArrBodyItems (prefix: ARRAY OF CHAR; t: INTEGER);
         W(bv);
         INC(i)
       END
+    ELSIF ((ecls = SymTab.ClRecord) OR (ecls = SymTab.ClClass))
+       AND HasNestedArray(elem) THEN
+      (* Per-element record expansion: nested-array field descriptors. *)
+      i := 0;
+      WHILE i < n DO
+        Cpy(sub, prefix); App(sub, "_");
+        IntStr(VAL(INTEGER, i), bv); App(sub, bv);
+        first := FALSE;
+        EmitRec(elem, sub, first);
+        INC(i)
+      END
     ELSE
       IF ecls = SymTab.ClChar THEN
         (* CHAR: n data bytes + one NUL terminator slot *)
@@ -2082,6 +2163,43 @@ PROCEDURE ArrData (name: ARRAY OF CHAR; t: INTEGER);
      elem: SymTab.TypeIndex;
      bv: QVal;
      sub: QVal;
+
+   (* Mirrors RecStatics, but nested-array sub-descriptors recurse into
+      the enclosing ArrData.  Nested here so no FORWARD is needed. *)
+   PROCEDURE EmitRecStatics (rpre: ARRAY OF CHAR; rt: SymTab.TypeIndex);
+     VAR k, j, an: CARDINAL;
+       fn: SymTab.Name;
+       ft, et: SymTab.TypeIndex;
+       cls: INTEGER;
+       rsub, bv2: QVal;
+   BEGIN
+     k := 0;
+     WHILE k < SymTab.FieldCount(rt) DO
+       SymTab.FieldName(rt, k, fn);
+       ft := SymTab.FieldType(rt, fn);
+       cls := SymTab.ClassOf(ft);
+       IF cls = SymTab.ClArray THEN
+         et := SymTab.ArrayElem(ft);
+         IF SymTab.ClassOf(et) = SymTab.ClArray THEN
+           an := SymTab.ArrayLen(ft);
+           j := 0;
+           WHILE j < an DO
+             Cpy(rsub, rpre); App(rsub, "_"); App(rsub, fn);
+             App(rsub, "_");
+             IntStr(VAL(INTEGER, j), bv2);
+             App(rsub, bv2);
+             ArrData(rsub, et);
+             INC(j)
+           END
+         END
+       ELSIF (cls = SymTab.ClRecord) OR (cls = SymTab.ClClass) THEN
+         Cpy(rsub, rpre); App(rsub, "_"); App(rsub, fn);
+         EmitRecStatics(rsub, ft)
+       END;
+       INC(k)
+     END
+   END EmitRecStatics;
+
    BEGIN
     IF NOT opened THEN RETURN END;
     W("data $"); W(name);
@@ -2099,6 +2217,20 @@ PROCEDURE ArrData (name: ARRAY OF CHAR; t: INTEGER);
         ArrData(sub, elem);
         INC(i)
       END
+    ELSIF ((SymTab.ClassOf(elem) = SymTab.ClRecord)
+           OR (SymTab.ClassOf(elem) = SymTab.ClClass))
+       AND HasNestedArray(elem) THEN
+      (* Sub-descriptors referenced (as $name_i_field_k) from the inline
+         items emitted by ArrBodyItems; one set per element. *)
+      n := SymTab.ArrayLen(t);
+      i := 0;
+      WHILE i < n DO
+        Cpy(sub, name); App(sub, "_");
+        IntStr(VAL(INTEGER, i), bv);
+        App(sub, bv);
+        EmitRecStatics(sub, elem);
+        INC(i)
+      END
     END
    END ArrData;
 

+ 50 - 0
compiler/tests/t_nestarr.mod

@@ -0,0 +1,50 @@
+MODULE TNestArr;
+(* Regression: a RECORD field that is a nested ARRAY OF ARRAY OF CHAR,
+   accessed as `arr[i].field[j][k]`, must not segfault.  Exit 42.
+   (Was docs/wip/nested-array-field-bug.mod.) *)
+VAR ExitCode : INTEGER;
+
+CONST MaxName = 63; MaxProc = 4;
+
+TYPE PE = RECORD
+  name  : ARRAY [0..MaxName] OF CHAR;
+  npar  : CARDINAL;
+  pname : ARRAY [0..3] OF ARRAY [0..MaxName] OF CHAR;  (* nested *)
+END;
+
+VAR procs : ARRAY [0..MaxProc-1] OF PE;
+    nProcs : CARDINAL;
+
+PROCEDURE Reset;
+BEGIN nProcs := 0 END Reset;
+
+PROCEDURE NoteProc (name : ARRAY OF CHAR);
+VAR i : CARDINAL;
+BEGIN
+  IF nProcs >= MaxProc THEN RETURN END;
+  i := 0;
+  WHILE (i < MaxName) AND (name[i] # CHR(0)) DO
+    procs[nProcs].name[i] := name[i]; INC(i)
+  END;
+  procs[nProcs].name[i] := CHR(0);
+  procs[nProcs].npar := 0; INC(nProcs)
+END NoteProc;
+
+PROCEDURE NoteParam (name : ARRAY OF CHAR);
+VAR j, i : CARDINAL;
+BEGIN
+  IF nProcs = 0 THEN RETURN END;
+  j := procs[nProcs-1].npar;
+  i := 0;
+  WHILE (i < MaxName) AND (name[i] # CHR(0)) DO
+    procs[nProcs-1].pname[j][i] := name[i]; INC(i)
+  END;
+  procs[nProcs-1].pname[j][i] := CHR(0);
+  procs[nProcs-1].npar := j + 1
+END NoteParam;
+
+BEGIN
+  Reset; NoteProc("Add"); NoteParam("a");
+  IF (nProcs = 1) AND (procs[0].pname[0][0] = "a")
+  THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TNestArr.

+ 11 - 2
docs/summary_lower_l4_l5.md

@@ -50,12 +50,21 @@ string concat).
 
 ## Remaining (documented, not blocking)
 
-- **Nested arrays** (`ARRAY OF ARRAY`) — the known codegen bug
-  (`docs/wip/nested-array-field-bug.mod`); Lower does not add support.
 - **UString** literals/ops.
 - **Aggregate constants** (record/array CONST values).
 - **Qualified names / imports / class methods** — L6 territory.
 
+## Fixed after L5
+
+- **Nested arrays** (`ARRAY OF ARRAY` record fields): `QbeGen.ArrBodyItems`
+  now expands array-of-record elements per element (nested `EmitRec`)
+  when the record recursively has a nested-array field, and `ArrData`
+  emits the matching per-element sub-descriptors (nested
+  `EmitRecStatics`).  Plain record arrays stay a compact zero blob, so
+  `AST.arena` does not balloon.  Regression: `tests/t_nestarr.mod`
+  (was `docs/wip/nested-array-field-bug.mod`).  Suite 192/192;
+  FIXPOINT OK (3,322,779 bytes).
+
 ## Not touched
 
 `QbeGen` remains at its committed state (stride fix only); the general

+ 6 - 5
docs/wip/nested-array-field-bug.mod

@@ -13,11 +13,12 @@ MODULE NestedArrayFieldBug;
        QbeGen.ArrBodyItems now uses SymTab.TypeSize for record/class/set
        array elements, so an ARRAY OF record has correct element size and
        flat field access works (see tests/t_recarr.mod, exit 42).
-     - This nested case STILL crashes: a record element inside an array
-       is emitted as a zero blob, and its nested-array field's
-       sub-descriptors (`$arr_i_field_k`) are never emitted, so the
-       field's pointers are null.  Fixing it needs per-element
-       RecStatics/RecItems in ArrBodyItems/ArrData.
+     - This nested case is FIXED: ArrBodyItems now expands array-of-record
+       elements per element (EmitRec) when the record (recursively) has a
+       nested-array field, and ArrData emits the matching per-element
+       sub-descriptors (EmitRecStatics).  Plain record arrays (no nested
+       array field) stay a compact zero blob, so AST.arena does not
+       balloon.  Regression test: tests/t_nestarr.mod (exit 42).
 
    Compare with docs/wip/local-array-bug.mod (local arrays) and the
    Stage-A array-of-record stride fix (QbeGen.ElemSize). *)