Selaa lähdekoodia

language: fix self-referential record layout and pointer chains

Publish an unresolved alias for a TYPE before parsing its body, and invalidate record/class field layouts when SetTarget completes an alias. This keeps TYPE List = POINTER TO RECORD next: List; value: INTEGER END from laying both fields at offset 0 and makes p^.next^.value and VAR field actuals work. Add t_ptrchain; lazyunique now compiles, runs, and prints 0,2,1. Gates: suite 276/276, fixpoint OK 4,143,847 bytes, corpus 512/612 compile-OK.
Eric Streit 1 päivä sitten
vanhempi
commit
e804e5756e

+ 2 - 2
README.md

@@ -47,8 +47,8 @@ docs/              STATUS.md, plan.md, plan-*.md, analysis_*.md, summary_*.md
 
 ## Status (2026-10-23)
 
-- Suite **275/275**; self-hosting **fixpoint OK** (byte-identical
-  stage2/stage3, 4,141,580 bytes); corpus **512/612 compile-OK,
+- Suite **276/276**; self-hosting **fixpoint OK** (byte-identical
+  stage2/stage3, 4,143,847 bytes); corpus **512/612 compile-OK,
   0 crashes** (`tools/v3-corpus`; the corpus is gm2's testsuite, so it
   mixes conforming Modula-2 with gm2 leniencies — see `docs/gaps/`).
 - Active branch **`master`**.

+ 1 - 0
compiler/run_tests.sh

@@ -74,6 +74,7 @@ expect_run t_proctype2.mod 42
 expect_run t_byte.mod 42
 expect_run t_word16.mod 42
 expect_run t_packedset.mod 42
+expect_run t_ptrchain.mod 42
 expect_run t_except_parse.mod 42
 expect_run t_except_throw.mod 42
 expect_run t_except_retry.mod 42

+ 19 - 5
compiler/src/M2.atg

@@ -1297,12 +1297,27 @@ PRODUCTIONS
                                            astDecl := astSeq; .) .
   TypeItem<isDef: BOOLEAN>              (. VAR n: SymTab.Name;
                                              t, op: SymTab.TypeIndex;
+                                             existing: BOOLEAN;
                                              astNode: AST.Node; .)
     = GetIdent<n>                       (. op := SymTab.OpaqueBase(n);
-                                           IF op = SymTab.InvalidType THEN
+                                           existing := op # SymTab.InvalidType;
+                                           IF NOT existing THEN
                                              IF NOT SymTab.Enter(n,
                                                 SymTab.KindType) THEN
-                                               SemError(200) END
+                                               SemError(200)
+                                             ELSE
+                                               (* publish an unresolved alias
+                                                  before parsing the body so a
+                                                  self-reference such as
+                                                  TYPE List = POINTER TO
+                                                    RECORD next: List ... END
+                                                  keeps the pointer type, not
+                                                  InvalidType. *)
+                                               op := SymTab.NewAlias();
+                                               IF op # SymTab.InvalidType THEN
+                                                 SymTab.SetSymType(n, op)
+                                               END
+                                             END
                                            END; .)
       ( "=" Type<t, FALSE> (. astNode := AST.MakeNode(AST.NkTypeDecl);
                                              AST.SetChild(astNode, 0,
@@ -1314,12 +1329,11 @@ PRODUCTIONS
                                            ELSE SymTab.SetSymType(n, t)
                                            END;
                                            IF SymTab.EnterEnumLits(t) > 0 THEN END; .)
-      |                                 (. IF op # SymTab.InvalidType THEN
+      |                                 (. IF existing THEN
                                              (* stays opaque *)
                                            ELSIF NOT isDef THEN
                                              SemError(231)
-                                           ELSE SymTab.SetSymType(n,
-                                             SymTab.NewAlias()) END; .) ) .
+                                           END; .) ) .
   Type<VAR t: SymTab.TypeIndex; allowOpen: BOOLEAN>
     = TypeIdent<t> [ Subrange<t> ]     (* anchored subrange: T[lo..hi] *)
     | Subrange<t>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2444 - 2444
compiler/src/M2.lst


+ 13 - 1
compiler/src/SymTab.mod

@@ -1077,9 +1077,21 @@ PROCEDURE PushClassScope (t: TypeIndex);
   END PushClassScope;
 
 PROCEDURE SetTarget (t, base: TypeIndex);
+(* Completing an alias can change the size of a field that was laid out
+   while the alias was still unresolved (e.g. a self-referential pointer
+   field inside its own record).  Invalidate record/class layouts so the
+   next FieldOffset/FieldName call recomputes them from resolved types. *)
+  VAR i: CARDINAL;
   BEGIN
     IF (t >= 0) AND (t < VAL(INTEGER, nTypes)) AND (tform[t] = FAlias) THEN
-      tref[t] := base
+      tref[t] := base;
+      i := 0;
+      WHILE i < nTypes DO
+        IF (tform[i] = FRecord) OR (tform[i] = FClass) THEN
+          tdone[i] := FALSE
+        END;
+        INC(i)
+      END
     END
   END SetTarget;
 

+ 37 - 0
compiler/tests/t_ptrchain.mod

@@ -0,0 +1,37 @@
+MODULE TPtrChain;
+(* Regression: self-referential record fields must lay out the pointer
+   and the following scalar at distinct offsets, and chained designators
+   (p^.next^.value) plus VAR field actuals must load/store the right
+   address.  This is the lazyunique pointer-chain root cause. *)
+TYPE
+  List = POINTER TO RECORD
+                    next : List;
+                    value: INTEGER;
+                  END;
+VAR ExitCode : INTEGER;
+VAR head, p : List;
+
+PROCEDURE Add (VAR q : List; val : INTEGER);
+BEGIN
+  NEW(q);
+  q^.value := val;
+  q^.next := NIL
+END Add;
+
+BEGIN
+  NEW(head);
+  head^.value := -1;
+  head^.next := NIL;
+  p := head;
+  Add(p^.next, 2);
+  IF (p^.next # NIL) AND (p^.next^.value = 2) THEN
+    p^.next^.value := 7;
+    IF p^.next^.value = 7 THEN
+      ExitCode := 42
+    ELSE
+      ExitCode := 1
+    END
+  ELSE
+    ExitCode := 2
+  END
+END TPtrChain.

+ 7 - 6
docs/STATUS.md

@@ -4,16 +4,17 @@ This answers "where are we now?".
 
 ## Numbers
 
-- Suite **275/275** (`compiler/run_tests.sh`).
+- Suite **276/276** (`compiler/run_tests.sh`).
 - Self-hosting **FIXPOINT OK** — stage2 and stage3 images are
-  byte-identical (4,141,580 bytes).
+  byte-identical (4,143,847 bytes).
 - Corpus **512 / 612** sources compile with 0 errors, **0 crashes**
   (`tools/v3-corpus/corpus.sh` over the gm2 testsuite; the harness now
   resolves V3's own shipped libraries); the rest are genuine language
   gaps, **gm2 leniencies V3 deliberately rejects**, or unresolved
-  imports of gm2-only modules.  Compile-only caveat: `lazyunique` now
-  parses with `EXCEPT` but its generated QBE still hits a pre-existing
-  pointer-chain (`p^.next^.value`) codegen gap.
+  imports of gm2-only modules.  `lazyunique` now compiles and runs
+  (`rc=0`); the corpus stays 512 because it was already compile-OK.
+  The pointer-chain (`p^.next^.value`) layout/codegen gap is fixed
+  (`t_ptrchain`).
 - Branch **`master`**, feature state **`v3-exceptions`**; tree clean.
 
 ## Architecture (current)
@@ -218,7 +219,7 @@ declarations still work (`t_fwdproc`, `t_fwdok`).
 ```sh
 cd compiler
 ./build.sh          # M2.atg -> scanner/parser/driver -> ./M2
-./run_tests.sh      # 275/275
+./run_tests.sh      # 276/276
 cd ..
 ./bootstrap/fixpoint.sh   # FIXPOINT OK (byte-identical stage2/stage3)
 ```

+ 1 - 1
docs/comparison-PIM4-gm2-V3.md

@@ -1,7 +1,7 @@
 # PIM4 vs GNU Modula-2 vs M2R10 vs TopSpeed V3 vs V3 — feature comparison
 
 **Date:** 2026-10-23 · **V3 state:** `v3-packedset` (suite
-275/275, self-hosting fixpoint OK 4,141,580 bytes, corpus 512/612,
+276/276, self-hosting fixpoint OK 4,143,847 bytes, corpus 512/612,
 0 crashes).
 
 **Scope and sources.**

+ 7 - 7
docs/design-exceptions.md

@@ -5,7 +5,7 @@
 `RETURN` unwinding, signal mapping, and compiler-emitted index/range/
 CASE traps are implemented.  `functionException`, `FINALLY`, coroutine/
 protection exceptions and complex arithmetic remain follow-ups.  Gates:
-suite **275/275**, fixpoint OK **4,141,580 bytes**, corpus **512/612**
+suite **276/276**, fixpoint OK **4,143,847 bytes**, corpus **512/612**
 compile-OK, 0 crashes.
 
 The handoff calls this the largest remaining language feature.  This
@@ -593,10 +593,10 @@ runtime/syslib/SysExceptions.def runtime/syslib/SysExceptions.mod
 
 ### 9.2 Corpus
 
-- `lazyunique` now compiles (corpus 512), but it is **not** a runtime
-  gain yet: its generated QBE hits a pre-existing pointer-chain
-  (`p^.next^.value`) codegen gap.  The corpus is compile-only; fix the
-  pointer-chain lowering separately before claiming `lazyunique` runs.
+- `lazyunique` now compiles and runs (`rc=0`, unique output `0, 2, 1`);
+  the corpus count remains 512 because it was already compile-OK.
+  Regression `t_ptrchain` covers the self-referential record layout and
+  `p^.next^.value` path.
 - `except2/4/5/7/8/9` still fail declaration order because `ip` is
   declared after the procedures.  Keep temporary copies with `ip`
   moved above the procedures and run them outside the corpus count.
@@ -613,8 +613,8 @@ cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
 tools/v3-corpus/corpus.sh
 ```
 
-Current M4 gates: suite **275/275**, fixpoint OK
-**4,141,580 bytes**, corpus **512/612 compile-OK**, 0 crashes.
+Current M4 gates: suite **276/276**, fixpoint OK
+**4,143,847 bytes**, corpus **512/612 compile-OK**, 0 crashes.
 Existing trap tests must stay rc=134.
 
 ---

+ 3 - 1
docs/features.md

@@ -83,7 +83,9 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
   set operations, packed array/record elements and parameters).
   Wider packed sets are a loud `230` implementation limit.
 - ✅ `POINTER TO`, `NIL` as a real type, `ADDRESS` ↔ any pointer
-  assignment (`p := ADR(x)`).
+  assignment (`p := ADR(x)`); self-referential pointer records
+  (`next: List`) and chained designators (`p^.next^.value`), regression
+  `t_ptrchain`.
 - ✅ `UCHAR` (32-bit codepoint) + `U'a'`/`U"…"` literals (strict
   RFC3629 decode → 234 on bad bytes), `UCHR`/`CHR8`/`UORD`, `U"…"`
   whole-string assignment to a 1-D `ARRAY OF UCHAR`, `+`

+ 9 - 5
docs/gaps/README.md

@@ -99,11 +99,15 @@ gap22_file_line.mod             invalid Fact            (__LINE__)
   `EXCEPTIONS`, `M2EXCEPTION`, `RETURN` unwinding, nested handlers,
   signal mapping, and catchable index/range/CASE traps work.  Remaining:
   `functionException`, `FINALLY`, coroutine/protection exceptions.
-  `lazyunique` now compiles (corpus 512) but its QBE hits a
-  pre-existing pointer-chain (`p^.next^.value`) codegen gap, so it is
-  not yet a runtime gain.  `except2/4/5/7/8/9` still fail declaration
-  order because `ip` is declared after the procedures.  See
-  `docs/design-exceptions.md`.
+  `lazyunique` compiles and runs after the pointer-chain fix (corpus
+  count still 512 because it was already compile-OK); `except2/4/5/7/
+  8/9` still fail declaration order because `ip` is declared after the
+  procedures.  See `docs/design-exceptions.md`.
+- **Self-referential record layout — fixed** (2026-10-23): a TYPE name
+  now publishes an unresolved alias before its body is parsed, and
+  completing an alias invalidates record/class layouts.  This fixes
+  `TYPE List = POINTER TO RECORD next: List; value: INTEGER END` and
+  `p^.next^.value`.  Regression `t_ptrchain`.
 - **`libc` prototypes — fixed** (2026-10-22): the known C functions
   now have real signatures (`exit`, `write`/`read`/`open`/`close`/
   `lseek`, `malloc`, `memcpy`/`memset`/`strncpy`, `system`/`getenv`,

+ 15 - 15
docs/handoff-2026-10-23-next.md

@@ -1,7 +1,7 @@
 # Next session — plan (from 2026-10-23)
 
 **Branch:** `master`.  **Feature state:** `v3-exceptions` (M5 done).
-**Gates:** suite **275/275**, fixpoint **OK (4,141,580 bytes)**,
+**Gates:** suite **276/276**, fixpoint **OK (4,143,847 bytes)**,
 corpus **512/612, 0 crashes**.
 
 Read first: `docs/STATUS.md`, `docs/features.md`,
@@ -60,15 +60,16 @@ abort (rc 134).  Tests: `t_except_trap`, `t_except_case`,
 `summary_session-2026-10-23-exceptions.md`; feature tag
 `v3-exceptions` at `02b3763`, session-end tag at the docs commit.
 Optional follow-ups: `functionException`, `FINALLY`, coroutine/
-protection exceptions, and the `lazyunique` pointer-chain codegen gap.
+protection exceptions.
 
-**Corpus reality:** `lazyunique` now **compiles** (corpus **512**) but
-is not a verified runtime +1: its QBE hits a pre-existing pointer-chain
-(`p^.next^.value`) codegen gap.  The corpus is compile-only; fix that
-lowering separately before claiming `lazyunique` runs.  `except2/4/5/7/
-8/9` still fail declaration order because `ip` is declared after the
-procedures, like `onebyte*`; keep moved-`ip` copies as semantic tests
-but do not count them in the corpus until the policy changes.
+**Corpus reality:** `lazyunique` now compiles and runs (`rc=0`,
+unique output `0, 2, 1`) after the pointer-chain follow-up; the corpus
+count stays **512** because it was already compile-OK.  Regression
+`t_ptrchain` covers the self-referential record layout and
+`p^.next^.value` path.  `except2/4/5/7/8/9` still fail declaration
+order because `ip` is declared after the procedures, like `onebyte*`;
+keep moved-`ip` copies as semantic tests but do not count them in the
+corpus until the policy changes.
 
 ## 3. Multi-dimensional open arrays — 8 files
 
@@ -108,7 +109,7 @@ this feature.
 ## Gates for every step
 
 ```sh
-cd compiler && ./build.sh && ./run_tests.sh          # 275/275
+cd compiler && ./build.sh && ./run_tests.sh          # 276/276
 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
 tools/v3-corpus/corpus.sh                            # 512 OK, 0 crash
 ```
@@ -121,13 +122,12 @@ session summary; commit with a `v3-*` tag.  Do not push without asking.
 ```sh
 cd .../m2compiler-V3
 git checkout master
-cd compiler && ./build.sh && ./run_tests.sh          # 275/275
+cd compiler && ./build.sh && ./run_tests.sh          # 276/276
 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
 tools/v3-corpus/corpus.sh                            # 512 OK, 0 crash
 ```
 
-**Recommendation:** fix the pre-existing pointer-chain codegen gap
-(`p^.next^.value`) so `lazyunique` can become a real runtime pass, or
-take the multi-dimensional open-array ABI decision.  Library folders and
-separate compilation remain deferred
+**Recommendation:** take the multi-dimensional open-array ABI
+decision, or start the separate-compilation/library-folder work.
+Library folders and separate compilation remain deferred
 (`docs/plan-separate-compilation.md`).

+ 17 - 10
docs/summary_session-2026-10-23-exceptions.md

@@ -5,8 +5,8 @@ Follows the same-day range-check/PACKEDSET session
 `2a30d8c`.
 
 **Branch:** `master`.  **Feature state:** `v3-exceptions`.
-**Gates at the end:** suite **275/275**, fixpoint **OK
-(4,141,580 bytes)**, corpus **512/612 compile-OK, 0 crashes**.
+**Gates at the end:** suite **276/276**, fixpoint **OK
+(4,143,847 bytes)**, corpus **512/612 compile-OK, 0 crashes**.
 
 ---
 
@@ -14,14 +14,14 @@ Follows the same-day range-check/PACKEDSET session
 
 | gate | at design start | at end |
 | --- | --- | --- |
-| suite (`compiler/run_tests.sh`) | 263/263 | **275/275** |
-| fixpoint (`bootstrap/fixpoint.sh`) | 4,075,853 B | **4,141,580 B** |
+| suite (`compiler/run_tests.sh`) | 263/263 | **276/276** |
+| fixpoint (`bootstrap/fixpoint.sh`) | 4,075,853 B | **4,143,847 B** |
 | corpus (`tools/v3-corpus/corpus.sh`) | 511/612 | **512/612 compile-OK, 0 crashes** |
 
-The corpus is compile-only.  `lazyunique` now parses and compiles, but
-its generated QBE still hits a pre-existing pointer-chain
-(`p^.next^.value`) codegen gap, so it is not yet a verified runtime
-+1.  `except2/4/5/7/8/9` still fail the strict declaration-order rule
+The corpus is compile-only.  After the follow-up pointer-chain fix,
+`lazyunique` compiles **and runs** (`rc=0`, unique output `0, 2, 1`);
+the corpus count stays 512 because it was already compile-OK.
+`except2/4/5/7/8/9` still fail the strict declaration-order rule
 because `ip` is declared after the procedures, like `onebyte*`.
 
 ---
@@ -103,6 +103,15 @@ because `ip` is declared after the procedures, like `onebyte*`.
 
 The design commit is `2a30d8c`.
 
+### Follow-up — pointer-chain codegen
+
+The self-referential `TYPE List = POINTER TO RECORD next: List ... END`
+and `p^.next^.value` gaps were fixed by publishing an unresolved alias
+for a TYPE before parsing its body, then invalidating record/class
+layout when `SetTarget` completes the alias.  Regression
+`t_ptrchain`; `lazyunique` now runs with rc 0.  This follow-up is
+tagged `v3-ptrchain`.
+
 ---
 
 ## Remaining work
@@ -112,8 +121,6 @@ The design commit is `2a30d8c`.
 - ISO `FINALLY` / module termination; coroutine (`coException`) and
   protection (`protException`) exceptions.
 - Complex-number exceptions (complex arithmetic is still a placeholder).
-- Pre-existing pointer-chain codegen (`p^.next^.value`) that keeps
-  `lazyunique` compile-only.
 - Multi-dimensional open arrays, separate compilation and the dialect
   library-folder layout (see `handoff-2026-10-23-next.md` and
   `plan-separate-compilation.md`).

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä