Преглед изворни кода

two-phase slice 5: statement AST (block, control flow, calls)

StatSeq builds NkBlock; assignment/call, IF/ELSIF/ELSE (incremental
NkIf chain), WHILE, REPEAT, LOOP/EXIT, FOR, RETURN and HALT build
statement nodes.  A module-level astStmt carries the result; a shared
AstCallNode helper builds statement calls.  Nodes are inert.

Deferred: CASE/WITH and builtin statements (NoNode), brace/set
literals, ResultComp, Lower.

Finding: a local ARRAY inside a PROCEDURE is miscompiled by V3
(pre-existing; repro docs/wip/local-array-bug.mod).  IfStat's local
arrays exposed it during self-hosting, so the IF chain is built with
scalars instead.

Gates: suite 178/178; fixpoint OK (stage2 == stage3, 3000988 bytes).

See docs/summary_two-phase-slice5.md.
Eric Streit пре 1 недеља
родитељ
комит
fb64345614
5 измењених фајлова са 3615 додато и 3323 уклоњено
  1. 139 42
      compiler/src/M2.atg
  2. 3376 3279
      compiler/src/M2.lst
  3. 8 2
      docs/plan-two-phase.md
  4. 74 0
      docs/summary_two-phase-slice5.md
  5. 18 0
      docs/wip/local-array-bug.mod

+ 139 - 42
compiler/src/M2.atg

@@ -73,6 +73,10 @@ VAR
   astNArgs: CARDINAL;
   astArgs: ARRAY [0 .. 31] OF AST.Node;
 
+  (* Statement-AST result slot (slice 5): the most recently parsed
+     statement.  StatSeq folds these into an NkBlock. *)
+  astStmt: AST.Node;
+
   (* Class of the method named by the last `obj.Method` designator
      (InvalidType when the callee is an ordinary procedure). Set by
      Design, consumed by the following ArgList. *)
@@ -140,6 +144,19 @@ PROCEDURE FwdVarFlush;
     nFvarRefs := 0
   END FwdVarFlush;
 
+PROCEDURE AstCallNode (callee: AST.Node) : AST.Node;
+(* Builds NkCall(callee, actuals...) from astArgs/astNArgs. *)
+  VAR n: AST.Node; j: CARDINAL;
+  BEGIN
+    n := AST.MakeNode(AST.NkCall);
+    AST.SetChild(n, 0, callee);
+    j := 0;
+    WHILE (j < astNArgs) AND (AST.NChild(n) < AST.MaxChild) DO
+      AST.SetChild(n, AST.NChild(n), astArgs[j]); INC(j)
+    END;
+    RETURN n
+  END AstCallNode;
+
 CHARACTERS
   eol      = CHR(13) .
   lf       = CHR(10) .
@@ -169,7 +186,7 @@ TOKENS
 
 PRODUCTIONS
   M2
-    = (. AST.Init; twoPhase := TRUE; astCur := AST.NoNode; .)
+    = (. AST.Init; twoPhase := TRUE; astCur := AST.NoNode; astStmt := AST.NoNode; .)
       Unit "." .
   (* Units: program modules compile fully; DEFINITION and
      IMPLEMENTATION modules parse + check now but lower in step 4
@@ -988,12 +1005,20 @@ PRODUCTIONS
       "END"
       GetIdent<m2>                      (. IF NOT SymTab.Equal(pn, m2) THEN
                                              SemError(202) END; .) .
-  StatSeq
-    = Statement { ";" [ Statement ] } .
+  StatSeq                               (. VAR astSeq: AST.Node; .)
+    =                                   (. astSeq := AST.MakeNode(AST.NkBlock); .)
+      Statement                         (. IF astStmt # AST.NoNode THEN
+                                             AST.SetChild(astSeq,
+                                               AST.NChild(astSeq), astStmt) END; .)
+      { ";" [ Statement                 (. IF astStmt # AST.NoNode THEN
+                                             AST.SetChild(astSeq,
+                                               AST.NChild(astSeq), astStmt) END; .) ] }
+                                        (. astStmt := astSeq; .) .
   (* Trailing/empty statements (`a; ; b`, Coco/R's generated `;;`)
      are accepted: the statement after ';' is optional. *)
   Statement                             (. VAR lx: QbeGen.QVal; .)
-    = AssOrCall
+    = (. astStmt := AST.NoNode; .)
+      ( AssOrCall
     | IfStat
     | WhileStat
     | RepeatStat
@@ -1009,7 +1034,8 @@ PRODUCTIONS
     | InclExclStat
     | "EXIT"                            (. IF QbeGen.TopLoop(lx) THEN
                                              QbeGen.Jmp(lx)
-                                           ELSE SemError(230) END; .) .
+                                           ELSE SemError(230) END;
+                                           astStmt := AST.MakeNode(AST.NkExit); .) ) .
   (* INCL(set, elem) / EXCL(set, elem): PIM set-element builtins. *)
   InclExclStat                          (. VAR at, et2: SymTab.TypeIndex;
                                              dk: INTEGER;
@@ -1148,8 +1174,8 @@ PRODUCTIONS
   WithStat                              (. VAR nW: CARDINAL; .)
     = "WITH"                            (. nW := 0; .)
       WithItem<nW> { "," WithItem<nW> }
-      "DO" [ StatSeq ] "END"
-                                        (. WHILE nW > 0 DO
+      "DO" [ StatSeq ] "END"              (. astStmt := AST.NoNode;
+                                           WHILE nW > 0 DO
                                              SymTab.PopScope;
                                              QbeGen.PopWith;
                                              DEC(nW)
@@ -1179,10 +1205,14 @@ PRODUCTIONS
                                              ct2, res0: SymTab.TypeIndex;
                                              q2, mg0: QbeGen.QVal;
                                              isR, conv, wconv: BOOLEAN;
-                                             called, sfx: BOOLEAN; .)
+                                             called, sfx: BOOLEAN;
+                                             astLhs: AST.Node; .)
     = Design<dt, dk, qd, qn, sfx>
+      (. astLhs := astCur; astNArgs := 0; .)
       ( ":="
-        Expr<et, qe>                    (. IF (dt # SymTab.InvalidType)
+        Expr<et, qe>                    (. astStmt := AST.MakeBin(
+                                             AST.NkAssign, 0, astLhs, astCur);
+                                           IF (dt # SymTab.InvalidType)
  AND (dk # SymTab.KindVar)
  AND (dk # SymTab.KindParam)
  AND (dk # SymTab.KindField) THEN
@@ -1291,11 +1321,13 @@ PRODUCTIONS
                                            END
                                          END; .)
       | ArgList<qn, dt, qd, FALSE, FALSE, methCls, ct2, q2, called>
+                                        (. astStmt := AstCallNode(astLhs); .)
       |                                 (* bare `P;`: proper parameterless
                                             procedure call; anything else
                                             here is 233 (was a bare syntax
                                             error before 4.2) *)
-                                        (. IF (dk = SymTab.KindProc)
+                                        (. astStmt := AstCallNode(astLhs);
+                                            IF (dk = SymTab.KindProc)
  AND NOT sfx THEN
                                              res0 := SymTab.ProcRes(qn);
                                              IF res0 #
@@ -1517,62 +1549,97 @@ PRODUCTIONS
   IfStat                                (. VAR t: SymTab.TypeIndex;
                                              q, lThen, lElse, lEnd:
                                                QbeGen.QVal;
-                                             hasElse: BOOLEAN; .)
-    = "IF"                              (. hasElse := FALSE; .)
-      Expr<t, q>                        (. IF NOT SymTab.BoolCheck(t) THEN
+                                             hasElse: BOOLEAN;
+                                             astCond, astIf, astLast,
+                                             astNode: AST.Node; .)
+    = "IF" (. hasElse := FALSE; .)
+      Expr<t, q> (. astCond := astCur; astStmt := AST.NoNode;
+                                           IF NOT SymTab.BoolCheck(t) THEN
                                              SemError(214) END;
                                            QbeGen.NewLabel(lThen);
                                            QbeGen.NewLabel(lElse);
                                            QbeGen.NewLabel(lEnd);
                                            QbeGen.Jnz(q, lThen, lElse);
                                            QbeGen.EmitLabel(lThen); .)
-      "THEN" [ StatSeq ]                (. QbeGen.Jmp(lEnd); .)
+      "THEN" [ StatSeq ]                (. astNode := AST.MakeNode(AST.NkIf);
+                                           AST.SetChild(astNode, 0, astCond);
+                                           AST.SetChild(astNode, 1, astStmt);
+                                           astIf := astNode;
+                                           astLast := astNode;
+                                           QbeGen.Jmp(lEnd); .)
       { "ELSIF"                         (. QbeGen.EmitLabel(lElse);
                                            QbeGen.NewLabel(lElse); .)
-        Expr<t, q>                      (. IF NOT SymTab.BoolCheck(t) THEN
+        Expr<t, q> (. astCond := astCur; astStmt := AST.NoNode;
+                                           IF NOT SymTab.BoolCheck(t) THEN
                                              SemError(214) END;
                                            QbeGen.NewLabel(lThen);
                                            QbeGen.Jnz(q, lThen, lElse);
                                            QbeGen.EmitLabel(lThen); .)
-        "THEN" [ StatSeq ]              (. QbeGen.Jmp(lEnd); .) }
+        "THEN" [ StatSeq ]              (. astNode := AST.MakeNode(AST.NkIf);
+                                           AST.SetChild(astNode, 0, astCond);
+                                           AST.SetChild(astNode, 1, astStmt);
+                                           AST.SetChild(astLast, 2, astNode);
+                                           astLast := astNode;
+                                           QbeGen.Jmp(lEnd); .) }
       [ "ELSE"                          (. QbeGen.EmitLabel(lElse);
-                                           hasElse := TRUE; .)
-        [ StatSeq ] ]
-      "END"                             (. IF hasElse THEN
+                                           hasElse := TRUE;
+                                           astStmt := AST.NoNode; .)
+        [ StatSeq ] (. AST.SetChild(astLast, 2, astStmt); .) ]
+      "END" (. astStmt := astIf;
+                                           IF hasElse THEN
                                              QbeGen.EmitLabel(lEnd)
                                            ELSE QbeGen.EmitLabel(lElse);
                                              QbeGen.EmitLabel(lEnd)
                                            END; .) .
   WhileStat                             (. VAR t: SymTab.TypeIndex;
                                              q, lTop, lBody, lEnd:
-                                               QbeGen.QVal; .)
-    = "WHILE"                           (. QbeGen.NewLabel(lTop);
+                                               QbeGen.QVal;
+                                             astCond, astNode: AST.Node; .)
+    = "WHILE" (. astStmt := AST.NoNode;
+                                           QbeGen.NewLabel(lTop);
                                            QbeGen.NewLabel(lBody);
                                            QbeGen.NewLabel(lEnd);
                                            QbeGen.EmitLabel(lTop); .)
-      Expr<t, q>                        (. IF NOT SymTab.BoolCheck(t) THEN
+      Expr<t, q> (. astCond := astCur;
+                                           IF NOT SymTab.BoolCheck(t) THEN
                                              SemError(214) END;
                                            QbeGen.Jnz(q, lBody, lEnd);
                                            QbeGen.EmitLabel(lBody); .)
-      "DO" [ StatSeq ]                      (. QbeGen.Jmp(lTop); .)
+      "DO" [ StatSeq ]                      (. astNode := AST.MakeNode(AST.NkWhile);
+                                           AST.SetChild(astNode, 0, astCond);
+                                           AST.SetChild(astNode, 1, astStmt);
+                                           astStmt := astNode;
+                                           QbeGen.Jmp(lTop); .)
       "END"                             (. QbeGen.EmitLabel(lEnd); .) .
   RepeatStat                            (. VAR t: SymTab.TypeIndex;
-                                             q, lTop, lEnd: QbeGen.QVal; .)
-    = "REPEAT"                          (. QbeGen.NewLabel(lTop);
+                                             q, lTop, lEnd: QbeGen.QVal;
+                                             astCond, astBody, astNode:
+                                               AST.Node; .)
+    = "REPEAT" (. astStmt := AST.NoNode;
+                                           QbeGen.NewLabel(lTop);
                                            QbeGen.NewLabel(lEnd);
                                            QbeGen.EmitLabel(lTop); .)
-      [ StatSeq ]
-      "UNTIL" Expr<t, q>                (. IF NOT SymTab.BoolCheck(t) THEN
+      [ StatSeq ] (. astBody := astStmt; .)
+      "UNTIL" Expr<t, q> (. astCond := astCur;
+                                           astNode := AST.MakeNode(AST.NkRepeat);
+                                           AST.SetChild(astNode, 0, astBody);
+                                           AST.SetChild(astNode, 1, astCond);
+                                           astStmt := astNode;
+                                           IF NOT SymTab.BoolCheck(t) THEN
                                              SemError(214) END;
                                            QbeGen.Jnz(q, lEnd, lTop);
                                            QbeGen.EmitLabel(lEnd); .) .
-  LoopStat                              (. VAR lTop, lEnd: QbeGen.QVal; .)
+  LoopStat                              (. VAR lTop, lEnd: QbeGen.QVal; astNode: AST.Node; .)
     = "LOOP"                            (. QbeGen.NewLabel(lTop);
+                                                  astStmt := AST.NoNode;
                                            QbeGen.NewLabel(lEnd);
                                            QbeGen.PushLoop(lEnd);
                                            QbeGen.EmitLabel(lTop); .)
       [ StatSeq ]
-      "END"                             (. QbeGen.Jmp(lTop);
+      "END"                             (. astNode := AST.MakeNode(AST.NkLoop);
+                                           AST.SetChild(astNode, 0, astStmt);
+                                           astStmt := astNode;
+                                           QbeGen.Jmp(lTop);
                                            QbeGen.PopLoop;
                                            QbeGen.EmitLabel(lEnd); .) .
   (* FOR with static-sign BY (literal, non-zero; 220 otherwise).
@@ -1586,9 +1653,14 @@ PRODUCTIONS
                                              lTop, lBody, lEnd:
                                                QbeGen.QVal;
                                              by: INTEGER;
-                                             ok: BOOLEAN; .)
+                                             ok: BOOLEAN;
+                                             astVar, astLo, astHi, astBy,
+                                             astNode: AST.Node; .)
     = "FOR"                             (. by := 1; .)
-      GetIdent<lv>                      (. ok := SymTab.Lookup(lv);
+      GetIdent<lv> (. astVar := AST.MakeLeaf(AST.NkIdent, lv);
+                                           astStmt := AST.NoNode;
+                                           astBy := AST.NoNode;
+                                           ok := SymTab.Lookup(lv);
                                            IF NOT ok THEN
                                              SemError(201)
                                            ELSIF (SymTab.SymKind(lv) #
@@ -1600,13 +1672,16 @@ PRODUCTIONS
                                                    SymTab.SymType(lv)) THEN
                                              SemError(220); ok := FALSE
                                            END; .)
-      ":=" Expr<tlo, qlo>               (. IF NOT SymTab.IsIntFamily(tlo) THEN
+      ":=" Expr<tlo, qlo> (. astLo := astCur;
+                                           IF NOT SymTab.IsIntFamily(tlo) THEN
                                              SemError(220); ok := FALSE
                                            END; .)
-      "TO" Expr<thi, qhi>               (. IF NOT SymTab.IsIntFamily(thi) THEN
+      "TO" Expr<thi, qhi> (. astHi := astCur;
+                                           IF NOT SymTab.IsIntFamily(thi) THEN
                                              SemError(220); ok := FALSE
                                            END; .)
-      [ "BY" Expr<tby, qby>             (. IF (tby #
+      [ "BY" Expr<tby, qby> (. astBy := astCur;
+                                           IF (tby #
                                              SymTab.InvalidType)
  AND NOT SymTab.IsIntFamily(tby) THEN
                                              SemError(220); ok := FALSE
@@ -1634,7 +1709,14 @@ PRODUCTIONS
                                            QbeGen.Jnz(qk, lBody, lEnd);
                                            QbeGen.EmitLabel(lBody); .)
       [ StatSeq ]
-      "END"                             (. IF ok THEN
+      "END" (. astNode := AST.MakeNode(AST.NkFor);
+                                           AST.SetChild(astNode, 0, astVar);
+                                           AST.SetChild(astNode, 1, astLo);
+                                           AST.SetChild(astNode, 2, astHi);
+                                           AST.SetChild(astNode, 3, astBy);
+                                           AST.SetChild(astNode, 4, astStmt);
+                                           astStmt := astNode;
+                                           IF ok THEN
                                              QbeGen.LoadVar(lv, FALSE,
                                                qt);
                                              QbeGen.IntStr(by, qb);
@@ -1651,7 +1733,8 @@ PRODUCTIONS
       "OF" CaseAlt<tsel, qsel, lEnd>
       { "|" CaseAlt<tsel, qsel, lEnd> }
       [ "ELSE" [ StatSeq ] ]
-      "END"                             (. QbeGen.EmitLabel(lEnd); .) .
+      "END"                             (. astStmt := AST.NoNode;
+                                           QbeGen.EmitLabel(lEnd); .) .
   (* Compare-chain lowering: each alternative ends its match-tests
      with "jmp lAfter", so the no-match fallthrough skips the body:
      "cmp; jnz(lBody,lF); lF: ... ; jmp lAfter; lBody: S; jmp lEnd;
@@ -1720,10 +1803,16 @@ PRODUCTIONS
   ReturnStat                            (. VAR t: SymTab.TypeIndex;
                                              q, qt: QbeGen.QVal;
                                              res: SymTab.TypeIndex;
-                                             hadE, conv: BOOLEAN; .)
-    = "RETURN"                          (. hadE := FALSE; .)
-      [ Expr<t, q>                      (. hadE := TRUE; .) ]
-                                        (. conv := FALSE;
+                                             hadE, conv: BOOLEAN;
+                                             astVal, astNode: AST.Node; .)
+    = "RETURN"                          (. hadE := FALSE; astStmt := AST.NoNode; .)
+      [ Expr<t, q> (. hadE := TRUE; astVal := astCur; .) ]
+                                        (. astNode := AST.MakeNode(AST.NkReturn);
+                                           IF hadE THEN
+                                             AST.SetChild(astNode, 0, astVal)
+                                           END;
+                                           astStmt := astNode;
+                                           conv := FALSE;
                                            IF NOT SymTab.InProc() THEN
                                              SemError(232)
                                            ELSE res := SymTab.CurRes();
@@ -1753,8 +1842,16 @@ PRODUCTIONS
                                              END
                                            END; .) .
   HaltStat                              (. VAR t: SymTab.TypeIndex;
-                                             q: QbeGen.QVal; .)
-    = "HALT" [ "(" Expr<t, q> ")" ]     (. QbeGen.HaltQ; .) .
+                                             q: QbeGen.QVal;
+                                             astVal, astNode: AST.Node; .)
+    = "HALT" (. astVal := AST.NoNode; .)
+      [ "(" Expr<t, q> (. astVal := astCur; .) ")" ]
+                                        (. astNode := AST.MakeNode(AST.NkHalt);
+                                           IF astVal # AST.NoNode THEN
+                                             AST.SetChild(astNode, 0, astVal)
+                                           END;
+                                           astStmt := astNode;
+                                           QbeGen.HaltQ; .) .
   (* Designator: scalar loads, array addresses, and index suffixes.
      Each index descends one level (bounds-checked, trap on breach);
      nested levels reload the inner descriptor address. q ends as the

Разлика између датотеке није приказан због своје велике величине
+ 3376 - 3279
compiler/src/M2.lst


+ 8 - 2
docs/plan-two-phase.md

@@ -78,8 +78,14 @@ Suggested slices, each ending in `build.sh` + `run_tests.sh` +
    `NOT`, builtins as named `NkCall`s).  Still `NoNode`: brace/set
    literals, `ResultComp` suffixes; `Lower` + `.ssa` compare not
    started.  Green (178/178, fixpoint OK).
-3. **Statements → AST**: `Block`/`Stat` build `NkAssign`/`NkIf`/`NkWhile`/
-   …; `Lower` them.
+3. **Statements → AST** — PARTIAL (branch `ast-stage-c`,
+   `docs/summary_two-phase-slice5.md`): `StatSeq` builds `NkBlock`;
+   assignment/call, `IF`/`ELSIF`/`ELSE`, `WHILE`, `REPEAT`, `LOOP`/
+   `EXIT`, `FOR`, `RETURN`, `HALT` build nodes.  Deferred: `CASE`,
+   `WITH`, builtin statements (`INCL`/`INC`/`NEW`/…), brace/set
+   literals, `ResultComp`, and `Lower`.  Green (178/178, fixpoint OK).
+   Finding: **local arrays in a PROCEDURE are miscompiled**
+   (`docs/wip/local-array-bug.mod`) — pre-existing, worked around here.
 4. **Declarations → AST**: `ConstBlock`/`TypeBlock`/`VarBlock`/
    `ProcDecl`; the unit's decls complete SymTab before any body is
    lowered.

+ 74 - 0
docs/summary_two-phase-slice5.md

@@ -0,0 +1,74 @@
+# Two-phase refactor — slice 5 (statements → AST)
+
+Branch `ast-stage-c`.  Suite **178/178**; self-hosting fixpoint **OK**
+(**3,000,988 bytes**; slice 4 was 2,987,484).
+
+## What landed
+
+Statements now build AST nodes.  A module-level `astStmt` carries each
+statement's node; `StatSeq` folds them into an `NkBlock`.
+
+| Statement | AST |
+| --- | --- |
+| `StatSeq` | `NkBlock` of the contained statements |
+| `x := e` | `NkAssign` (child 0 = designator, 1 = value) |
+| `P(…)` / `P` statement | `NkCall` (shared `AstCallNode` helper) |
+| `IF … THEN … ELSIF … ELSE … END` | `NkIf` chain: each `ELSIF` becomes the `else` child of the previous `NkIf` |
+| `WHILE c DO … END` | `NkWhile(cond, body)` |
+| `REPEAT … UNTIL c` | `NkRepeat(body, cond)` |
+| `LOOP … END` | `NkLoop(body)` |
+| `EXIT` | `NkExit` |
+| `FOR v := lo TO hi [BY s] DO … END` | `NkFor(var, lo, hi, by, body)` |
+| `RETURN [e]` | `NkReturn([e])` |
+| `HALT [(e)]` | `NkHalt([e])` |
+
+The `IF`/`ELSIF` chain is built incrementally (one `NkIf` per clause,
+linked through the `else` child) rather than with a local array — see the
+finding below.
+
+## Finding — local arrays are miscompiled (pre-existing)
+
+While self-hosting this slice, the `IfStat` `VAR … ARRAY [0..7] OF
+AST.Node` locals exposed a **pre-existing V3 codegen bug**: a local
+`ARRAY` inside a `PROCEDURE` is miscompiled.
+
+```modula2
+PROCEDURE P;
+  VAR a : ARRAY [0..3] OF INTEGER; i : INTEGER;
+BEGIN
+  i := 0;
+  WHILE i < 4 DO a[i] := i * 10; INC(i) END;
+  ExitCode := a[0] + a[1] + a[2] + a[3]   (* want 60, got 1 *)
+END P;
+```
+
+Module-level arrays are fine; **local** ones are not.  Repro parked in
+`docs/wip/local-array-bug.mod`.  It is **not** caused by this refactor
+(the code is inert and does not touch local-array lowering); it was
+merely *triggered* by using local arrays in the grammar.  The grammar now
+uses scalars/globals.  This should be a dedicated fix (like the
+array-of-record stride bug from Stage A).
+
+## Verified (temporary `AST.Dump` of the body, then removed)
+
+A program with assignment, `IF`/`ELSE`, `WHILE`, `FOR` (no `BY`) and a
+statement call produced the expected `NkBlock`:
+
+- `NkAssign(NkDesignator x, 1)`
+- `NkIf(Lt(x,2), Block[Assign(x,3)], Block[Assign(x,4)])`
+- `NkWhile(Lt(x,5), Block[Assign(x, x+1)])`
+- `NkFor(Ident i, 1, 3, nil, Block[Assign(x, x+i)])`
+- `NkCall(NkDesignator P, NkDesignator x)`
+
+## Deliberately deferred
+
+- `CASE` and `WITH` statements: explicit `astStmt := NoNode`.
+- Builtin statements `INCL`/`EXCL`/`INC`/`DEC`/`NEW`/`DISPOSE`: they do
+  not set `astStmt` (the `Statement` leading reset leaves `NoNode`).
+- Brace/set literals and `ResultComp` call suffixes (from slice 4).
+- `Lower` + `.ssa` byte-compare (not started).
+
+## Next
+
+Declarations (`CONST`/`TYPE`/`VAR`/`PROC` bodies → AST), then `CASE`/
+`WITH`/builtin statements and brace/set literals, then the `Lower` walk.

+ 18 - 0
docs/wip/local-array-bug.mod

@@ -0,0 +1,18 @@
+MODULE local_array_bug;
+(* Repro: a local ARRAY inside a PROCEDURE is miscompiled by V3.
+   Expected exit 60; observed 1 (as of v3-ast-slice5, 2026-10-03).
+   Found while self-hosting the two-phase statement AST (IfStat used a
+   local ARRAY OF AST.Node); moved to module scope to keep the fixpoint
+   green.  Module-level arrays work; local ones do not. *)
+
+VAR ExitCode : INTEGER;
+
+PROCEDURE P;
+  VAR a : ARRAY [0..3] OF INTEGER; i : INTEGER;
+BEGIN
+  i := 0;
+  WHILE i < 4 DO a[i] := i * 10; INC(i) END;
+  ExitCode := a[0] + a[1] + a[2] + a[3]   (* 0+10+20+30 = 60 *)
+END P;
+
+BEGIN P END local_array_bug.

Неке датотеке нису приказане због велике количине промена