Explorar o código

language: raise functionException on missing RETURN

EndFunc now traps with language code 4 when a result function reaches its end instead of silently returning 0. Add t_except_funcret (caught by M2EXCEPTION) and t_trap_funcret (unhandled rc=134). Gates: suite 278/278, fixpoint OK 4,156,434 bytes, corpus 512/612 compile-OK.
Eric Streit hai 1 día
pai
achega
4238f677c3

+ 2 - 2
README.md

@@ -47,8 +47,8 @@ docs/              STATUS.md, plan.md, plan-*.md, analysis_*.md, summary_*.md
 
 ## Status (2026-10-23)
 
-- Suite **276/276**; self-hosting **fixpoint OK** (byte-identical
-  stage2/stage3, 4,143,847 bytes); corpus **512/612 compile-OK,
+- Suite **278/278**; self-hosting **fixpoint OK** (byte-identical
+  stage2/stage3, 4,156,434 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/`).
   `lazyunique` now runs; the remaining `except*` corpus files still hit

+ 6 - 0
compiler/run_tests.sh

@@ -259,6 +259,7 @@ expect_trap t_trap_setspan.mod
 expect_run t_strempty.mod 42
 expect_trap t_trap_chr8.mod
 expect_trap t_except_unhandled.mod
+expect_trap t_trap_funcret.mod
 expect_fail t_defunit.mod ""
 expect_fail t_implunit.mod ""
 expect_run t_nested.mod 42
@@ -340,6 +341,11 @@ expect_run_files TExceptCase 42 \
   ../runtime/syslib/SysExceptions.mod \
   ../stdlib/m2exception.def ../stdlib/m2exception.mod \
   t_except_case.mod
+expect_run_files TExceptFuncret 42 \
+  ../runtime/syslib/SysExceptions.def \
+  ../runtime/syslib/SysExceptions.mod \
+  ../stdlib/m2exception.def ../stdlib/m2exception.mod \
+  t_except_funcret.mod
 expect_run_files TAst 42 \
   ../runtime/syslib/SysShim.def ../runtime/syslib/SysShim.mod \
   ../runtime/syslib/FileIO.def ../runtime/syslib/FileIO.mod \

+ 5 - 1
compiler/src/QbeGen.mod

@@ -1238,7 +1238,11 @@ PROCEDURE EndFunc (resT: INTEGER);
   BEGIN
     rc := ResClass(resT);
     Revive;
-    IF rc = "d" THEN WL("  ret d_0.0")
+    IF resT # SymTab.InvalidType THEN
+      (* A function that reaches its end without RETURN raises the
+         language functionException instead of silently returning 0. *)
+      TrapCode(4)
+    ELSIF rc = "d" THEN WL("  ret d_0.0")
     ELSIF rc = "l" THEN WL("  ret 0")
     ELSE WL("  ret 0")
     END;

+ 30 - 0
compiler/tests/t_except_funcret.mod

@@ -0,0 +1,30 @@
+MODULE TExceptFuncret;
+(* functionException: a function that falls off its end without RETURN
+   raises a catchable language exception. *)
+FROM M2EXCEPTION IMPORT
+  M2Exception, IsM2Exception, functionException;
+
+VAR ExitCode : INTEGER;
+
+PROCEDURE Missing : INTEGER;
+VAR i : INTEGER;
+BEGIN
+  i := 1;
+  IF i = 0 THEN RETURN 1 END
+END Missing;
+
+PROCEDURE Caller;
+BEGIN
+  ExitCode := Missing()
+EXCEPT
+  IF IsM2Exception() AND (M2Exception() = functionException) THEN
+    ExitCode := 42
+  ELSE
+    ExitCode := 1
+  END
+END Caller;
+
+BEGIN
+  ExitCode := 0;
+  Caller
+END TExceptFuncret.

+ 14 - 0
compiler/tests/t_trap_funcret.mod

@@ -0,0 +1,14 @@
+MODULE TTrapFuncret;
+(* Unhandled functionException is still a termination event (rc 134). *)
+VAR N : INTEGER;
+
+PROCEDURE Missing : INTEGER;
+VAR i : INTEGER;
+BEGIN
+  i := 1;
+  IF i = 0 THEN RETURN 1 END
+END Missing;
+
+BEGIN
+  N := Missing
+END TTrapFuncret.

+ 5 - 5
docs/STATUS.md

@@ -4,9 +4,9 @@ This answers "where are we now?".
 
 ## Numbers
 
-- Suite **276/276** (`compiler/run_tests.sh`).
+- Suite **278/278** (`compiler/run_tests.sh`).
 - Self-hosting **FIXPOINT OK** — stage2 and stage3 images are
-  byte-identical (4,143,847 bytes).
+  byte-identical (4,156,434 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
@@ -174,8 +174,8 @@ declarations still work (`t_fwdproc`, `t_fwdok`).
    `INC`/`DEC`, leading CASE `|`, `__FUNCTION__`).  Corpus is **511/612**
    at the stop point.  The remaining `LANG` entries are mostly:
    **declaration order** cases V3 rejects by policy (`docs/gaps/`); ISO
-   exceptions (M4 done; `functionException`/`FINALLY`/coroutine/
-   protection follow-ups — see `docs/design-exceptions.md`);
+   exceptions (M4 + `functionException`; `FINALLY`/coroutine/protection
+   follow-ups — see `docs/design-exceptions.md`);
    multi-dimensional
    open arrays (representation, see `docs/handoff-2026-10-23-next.md`);
    and gm2-only extensions.  See `docs/analysis_gaps.md`.
@@ -219,7 +219,7 @@ declarations still work (`t_fwdproc`, `t_fwdok`).
 ```sh
 cd compiler
 ./build.sh          # M2.atg -> scanner/parser/driver -> ./M2
-./run_tests.sh      # 276/276
+./run_tests.sh      # 278/278
 cd ..
 ./bootstrap/fixpoint.sh   # FIXPOINT OK (byte-identical stage2/stage3)
 ```

+ 4 - 4
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-ptrchain` (suite
-276/276, self-hosting fixpoint OK 4,143,847 bytes, corpus 512/612,
+278/278, self-hosting fixpoint OK 4,156,434 bytes, corpus 512/612,
 0 crashes).
 
 **Scope and sources.**
@@ -103,7 +103,7 @@ change individual cells.
 | Runtime subrange range checks | ⚠️ error per language; checks implementation-defined | ✅ | ✅ (R10 safety) | 🔄 | ✅ assignment/value actual/`RETURN`/`INC`/`DEC`; catchable `rangeException`; `FOR` final increment exempt |
 | `SHIFT`/`ROTATE` | ⚠️ implementation SYSTEM extension | ✅ | ❌ | ✅ (`<<`/`>>` operators) | ✅ |
 | `TBITSIZE` | ❌ | ✅ | ❌ | ❌ | ❌ |
-| ISO exceptions (`EXCEPT`, `RETRY`, `EXCEPTIONS`) | ❌ | ✅ | ❌ (not in the base R10 grammar) | ❌ | 🔄 `EXCEPT`/`RETRY`/`SYSTEM.THROW`, `EXCEPTIONS`/`M2EXCEPTION`, index/range/CASE traps; `functionException`/`FINALLY` pending |
+| ISO exceptions (`EXCEPT`, `RETRY`, `EXCEPTIONS`) | ❌ | ✅ | ❌ (not in the base R10 grammar) | ❌ | 🔄 `EXCEPT`/`RETRY`/`SYSTEM.THROW`, `EXCEPTIONS`/`M2EXCEPTION`, index/range/CASE/function traps; `FINALLY` pending |
 | Module finalization (`FINALLY`) | ❌ | ✅ | ❌ | ❌ | ❌ |
 | `__FILE__` / `__LINE__` / `__FUNCTION__` | ❌ | ✅ | 🔄 COMPILER pseudo-module introspection | ❌ | ✅ / ✅ / ✅ |
 | `__BUILTIN__` / `__ATTRIBUTE__` pragmas | ❌ | ✅ | ✅ language pragmas `<* … *>` | ⚠️ compiler directives | ✅ (parsed, ignored) |
@@ -153,8 +153,8 @@ change individual cells.
   scalar→`ARRAY OF BYTE`, same-size `VAR`, bare proc values, variant
   extensions, set `CASE` labels, libc prototypes).  ISO exceptions are
   **mostly complete** (`EXCEPT`/`RETRY`/`SYSTEM.THROW`, `EXCEPTIONS`/
-  `M2EXCEPTION`, index/range/CASE traps) with `functionException` and
-  `FINALLY` pending.  Its hard gaps are **complex arithmetic,
+  `M2EXCEPTION`, index/range/CASE/function traps) with `FINALLY`
+  pending.  Its hard gaps are **complex arithmetic,
   finalization/protection, multi-dimensional open arrays, ISO OO** and
   parts of the **word model** (`SHORTREAL`, `REAL` width), plus a
   **65536-bit `SET` span limit** and a **32-bit `PACKEDSET` limit**.

+ 17 - 21
docs/design-exceptions.md

@@ -1,12 +1,12 @@
 # Design — ISO exceptions for V3
 
-**Status: M1–M5 complete (2026-10-23), tag `v3-exceptions`.**  `EXCEPT`/
-`RETRY`, the handler stack, `SYSTEM.THROW`, `EXCEPTIONS`, `M2EXCEPTION`,
-`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 **276/276**, fixpoint OK **4,143,847 bytes**, corpus **512/612**
-compile-OK, 0 crashes.
+**Status: M1–M5 complete, plus the `functionException` follow-up
+(2026-10-23), tag `v3-exceptions`.**  `EXCEPT`/`RETRY`, the handler
+stack, `SYSTEM.THROW`, `EXCEPTIONS`, `M2EXCEPTION`, `RETURN` unwinding,
+signal mapping, and compiler-emitted index/range/CASE/function traps are
+implemented.  `FINALLY`, coroutine/protection exceptions and complex
+arithmetic remain follow-ups.  Gates: suite **278/278**, fixpoint OK
+**4,156,434 bytes**, corpus **512/612** compile-OK, 0 crashes.
 
 The handoff calls this the largest remaining language feature.  This
 document fixes the syntax, AST, runtime model, code-generation contract,
@@ -533,9 +533,9 @@ through the existing external-procedure path.
 | --- | --- |
 | `indexException` | array index check; `FPE_FLTSUB` |
 | `rangeException` | subrange assignment/actual/`RETURN`/`INC`/`DEC`; set element; set span |
-| `caseSelectException` | `CASE` no match, no `ELSE` (add explicit trap) |
+| `caseSelectException` | `CASE` no match, no `ELSE` |
 | `invalidLocation` | `SIGSEGV`/`SIGBUS`, NIL/uninitialised pointer |
-| `functionException` | function body ends without `RETURN` (follow-up; current V3 returns 0) |
+| `functionException` | function body reaches its end without `RETURN` |
 | `wholeValueException` | `SIGFPE` integer overflow |
 | `wholeDivException` | `SIGFPE` integer divide-by-zero; optional negative divisor check |
 | `realValueException` | `SIGFPE` real overflow/underflow/invalid |
@@ -577,11 +577,13 @@ Implemented in M1–M3 (`compiler/run_tests.sh`):
   `indexException`/`rangeException`.
 - `t_except_case`: no matching `CASE` label gives
   `caseSelectException`.
+- `t_except_funcret`: a result function falling off its end raises
+  `functionException`.
+- `t_trap_funcret`: unhandled `functionException` aborts with rc 134.
 - `t_except_unhandled`: expect_trap (rc=134).
 - `t_retry_bad`: `RETRY` outside `EXCEPT` rejected with `237`.
 
-`functionException` and real/complex/protection/coroutine detection are
-still follow-ups.
+Real/complex/protection/coroutine detection are still follow-ups.
 
 Use `expect_run_files` with:
 
@@ -613,8 +615,8 @@ cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
 tools/v3-corpus/corpus.sh
 ```
 
-Current M4 gates: suite **276/276**, fixpoint OK
-**4,143,847 bytes**, corpus **512/612 compile-OK**, 0 crashes.
+Current gates: suite **278/278**, fixpoint OK
+**4,156,434 bytes**, corpus **512/612 compile-OK**, 0 crashes.
 Existing trap tests must stay rc=134.
 
 ---
@@ -645,8 +647,8 @@ Existing trap tests must stay rc=134.
      `INDEX.md`; tag `v3-exceptions` at the M4 commit and a session-end
      tag at the docs commit.
 
-`functionException`, real/complex/protection/coroutine detection can
-follow separately; they should not block M1–M4.
+Real/complex/protection/coroutine detection can follow separately;
+they should not block M1–M4.
 
 ---
 
@@ -669,9 +671,6 @@ follow separately; they should not block M1–M4.
   needs its own design.
 - **Coroutines:** the state/handler stack is global, not per-coroutine.
   `coException` remains undetected.
-- **Function result:** current V3 returns 0 when a function falls off
-  the end; raising `functionException` is a follow-up because it can
-  change existing behaviour.
 - **Separate compilation:** the runtime ABI (`m2_exc_*`) is stable, but
   per-unit exception tables/handler metadata would be needed once
   objects are separate.  Not part of this milestone.
@@ -689,6 +688,3 @@ follow separately; they should not block M1–M4.
    initialization/finalization).  Proposed: yes.
 3. Decide the exact `THROW(i)` mapping when `i` is outside
    `0 .. 14` (proposed: `exException`).
-4. Confirm whether `functionException` should be raised now for
-   functions with a handler whose body/handler falls through without
-   `RETURN`; proposed: follow-up, not M1.

+ 2 - 2
docs/features.md

@@ -158,8 +158,8 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
   cycles.
 - 🔄 ISO exceptions: **M4** — `EXCEPT`/`RETRY`, handler stack,
   `SYSTEM.THROW`, `EXCEPTIONS`, `M2EXCEPTION`, signal mapping,
-  `RETURN`/nested handlers, and catchable index/range/CASE traps.
-  Remaining follow-ups: `functionException`, `FINALLY`, coroutine/
+  `RETURN`/nested handlers, catchable index/range/CASE traps, and
+  `functionException`.  Remaining follow-ups: `FINALLY`, coroutine/
   protection exceptions.  Design in `docs/design-exceptions.md`.
 
 ## Backend (QBE)

+ 3 - 2
docs/gaps/README.md

@@ -97,8 +97,9 @@ gap22_file_line.mod             invalid Fact            (__LINE__)
 - **ISO exceptions — M4 done, follow-ups remain** (2026-10-23):
   `EXCEPT`/`RETRY`, handler stack + saved state, `SYSTEM.THROW`,
   `EXCEPTIONS`, `M2EXCEPTION`, `RETURN` unwinding, nested handlers,
-  signal mapping, and catchable index/range/CASE traps work.  Remaining:
-  `functionException`, `FINALLY`, coroutine/protection exceptions.
+  signal mapping, catchable index/range/CASE traps, and
+  `functionException` work.  Remaining: `FINALLY`, coroutine/protection
+  exceptions.
   `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

+ 5 - 6
docs/handoff-2026-10-23-next.md

@@ -1,7 +1,7 @@
 # Next session — plan (from 2026-10-23)
 
-**Branch:** `master`.  **Feature state:** `v3-exceptions` + `v3-ptrchain`.
-**Gates:** suite **276/276**, fixpoint **OK (4,143,847 bytes)**,
+**Branch:** `master`.  **Feature state:** `v3-exceptions` + `v3-ptrchain` + `v3-function-exception`.
+**Gates:** suite **278/278**, fixpoint **OK (4,156,434 bytes)**,
 corpus **512/612, 0 crashes**.
 
 Read first: `docs/STATUS.md`, `docs/features.md`,
@@ -59,8 +59,7 @@ abort (rc 134).  Tests: `t_except_trap`, `t_except_case`,
 **M5 finalization — done (2026-10-23).**  Session summary
 `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.
+Optional follow-ups: `FINALLY`, coroutine/protection exceptions.
 
 **Corpus reality:** `lazyunique` now compiles and runs (`rc=0`,
 unique output `0, 2, 1`) after the pointer-chain follow-up; the corpus
@@ -109,7 +108,7 @@ this feature.
 ## Gates for every step
 
 ```sh
-cd compiler && ./build.sh && ./run_tests.sh          # 276/276
+cd compiler && ./build.sh && ./run_tests.sh          # 278/278
 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
 tools/v3-corpus/corpus.sh                            # 512 OK, 0 crash
 ```
@@ -122,7 +121,7 @@ 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          # 276/276
+cd compiler && ./build.sh && ./run_tests.sh          # 278/278
 cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
 tools/v3-corpus/corpus.sh                            # 512 OK, 0 crash
 ```

+ 13 - 7
docs/summary_session-2026-10-23-exceptions.md

@@ -4,9 +4,9 @@ Follows the same-day range-check/PACKEDSET session
 (`summary_session-2026-10-23.md`) and the earlier design commit
 `2a30d8c`.
 
-**Branch:** `master`.  **Feature state:** `v3-exceptions` + `v3-ptrchain`.
-**Gates at the end:** suite **276/276**, fixpoint **OK
-(4,143,847 bytes)**, corpus **512/612 compile-OK, 0 crashes**.
+**Branch:** `master`.  **Feature state:** `v3-exceptions` + `v3-ptrchain` + `v3-function-exception`.
+**Gates at the end:** suite **278/278**, fixpoint **OK
+(4,156,434 bytes)**, corpus **512/612 compile-OK, 0 crashes**.
 
 ---
 
@@ -14,8 +14,8 @@ Follows the same-day range-check/PACKEDSET session
 
 | gate | at design start | at end |
 | --- | --- | --- |
-| suite (`compiler/run_tests.sh`) | 263/263 | **276/276** |
-| fixpoint (`bootstrap/fixpoint.sh`) | 4,075,853 B | **4,143,847 B** |
+| suite (`compiler/run_tests.sh`) | 263/263 | **278/278** |
+| fixpoint (`bootstrap/fixpoint.sh`) | 4,075,853 B | **4,156,434 B** |
 | corpus (`tools/v3-corpus/corpus.sh`) | 511/612 | **512/612 compile-OK, 0 crashes** |
 
 The corpus is compile-only.  After the follow-up pointer-chain fix,
@@ -101,6 +101,7 @@ because `ip` is declared after the procedures, like `onebyte*`.
 | `02b3763` | `v3-exceptions` | M4 catchable index/range/CASE traps |
 | `96a3080` | `v3-session-2026-10-23-exceptions-end` | M5 summary/docs |
 | `e804e57` | `v3-ptrchain` | self-referential record layout / pointer chains |
+| functionException fix | `v3-function-exception` | result-function fall-through trap |
 
 The design commit is `2a30d8c`.
 
@@ -113,12 +114,17 @@ layout when `SetTarget` completes the alias.  Regression
 `t_ptrchain`; `lazyunique` now runs with rc 0.  This follow-up is
 tagged `v3-ptrchain` (`e804e57`).
 
+### Follow-up — functionException
+
+`QbeGen.EndFunc` now traps with language code 4 when a result function
+reaches its end without `RETURN`, instead of silently returning 0.
+`t_except_funcret` catches the exception and `t_trap_funcret` verifies
+the unhandled rc=134 path.  Tagged `v3-function-exception`.
+
 ---
 
 ## Remaining work
 
-- `functionException` for a function that falls off the end without
-  `RETURN`.
 - ISO `FINALLY` / module termination; coroutine (`coException`) and
   protection (`protException`) exceptions.
 - Complex-number exceptions (complex arithmetic is still a placeholder).