Просмотр исходного кода

v3 step 2 — REAL lowering, scalar story complete (30/30 tests green)

Eric Streit 3 недель назад
Родитель
Сommit
fe30bddf91

+ 2 - 0
compiler/run_tests.sh

@@ -59,6 +59,7 @@ expect_run t_loop.mod 55
 expect_run t_for.mod 85
 expect_run t_case.mod 103
 expect_run t_bool.mod 42
+expect_run t_real.mod 31
 expect_run showcase3.mod 183
 expect_fail t_enumdecl.mod "not supported yet"
 expect_fail t_proc.mod "not supported yet"
@@ -71,6 +72,7 @@ expect_fail t_bad_while.mod "BOOLEAN condition required"
 expect_fail t_bad_exit.mod "not supported yet"
 expect_fail t_bad_return.mod "bad RETURN"
 expect_fail t_bad_for.mod "FOR needs integer variable and bounds"
+expect_fail t_bad_mixed.mod "incompatible assignment"
 expect_fail t_defunit.mod "not supported yet"
 expect_fail t_implunit.mod "not supported yet"
 expect_fail t_nested.mod "not supported yet"

+ 49 - 23
compiler/src/M2.atg

@@ -254,7 +254,8 @@ PRODUCTIONS
                                            IF (t # SymTab.InvalidType)
                                               & (cls # SymTab.ClInt)
                                               & (cls # SymTab.ClBool)
-                                              & (cls # SymTab.ClChar) THEN
+                                              & (cls # SymTab.ClChar)
+                                              & (cls # SymTab.ClReal) THEN
                                              SemError(230) END;
                                            i := 0;
                                            WHILE i < SymTab.PendCount() DO
@@ -324,8 +325,9 @@ PRODUCTIONS
                                            ELSE SemError(230) END; .) .
   Assign                                (. VAR dt, et: SymTab.TypeIndex;
                                              dk: INTEGER;
-                                             qd, qe: QbeGen.QVal;
-                                             qn: SymTab.Name; .)
+                                             qd, qe, qt: QbeGen.QVal;
+                                             qn: SymTab.Name;
+                                             isR, conv: BOOLEAN; .)
     = Design<dt, dk, qd, qn> ":="
       Expr<et, qe>                      (. IF (dt # SymTab.InvalidType)
                                            & (dk # SymTab.KindVar)
@@ -334,11 +336,22 @@ PRODUCTIONS
                                          ELSIF ~SymTab.Assignable(et,
                                                   dt) THEN
                                            SemError(210) END;
+                                         isR := (dt #
+                                                 SymTab.InvalidType)
+                                                & (SymTab.ClassOf(dt)
+                                                   = SymTab.ClReal);
+                                         conv := isR
+                                                 & SymTab.IsIntFamily(et);
                                          IF ((dk = SymTab.KindVar)
                                              OR (dk = SymTab.KindParam))
                                             & (dt # SymTab.InvalidType)
                                             & (et # SymTab.InvalidType) THEN
-                                           QbeGen.StoreVar(qn, qe, FALSE)
+                                           IF conv THEN
+                                             QbeGen.ConvIR(qe, qt);
+                                             QbeGen.StoreVar(qn, qt, TRUE)
+                                           ELSE
+                                             QbeGen.StoreVar(qn, qe, isR)
+                                           END
                                          END; .) .
   IfStat                                (. VAR t: SymTab.TypeIndex;
                                              q, lThen, lElse, lEnd:
@@ -585,9 +598,11 @@ PRODUCTIONS
                                                      SymTab.InvalidType)
                                                     & ((cls = SymTab.ClInt)
                                                     OR (cls
-                                                        = SymTab.ClChar)) THEN
+                                                        = SymTab.ClChar)
+                                                    OR (cls
+                                                        = SymTab.ClReal)) THEN
                                                    QbeGen.LoadVar(n,
-                                                     FALSE, q)
+                                                     cls = SymTab.ClReal, q)
                                                  ELSE
                                                    IF t #
                                                       SymTab.InvalidType THEN
@@ -602,9 +617,11 @@ PRODUCTIONS
                                                   SymTab.ClassOf(t);
                                                 IF (cls = SymTab.ClInt)
                                                    OR (cls = SymTab.ClBool)
-                                                   OR (cls = SymTab.ClChar) THEN
+                                                   OR (cls = SymTab.ClChar)
+                                                   OR (cls
+                                                       = SymTab.ClReal) THEN
                                                   QbeGen.LoadVar(n,
-                                                    FALSE, q)
+                                                    cls = SymTab.ClReal, q)
                                                 ELSE SemError(230);
                                                   QbeGen.CopyOp("0", q)
                                                 END
@@ -618,14 +635,17 @@ PRODUCTIONS
   Expr<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
                                         (. VAR t2: SymTab.TypeIndex;
                                              op: INTEGER;
-                                             q2, qt: QbeGen.QVal; .)
+                                             q2, qt: QbeGen.QVal;
+                                             isR: BOOLEAN; .)
     = SimExpr<t, q>
       [ Rel<op> SimExpr<t2, q2>
         (. IF SymTab.RelCheck(t, t2, op) THEN
+             isR := SymTab.ClassOf(t) = SymTab.ClReal;
              t := SymTab.BoolType()
-           ELSE SemError(213); t := SymTab.InvalidType END;
+           ELSE SemError(213); t := SymTab.InvalidType;
+             isR := FALSE END;
            IF t # SymTab.InvalidType THEN
-             QbeGen.Cmp(op, q, q2, qt, FALSE);
+             QbeGen.Cmp(op, q, q2, qt, isR);
              QbeGen.CopyOp(qt, q)
            ELSE QbeGen.CopyOp("0", q)
            END; .) ] .
@@ -640,14 +660,16 @@ PRODUCTIONS
                                         (. VAR t2, res2: SymTab.TypeIndex;
                                              op: INTEGER;
                                              q2, qt: QbeGen.QVal;
-                                             neg: BOOLEAN; .)
+                                             neg, isR: BOOLEAN; .)
     =                                   (. neg := FALSE; .)
       [ "+" | "-"                       (. neg := TRUE; .) ]
       Term<t, q>                        (. IF neg THEN
                                            IF QbeGen.IsImm(q) THEN
                                              QbeGen.NegFold(q, q)
                                            ELSE QbeGen.NewTemp(qt);
-                                             QbeGen.NegQ(q, qt, FALSE);
+                                             QbeGen.NegQ(q, qt,
+                                               SymTab.ClassOf(t)
+                                               = SymTab.ClReal);
                                              QbeGen.CopyOp(qt, q)
                                            END
                                          END; .)
@@ -666,12 +688,13 @@ PRODUCTIONS
              IF SymTab.ArithCheck(t, t2, FALSE, res2) THEN
                t := res2
              ELSE SemError(211); t := SymTab.InvalidType END;
+             isR := SymTab.ClassOf(t) = SymTab.ClReal;
              IF t # SymTab.InvalidType THEN
                QbeGen.NewTemp(qt);
                IF op = SymTab.OpAdd THEN
-                 QbeGen.Op3("add", qt, q, q2, FALSE)
+                 QbeGen.Op3("add", qt, q, q2, isR)
                ELSE
-                 QbeGen.Op3("sub", qt, q, q2, FALSE)
+                 QbeGen.Op3("sub", qt, q, q2, isR)
                END;
                QbeGen.CopyOp(qt, q)
              ELSE QbeGen.CopyOp("0", q)
@@ -684,7 +707,8 @@ PRODUCTIONS
   Term<VAR t: SymTab.TypeIndex; VAR q: QbeGen.QVal>
                                         (. VAR t2, res2: SymTab.TypeIndex;
                                              op: INTEGER;
-                                             q2, qt: QbeGen.QVal; .)
+                                             q2, qt: QbeGen.QVal;
+                                             isR: BOOLEAN; .)
     = Fact<t, q> { MulOp<op> Fact<t2, q2>
       (. IF op = SymTab.OpAnd THEN
            IF SymTab.BoolCheck(t) & SymTab.BoolCheck(t2) THEN
@@ -701,14 +725,16 @@ PRODUCTIONS
                 (op = SymTab.OpDiv) OR (op = SymTab.OpMod),
                 res2) THEN t := res2
            ELSE SemError(211); t := SymTab.InvalidType END;
+           isR := SymTab.ClassOf(t) = SymTab.ClReal;
            IF t # SymTab.InvalidType THEN
              QbeGen.NewTemp(qt);
              IF op = SymTab.OpTimes THEN
-               QbeGen.Op3("mul", qt, q, q2, FALSE)
-             ELSIF op = SymTab.OpDiv THEN
-               QbeGen.Op3("div", qt, q, q2, FALSE)
+               QbeGen.Op3("mul", qt, q, q2, isR)
+             ELSIF (op = SymTab.OpDiv)
+                OR (op = SymTab.OpSlash) THEN
+               QbeGen.Op3("div", qt, q, q2, isR)
              ELSE
-               QbeGen.Op3("rem", qt, q, q2, FALSE)
+               QbeGen.Op3("rem", qt, q, q2, isR)
              END;
              QbeGen.CopyOp(qt, q)
            ELSE QbeGen.CopyOp("0", q)
@@ -716,6 +742,7 @@ PRODUCTIONS
          END; .) } .
   MulOp<VAR op: INTEGER>
     = "*"                               (. op := SymTab.OpTimes; .)
+    | "/"                               (. op := SymTab.OpSlash; .)
     | "DIV"                             (. op := SymTab.OpDiv; .)
     | "MOD"                             (. op := SymTab.OpMod; .)
     | "AND"                             (. op := SymTab.OpAnd; .) .
@@ -729,9 +756,8 @@ PRODUCTIONS
                                            QbeGen.NormInt(s, q);
                                            t := SymTab.IntType(); .)
     | real                              (. LexString(s);
-                                           t := SymTab.RealType();
-                                           SemError(230);
-                                           QbeGen.CopyOp("0", q); .)
+                                           QbeGen.NormReal(s, q);
+                                           t := SymTab.RealType(); .)
     | string                            (. LexString(s);
                                            IF SymTab.StrLen(s) <= 3 THEN
                                              t := SymTab.CharType();

+ 4 - 4
compiler/src/M2.lst

@@ -14,11 +14,11 @@ LL(1) conditions:         --  ok  --
 
 Statistics:
 
-  nr of terminals:        61 (limit   400)
+  nr of terminals:        62 (limit   400)
   nr of non-terminals:    49 (limit   210)
-  nr of pragmas:           0 (limit   439)
-  nr of symbolnodes:     110 (limit   500)
-  nr of graphnodes:      439 (limit  1500)
+  nr of pragmas:           0 (limit   438)
+  nr of symbolnodes:     111 (limit   500)
+  nr of graphnodes:      442 (limit  1500)
   nr of conditionsets:     5 (limit   100)
   nr of charactersets:    11 (limit   250)
 

+ 7 - 1
compiler/src/QbeGen.def

@@ -74,10 +74,16 @@ PROCEDURE Op3 (mn: ARRAY OF CHAR; res, l, r: ARRAY OF CHAR;
 PROCEDURE NegQ (a: ARRAY OF CHAR; VAR q: QVal; isReal: BOOLEAN);
 (* q := fresh temp holding "-a". a and q must differ. *)
 
+PROCEDURE ConvIR (a: ARRAY OF CHAR; VAR q: QVal);
+(* q := fresh double holding "swtof a" (INTEGER -> REAL widening). *)
+
 PROCEDURE CopyOp (s: ARRAY OF CHAR; VAR d: ARRAY OF CHAR);
 PROCEDURE IntStr (v: INTEGER; VAR s: QVal);
 PROCEDURE NormInt (s: ARRAY OF CHAR; VAR d: QVal);
-(* "0FFH" -> "255"; plain decimals copied through. *)
+(* "0xFF" -> "255"; plain decimals copied through. *)
+
+PROCEDURE NormReal (s: ARRAY OF CHAR; VAR d: QVal);
+(* "3.14" -> "d_3.14" (QBE double immediates carry a d_ prefix). *)
 
 PROCEDURE CharVal (s: ARRAY OF CHAR): INTEGER;
 (* ORD of a 1-character literal such as 'a'. *)

+ 104 - 24
compiler/src/QbeGen.mod

@@ -14,6 +14,9 @@ VAR
   nLab   : CARDINAL;
   loopTop : CARDINAL;
   loopSt  : ARRAY [0 .. 15] OF QVal;
+  nR     : CARDINAL;  (* non-zero REAL consts flushed at BeginBody *)
+  rNames : ARRAY [0 .. 31] OF QVal;
+  rVals  : ARRAY [0 .. 31] OF QVal;
 
 (* ---------------- small string utilities ---------------- *)
 
@@ -135,6 +138,13 @@ PROCEDURE NormInt (s: ARRAY OF CHAR; VAR d: QVal);
     END
   END NormInt;
 
+PROCEDURE NormReal (s: ARRAY OF CHAR; VAR d: QVal);
+(* "3.14" -> "d_3.14" (QBE double immediates carry a d_ prefix). *)
+  BEGIN
+    Cpy(d, "d_");
+    App(d, s)
+  END NormReal;
+
 PROCEDURE CharVal (s: ARRAY OF CHAR): INTEGER;
   BEGIN
     IF Len(s) >= 2 THEN RETURN ORD(s[1]) END;
@@ -143,7 +153,7 @@ PROCEDURE CharVal (s: ARRAY OF CHAR): INTEGER;
 
 PROCEDURE NegFold (a: ARRAY OF CHAR; VAR q: QVal);
 (* Safe for a and q being the same variable. *)
-  VAR i, j : CARDINAL;
+  VAR i, j, L : CARDINAL;
     tmp : QVal;
   BEGIN
     Cpy(tmp, a);
@@ -154,6 +164,14 @@ PROCEDURE NegFold (a: ARRAY OF CHAR; VAR q: QVal);
         q[j] := tmp[i]; INC(i); INC(j)
       END;
       IF j <= HIGH(q) THEN q[j] := 0C END
+    ELSIF (tmp[0] = "d") & (Len(tmp) > 1) & (tmp[1] = "_") THEN
+      Cpy(q, "d_-");
+      i := 2;
+      WHILE tmp[i] # 0C DO
+        L := Len(q);
+        IF L < HIGH(q) THEN q[L] := tmp[i]; q[L + 1] := 0C END;
+        INC(i)
+      END
     ELSE
       Cpy(q, "-");
       App(q, tmp)
@@ -164,6 +182,7 @@ PROCEDURE IsImm (s: ARRAY OF CHAR): BOOLEAN;
   BEGIN
     IF Len(s) = 0 THEN RETURN FALSE END;
     RETURN ((s[0] >= "0") & (s[0] <= "9")) OR (s[0] = "-")
+           OR (s[0] = "d")
   END IsImm;
 
 (* ---------------- module / data section ---------------- *)
@@ -179,45 +198,88 @@ PROCEDURE OpenModule (name: ARRAY OF CHAR);
     opened := FileIO.Okay;
     inBody := FALSE;
     dead := FALSE;
-    nTemp := 0; nLab := 0; loopTop := 0;
+    nTemp := 0; nLab := 0; loopTop := 0; nR := 0;
     WL("# QBE IR generated by the V3 step-1 backend");
     WL("")
   END OpenModule;
 
-PROCEDURE DataLine (name: ARRAY OF CHAR; init: ARRAY OF CHAR);
+PROCEDURE DataLine (name: ARRAY OF CHAR; isReal: BOOLEAN;
+                   init: ARRAY OF CHAR);
   BEGIN
     IF ~opened THEN RETURN END;
     W("data $"); W(name);
-    W(" = { w ");
+    IF isReal THEN W(" = { d ") ELSE W(" = { w ") END;
     W(init);
     WL(" }")
   END DataLine;
 
 PROCEDURE DeclVar (name: ARRAY OF CHAR; t: INTEGER);
   BEGIN
-    (* Step 1 only sees INTEGER; t is reserved for later steps. *)
-    DataLine(name, "0")
+    IF SymTab.ClassOf(t) = SymTab.ClReal THEN DataLine(name, TRUE, "0")
+    ELSE DataLine(name, FALSE, "0")
+    END
   END DeclVar;
 
+PROCEDURE IsZeroReal (val: ARRAY OF CHAR): BOOLEAN;
+(* TRUE when the "d_..." literal denotes zero (checked textually). *)
+  VAR i : CARDINAL;
+    seen : BOOLEAN;
+    ch : CHAR;
+  BEGIN
+    i := 0;
+    IF (Len(val) > 1) & (val[0] = "d") & (val[1] = "_") THEN i := 2 END;
+    IF (Len(val) > i) & (val[i] = "-") THEN INC(i) END;
+    seen := FALSE;
+    WHILE val[i] # 0C DO
+      ch := val[i];
+      IF (ch >= "0") & (ch <= "9") THEN
+        seen := TRUE;
+        IF ch # "0" THEN RETURN FALSE END
+      ELSIF (ch # ".") & (ch # "E") & (ch # "e")
+            & (ch # "+") & (ch # "-") THEN
+        RETURN FALSE
+      END;
+      INC(i)
+    END;
+    RETURN seen
+  END IsZeroReal;
+
 PROCEDURE DeclConst (name: ARRAY OF CHAR; val: ARRAY OF CHAR; t: INTEGER);
+  VAR cls : INTEGER;
   BEGIN
-    IF (SymTab.ClassOf(t) = SymTab.ClStr) OR ~IsImm(val) THEN
-      DataLine(name, "0")
-    ELSE
-      DataLine(name, val)
+    cls := SymTab.ClassOf(t);
+    IF (cls = SymTab.ClStr) OR ~IsImm(val) THEN
+      DataLine(name, FALSE, "0"); RETURN
+    END;
+    IF cls = SymTab.ClReal THEN
+      (* This qbe accepts only integer/zero literals in "data":
+         non-zero REALs flush at BeginBody. *)
+      DataLine(name, TRUE, "0");
+      IF ~IsZeroReal(val) & (nR <= HIGH(rNames)) THEN
+        Cpy(rNames[nR], name); Cpy(rVals[nR], val); INC(nR)
+      END
+    ELSE DataLine(name, FALSE, val)
     END
   END DeclConst;
 
 (* ---------------- function body ---------------- *)
 
 PROCEDURE BeginBody;
+  VAR i : CARDINAL;
+    t : QVal;
   BEGIN
     IF ~opened OR inBody THEN RETURN END;
     WL("");
     WL("export function w $main() {");
     WL("@start");
     inBody := TRUE;
-    dead := FALSE
+    i := 0;
+    WHILE i < nR DO
+      NewTemp(t);
+      W("  "); W(t); W(" =d copy "); WL(rVals[i]);
+      StoreVar(rNames[i], t, TRUE);
+      INC(i)
+    END
   END BeginBody;
 
 PROCEDURE CloseModule;
@@ -256,18 +318,17 @@ PROCEDURE NewTemp (VAR t: QVal);
 
 PROCEDURE LoadVar (name: ARRAY OF CHAR; isReal: BOOLEAN; VAR q: QVal);
   BEGIN
-    (* isReal reserved for step 2+; step 1 always passes FALSE. *)
     NewTemp(q);
     Revive;
     W("  "); W(q);
-    W(" =w loadw $");
+    IF isReal THEN W(" =d loadd $") ELSE W(" =w loadw $") END;
     WL(name)
   END LoadVar;
 
 PROCEDURE StoreVar (name: ARRAY OF CHAR; q: ARRAY OF CHAR; isReal: BOOLEAN);
   BEGIN
     Revive;
-    W("  storew ");
+    IF isReal THEN W("  stored ") ELSE W("  storew ") END;
     W(q); W(", $"); WL(name)
   END StoreVar;
 
@@ -276,16 +337,26 @@ PROCEDURE Op3 (mn: ARRAY OF CHAR; res, l, r: ARRAY OF CHAR;
   BEGIN
     Revive;
     W("  "); W(res);
-    W(" =w ");
+    IF isReal THEN W(" =d ") ELSE W(" =w ") END;
     W(mn); W(" "); W(l); W(", "); WL(r)
   END Op3;
 
 PROCEDURE NegQ (a: ARRAY OF CHAR; VAR q: QVal; isReal: BOOLEAN);
   BEGIN
     NewTemp(q);
-    Op3("sub", q, "0", a, FALSE)
+    IF isReal THEN Op3("sub", q, "d_0.0", a, TRUE)
+    ELSE Op3("sub", q, "0", a, FALSE)
+    END
   END NegQ;
 
+PROCEDURE ConvIR (a: ARRAY OF CHAR; VAR q: QVal);
+(* INTEGER -> REAL widening for assignments (swtof). *)
+  BEGIN
+    NewTemp(q);
+    Revive;
+    W("  "); W(q); W(" =d swtof "); WL(a)
+  END ConvIR;
+
 PROCEDURE NotQ (a: ARRAY OF CHAR; VAR q: QVal);
   BEGIN
     NewTemp(q);
@@ -340,13 +411,22 @@ PROCEDURE Cmp (op: INTEGER; l, r: ARRAY OF CHAR; VAR q: QVal;
   VAR mn : ARRAY [0 .. 7] OF CHAR;
   BEGIN
     mn[0] := 0C;
-    (* Step 2: integer comparisons only (OpEq..OpGe). *)
-    IF op = SymTab.OpEq THEN Cpy(mn, "ceqw")
-    ELSIF (op = SymTab.OpNeq1) OR (op = SymTab.OpNeq2) THEN Cpy(mn, "cnew")
-    ELSIF op = SymTab.OpLt THEN Cpy(mn, "csltw")
-    ELSIF op = SymTab.OpLe THEN Cpy(mn, "cslew")
-    ELSIF op = SymTab.OpGt THEN Cpy(mn, "csgtw")
-    ELSE Cpy(mn, "csgew")
+    IF isReal THEN
+      IF op = SymTab.OpEq THEN Cpy(mn, "ceqd")
+      ELSIF (op = SymTab.OpNeq1) OR (op = SymTab.OpNeq2) THEN Cpy(mn, "cned")
+      ELSIF op = SymTab.OpLt THEN Cpy(mn, "cltd")
+      ELSIF op = SymTab.OpLe THEN Cpy(mn, "cled")
+      ELSIF op = SymTab.OpGt THEN Cpy(mn, "cgtd")
+      ELSE Cpy(mn, "cged")
+      END
+    ELSE
+      IF op = SymTab.OpEq THEN Cpy(mn, "ceqw")
+      ELSIF (op = SymTab.OpNeq1) OR (op = SymTab.OpNeq2) THEN Cpy(mn, "cnew")
+      ELSIF op = SymTab.OpLt THEN Cpy(mn, "csltw")
+      ELSIF op = SymTab.OpLe THEN Cpy(mn, "cslew")
+      ELSIF op = SymTab.OpGt THEN Cpy(mn, "csgtw")
+      ELSE Cpy(mn, "csgew")
+      END
     END;
     NewTemp(q);
     Op3(mn, q, l, r, FALSE)
@@ -381,5 +461,5 @@ BEGIN
   opened := FALSE;
   inBody := FALSE;
   dead := FALSE;
-  nTemp := 0; nLab := 0; loopTop := 0
+  nTemp := 0; nLab := 0; loopTop := 0; nR := 0
 END QbeGen.

+ 7 - 0
compiler/tests/t_bad_mixed.mod

@@ -0,0 +1,7 @@
+MODULE TBadMixed;
+VAR i : INTEGER;
+VAR r : REAL;
+BEGIN
+  r := 1.5;
+  i := r
+END TBadMixed.

+ 19 - 0
compiler/tests/t_real.mod

@@ -0,0 +1,19 @@
+MODULE TReal;
+// REAL vars, literals, CONSTs (zero + flushed), conversions,
+// all six comparisons, unary minus on temps. Exit 31.
+CONST Half = 0.5;
+CONST Pi = 3.14;
+CONST Zero = 0.0;
+VAR ExitCode : INTEGER;
+VAR r, s : REAL;
+BEGIN
+  r := 3;
+  s := 2.0 * r + 1.5;
+  IF s > 7.0 THEN ExitCode := 30 ELSE ExitCode := 0 END;
+  IF Pi > 3.0 THEN ExitCode := ExitCode + 1 END;
+  r := s / 2.5 + Half - 0.5 + (-s) + s + Zero - Zero;
+  IF (r = 3.0) AND (r <= 3.0) AND (r >= 3.0) AND (r # 3.5)
+     AND (r < 4.0) THEN ExitCode := ExitCode
+  ELSE ExitCode := 0
+  END
+END TReal.

+ 44 - 19
docs/summary_step2.md

@@ -1,26 +1,51 @@
-# V3 step 2 — scalar type system (planned)
+# V3 step 2 — REAL lowering: the scalar story is complete (done 2026-09-19)
 
-Status: not started. Blaise Phase-2 equivalent (type system).
+With step 1.9's statements + this section, every scalar kind lowers:
+INTEGER/CARDINAL-family, BOOLEAN, CHAR, REAL/LONGREAL (widened to
+`d`), with conversions, comparisons and full control flow.
+Suite 30/30 (14 run + 16 reject), LL(1)-clean, zero regressions.
 
-## Scope
+## Backend (`QbeGen`)
 
-- Types: `REAL` (QBE `d`), `BOOLEAN` (+ `NOT/AND/OR`), `CHAR`,
-  enumerations; `CARDINAL` family via SymTab integer family.
-- Relations (`= # < <= > >=`) → QBE `ceqw/cnew/csltw/...` + `jnz`;
-  `IF/ELSIF/ELSE`, `WHILE` (labels via `NewLabel/EmitLabel/Jmp/Jnz`
-  added to QbeGen).
-- Mixed INTEGER/REAL rules (Test1 parity: INTEGER assigns to REAL
-  via `swtof`, no mixed arithmetic), 212/213/214 errors.
-- `H` hex + `0x` prefix decision (Redux option) recorded here.
+- `d_` immediates (`NormReal`); `IsImm` accepts them; `NegFold`
+  folds `d_` literals (Test2 recipe).
+- `DeclVar`: `data $x = { d 0 }` for ClReal. `DeclConst`: zero
+  reals as `d 0` (textual `IsZeroReal`, incl. `-0.0`); non-zero
+  REALs as `d 0` + a recorded runtime `=d copy` flushed at
+  `BeginBody` (this qbe rejects `d 1.5` in `data` — same limit
+  Test2 hit, same workaround).
+- Threaded `isReal` through LoadVar (`loadd`) / StoreVar (`stored`)
+  / Op3 (`=d`); `Cmp` gains ceqd/cned/cltd/cled/cgtd/cged;
+  `NegQ` via `sub d_0.0`; new `ConvIR` (`swtof`) for
+  INTEGER→REAL assignment widening.
 
-## Acceptance (planned)
+## Grammar (isReal threading, Test1 type rules)
 
-- ~15 run tests (bool logic, real arith + conversions, char/enum,
-  IF/WHILE nesting) + reject tests (212/213/214); full suite green.
-- Grammar stays LL(1)-clean; `.ssa` deterministic.
+- Real literals fold (`NormReal`, no more 230); `/` added to MulOp
+  (OpSlash → `div`, Test2 parity: also defined on integers as
+  truncating division — documented, matches the lenient family).
+- `VAR x: REAL` accepted; loads/stores carry the class flag in
+  Design (var/param/const), SimExpr/Term (arith + unary minus),
+  Expr (comparisons; flag read before the type becomes BOOLEAN).
+- Conversions ONLY via assignment (`r := 3` → swtof); mixed
+  arithmetic and mixed comparisons stay 211/213; REAL→INTEGER
+  is 210. No implicit anything else.
 
-## Files (planned)
+## Tests
 
-- `compiler/src/M2.atg` § types/statements/expressions growth.
-- `compiler/src/QbeGen` + comparison/branch emission.
-- `compiler/tests/` additions; this file filled in on landing.
+`t_real` → 31 (zero + flushed CONSTs, conversion, all six real
+comparisons, `/`, real unary-minus temps, int/real branches) with
+hand-checked `.ssa` (flush block, swtof, cgtd all present);
+`t_bad_mixed` (`i := r`) → 210.
+
+## Honest leftovers (scalar)
+
+- CHAR arithmetic is 211; enum literals don't lower (use is 230);
+  enum/char subrange bounds are integer-only.
+- `LONGINT`/`LONGREAL` are aliases (true quads = later step).
+- `5 / 2` on integers truncates (Test2 parity, not Wirth `/`).
+- Division by zero traps via QBE/SIGFPE (no language Trap yet —
+  arrives with `runtime/syslib/Trap`).
+
+Next: step 3 composites (ARRAY/RECORD/SET/POINTER + the locked
+length-prefixed layout + WITH + IN).