Przeglądaj źródła

showcase23: generic doubly linked list + fix global record data size

Showcase: a generic DList(T) instantiated as IntList = DList(INTEGER)
builds/reverses/sums a doubly linked list (prints, exits 35).
Wired as expect_run_files_out Showcase23.

Found and fixed a general codegen bug while writing it: QbeGen.RecItems
emitted 'w 0' (4 bytes) for every scalar record field, including
pointers/LONGINT/proc (8 bytes), so a global of such a record was
under-sized and later globals clobbered its tail (List.count read as 0).
Now emits 'l 0' for ClPtr/ClLong/ClProc. Regression test t_recptr.

Suite 218/218; fixpoint OK; corpus unchanged (372 OK, 0 crash).
Eric Streit 1 dzień temu
rodzic
commit
d8f2b98a4f

+ 5 - 0
compiler/run_tests.sh

@@ -95,6 +95,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_recptr.mod 42
 expect_run t_nestarr.mod 42
 expect_run t_arrrecfld.mod 42
 expect_run t_arrrecbig.mod 42
@@ -305,6 +306,10 @@ expect_run_files WrapProg 42 \
   ../stdlib/wraptime.def ../stdlib/wraptime.mod \
   ../stdlib/wrapc.def ../stdlib/wrapc.mod \
   wrapprog.mod
+expect_run_files_out Showcase23 35 "list: 5 10 20" \
+  ../stdlib/sysio.def ../stdlib/sysio.mod \
+  showcase23lib.def showcase23lib.mod \
+  showcase23int.def showcase23int.mod showcase23.mod
 expect_run_files_out Showcase22 42 "showcase22 total=42" \
   ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod \
   ../stdlib/iochan.def ../stdlib/iochan.mod \

+ 5 - 0
compiler/src/QbeGen.mod

@@ -2171,6 +2171,11 @@ PROCEDURE RecItems (t: SymTab.TypeIndex; prefix: ARRAY OF CHAR;
       ELSIF (cls = SymTab.ClRecord) OR (cls = SymTab.ClClass) THEN
         Cpy(sub, prefix); App(sub, "_"); App(sub, fn);
         RecItems(ft, sub, first)
+      ELSIF (cls = SymTab.ClPtr) OR (cls = SymTab.ClLong)
+         OR (cls = SymTab.ClProc) THEN
+        (* 8-byte fields: match the layout's pointer/long size (a `w 0`
+           here would shrink the global and clobber the next symbol) *)
+        Sep; W("l 0")
       ELSE Sep; W("w 0")
       END;
       INC(i)

+ 37 - 0
compiler/tests/showcase23.mod

@@ -0,0 +1,37 @@
+MODULE Showcase23;
+(* Showcase: a generic doubly linked list.  DList(T) is instantiated as
+   IntList = DList(INTEGER); the program builds a list, walks it with
+   Get(i), reverses it and sums it.  Prints, then exits with the sum. *)
+IMPORT SysIO, IntList;
+VAR l : IntList.List; i, sum, ExitCode : INTEGER;
+
+PROCEDURE Show(pfx : ARRAY OF CHAR);
+BEGIN
+  SysIO.Write(pfx);
+  i := 0;
+  WHILE i < IntList.Count(l) DO
+    SysIO.Write(" ");
+    SysIO.WriteInt(IntList.Get(l, i));
+    i := i + 1
+  END;
+  SysIO.WriteLn
+END Show;
+
+BEGIN
+  IntList.Init(l);
+  IntList.Append(l, 10);
+  IntList.Append(l, 20);
+  IntList.Prepend(l, 5);
+  Show("list:");
+
+  IntList.Reverse(l);
+  Show("reversed:");
+
+  sum := 0; i := 0;
+  WHILE i < IntList.Count(l) DO
+    sum := sum + IntList.Get(l, i);
+    i := i + 1
+  END;
+  SysIO.Write("sum="); SysIO.WriteInt(sum); SysIO.WriteLn;
+  ExitCode := sum                       (* 35 *)
+END Showcase23.

+ 2 - 0
compiler/tests/showcase23int.def

@@ -0,0 +1,2 @@
+DEFINITION MODULE IntList = DList(INTEGER);
+END IntList.

+ 2 - 0
compiler/tests/showcase23int.mod

@@ -0,0 +1,2 @@
+IMPLEMENTATION MODULE IntList = DList(INTEGER);
+END IntList.

+ 20 - 0
compiler/tests/showcase23lib.def

@@ -0,0 +1,20 @@
+GENERIC DEFINITION MODULE DList (T : TYPE);
+TYPE
+  Node = POINTER TO NodeRec;
+  NodeRec = RECORD
+    val        : T;
+    prev, next : Node;
+  END;
+  List = RECORD
+    head, tail : Node;
+    count      : INTEGER;
+  END;
+PROCEDURE Init(VAR l : List);
+PROCEDURE Append(VAR l : List; v : T);
+PROCEDURE Prepend(VAR l : List; v : T);
+PROCEDURE Count(VAR l : List) : INTEGER;
+PROCEDURE Get(VAR l : List; i : INTEGER) : T;
+PROCEDURE First(VAR l : List) : T;
+PROCEDURE Last(VAR l : List) : T;
+PROCEDURE Reverse(VAR l : List);
+END DList.

+ 47 - 0
compiler/tests/showcase23lib.mod

@@ -0,0 +1,47 @@
+GENERIC IMPLEMENTATION MODULE DList;
+PROCEDURE Init(VAR l : List);
+BEGIN l.head := NIL; l.tail := NIL; l.count := 0 END Init;
+
+PROCEDURE Append(VAR l : List; v : T);
+VAR n : Node;
+BEGIN
+  NEW(n); n^.val := v; n^.next := NIL; n^.prev := l.tail;
+  IF l.tail # NIL THEN l.tail^.next := n ELSE l.head := n END;
+  l.tail := n; l.count := l.count + 1
+END Append;
+
+PROCEDURE Prepend(VAR l : List; v : T);
+VAR n : Node;
+BEGIN
+  NEW(n); n^.val := v; n^.prev := NIL; n^.next := l.head;
+  IF l.head # NIL THEN l.head^.prev := n ELSE l.tail := n END;
+  l.head := n; l.count := l.count + 1
+END Prepend;
+
+PROCEDURE Count(VAR l : List) : INTEGER;
+BEGIN RETURN l.count END Count;
+
+PROCEDURE Get(VAR l : List; i : INTEGER) : T;
+VAR n : Node; k : INTEGER;
+BEGIN
+  n := l.head; k := 0;
+  WHILE k < i DO n := n^.next; k := k + 1 END;
+  RETURN n^.val
+END Get;
+
+PROCEDURE First(VAR l : List) : T;
+BEGIN RETURN l.head^.val END First;
+
+PROCEDURE Last(VAR l : List) : T;
+BEGIN RETURN l.tail^.val END Last;
+
+PROCEDURE Reverse(VAR l : List);
+VAR n, nx : Node;
+BEGIN
+  n := l.head;
+  WHILE n # NIL DO
+    nx := n^.next; n^.next := n^.prev; n^.prev := nx; n := nx
+  END;
+  n := l.head; l.head := l.tail; l.tail := n
+END Reverse;
+END DList.

+ 12 - 0
compiler/tests/t_recptr.mod

@@ -0,0 +1,12 @@
+MODULE TRecPtr;
+// A global record with pointer fields must emit 8-byte slots in its
+// data initialiser (matching the layout); a `w 0` would shrink the
+// global so the next globals clobber its tail.  Exit 42.
+TYPE P = POINTER TO R; R = RECORD a : INTEGER; END;
+TYPE L = RECORD head, tail : P; n : INTEGER; END;
+VAR g : L; a, b, ExitCode : INTEGER;
+BEGIN
+  g.n := 5;
+  a := 37; b := 0;          (* would overwrite g.n if g were undersized *)
+  ExitCode := g.n + a       (* 42 *)
+END TRecPtr.

+ 80 - 0
docs/summary_showcase23-dlist-2026-10-16.md

@@ -0,0 +1,80 @@
+# Showcase 23 — generic doubly linked list (2026-10-16)
+
+Branch **`resolve-check`**, tree clean.  Suite **218/218**; fixpoint
+**OK (3,711,046 bytes)**; corpus **372 OK / 0 crash**.
+
+## The showcase
+
+`showcase23.mod` builds a doubly linked list with the generic module
+`DList (T : TYPE)` instantiated as `IntList = DList(INTEGER)`:
+
+```modula2
+GENERIC DEFINITION MODULE DList (T : TYPE);
+TYPE
+  Node = POINTER TO NodeRec;
+  NodeRec = RECORD val : T; prev, next : Node; END;
+  List = RECORD head, tail : Node; count : INTEGER; END;
+PROCEDURE Init(VAR l : List);
+PROCEDURE Append(VAR l : List; v : T);
+PROCEDURE Prepend(VAR l : List; v : T);
+PROCEDURE Count(VAR l : List) : INTEGER;
+PROCEDURE Get(VAR l : List; i : INTEGER) : T;
+PROCEDURE First / Last(VAR l : List) : T;
+PROCEDURE Reverse(VAR l : List);
+END DList.
+```
+
+Run wired as `expect_run_files_out Showcase23 35 "list: 5 10 20"`:
+
+```
+list: 5 10 20
+reversed: 20 10 5
+sum=35
+```
+
+Files: `showcase23lib.def/.mod` (the generic), `showcase23int.def/.mod`
+(`IntList = DList(INTEGER)`), `showcase23.mod` (the program).
+
+## Bug found and fixed (general, not generic-specific)
+
+While writing it, `WHILE i < IntList.Count(l) DO … END` returned 0.  The
+cause was in `QbeGen.RecItems`: a record's **static data initialiser**
+emitted `w 0` (4 bytes) for *every* scalar field, including **pointers**
+and `LONGINT`/proc (8-byte) fields, while the record layout gives them 8
+bytes.  A global of such a record was therefore **under-sized**, and the
+next globals overlapped and clobbered its tail — here `List.count`.
+
+```modula2
+VAR g : L; a : INTEGER;      (* L = RECORD head, tail : P; n : INTEGER END *)
+g.n := 5; a := 37;           (* a clobbered g.n, at offset 16 *)
+```
+
+Fixed by emitting `l 0` for `ClPtr`/`ClLong`/`ClProc` fields (real/char/
+set/array/record were already size-correct).  Regression: `t_recptr`
+(exits 42 only when `g.n` survives).  `$g` now emits
+`{ l 0, l 0, w 0 }` instead of `{ w 0, w 0, w 0 }`.
+
+## Limitations met (not fixed — recorded)
+
+1. **Arithmetic on a type formal in a template.**  `Sum` over `T`
+   (`s : T; s := s + n^.val`) is `211` at *template* parse: an unbound
+   formal alias is class `ClPtr` (pointer-like), so numeric checks fail.
+   Workaround used: do the arithmetic in the concrete program
+   (`acc := acc + Get(l, i)`).  A principled fix is to let checks skip an
+   *unbound type formal* (a distinct form from an opaque alias).
+2. **Proc-typed formals in a DEFINITION/IMPLEMENTATION pair** trip a
+   false `235` ("implementation does not match definition"): proc types
+   are not deduped, so the def's and impl's constructed proc type have
+   different `TypeIndex`es and the signature snapshot compares unequal.
+   Not used in the showcase for that reason.
+
+Both are follow-ups; neither affects the showcase.
+
+## Verify
+
+```sh
+cd .../m2compiler-V3/compiler
+./M2 ../stdlib/sysio.def ../stdlib/sysio.mod \
+    tests/showcase23lib.def tests/showcase23lib.mod \
+    tests/showcase23int.def tests/showcase23int.mod tests/showcase23.mod
+```