Kaynağa Gözat

feat: add ABI stamp and definition-only objects

Eric Streit 1 gün önce
ebeveyn
işleme
a554873da5

+ 2 - 2
README.md

@@ -87,13 +87,13 @@ docs/              STATUS.md, plan.md, plan-*.md, analysis_*.md, summary_*.md
 
 ## Status (2026-10-24)
 
-- Suite **304/304**; self-hosting **fixpoint OK** (byte-identical
+- Suite **305/305**; self-hosting **fixpoint OK** (byte-identical
   stage2/stage3, 3,806,078 bytes); corpus **519/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/`).
   The remaining `except*`/`onebyte*`-style corpus files still hit V3's
   declaration-order policy.
-- Active branch **`master`**, feature state **`v3-separate-fixpoint`**.
+- Active branch **`master`**, feature state **`v3-separate-abi-vtables`**.
   Recent milestones: `PACKEDSET`, ISO exceptions (`EXCEPT`/`RETRY`,
   `EXCEPTIONS`, `M2EXCEPTION`, catchable language traps including
   `functionException`, module `FINALLY`), self-referential pointer

+ 4 - 3
TUTORIAL.md

@@ -10,7 +10,7 @@ From the repository root:
 ```sh
 cd compiler
 ./build.sh          # M2.atg -> scanner/parser/driver -> ./M2
-./run_tests.sh      # 304/304
+./run_tests.sh      # 305/305
 ```
 
 `M2` reads Modula-2 sources and writes QBE IR to `gen_ssa/`.
@@ -149,7 +149,7 @@ TMP="$PWD/.scratch/fixpoint-separate" ./bootstrap/fixpoint-separate.sh
 tools/v3-corpus/corpus.sh
 ```
 
-Current gates: suite **304/304**, fixpoint **OK (3,806,078 bytes)**,
+Current gates: suite **305/305**, fixpoint **OK (3,806,078 bytes)**,
 corpus **519/612 compile-OK, 0 crashes**.
 
 ## 7. Current limits
@@ -157,7 +157,8 @@ corpus **519/612 compile-OK, 0 crashes**.
 - `M2 -I` transitive interface scanning is implemented.
 - `M2make --separate` rebuilds only stale objects, relinks when an object,
   the shim, or the output changes, and treats a newer compiler binary as an
-  ABI event.  There is no content-based ABI version marker yet.
+  ABI event.  A small content ABI/flags stamp (`gen_ssa/.m2abi`) forces
+  a rebuild when the compiler ABI or `--m2flag` settings change.
 - Generic instance objects are not emitted separately yet.
 - Per-object self-hosting is a green gate via
   `bootstrap/fixpoint-separate.sh`; compiler units use

+ 1 - 0
compiler/run_tests.sh

@@ -754,6 +754,7 @@ expect_separate_auto() {
 }
 
 expect_separate_auto SepResProg 42 SepRes sepres.mod sepres_prog.mod
+expect_separate_auto CDefProg 8 CDef cdef.def cdef_prog.mod
 
 # --- M2 process exit codes: 0 = ok, 1 = compile errors, 2 = usage/IO ---
 check_exit() {

+ 8 - 0
compiler/tests/cdef.def

@@ -0,0 +1,8 @@
+DEFINITION MODULE CDef;
+(* Definition-only class: the separate build must still emit its vtable. *)
+TYPE
+  CLASS Shape;
+    x : INTEGER;
+    VIRTUAL PROCEDURE Area () : INTEGER;
+  END Shape;
+END CDef.

+ 8 - 0
compiler/tests/cdef_prog.mod

@@ -0,0 +1,8 @@
+MODULE CDefProg;
+IMPORT CDef;
+VAR ExitCode : INTEGER;
+VAR s : CDef.Shape;
+BEGIN
+  s.x := 8;
+  ExitCode := s.x
+END CDefProg.

+ 4 - 4
docs/STATUS.md

@@ -4,7 +4,7 @@ This answers "where are we now?".
 
 ## Numbers
 
-- Suite **304/304** (`compiler/run_tests.sh`).
+- Suite **305/305** (`compiler/run_tests.sh`).
 - Self-hosting **FIXPOINT OK** — stage2 and stage3 images are
   byte-identical (3,806,078 bytes).
 - Corpus **519 / 612** sources compile with 0 errors, **0 crashes**
@@ -14,7 +14,7 @@ This answers "where are we now?".
   imports of gm2-only modules.  Open-array corpus cases `unbounded`,
   `unbounded2`, `unbounded3`, `unbounded4` and `array9` now compile;
   `unbounded`/`unbounded2`/`unbounded3`/`unbounded4` also run.
-- Branch **`master`**, feature state **`v3-separate-fixpoint`**; tree
+- Branch **`master`**, feature state **`v3-separate-abi-vtables`**; tree
   clean.
 
 ## Architecture (current)
@@ -202,8 +202,8 @@ declarations still work (`t_fwdproc`, `t_fwdok`).
    per-unit objects (libraries have no `main`), with stable module/class
    symbols, `export data`, and dependency-ordered init calls.  `M2make
    --separate` builds stale objects incrementally and links them.  Core
-   shipped; generic-instance objects and content-based ABI markers
-   remain.  Per-object self-hosting now has its own green gate.  See
+   shipped; generic-instance objects remain.  Per-object self-hosting
+   now has its own green gate.  See
    `docs/separate-compilation.md`;
    summary: `summary_session-2026-10-24-separate-compilation.md`.
 5. **Library folders** — `stdlib/` is retired; libraries live in

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

@@ -2,7 +2,7 @@
 
 **Date:** 2026-10-24 · **V3 state:** open arrays, dialect library
 folders and separate compilation with `M2 -I`/incremental objects
-(suite 304/304, self-hosting fixpoint OK 3,806,078 bytes, corpus
+(suite 305/305, self-hosting fixpoint OK 3,806,078 bytes, corpus
 519/612, 0 crashes).
 
 **Scope and sources.**
@@ -118,7 +118,7 @@ change individual cells.
 | Local modules | ✅ | ✅ | ❌ removed | ✅ | ✅ |
 | Separate compilation | ✅ | ✅ (object files/archives) | ✅ compilation units | ✅ | ✅ `M2 -c -I` per-unit objects + `M2make --separate`; one-shot remains default |
 | Stable cross-object symbols / init order | n/a | ✅ | ✅ | ✅ | ✅ `<Module>__proc`, module-qualified pools/vtables, dependency-ordered `<Module>_init` |
-| Incremental object rebuild / ABI marker | n/a | ✅ | ✅ | ✅ | 🔄 stale mtime rebuilds, compiler/shim ABI/relink triggers; no content-based ABI marker |
+| Incremental object rebuild / ABI marker | n/a | ✅ | ✅ | ✅ | ✅ stale mtime rebuilds, content ABI/flags stamp, compiler/shim relink triggers |
 | Generics | ❌ | ✅ ISO 10514-2 | ❌ external templates (`GENLIB`/`BLUEPRINT`) | ❌ | ✅ ISO 10514-2 refinement |
 | Object orientation | ❌ | ❌ | ❌ (extensible records only) | ✅ Clarion-form `CLASS`, `VIRTUAL`, inheritance | 🔄 Clarion-form `CLASS` (single inheritance, virtual dispatch), not ISO 10514-3 |
 | PIM coroutines (`PROCESS`, `NEWPROCESS`, `TRANSFER`, `IOTRANSFER`) | ✅ | ✅ | 🔄 `COROUTINE` type/`YIELD` (R10 phase II) | ✅ PIM-style | ✅ (`IOTRANSFER` degrades to a transfer) |
@@ -204,9 +204,9 @@ change individual cells.
   `<Module>__<Class>__vt`.  `M2 -I` resolves interfaces transitively;
   `M2make --separate` rebuilds stale objects and relinks on source/
   interface/compiler/shim changes.  The one-shot mode remains the
-  default for the suite/corpus.  Still deferred: content-based ABI
-  markers, separate generic-instance objects and a green per-object
-  self-host fixpoint.
+  default for the suite/corpus.  Still deferred: separate
+  generic-instance objects; per-object self-hosting and definition-only
+  class vtables are covered.
 - **OO** is the Clarion-form `CLASS` dialect (single inheritance,
   virtual dispatch), not ISO 10514-3.
 

+ 4 - 3
docs/features.md

@@ -1,4 +1,4 @@
-# m2compiler-V3 — feature status (2026-10-24; suite 304/304 green)
+# m2compiler-V3 — feature status (2026-10-24; suite 305/305 green)
 
 Pipeline: Coco/R `M2.atg` (1748 lines, 73 productions) → `gm2`-built
 `M2` → QBE `.ssa` → `qbe` → `cc` → run. `SymTab.mod` 1623 lines,
@@ -177,8 +177,9 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
 - ✅ Procedure-type variables / indirect calls (`t_proctype`); ✅
   composite function results (record/array/set/class, sret) and chained
   post-call method calls (`F().M()`); 🔄 per-file objects and `M2 -I`
-  scanning exist; content-based ABI markers and generic-instance objects
-  are deferred; import cycles are rejected.
+  scanning, content ABI/flags stamps and definition-only class objects
+  exist; separate generic-instance objects are deferred; import cycles
+  are rejected.
 - 🔄 ISO exceptions: **M4** — `EXCEPT`/`RETRY`, handler stack,
   `SYSTEM.THROW`, `EXCEPTIONS`, `M2EXCEPTION`, signal mapping,
   `RETURN`/nested handlers, catchable index/range/CASE traps, and

+ 5 - 7
docs/plan-separate-compilation.md

@@ -5,7 +5,7 @@
 one-shot mode remains the default for the suite/corpus; this document
 keeps the design record and the deferred phases.
 
-**Baseline now:** suite 304/304, fixpoint OK 3,806,078 bytes, corpus
+**Baseline now:** suite 305/305, fixpoint OK 3,806,078 bytes, corpus
 519/612, 0 crashes.
 
 ---
@@ -154,12 +154,10 @@ cc Prog.o Lib.o runtime/syslib/shim.o -lm -o Prog
 3. **Init order + program/main split.**  DONE: library objects have no
    main, every object exports `<mod>_init`, and the program object calls
    imported inits in dependency order.
-4. **Generics and classes.**  PARTIAL: class vtable/method symbols are
-   stable across objects; generic instances are still client-local.
-5. **M2make incremental builds.**  DONE at mtime level: stale objects
-   are rebuilt, the compiler binary is an ABI trigger, and relinking
-   follows object/shim changes.  Content-based ABI markers are still
-   deferred.
+4. **Generics and classes.**  Classes are done for the normal and
+   definition-only cases; generic instances are still client-local.
+5. **M2make incremental builds.**  DONE: mtime stale checks, compiler
+   binary/shim relink triggers, and a content ABI/flags stamp.
 6. **Self-host with separate compilation.**  DONE:
    `bootstrap/fixpoint-separate.sh` builds stage2/stage3 as objects and
    compares all per-object `.ssa`; the compiler uses

+ 6 - 5
docs/separate-compilation.md

@@ -67,15 +67,16 @@ The implementation is split across:
 
 ## Limits / next phases
 
-- **Incremental checks are mtime-based.**  Sources/interfaces, the
-  compiler binary and the shim trigger rebuilds/relinks; there is no
-  content-based ABI/version marker or flag fingerprint.
+- **Build freshness** uses mtimes plus a small content ABI/flags stamp
+  (`gen_ssa/.m2abi`).  The stamp covers the ABI version and `--m2flag`
+  settings; per-object binary metadata is not implemented.
 - **Generics** still need the generic implementation source at the
   client; separate generic instances are not emitted as their own
   objects yet.
 - **Classes** have stable vtable/method symbols for normal imported
-  class use; a class that exists only in a `.def` (no implementation
-  object) must still have its vtable materialised by a user object.
+  class use.  M2make now also builds a definition-only object for a
+  module with no `.mod`, so a class declared only in a `.def` gets its
+  (zero-method) vtable object.
 - **Per-object self-hosting is green:** `bootstrap/fixpoint-separate.sh`
   builds the compiler as objects with stage1, links stage2, rebuilds with
   stage2, links stage3, and compares all per-object `.ssa`.  Compiler

+ 12 - 1
docs/summary_session-2026-10-24-separate-compilation.md

@@ -4,7 +4,7 @@ V3 now builds per-unit objects.  The one-shot mode remains the default
 for the suite/corpus.
 
 **Branch:** `master`.  **Feature state:** `v3-separate-compilation`.
-**Gates:** suite **304/304**, fixpoint **OK (3,806,078 bytes)**,
+**Gates:** suite **305/305**, fixpoint **OK (3,806,078 bytes)**,
 corpus **519/612 compile-OK, 0 crashes**.
 
 ---
@@ -79,6 +79,17 @@ Manual M2make cross-object class check: `CLib.def/.mod` + `CProg.mod`
   builds stage2/stage3 as objects and compares all per-object `.ssa`
   (11 objects).
 
+## Follow-up — ABI stamp and definition-only objects (2026-10-24)
+
+- `M2make --separate` now records `gen_ssa/.m2abi` containing the ABI
+  version and `--m2flag` settings; a missing/changed stamp rebuilds all
+  objects.
+- Modules with a `.def` but no `.mod` are compiled to
+  `gen_ssa/<Module>.o`, so definition-only data and class vtables link.
+- Added `CDefProg` (class-only `.def` vtable) and M2make checks for the
+  ABI stamp and definition-only object.
+- Suite is now 305/305.
+
 ## Commits and tags
 
 | commit | tag | work |

+ 58 - 6
tools/m2make/M2make.mod

@@ -15,6 +15,8 @@ MODULE M2make;
 IMPORT FileIO, M2makeOS;
 
 CONST
+  AbiVersion = "v3-separate-abi-1";
+  AbiStampPath = "gen_ssa/.m2abi";
   MaxMods = 256;
   MaxDeps = 64;
   MaxDirs = 16;
@@ -866,24 +868,67 @@ BEGIN
   RETURN FALSE
 END SepObjStale;
 
+PROCEDURE AbiStampOK (): BOOLEAN;
+(* Content check for the object ABI/flags stamp.  This is deliberately
+   a small build-system marker, not a per-object binary format. *)
+VAR h: M2makeOS.RdFile; line: Line; want: Path; ok: BOOLEAN;
+BEGIN
+  h := M2makeOS.OpenRead(AbiStampPath);
+  IF h = NIL THEN RETURN FALSE END;
+  ok := M2makeOS.ReadLine(h, line, HIGH(line) + 1);
+  M2makeOS.CloseRead(h);
+  IF NOT ok THEN RETURN FALSE END;
+  Copy(AbiVersion, want);
+  IF Len(m2Flags) > 0 THEN
+    Cat(want, " ", tmpA);
+    Cat(tmpA, m2Flags, want)
+  END;
+  RETURN Cmp(line, want) = 0
+END AbiStampOK;
+
+PROCEDURE WriteAbiStamp;
+VAR cmd: Cmd;
+BEGIN
+  Zero(cmd);
+  AppendSeg(cmd, "mkdir -p gen_ssa && printf '%s\n' '");
+  AppendSeg(cmd, AbiVersion);
+  IF Len(m2Flags) > 0 THEN
+    AppendSeg(cmd, " ");
+    AppendSeg(cmd, m2Flags)
+  END;
+  AppendSeg(cmd, "' > ");
+  AppendSeg(cmd, AbiStampPath);
+  IF M2makeOS.ExecCmd(cmd) # 0 THEN
+    Out("M2make: could not write ABI stamp"); OutLn;
+    M2makeOS.ExitNow(1)
+  END
+END WriteAbiStamp;
+
 PROCEDURE BuildM2Separate(out: ARRAY OF CHAR);
 (* Compile stale modules to their own QBE objects, then link.  The
    program module is last in the topological order and is the only
    object with main; every module object exports <mod>_init. *)
 VAR cmd: Cmd; k, m: CARDINAL; rc: INTEGER;
     obj, src, ssafile, sfile, flags: Path;
-    outT: LONGINT; compiledAny, linkNeeded: BOOLEAN;
+    outT: LONGINT; compiledAny, linkNeeded, abiOK: BOOLEAN;
 BEGIN
   Zero(cmd);
+  abiOK := AbiStampOK();
   compiledAny := FALSE;
   k := 0;
   WHILE k < nOrder DO
     m := order[k];
     GetSrc(m, src);
-    IF Len(src) > 0 THEN
+    GetDef(m, tmpQ);
+    IF (Len(src) > 0) OR ((NOT modIsMain[m]) AND (Len(tmpQ) > 0)) THEN
+      IF Len(src) = 0 THEN
+        (* A definition-only module still contributes exported data and
+           class vtables to the link. *)
+        Copy(tmpQ, src)
+      END;
       IF modIsMain[m] THEN Copy(progName, tmpN) ELSE GetName(m, tmpN) END;
       Zero(obj); Cat("gen_ssa/", tmpN, tmpA); Cat(tmpA, ".o", obj);
-      IF SepObjStale(m, obj) THEN
+      IF (NOT abiOK) OR SepObjStale(m, obj) THEN
         CpyPath(ssafile, "gen_ssa/"); Cat(ssafile, tmpN, tmpA); Cat(tmpA, ".ssa", ssafile);
         CpyPath(sfile, "gen_ssa/"); Cat(sfile, tmpN, tmpA); Cat(tmpA, ".s", sfile);
         Zero(tmpCmd);
@@ -959,7 +1004,8 @@ BEGIN
   WHILE k < nOrder DO
     m := order[k];
     GetSrc(m, src);
-    IF Len(src) > 0 THEN
+    GetDef(m, tmpQ);
+    IF (Len(src) > 0) OR ((NOT modIsMain[m]) AND (Len(tmpQ) > 0)) THEN
       IF modIsMain[m] THEN Copy(progName, tmpN) ELSE GetName(m, tmpN) END;
       Zero(obj); Cat("gen_ssa/", tmpN, tmpA); Cat(tmpA, ".o", obj);
       AppendTok(tmpCmd, obj)
@@ -980,7 +1026,8 @@ BEGIN
     Out("M2make: separate build failed");
     OutLn;
     M2makeOS.ExitNow(1)
-  END
+  END;
+  WriteAbiStamp
 END BuildM2Separate;
 
 PROCEDURE CpyPath(VAR dst: Path; src: ARRAY OF CHAR);
@@ -1288,7 +1335,12 @@ BEGIN
   IF NOT outGiven THEN Copy(progName, outName) END;
   TopoSort;
   IF verbose OR dryRun THEN PrintOrder END;
-  rebuild := NeedBuild(outName, missing);
+  IF separate THEN
+    (* BuildM2Separate owns per-object freshness and the ABI stamp. *)
+    rebuild := TRUE
+  ELSE
+    rebuild := NeedBuild(outName, missing)
+  END;
   IF NOT rebuild THEN
     Out("M2make: ");
     Out(outName);

+ 2 - 2
tools/m2make/README.md

@@ -33,8 +33,8 @@ M2make [-n] [-v] [-c m2|gm2] [-I dir] [-o out]
   `M2 -c -I ...`, then link the objects with `cc` and the runtime shim.
   The program object is the only one with `main`; library objects export
   `<Module>_init`.  Source/interface mtimes, a newer compiler binary and
-  the shim drive rebuild/relink; there is no content-based ABI marker
-  yet.
+  the shim drive rebuild/relink, and `gen_ssa/.m2abi` records the
+  compiler ABI/flags content.
 
 `m2` mode (default, one-shot) runs one session — `<m2bin> <ordered .def/.mod files>`
 then `qbe` then `cc <image>.s <shim> [<extra>] -o <out> -lm` — and

+ 8 - 0
tools/m2make/tests/CDef.def

@@ -0,0 +1,8 @@
+DEFINITION MODULE CDef;
+(* Definition-only class for separate-object M2make tests. *)
+TYPE
+  CLASS Shape;
+    x : INTEGER;
+    VIRTUAL PROCEDURE Area () : INTEGER;
+  END Shape;
+END CDef.

+ 8 - 0
tools/m2make/tests/CProg.mod

@@ -0,0 +1,8 @@
+MODULE CProg;
+IMPORT CDef;
+VAR ExitCode : INTEGER;
+VAR s : CDef.Shape;
+BEGIN
+  s.x := 8;
+  ExitCode := s.x
+END CProg.

+ 21 - 1
tools/m2make/tests/run_tests.sh

@@ -131,6 +131,26 @@ case "$out" in
 esac
 touch tests/Calc.def tests/Calc.mod tests/Main.mod
 
+# --- definition-only class object ---
+rm -rf gen_ssa CDefProg
+if $V3M --separate -c m2 --m2bin ../../compiler/M2 --shim ../../runtime/syslib/shim.c \
+     -I . -o CDefProg tests/CProg.mod > /tmp/m2make_t.log 2>&1; then
+  ./CDefProg > /dev/null 2>&1; rc=$?
+  if [ "$rc" = "8" ]; then ok "separate-mode definition-only class object"
+  else no "separate-mode definition-only class object (rc=$rc)"; fi
+else
+  no "separate-mode definition-only class object (build)"
+fi
+
+# --- content ABI stamp ---
+rm -f gen_ssa/.m2abi
+out=$($V3M --separate -c m2 --m2bin ../../compiler/M2 --shim ../../runtime/syslib/shim.c \
+  -I . -o CDefProg tests/CProg.mod 2>&1)
+case "$out" in
+  *"tests/CProg.mod"*) ok "separate-mode ABI stamp forces rebuild" ;;
+  *) no "separate-mode ABI stamp: $out" ;;
+esac
+
 # --- cross-check: gm2-built M2make drives an m2 build too ---
 rm -rf gen_ssa MainX m2make.log
 if $GM2 -c m2 --m2bin ../../compiler/M2 --shim ../../runtime/syslib/shim.c \
@@ -141,7 +161,7 @@ else
   no "gm2-built M2make drives m2 build"
 fi
 
-rm -f Main MainX Main2 SepMain *.o
+rm -f Main MainX Main2 SepMain CDefProg *.o
 rm -rf gen_ssa
 rm -f m2make.log