← board

open array of static-array elements: call-site marshaling broken

Repro

type T = array[1..3] of Integer;
procedure P(a: array of T);
var r: T;
begin
  writeln(Length(a));   { expect 2; pxx: ~1000+ }
  r := a[1];
  writeln(r[1]);        { expect 3; pxx: an address }
end;
var g0, g1: T;
begin
  g0[1] := 1; g1[1] := 3;  { ... }
  P([g0, g1]);
end.

Scalar open arrays are fine; the aggregate-element case mis-builds the (pointer, length) pair at the call site (or indexes with base-element stride). Check the open-array literal construction for aggregate elements AND the callee's Length/stride metadata for array-typed elements.

Recon (2026-07-15 night, parked)

Recon 2 (2026-07-15, agent-A — root confirmed, feature-sized)

Confirmed this is NOT open-array-param-specific: a plain d: array of T (T a static-array type) in main body already mis-indexes. Length(d) is now correct (2), but d[i] yields addresses/garbage — the dynarray uses a SCALAR element stride (base-type size, e.g. 4) instead of the element ROW size (e.g. 12).

Root: parser.inc dynamic array of <element> parse (~15680–15713) handles a named DYN-array-alias element (array of TA, TA dyn) but has NO case for a named FIXED-array element (array of T, T = array[1..3] of Integer) — the element collapses to its base scalar via ParseTypeKind, dropping T's dimension. This is the exact mirror of the handled FIXED-outer case at ~15732 ("Without this ParseTypeKind drops TG's dimension and the element stride collapses to the base type's size"). The inline form array of array[1..N] of Integer is explicitly rejected (parser.inc:15690 "mixed static/dynamic nested arrays not supported"), so aggregate-element dynamic arrays are currently an UNSUPPORTED FEATURE, not a small metadata miss.

A proper fix is multi-layer and cross-sensitive:

  1. Decl: record the element's fixed-array shape (row byte-size + inner dims) on the dynarray symbol so index/SetLength/copy can size a row.
  2. SetLength: allocate count × rowSize (not count × baseSize).
  3. Index d[i]: stride = rowSize; return the row as an aggregate lvalue so d[i][j] sub-indexes and r := d[i] row-copies.
  4. Open-array param marshalling: element = row.
  5. Cross backends (shared dyn-array header layout).

Parked as feature-sized. tforin14.pp depends on this (its residual after the tforin25 fix). Byte-identical parity for both awaits this feature.

Acceptance

Recon 3 (2026-07-15, agent-A — exact gap + representation options)

Pinned the precise parser gap and the representation choice for a future session:

Still feature-sized; the above is the executable plan for a dedicated session.

Resolution (2026-07-15, agent-ACP — commit f6a843f0)

Landed the full row model per Recon 3: SymDynElemRowLen/SymDynElemRowLo (+ ArrTypeElemRowLen/Lo, ProcParamElemRowLen/Lo mirrors, all Alloc* resets), parse support at var-decl / type-alias / open-array-param sites (managed row elements rejected loudly), IR row stride + sub-index low bound + row-copy both directions, SetLength count*rowsize (x86-64 inline + shared PXXDynSetLen descriptor so cross rides), row-sized open-array ctor, and the for-in open-array-param Length fix (ArrLen=1000 placeholder no longer used as a static bound). tforin14 passes byte-identical to FPC; regression test/test_dynarray_of_fixed_array.pas (13 checks, FPC-differential identical, wired into make test). Deliberately NOT covered (still loud errors or parked): array of array of <fixed T> (depth>1), record/class FIELDS of this shape, static-outer P(garr) copy-in (NDims=2 guard), managed row elements.

Log