Ver Fonte

language: add open multi-dimensional array views (M3)

Eric Streit há 1 dia atrás
pai
commit
557addbb38

+ 8 - 0
compiler/run_tests.sh

@@ -68,6 +68,14 @@ expect_run t_real.mod 31
 expect_run t_array.mod 108
 expect_run t_array.mod 108
 expect_run t_multidim.mod 42
 expect_run t_multidim.mod 42
 expect_run t_multidim_local.mod 42
 expect_run t_multidim_local.mod 42
+expect_run t_open2d.mod 42
+expect_run t_open2d_high.mod 42
+expect_run t_open2d_value.mod 42
+expect_run t_open3d.mod 42
+expect_run t_open3d_row.mod 42
+expect_run t_open_row_to_1d.mod 42
+expect_run t_open_flatten_byte.mod 42
+expect_run t_open2d_nested.mod 42
 expect_run t_subrange.mod 42
 expect_run t_subrange.mod 42
 expect_run t_emptychar.mod 42
 expect_run t_emptychar.mod 42
 expect_run t_system.mod 42
 expect_run t_system.mod 42

+ 15 - 3
compiler/src/Lower.mod

@@ -1241,11 +1241,12 @@ PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
       ct: SymTab.TypeIndex; csfx: BOOLEAN; cl: CHAR;
       ct: SymTab.TypeIndex; csfx: BOOLEAN; cl: CHAR;
       seq, next, arg: AST.Node;
       seq, next, arg: AST.Node;
       vaD, vaB, vaN: ARRAY [0 .. 63] OF QbeGen.QVal;
       vaD, vaB, vaN: ARRAY [0 .. 63] OF QbeGen.QVal;
-      nVa, kva: CARDINAL;
+      freeDesc: ARRAY [0 .. 127] OF QbeGen.QVal;
+      nVa, nFree, kva, kfree: CARDINAL;
   BEGIN
   BEGIN
     qual := CalleeAlias(n, nm);
     qual := CalleeAlias(n, nm);
     ind := FALSE; isC := FALSE; va := FALSE; pi := -1; ext := FALSE;
     ind := FALSE; isC := FALSE; va := FALSE; pi := -1; ext := FALSE;
-    nVa := 0;
+    nVa := 0; nFree := 0; kfree := 0;
     IF qual AND qualIsVar THEN
     IF qual AND qualIsVar THEN
       (* M.Error where Error is a procedure-typed variable: indirect. *)
       (* M.Error where Error is a procedure-typed variable: indirect. *)
       LowerDesign(AST.Child(n, 0), ct, ck, qa, cqn, csfx);
       LowerDesign(AST.Child(n, 0), ct, ck, qa, cqn, csfx);
@@ -1361,6 +1362,9 @@ PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
                 QbeGen.CopyOp(qb, vaB[nVa]);
                 QbeGen.CopyOp(qb, vaB[nVa]);
                 QbeGen.CopyOp(qn, vaN[nVa]);
                 QbeGen.CopyOp(qn, vaN[nVa]);
                 INC(nVa)
                 INC(nVa)
+              END;
+              IF nFree <= HIGH(freeDesc) THEN
+                QbeGen.CopyOp(qt, freeDesc[nFree]); INC(nFree)
               END
               END
             ELSIF (SymTab.ClassOf(at) = SymTab.ClChar)
             ELSIF (SymTab.ClassOf(at) = SymTab.ClChar)
                AND (SymTab.ClassOf(ft) = SymTab.ClArray)
                AND (SymTab.ClassOf(ft) = SymTab.ClArray)
@@ -1404,7 +1408,10 @@ PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
               IF lastView AND (SymTab.ClassOf(ft) = SymTab.ClArray)
               IF lastView AND (SymTab.ClassOf(ft) = SymTab.ClArray)
                  AND (SymTab.ArrayDepth(ft) = 1) THEN
                  AND (SymTab.ArrayDepth(ft) = 1) THEN
                 QbeGen.ViewToOpen1D(qe, ft, qt, qb, qn);
                 QbeGen.ViewToOpen1D(qe, ft, qt, qb, qn);
-                QbeGen.CopyOp(qt, qe)
+                QbeGen.CopyOp(qt, qe);
+                IF nFree <= HIGH(freeDesc) THEN
+                  QbeGen.CopyOp(qt, freeDesc[nFree]); INC(nFree)
+                END
               END;
               END;
               (* A SET/PACKEDSET value actual with a different
               (* A SET/PACKEDSET value actual with a different
                  representation is converted to a temporary of the
                  representation is converted to a temporary of the
@@ -1447,6 +1454,11 @@ PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
     WHILE kva < nVa DO
     WHILE kva < nVa DO
       QbeGen.ViewCopyBack(vaD[kva], vaB[kva], vaN[kva]);
       QbeGen.ViewCopyBack(vaD[kva], vaB[kva], vaN[kva]);
       INC(kva)
       INC(kva)
+    END;
+    kfree := 0;
+    WHILE kfree < nFree DO
+      QbeGen.ViewFree(freeDesc[kfree]);
+      INC(kfree)
     END
     END
   END LowerCall;
   END LowerCall;
 
 

+ 1 - 1
compiler/src/M2.atg

@@ -1482,7 +1482,7 @@ PRODUCTIONS
                                              ilo, ihi: INTEGER; .)
                                              ilo, ihi: INTEGER; .)
     = "ARRAY"                           (. allowFwdType := FALSE; .)
     = "ARRAY"                           (. allowFwdType := FALSE; .)
       ( "OF"
       ( "OF"
-        Type<elem, FALSE>               (. LexName(eb); (* the element ident *)
+        Type<elem, allowOpen>           (. LexName(eb); (* the element ident *)
                                            IF NOT allowOpen THEN
                                            IF NOT allowOpen THEN
                                              SemError(230) END;
                                              SemError(230) END;
                                            t := SymTab.NewOpenArray(elem);
                                            t := SymTab.NewOpenArray(elem);

+ 1 - 1
compiler/src/M2.lst

@@ -1506,7 +1506,7 @@ Listing:
  1482                                               ilo, ihi: INTEGER; .)
  1482                                               ilo, ihi: INTEGER; .)
  1483      = "ARRAY"                           (. allowFwdType := FALSE; .)
  1483      = "ARRAY"                           (. allowFwdType := FALSE; .)
  1484        ( "OF"
  1484        ( "OF"
- 1485          Type<elem, FALSE>               (. LexName(eb); (* the element ident *)
+ 1485          Type<elem, allowOpen>           (. LexName(eb); (* the element ident *)
  1486                                             IF NOT allowOpen THEN
  1486                                             IF NOT allowOpen THEN
  1487                                               SemError(230) END;
  1487                                               SemError(230) END;
  1488                                             t := SymTab.NewOpenArray(elem);
  1488                                             t := SymTab.NewOpenArray(elem);

+ 3 - 0
compiler/src/QbeGen.def

@@ -383,6 +383,9 @@ PROCEDURE ViewToOpen1D (view: ARRAY OF CHAR; ft: INTEGER;
 PROCEDURE ViewCopyBack (desc, base, nbytes: ARRAY OF CHAR);
 PROCEDURE ViewCopyBack (desc, base, nbytes: ARRAY OF CHAR);
 (* Copy a temp descriptor's payload back into a view base after a call. *)
 (* Copy a temp descriptor's payload back into a view base after a call. *)
 
 
+PROCEDURE ViewFree (desc: ARRAY OF CHAR);
+(* Release a descriptor allocated by ViewToOpen1D. *)
+
 PROCEDURE ElemLoad (addr: ARRAY OF CHAR; t: INTEGER; VAR q: QVal);
 PROCEDURE ElemLoad (addr: ARRAY OF CHAR; t: INTEGER; VAR q: QVal);
 PROCEDURE ElemStore (addr, v: ARRAY OF CHAR; t: INTEGER);
 PROCEDURE ElemStore (addr, v: ARRAY OF CHAR; t: INTEGER);
 (* Element access by element-type index (CHAR→loadub … nested→loadl). *)
 (* Element access by element-type index (CHAR→loadub … nested→loadl). *)

+ 6 - 0
compiler/src/QbeGen.mod

@@ -3399,6 +3399,12 @@ PROCEDURE ViewCopyBack (desc, base, nbytes: ARRAY OF CHAR);
     W(base); W(", l "); W(p); W(", l "); W(nbytes); WL(")")
     W(base); W(", l "); W(p); W(", l "); W(nbytes); WL(")")
   END ViewCopyBack;
   END ViewCopyBack;
 
 
+PROCEDURE ViewFree (desc: ARRAY OF CHAR);
+  BEGIN
+    Revive;
+    W("  call $free(l "); W(desc); WL(")")
+  END ViewFree;
+
 PROCEDURE ElemLoad (addr: ARRAY OF CHAR; t: INTEGER; VAR q: QVal);
 PROCEDURE ElemLoad (addr: ARRAY OF CHAR; t: INTEGER; VAR q: QVal);
 (* Loads one element of (element-)type t. Nested/pointer elements
 (* Loads one element of (element-)type t. Nested/pointer elements
    are addresses (loadl). *)
    are addresses (loadl). *)

+ 35 - 12
compiler/src/SymTab.mod

@@ -357,6 +357,22 @@ PROCEDURE EnterPending (name: ARRAY OF CHAR; kind: INTEGER): BOOLEAN;
     RETURN node # NIL
     RETURN node # NIL
   END EnterPending;
   END EnterPending;
 
 
+PROCEDURE ArrayShapeOk (actual, formal: TypeIndex): BOOLEAN;
+(* TRUE when two array types have the same rank and the same leaf type,
+   allowing fixed levels to match open levels at any position.  Used for
+   open-array parameter compatibility (M3). *)
+  VAR a, f: TypeIndex; n: CARDINAL;
+  BEGIN
+    IF (ClassOf(actual) # ClArray) OR (ClassOf(formal) # ClArray) THEN
+      RETURN FALSE
+    END;
+    a := actual; f := formal; n := 0;
+    WHILE (ClassOf(a) = ClArray) AND (ClassOf(f) = ClArray) DO
+      a := ArrayElem(a); f := ArrayElem(f); INC(n)
+    END;
+    RETURN (n > 0) AND SameType(a, f)
+  END ArrayShapeOk;
+
 PROCEDURE VarParamOk (actual, formal: TypeIndex): BOOLEAN;
 PROCEDURE VarParamOk (actual, formal: TypeIndex): BOOLEAN;
 (* Addressable-designator compatibility for VAR formals: same type,
 (* Addressable-designator compatibility for VAR formals: same type,
    or fixed array into open array with same element type, or string
    or fixed array into open array with same element type, or string
@@ -375,10 +391,13 @@ PROCEDURE VarParamOk (actual, formal: TypeIndex): BOOLEAN;
     IF (actual = dAddr) AND (ClassOf(formal) = ClPtr) THEN
     IF (actual = dAddr) AND (ClassOf(formal) = ClPtr) THEN
       RETURN TRUE
       RETURN TRUE
     END;
     END;
-    IF IsOpenArray(formal)
- AND (ClassOf(actual) = ClArray) AND (ArrayDepth(actual) = 1) THEN
-      fa := ArrayElem(actual); fe := ArrayElem(formal);
-      IF SameType(fa, fe) THEN RETURN TRUE END
+    IF IsOpenArray(formal) AND (ClassOf(actual) = ClArray) THEN
+      IF IsByteBuf(formal) THEN RETURN TRUE END;
+      IF ArrayShapeOk(actual, formal) THEN RETURN TRUE END;
+      IF ArrayDepth(actual) = 1 THEN
+        fa := ArrayElem(actual); fe := ArrayElem(formal);
+        IF SameType(fa, fe) THEN RETURN TRUE END
+      END
     END;
     END;
     IF IsOpenArray(formal) AND (ClassOf(actual) = ClStr) THEN
     IF IsOpenArray(formal) AND (ClassOf(actual) = ClStr) THEN
       fe := ArrayElem(formal);
       fe := ArrayElem(formal);
@@ -424,11 +443,11 @@ PROCEDURE ParamTypesOk (x, y: TypeIndex): BOOLEAN;
   BEGIN
   BEGIN
     IF SameType(x, y) THEN RETURN TRUE END;
     IF SameType(x, y) THEN RETURN TRUE END;
     IF (x = InvalidType) OR (y = InvalidType) THEN RETURN TRUE END;
     IF (x = InvalidType) OR (y = InvalidType) THEN RETURN TRUE END;
-    IF IsOpenArray(x) AND (ClassOf(y) = ClArray) AND (ArrayDepth(y) = 1) THEN
-      RETURN SameType(ArrayElem(x), ArrayElem(y))
+    IF IsOpenArray(x) AND (ClassOf(y) = ClArray) THEN
+      RETURN ArrayShapeOk(y, x)
     END;
     END;
-    IF IsOpenArray(y) AND (ClassOf(x) = ClArray) AND (ArrayDepth(x) = 1) THEN
-      RETURN SameType(ArrayElem(x), ArrayElem(y))
+    IF IsOpenArray(y) AND (ClassOf(x) = ClArray) THEN
+      RETURN ArrayShapeOk(x, y)
     END;
     END;
     RETURN FALSE
     RETURN FALSE
   END ParamTypesOk;
   END ParamTypesOk;
@@ -474,7 +493,7 @@ PROCEDURE FormalOk (a, b: TypeIndex): BOOLEAN;
        descriptor, so a DEFINITION/IMPLEMENTATION pair never shares the
        descriptor, so a DEFINITION/IMPLEMENTATION pair never shares the
        type index and SameType would otherwise reject it. *)
        type index and SameType would otherwise reject it. *)
     IF IsOpenArray(a) AND IsOpenArray(b) THEN
     IF IsOpenArray(a) AND IsOpenArray(b) THEN
-      RETURN SameType(ArrayElem(a), ArrayElem(b))
+      RETURN ArrayShapeOk(a, b)
     END;
     END;
     (* Procedure types are built fresh per parse, so a
     (* Procedure types are built fresh per parse, so a
        DEFINITION/IMPLEMENTATION pair never shares the index; compare
        DEFINITION/IMPLEMENTATION pair never shares the index; compare
@@ -1283,7 +1302,7 @@ PROCEDURE ByteBufOk (at, ft: TypeIndex): BOOLEAN;
         OR (c = ClPtr) OR (c = ClLong) OR (c = ClStr)
         OR (c = ClPtr) OR (c = ClLong) OR (c = ClStr)
         OR (c = ClByte) OR (c = ClSByte)
         OR (c = ClByte) OR (c = ClSByte)
         OR (c = ClWord16) OR (c = ClSWord16)
         OR (c = ClWord16) OR (c = ClSWord16)
-        OR (c = ClWord16) OR (c = ClSWord16)
+        OR (c = ClArray)
   END ByteBufOk;
   END ByteBufOk;
 
 
 PROCEDURE TypesOverflow (): BOOLEAN;
 PROCEDURE TypesOverflow (): BOOLEAN;
@@ -3328,8 +3347,12 @@ PROCEDURE Assignable (src, dst: TypeIndex): BOOLEAN;
     IF (ClassOf(src) = ClArray) AND (ClassOf(dst) = ClArray) THEN
     IF (ClassOf(src) = ClArray) AND (ClassOf(dst) = ClArray) THEN
       (* a fixed 1-D array is compatible with an open formal of the
       (* a fixed 1-D array is compatible with an open formal of the
          same element type (value or VAR) *)
          same element type (value or VAR) *)
-      IF IsOpenArray(dst) AND (ArrayDepth(src) = 1) THEN
-        RETURN SameType(ArrayElem(src), ArrayElem(dst))
+      IF IsOpenArray(dst) THEN
+        IF IsByteBuf(dst) THEN RETURN TRUE END;
+        IF ArrayShapeOk(src, dst) THEN RETURN TRUE END;
+        IF ArrayDepth(src) = 1 THEN
+          RETURN SameType(ArrayElem(src), ArrayElem(dst))
+        END
       END;
       END;
       RETURN SameType(src, dst)
       RETURN SameType(src, dst)
     END;
     END;

+ 19 - 0
compiler/tests/t_open2d.mod

@@ -0,0 +1,19 @@
+MODULE TOpen2D;
+(* M3: fixed 2-D actual passed to an open 2-D VAR formal. *)
+TYPE Mat = ARRAY [0..1], [0..2] OF INTEGER;
+VAR ExitCode : INTEGER;
+    m : Mat;
+
+PROCEDURE Fill (VAR a : ARRAY OF ARRAY OF INTEGER);
+BEGIN
+  IF (HIGH(a) # 1) OR (HIGH(a[0]) # 2) THEN
+    ExitCode := 1
+  ELSE
+    a[1, 2] := 42
+  END
+END Fill;
+
+BEGIN
+  Fill(m);
+  IF m[1, 2] = 42 THEN ExitCode := 42 ELSE ExitCode := 2 END
+END TOpen2D.

+ 14 - 0
compiler/tests/t_open2d_high.mod

@@ -0,0 +1,14 @@
+MODULE TOpen2DHigh;
+(* M3: HIGH on open multi-dimensional views and their row subviews. *)
+TYPE Mat = ARRAY [0..1], [0..2] OF INTEGER;
+VAR ExitCode : INTEGER;
+    m : Mat;
+
+PROCEDURE Check (a : ARRAY OF ARRAY OF INTEGER) : BOOLEAN;
+BEGIN
+  RETURN (HIGH(a) = 1) AND (HIGH(a[0]) = 2) AND (LEN(a) = 2)
+END Check;
+
+BEGIN
+  IF Check(m) THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TOpen2DHigh.

+ 20 - 0
compiler/tests/t_open2d_nested.mod

@@ -0,0 +1,20 @@
+MODULE TOpen2DNested;
+(* M3: an open 2-D formal forwarded to another open 2-D formal. *)
+TYPE Mat = ARRAY [0..1], [0..2] OF INTEGER;
+VAR ExitCode : INTEGER;
+    m : Mat;
+
+PROCEDURE Inner (VAR a : ARRAY OF ARRAY OF INTEGER);
+BEGIN
+  a[1, 2] := 42
+END Inner;
+
+PROCEDURE Outer (VAR a : ARRAY OF ARRAY OF INTEGER);
+BEGIN
+  Inner(a)
+END Outer;
+
+BEGIN
+  Outer(m);
+  IF m[1, 2] = 42 THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TOpen2DNested.

+ 15 - 0
compiler/tests/t_open2d_value.mod

@@ -0,0 +1,15 @@
+MODULE TOpen2DValue;
+(* M3: value open 2-D formal reads through a fixed-array view. *)
+TYPE Mat = ARRAY [0..1], [0..2] OF INTEGER;
+VAR ExitCode : INTEGER;
+    m : Mat;
+
+PROCEDURE Sum (a : ARRAY OF ARRAY OF INTEGER) : INTEGER;
+BEGIN
+  RETURN a[0, 1] + a[1, 2]
+END Sum;
+
+BEGIN
+  m[0, 1] := 10; m[1, 2] := 32;
+  IF Sum(m) = 42 THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TOpen2DValue.

+ 15 - 0
compiler/tests/t_open3d.mod

@@ -0,0 +1,15 @@
+MODULE TOpen3D;
+(* M3: fixed 3-D actual passed to an open 3-D VAR formal. *)
+TYPE Cube = ARRAY [0..1], [0..1], [0..1] OF INTEGER;
+VAR ExitCode : INTEGER;
+    c : Cube;
+
+PROCEDURE SetIt (VAR a : ARRAY OF ARRAY OF ARRAY OF INTEGER);
+BEGIN
+  a[1, 0, 1] := 42
+END SetIt;
+
+BEGIN
+  SetIt(c);
+  IF c[1, 0, 1] = 42 THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TOpen3D.

+ 20 - 0
compiler/tests/t_open3d_row.mod

@@ -0,0 +1,20 @@
+MODULE TOpen3DRow;
+(* M3: a rank-2 subview of an open 3-D array forwarded to open 2-D. *)
+TYPE Cube = ARRAY [0..1], [0..1], [0..2] OF INTEGER;
+VAR ExitCode : INTEGER;
+    c : Cube;
+
+PROCEDURE SetPlane (VAR a : ARRAY OF ARRAY OF INTEGER);
+BEGIN
+  a[0, 2] := 42
+END SetPlane;
+
+PROCEDURE Outer (VAR a : ARRAY OF ARRAY OF ARRAY OF INTEGER);
+BEGIN
+  SetPlane(a[1])
+END Outer;
+
+BEGIN
+  Outer(c);
+  IF c[1, 0, 2] = 42 THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TOpen3DRow.

+ 19 - 0
compiler/tests/t_open_flatten_byte.mod

@@ -0,0 +1,19 @@
+MODULE TOpenFlattenByte;
+(* M3: a 2-D array passed to VAR ARRAY OF BYTE; the byte view aliases the
+   original payload. *)
+TYPE Mat = ARRAY [0..1], [0..2] OF CARDINAL;
+VAR ExitCode : INTEGER;
+    m : Mat;
+
+PROCEDURE Fill (VAR b : ARRAY OF BYTE);
+BEGIN
+  b[0] := 42;
+  b[1] := 0;
+  b[2] := 0;
+  b[3] := 0
+END Fill;
+
+BEGIN
+  Fill(m);
+  IF m[0, 0] = 42 THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TOpenFlattenByte.

+ 21 - 0
compiler/tests/t_open_row_to_1d.mod

@@ -0,0 +1,21 @@
+MODULE TOpenRowTo1D;
+(* M3: a row subview of an open 2-D array passed to a 1-D open VAR
+   formal; writes must reach the original row. *)
+TYPE Mat = ARRAY [0..1], [0..2] OF INTEGER;
+VAR ExitCode : INTEGER;
+    m : Mat;
+
+PROCEDURE SetRow (VAR r : ARRAY OF INTEGER);
+BEGIN
+  r[2] := 42
+END SetRow;
+
+PROCEDURE Outer (VAR a : ARRAY OF ARRAY OF INTEGER);
+BEGIN
+  SetRow(a[0])
+END Outer;
+
+BEGIN
+  Outer(m);
+  IF m[0, 2] = 42 THEN ExitCode := 42 ELSE ExitCode := 1 END
+END TOpenRowTo1D.

+ 26 - 11
docs/design-open-arrays.md

@@ -1,11 +1,10 @@
 # Design — multi-dimensional open arrays
 # Design — multi-dimensional open arrays
 
 
-**Status: design + M1 helpers + M2 fixed storage (2026-10-23).**
-Rank-2/3 fixed arrays now use a flat payload with per-level view
-descriptors; open arrays and builtins remain M3/M4.  Current gates:
-suite **284/284**, fixpoint OK **3,439,456 bytes**, corpus **512/612
-compile-OK**, 0 crashes.  The view descriptor ABI is designed but open
-view descriptors/call conversion are not implemented yet.
+**Status: design + M1 helpers + M2 fixed storage + M3 open views
+(2026-10-23).**  Rank-2/3 fixed and open arrays now use the view ABI;
+`SIZE`/`ADR` and the remaining 1-D descriptor migration are M4.  Current
+gates: suite **292/292**, fixpoint OK **3,453,395 bytes**, corpus
+**518/612 compile-OK**, 0 crashes.
 
 
 Interactive decisions already taken:
 Interactive decisions already taken:
 
 
@@ -386,11 +385,27 @@ Gates after each step: suite, fixpoint, corpus.
    - Tests: `t_multidim`, `t_multidim_local`, `t_array`, `t_ctor3`,
    - Tests: `t_multidim`, `t_multidim_local`, `t_array`, `t_ctor3`,
      `t_nestidx`, `t_nestarr`, `t_ptrnest`, `showcase6`; suite 284/284,
      `t_nestidx`, `t_nestarr`, `t_ptrnest`, `showcase6`; suite 284/284,
      fixpoint OK, corpus 512/612 with 0 crashes.
      fixpoint OK, corpus 512/612 with 0 crashes.
-3. **M3 — open view descriptors** (stack views, calls, row subviews,
-   flattening).  Replace the M2 copy-in/copy-back adapter with real view
-   descriptors, including `VAR` writes and row/subarray lifetime.
-4. **M4 — builtins/indexing/assignment** (`SIZE`, `HIGH`, `ADR`,
-   `ElemAddr`, `CopyArray`).
+3. **M3 — open view descriptors** — **done (2026-10-23)**
+   - Nested open array formals (`ARRAY OF ARRAY OF ...`) parse correctly.
+   - Fixed/open multi-dimensional actuals and rank-2/3 subviews are
+     passed as `ArrayView` pointers; `VAR` writes go straight to `base`.
+   - Open multi formals index through the view (`count`, `stride`,
+     `base`, `next`), and `HIGH`/`LEN` read the view count.  A fixed
+     array, an open formal, a row subview and a forwarded subview all
+     work without rebuilding the payload.
+   - `ARRAY OF BYTE`/`SBYTE` and row → 1-D conversions remain adapted to
+     the legacy 1-D `{count, data}` descriptor.  The temporary adapter
+     allocates a descriptor and, for `VAR`, copies the payload back;
+     this preserves write-through while the compiler still has the old
+     1-D ABI and C shims.  It is freed after the call.  Migrating that
+     legacy 1-D path to a pointer-based view is part of M4.
+   - Tests: `t_open2d`, `t_open2d_value`, `t_open2d_high`,
+     `t_open2d_nested`, `t_open3d`, `t_open3d_row`,
+     `t_open_row_to_1d`, `t_open_flatten_byte`.  Corpus gains
+     `unbounded`, `unbounded2`, `unbounded3`, `unbounded4` and
+     `array9` (compile gate).
+4. **M4 — builtins/legacy 1-D views** (`SIZE`, `TSIZE`, `ADR`, open 1-D
+   descriptor migration, remaining `CopyArray`/assignment cases).
 5. **M5 — records/constructors/corpus**
 5. **M5 — records/constructors/corpus**
    (multi array fields, records arrays, constructors; run the 8 corpus
    (multi array fields, records arrays, constructors; run the 8 corpus
    cases).
    cases).

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

@@ -1,8 +1,8 @@
 # Next session — plan (from 2026-10-23)
 # Next session — plan (from 2026-10-23)
 
 
-**Branch:** `master`.  **Feature state:** `v3-exceptions` + `v3-ptrchain` + `v3-function-exception` + `v3-finally` + open-array M1/M2.
-**Gates:** suite **284/284**, fixpoint **OK (3,439,456 bytes)**,
-corpus **512/612, 0 crashes**.
+**Branch:** `master`.  **Feature state:** `v3-exceptions` + `v3-ptrchain` + `v3-function-exception` + `v3-finally` + open-array M1/M2/M3.
+**Gates:** suite **292/292**, fixpoint **OK (3,453,395 bytes)**,
+corpus **518/612, 0 crashes**.
 
 
 Read first: `docs/STATUS.md`, `docs/features.md`,
 Read first: `docs/STATUS.md`, `docs/features.md`,
 `docs/gaps/README.md`, `docs/summary_session-2026-10-23-exceptions.md`.
 `docs/gaps/README.md`, `docs/summary_session-2026-10-23-exceptions.md`.
@@ -98,8 +98,16 @@ Suite **284/284**, fixpoint **OK**, corpus **512/612, 0 crashes**.
 Corpus: `unbounded`, `unbounded2/3/4/6/8`, `array9`, `adrunbounded3`.
 Corpus: `unbounded`, `unbounded2/3/4/6/8`, `array9`, `adrunbounded3`.
 Do not just accept the files; the ABI and `ADR(m) = ADR(m[0])` checks
 Do not just accept the files; the ABI and `ADR(m) = ADR(m[0])` checks
 must be real.  `unbounded5` remains a declaration-order case.  Next is
 must be real.  `unbounded5` remains a declaration-order case.  Next is
-M3: real open view descriptors, stack views, calls and row/subarray
-conversion, replacing the M2 temporary copy adapter.
+**M3 — open view descriptors — done (2026-10-23).**  Nested open-array
+formals parse; fixed/open rank-2/3 arrays and rank-2/3 subviews are
+passed as `ArrayView` pointers; open multi formals index through
+`count/stride/base/next`; `HIGH`/`LEN` read the view count; `VAR` and
+value open multi calls share the view (no payload copy).  Row → 1-D and
+`ARRAY OF BYTE` conversions still use the legacy descriptor adapter, now
+with copy-back for `VAR` and `free` after the call; migrating the 1-D
+descriptor itself is M4.  Tests: `t_open2d*`, `t_open3d*`,
+`t_open_row_to_1d`, `t_open_flatten_byte`; corpus **518/612**.  Next is
+M4: `SIZE`/`TSIZE`/`ADR`, 1-D view migration, remaining copy cases.
 
 
 ## 4. Runtime range checks — done, follow-ups
 ## 4. Runtime range checks — done, follow-ups