Bladeren bron

fix: init inline array-field length in array-of-record static data

An array-record element whose record has an ARRAY field was emitted as
a compact zero blob, so the field's inline descriptor length word stayed
0; passing the field as an open-array actual made the callee read length
0 and trap (docs/wip/arrrec-field-openarray.mod, rc 134).

Generalise the per-element expansion predicate from HasNestedArray
(ARRAY OF ARRAY only) to HasArrayField (any array field), so ArrBodyItems
inits each inline array field and ArrData emits the matching nested
sub-descriptors.  Bounded to record arrays <= 131072 bytes: expanding a
huge table (AST.arena is 262144 records) would push the image past the
4 MiB QbeGen.sessBuf -- those keep the compact blob (latent 0-count bug;
the principled fix is a runtime init loop).

Regression: compiler/tests/t_arrrecfld.mod (exit 42).
Suite 182/182; FIXPOINT OK (3,367,970 bytes).
Eric Streit 4 dagen geleden
bovenliggende
commit
2ebf662fcd
4 gewijzigde bestanden met toevoegingen van 68 en 17 verwijderingen
  1. 1 0
      compiler/run_tests.sh
  2. 16 14
      compiler/src/QbeGen.mod
  3. 36 0
      compiler/tests/t_arrrecfld.mod
  4. 15 3
      docs/wip/arrrec-field-openarray.mod

+ 1 - 0
compiler/run_tests.sh

@@ -93,6 +93,7 @@ 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_arrrecfld.mod 42
 expect_run t_under.mod 42
 expect_run t_with.mod 114
 expect_run t_set.mod 131

+ 16 - 14
compiler/src/QbeGen.mod

@@ -2012,28 +2012,27 @@ PROCEDURE Trap;
 (* 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;
+PROCEDURE HasArrayField (t: SymTab.TypeIndex): BOOLEAN;
+(* TRUE when record type t (recursively) has any ARRAY field.  Such
+   record arrays need per-element expansion so each inline array field's
+   descriptor length word is initialised; a compact zero blob would
+   leave the count 0 and trap when the field is passed as an open array.
+   Records with no array field still stay a compact blob. *)
+  VAR i: CARDINAL; fn: SymTab.Name; ft: 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
+      IF cls = SymTab.ClArray THEN RETURN TRUE
       ELSIF (cls = SymTab.ClRecord) OR (cls = SymTab.ClClass) THEN
-        IF HasNestedArray(ft) THEN RETURN TRUE END
+        IF HasArrayField(ft) THEN RETURN TRUE END
       END;
       INC(i)
     END;
     RETURN FALSE
-  END HasNestedArray;
+  END HasArrayField;
 
 PROCEDURE ArrBodyItems (prefix: ARRAY OF CHAR; t: INTEGER);
 (* Inline contents of an array descriptor (no "data $name = {" wrapper
@@ -2107,8 +2106,9 @@ PROCEDURE ArrBodyItems (prefix: ARRAY OF CHAR; t: INTEGER);
         INC(i)
       END
     ELSIF ((ecls = SymTab.ClRecord) OR (ecls = SymTab.ClClass))
-       AND HasNestedArray(elem) THEN
-      (* Per-element record expansion: nested-array field descriptors. *)
+        AND HasArrayField(elem)
+        AND (VAL(INTEGER, n) * VAL(INTEGER, SymTab.TypeSize(elem))
+             <= 131072) THEN
       i := 0;
       WHILE i < n DO
         Cpy(sub, prefix); App(sub, "_");
@@ -2209,7 +2209,9 @@ PROCEDURE ArrData (name: ARRAY OF CHAR; t: INTEGER);
       END
     ELSIF ((SymTab.ClassOf(elem) = SymTab.ClRecord)
            OR (SymTab.ClassOf(elem) = SymTab.ClClass))
-       AND HasNestedArray(elem) THEN
+        AND HasArrayField(elem)
+        AND (VAL(INTEGER, SymTab.ArrayLen(t))
+             * VAL(INTEGER, SymTab.TypeSize(elem)) <= 131072) THEN
       (* Sub-descriptors referenced (as $name_i_field_k) from the inline
          items emitted by ArrBodyItems; one set per element. *)
       n := SymTab.ArrayLen(t);

+ 36 - 0
compiler/tests/t_arrrecfld.mod

@@ -0,0 +1,36 @@
+MODULE TArrRecFld;
+(* Regression for docs/wip/arrrec-field-openarray.mod: an array field of
+   an array-of-record element passed as an open-array actual.  The field's
+   inline descriptor length word must be initialised in the static
+   array-of-record data, else the callee reads 0 and traps.  Exit 42. *)
+VAR ExitCode : INTEGER;
+
+TYPE
+  ProcEnt = RECORD
+    name : ARRAY [0..63] OF CHAR;
+  END;
+VAR
+  procs  : ARRAY [0..3] OF ProcEnt;
+  nProcs : CARDINAL;
+
+PROCEDURE CopyN (src : ARRAY OF CHAR; VAR dst : ARRAY OF CHAR);
+  VAR i : CARDINAL;
+BEGIN
+  i := 0;
+  WHILE (i < 63) AND (src[i] # CHR(0)) DO dst[i] := src[i]; INC(i) END;
+  dst[i] := CHR(0)
+END CopyN;
+
+PROCEDURE NoteProc (name : ARRAY OF CHAR);
+BEGIN
+  CopyN(name, procs[nProcs].name);
+  INC(nProcs)
+END NoteProc;
+
+BEGIN
+  NoteProc("hello");
+  IF (procs[0].name[0] = "h") AND (procs[0].name[4] = "o") THEN
+    ExitCode := 42
+  ELSE ExitCode := 1
+  END
+END TArrRecFld.

+ 15 - 3
docs/wip/arrrec-field-openarray.mod

@@ -5,9 +5,21 @@ MODULE ArrRecFieldOpenArray;
    traps.  V3-built compilers miscompile this; gm2-built ones happen to
    work.  Expected: exit 0.  Actual (buggy V3): SIGABRT (134).
 
-   Status: UNFIXED in the general codegen path (see
-   docs/summary_l3_selfhost_fix.md).  Lower's own tables avoid the
-   pattern (flat arrays), so self-hosting is unaffected. *)
+   Status (2026-10-08): FIXED for record arrays whose data is at most
+   131072 bytes — `QbeGen.ArrBodyItems`/`ArrData` now expand record/
+   class elements per element (predicate `HasArrayField`, not just
+   nested ARRAY OF ARRAY) so each inline array field's length word is
+   initialised.  This case used to be a single zero blob, so the field's
+   count stayed 0 and the callee trapped.  Regression: compiler/tests/
+   t_arrrecfld.mod (exit 42).
+
+   Larger tables (e.g. AST.arena, 262144 records) keep the compact zero
+   blob on purpose: expanding them (even interleaved `l <len>, z <n>`)
+   would push the compiler image past the 4 MiB QbeGen.sessBuf.  The
+   principled general fix is to initialise such counts at runtime (a
+   generated init loop), not in the static data; until then the latent
+   0-count bug remains for very large record arrays.  See
+   docs/summary_l3_selfhost_fix.md. *)
 
 TYPE
   ProcEnt = RECORD