Переглянути джерело

lower phase slice 1: relations, booleans, short-circuit

Lower now emits scalar relations via QbeGen.Cmp (AST op -> SymTab op),
NOT via NotQ, and short-circuit AND/OR with the same DelayBegin/DelayEnd
bracketing and Slot4 merge block as the grammar, so temps/labels line up.
TRUE/FALSE consts and bool/char/enum scalars added to CanLower.

Gates: suite 181/181 (lower_ok t_lower1); fixpoint OK (stage2 == stage3,
3104868 bytes).

See docs/summary_two-phase-lower1.md.
Eric Streit 1 тиждень тому
батько
коміт
3335d6e1fa

+ 1 - 0
compiler/run_tests.sh

@@ -465,6 +465,7 @@ lower_ok() {
 }
 lower_ok t_exit.mod
 lower_ok t_arith.mod
+lower_ok t_lower1.mod
 
 # --- step 7 gate: compiler sources free of gm2-only imports.
 #     FileIO is the last module to port (see docs/summary_step7.md);

+ 128 - 39
compiler/src/Lower.mod

@@ -1,25 +1,47 @@
 IMPLEMENTATION MODULE Lower;
-(* Scalar L0 walk: see Lower.def and docs/plan-lower.md. *)
+(* Scalar + boolean walk (L0/L1): see Lower.def and docs/plan-lower.md. *)
 
 IMPORT AST, SymTab, QbeGen;
 
 PROCEDURE Name (n: AST.Node; VAR s: ARRAY OF CHAR);
-(* The interned text of a leaf node (NkIdent / NkIntLit / ...). *)
   BEGIN
     IF n = AST.NoNode THEN s[0] := CHR(0)
     ELSE AST.Txt(AST.Child(n, 0), s)
     END
   END Name;
 
-PROCEDURE IsIntOp (op: INTEGER): BOOLEAN;
+PROCEDURE IsArith (op: INTEGER): BOOLEAN;
   BEGIN
     RETURN (op = AST.OpAdd) OR (op = AST.OpSub) OR (op = AST.OpMul)
         OR (op = AST.OpDiv) OR (op = AST.OpMod)
-  END IsIntOp;
+  END IsArith;
+
+PROCEDURE IsRel (op: INTEGER): BOOLEAN;
+  BEGIN
+    RETURN (op >= AST.OpEq) AND (op <= AST.OpGe)
+  END IsRel;
+
+PROCEDURE RelSym (a: INTEGER; VAR s: INTEGER): BOOLEAN;
+  BEGIN
+    IF a = AST.OpEq THEN s := SymTab.OpEq
+    ELSIF a = AST.OpNe THEN s := SymTab.OpNeq1
+    ELSIF a = AST.OpLt THEN s := SymTab.OpLt
+    ELSIF a = AST.OpLe THEN s := SymTab.OpLe
+    ELSIF a = AST.OpGt THEN s := SymTab.OpGt
+    ELSIF a = AST.OpGe THEN s := SymTab.OpGe
+    ELSE RETURN FALSE
+    END;
+    RETURN TRUE
+  END RelSym;
+
+PROCEDURE IsScalarClass (cls: INTEGER): BOOLEAN;
+  BEGIN
+    RETURN (cls = SymTab.ClInt) OR (cls = SymTab.ClBool)
+        OR (cls = SymTab.ClChar) OR (cls = SymTab.ClUChar)
+        OR (cls = SymTab.ClEnum)
+  END IsScalarClass;
 
 PROCEDURE IsSimpleScalar (n: AST.Node): BOOLEAN;
-(* A parenthesised-free identifier designator whose symbol is a scalar
-   INTEGER VAR/PARAM/CONST. *)
   VAR nm: SymTab.Name; k: INTEGER;
   BEGIN
     IF (AST.Kind(n) # AST.NkDesignator) OR (AST.NChild(n) # 1) THEN
@@ -31,20 +53,28 @@ PROCEDURE IsSimpleScalar (n: AST.Node): BOOLEAN;
     IF (k # SymTab.KindVar) AND (k # SymTab.KindParam)
        AND (k # SymTab.KindConst) THEN
       RETURN FALSE END;
-    RETURN SymTab.ClassOf(SymTab.SymType(nm)) = SymTab.ClInt
+    IF SymTab.Equal(nm, "TRUE") OR SymTab.Equal(nm, "FALSE") THEN
+      RETURN TRUE END;
+    RETURN IsScalarClass(SymTab.ClassOf(SymTab.SymType(nm)))
   END IsSimpleScalar;
 
 PROCEDURE CanExpr (n: AST.Node): BOOLEAN;
+  VAR op: INTEGER;
   BEGIN
     IF n = AST.NoNode THEN RETURN FALSE END;
     IF AST.Kind(n) = AST.NkIntLit THEN RETURN TRUE END;
     IF AST.Kind(n) = AST.NkDesignator THEN RETURN IsSimpleScalar(n) END;
     IF AST.Kind(n) = AST.NkUnary THEN
-      RETURN (AST.Op(n) = AST.OpSub) AND CanExpr(AST.Child(n, 0))
+      op := AST.Op(n);
+      IF (op # AST.OpSub) AND (op # AST.OpNot) THEN RETURN FALSE END;
+      RETURN CanExpr(AST.Child(n, 0))
     END;
     IF AST.Kind(n) = AST.NkBinExpr THEN
-      RETURN IsIntOp(AST.Op(n)) AND CanExpr(AST.Child(n, 0))
-             AND CanExpr(AST.Child(n, 1))
+      op := AST.Op(n);
+      IF NOT (IsArith(op) OR IsRel(op)
+              OR (op = AST.OpAnd) OR (op = AST.OpOr)) THEN
+        RETURN FALSE END;
+      RETURN CanExpr(AST.Child(n, 0)) AND CanExpr(AST.Child(n, 1))
     END;
     RETURN FALSE
   END CanExpr;
@@ -53,7 +83,7 @@ PROCEDURE CanDecl (c: AST.Node): BOOLEAN;
   VAR i: CARDINAL;
   BEGIN
     IF AST.Kind(c) = AST.NkVarDecl THEN
-      IF SymTab.ClassOf(AST.Ty(c)) # SymTab.ClInt THEN RETURN FALSE END;
+      IF NOT IsScalarClass(SymTab.ClassOf(AST.Ty(c))) THEN RETURN FALSE END;
       IF AST.NChild(c) < 1 THEN RETURN FALSE END;
       i := 0;
       WHILE i < AST.NChild(c) DO
@@ -63,7 +93,7 @@ PROCEDURE CanDecl (c: AST.Node): BOOLEAN;
       RETURN TRUE
     END;
     IF AST.Kind(c) = AST.NkConstDecl THEN
-      RETURN (SymTab.ClassOf(AST.Ty(c)) = SymTab.ClInt)
+      RETURN IsScalarClass(SymTab.ClassOf(AST.Ty(c)))
              AND (AST.Kind(AST.Child(c, 1)) = AST.NkIntLit)
     END;
     RETURN FALSE
@@ -130,7 +160,11 @@ PROCEDURE LowerDesign (n: AST.Node; VAR t: SymTab.TypeIndex; VAR k: INTEGER;
     END;
     t := SymTab.SymType(nm); k := SymTab.SymKind(nm);
     IF k = SymTab.KindConst THEN
-      IF SymTab.GetSymVal(nm, cv) AND QbeGen.IsImm(cv) THEN
+      IF SymTab.Equal(nm, "TRUE") THEN
+        t := SymTab.BoolType(); QbeGen.CopyOp("1", q)
+      ELSIF SymTab.Equal(nm, "FALSE") THEN
+        t := SymTab.BoolType(); QbeGen.CopyOp("0", q)
+      ELSIF SymTab.GetSymVal(nm, cv) AND QbeGen.IsImm(cv) THEN
         QbeGen.CopyOp(cv, q)
       ELSE QbeGen.CopyOp("0", q)
       END
@@ -143,8 +177,10 @@ PROCEDURE LowerDesign (n: AST.Node; VAR t: SymTab.TypeIndex; VAR k: INTEGER;
 
 PROCEDURE LowerExpr (n: AST.Node; VAR t: SymTab.TypeIndex;
                      VAR q: QbeGen.QVal);
-  VAR t2: SymTab.TypeIndex; q2, qt, qf: QbeGen.QVal;
-      kind, op, k: INTEGER; qn: SymTab.Name; sfx: BOOLEAN; folded: BOOLEAN;
+  VAR t2: SymTab.TypeIndex; q2, qt, qf, qs, qr: QbeGen.QVal;
+      lNext, lFalse, lTrue, lDone: QbeGen.QVal;
+      kind, op, k, rop: INTEGER; qn: SymTab.Name; sfx: BOOLEAN;
+      folded, isR: BOOLEAN;
   BEGIN
     IF AST.Kind(n) = AST.NkIntLit THEN
       Name(n, qn); QbeGen.NormInt(qn, q); t := SymTab.IntType()
@@ -152,34 +188,87 @@ PROCEDURE LowerExpr (n: AST.Node; VAR t: SymTab.TypeIndex;
       LowerDesign(n, t, k, q, qn, sfx)
     ELSIF AST.Kind(n) = AST.NkUnary THEN
       LowerExpr(AST.Child(n, 0), t, q);
-      IF QbeGen.IsImm(q) THEN QbeGen.NegFold(q, q)
-      ELSE QbeGen.NewTemp(qt); QbeGen.NegQ(q, qt, FALSE); QbeGen.CopyOp(qt, q)
+      IF AST.Op(n) = AST.OpSub THEN
+        IF QbeGen.IsImm(q) THEN QbeGen.NegFold(q, q)
+        ELSE QbeGen.NewTemp(qt); QbeGen.NegQ(q, qt, FALSE); QbeGen.CopyOp(qt, q)
+        END
+      ELSE
+        QbeGen.NotQ(q, qt); QbeGen.CopyOp(qt, q); t := SymTab.BoolType()
       END
     ELSIF AST.Kind(n) = AST.NkBinExpr THEN
-      LowerExpr(AST.Child(n, 0), t, q);
-      LowerExpr(AST.Child(n, 1), t2, q2);
-      op := AST.Op(n); kind := 0;
-      IF op = AST.OpSub THEN kind := 1
-      ELSIF op = AST.OpMul THEN kind := 2
-      ELSIF op = AST.OpDiv THEN kind := 3
-      ELSIF op = AST.OpMod THEN kind := 4
-      END;
-      folded := FALSE;
-      IF QbeGen.IsImm(q) AND QbeGen.IsImm(q2) THEN
-        folded := QbeGen.Fold2(kind, q, q2, qf)
-      END;
-      IF folded THEN QbeGen.CopyOp(qf, q)
+      op := AST.Op(n);
+      IF IsRel(op) THEN
+        LowerExpr(AST.Child(n, 0), t, q);
+        LowerExpr(AST.Child(n, 1), t2, q2);
+        rop := SymTab.OpEq; folded := RelSym(op, rop);
+        isR := (t # SymTab.InvalidType) AND (SymTab.ClassOf(t) = SymTab.ClReal);
+        t := SymTab.BoolType();
+        QbeGen.Cmp(rop, q, q2, qt, isR);
+        QbeGen.CopyOp(qt, q)
+      ELSIF op = AST.OpAnd THEN
+        LowerExpr(AST.Child(n, 0), t, q);
+        QbeGen.DelayBegin;
+        LowerExpr(AST.Child(n, 1), t2, q2);
+        QbeGen.DelayEnd;
+        QbeGen.Slot4(qs);
+        QbeGen.NewLabel(lNext); QbeGen.NewLabel(lFalse); QbeGen.NewLabel(lDone);
+        QbeGen.Jnz(q, lNext, lFalse);
+        QbeGen.EmitLabel(lNext);
+        QbeGen.DelayFlush;
+        QbeGen.StoreW(qs, q2);
+        QbeGen.Jmp(lDone);
+        QbeGen.EmitLabel(lFalse);
+        QbeGen.StoreW(qs, "0");
+        QbeGen.Jmp(lDone);
+        QbeGen.EmitLabel(lDone);
+        QbeGen.LoadW(qs, qr);
+        QbeGen.CopyOp(qr, q);
+        t := SymTab.BoolType()
+      ELSIF op = AST.OpOr THEN
+        LowerExpr(AST.Child(n, 0), t, q);
+        QbeGen.DelayBegin;
+        LowerExpr(AST.Child(n, 1), t2, q2);
+        QbeGen.DelayEnd;
+        QbeGen.Slot4(qs);
+        QbeGen.NewLabel(lTrue); QbeGen.NewLabel(lNext); QbeGen.NewLabel(lDone);
+        QbeGen.Jnz(q, lTrue, lNext);
+        QbeGen.EmitLabel(lTrue);
+        QbeGen.StoreW(qs, "1");
+        QbeGen.Jmp(lDone);
+        QbeGen.EmitLabel(lNext);
+        QbeGen.DelayFlush;
+        QbeGen.StoreW(qs, q2);
+        QbeGen.Jmp(lDone);
+        QbeGen.EmitLabel(lDone);
+        QbeGen.LoadW(qs, qr);
+        QbeGen.CopyOp(qr, q);
+        t := SymTab.BoolType()
       ELSE
-        QbeGen.NewTemp(qt);
-        IF op = AST.OpAdd THEN QbeGen.Op3("add", qt, q, q2, FALSE)
-        ELSIF op = AST.OpSub THEN QbeGen.Op3("sub", qt, q, q2, FALSE)
-        ELSIF op = AST.OpMul THEN QbeGen.Op3("mul", qt, q, q2, FALSE)
-        ELSIF op = AST.OpDiv THEN QbeGen.Op3("div", qt, q, q2, FALSE)
-        ELSE QbeGen.Op3("rem", qt, q, q2, FALSE)
+        LowerExpr(AST.Child(n, 0), t, q);
+        LowerExpr(AST.Child(n, 1), t2, q2);
+        kind := 0;
+        IF op = AST.OpSub THEN kind := 1
+        ELSIF op = AST.OpMul THEN kind := 2
+        ELSIF op = AST.OpDiv THEN kind := 3
+        ELSIF op = AST.OpMod THEN kind := 4
         END;
-        QbeGen.CopyOp(qt, q)
-      END;
-      t := SymTab.IntType()
+        folded := FALSE;
+        IF QbeGen.IsImm(q) AND QbeGen.IsImm(q2) THEN
+          folded := QbeGen.Fold2(kind, q, q2, qf)
+        END;
+        IF folded THEN QbeGen.CopyOp(qf, q)
+        ELSE
+          QbeGen.NewTemp(qt);
+          IF op = AST.OpAdd THEN QbeGen.Op3("add", qt, q, q2, FALSE)
+          ELSIF op = AST.OpSub THEN QbeGen.Op3("sub", qt, q, q2, FALSE)
+          ELSIF op = AST.OpMul THEN QbeGen.Op3("mul", qt, q, q2, FALSE)
+          ELSIF op = AST.OpDiv THEN QbeGen.Op3("div", qt, q, q2, FALSE)
+          ELSE QbeGen.Op3("rem", qt, q, q2, FALSE)
+          END;
+          QbeGen.CopyOp(qt, q)
+        END;
+        t := SymTab.IntType()
+      END
     END
   END LowerExpr;
 

+ 10 - 0
compiler/tests/t_lower1.mod

@@ -0,0 +1,10 @@
+MODULE TLower1;
+(* Lower L1: relations, boolean operators, short-circuit AND/OR, NOT.
+   Exit 0. *)
+VAR b, c : BOOLEAN; x, y : INTEGER;
+VAR ExitCode : INTEGER;
+BEGIN
+  b := (x < y) AND (y > 0);
+  c := NOT (x = y) OR (x <= y);
+  ExitCode := 0
+END TLower1.

+ 1 - 1
docs/plan-lower.md

@@ -49,7 +49,7 @@ types/kinds) and the node `ty` field.
 | Step | Subset | Notes |
 | --- | --- | --- |
 | **L0** | program unit; scalar `CONST`/`VAR`; body of `x := <int expr>`; int literals/idents, `+ - * DIV MOD`, unary `-` | mechanism proof; `t_exit`, `t_arith` |
-| L1 | relations, booleans, short-circuit `AND`/`OR` (Delay) | |
+| **L1** | relations, booleans, short-circuit `AND`/`OR` (Delay), `NOT` | `t_lower1`; `docs/summary_two-phase-lower1.md` |
 | L2 | control flow: `IF`/`WHILE`/`REPEAT`/`LOOP`/`FOR`/`RETURN`/`HALT` | |
 | L3 | procedures/functions: `BeginFunc`/params/locals/`CallBegin`… | |
 | L4 | calls, builtins, `WITH`, `CASE` | |

+ 31 - 0
docs/summary_two-phase-lower1.md

@@ -0,0 +1,31 @@
+# Lower phase — slice 1 (relations, booleans, short-circuit)
+
+Branch `ast-stage-c`.  Suite **181/181**; self-hosting fixpoint **OK**
+(**3,104,868 bytes**; L0 was 3,090,949).  Plan: `docs/plan-lower.md`.
+
+## What landed
+
+`Lower` now covers, in addition to L0:
+
+- **Relations** `=` `#` `<` `<=` `>` `>=` on scalar operands →
+  `QbeGen.Cmp(op, …)` (`AST.Op*` mapped to `SymTab.Op*`), result
+  `BOOLEAN`.
+- **`NOT`** / `~` → `QbeGen.NotQ`.
+- **Short-circuit `AND`** (`Term`) and **`OR`** (`SimExpr`): the walk
+  brackets the RHS with `DelayBegin`/`DelayEnd`, then emits the same
+  merge block (`Slot4`, `NewLabel` ×3, `Jnz`, `EmitLabel`, `DelayFlush`,
+  `StoreW`, `Jmp`, `LoadW`, `CopyOp`) as the grammar — so the temp/label
+  counters line up and the image is byte-identical.
+- **`TRUE`/`FALSE`** constants in `LowerDesign`.
+- `CanLower` accepts scalar classes `INT/BOOLEAN/CHAR/UCHAR/ENUM`.
+
+## Verified
+
+`gen_ssa/TLower1L.ssa` (a program with `(x<y) AND (y>0)` and
+`NOT (x=y) OR (x<=y)`) is **byte-identical** to the legacy image
+(`lower_ok t_lower1.mod`).
+
+## Next
+
+L2: control flow (`IF`/`WHILE`/`REPEAT`/`LOOP`/`FOR`/`RETURN`/`HALT`) —
+statements, so `CanBody` grows and `LowerUnit` walks statement nodes.