ソースを参照

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

+ 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_charbounds.mod 3
 expect_run t_record.mod 160
 expect_run t_record.mod 160
 expect_run t_recarr.mod 42
 expect_run t_recarr.mod 42
+expect_run t_nestarr.mod 42
 expect_run t_with.mod 114
 expect_run t_with.mod 114
 expect_run t_set.mod 131
 expect_run t_set.mod 131
 expect_run t_setchar.mod 77
 expect_run t_setchar.mod 77

+ 136 - 4
compiler/src/QbeGen.mod

@@ -2019,18 +2019,88 @@ PROCEDURE Trap;
   END Trap;
   END Trap;
 
 
 (* ---------------- records: flat blobs, pointer fields ---------------- *)
 (* ---------------- 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);
 PROCEDURE ArrBodyItems (prefix: ARRAY OF CHAR; t: INTEGER);
 (* Inline contents of an array descriptor (no "data $name = {" wrapper
 (* Inline contents of an array descriptor (no "data $name = {" wrapper
    and no closing brace): "l <n>[, <elem>...]".  Nested levels are
    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;
    VAR n, i: CARDINAL;
      elem: SymTab.TypeIndex;
      elem: SymTab.TypeIndex;
      ecls: INTEGER;
      ecls: INTEGER;
      esz: CARDINAL;
      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
    BEGIN
     n := SymTab.ArrayLen(t);
     n := SymTab.ArrayLen(t);
     elem := SymTab.ArrayElem(t);
     elem := SymTab.ArrayElem(t);
@@ -2046,6 +2116,17 @@ PROCEDURE ArrBodyItems (prefix: ARRAY OF CHAR; t: INTEGER);
         W(bv);
         W(bv);
         INC(i)
         INC(i)
       END
       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
     ELSE
       IF ecls = SymTab.ClChar THEN
       IF ecls = SymTab.ClChar THEN
         (* CHAR: n data bytes + one NUL terminator slot *)
         (* CHAR: n data bytes + one NUL terminator slot *)
@@ -2082,6 +2163,43 @@ PROCEDURE ArrData (name: ARRAY OF CHAR; t: INTEGER);
      elem: SymTab.TypeIndex;
      elem: SymTab.TypeIndex;
      bv: QVal;
      bv: QVal;
      sub: 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
    BEGIN
     IF NOT opened THEN RETURN END;
     IF NOT opened THEN RETURN END;
     W("data $"); W(name);
     W("data $"); W(name);
@@ -2099,6 +2217,20 @@ PROCEDURE ArrData (name: ARRAY OF CHAR; t: INTEGER);
         ArrData(sub, elem);
         ArrData(sub, elem);
         INC(i)
         INC(i)
       END
       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
    END ArrData;
    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)
 ## 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.
 - **UString** literals/ops.
 - **Aggregate constants** (record/array CONST values).
 - **Aggregate constants** (record/array CONST values).
 - **Qualified names / imports / class methods** — L6 territory.
 - **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
 ## Not touched
 
 
 `QbeGen` remains at its committed state (stride fix only); the general
 `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
        QbeGen.ArrBodyItems now uses SymTab.TypeSize for record/class/set
        array elements, so an ARRAY OF record has correct element size and
        array elements, so an ARRAY OF record has correct element size and
        flat field access works (see tests/t_recarr.mod, exit 42).
        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
    Compare with docs/wip/local-array-bug.mod (local arrays) and the
    Stage-A array-of-record stride fix (QbeGen.ElemSize). *)
    Stage-A array-of-record stride fix (QbeGen.ElemSize). *)