← board

Why this is a decision and not a ticket

bug-a-address-of-an-open-array-element-points-at-the-marshalling-temp (A, p55) is correctly diagnosed and correctly parked. It has been read by three sessions independently — the author (twice, the second time to correct its own claims against FPC), franka-29, and frankuser — and every reader stopped at the same sentence: "633 IsArray sites across 27 files, so do not start it casually."

Each stop looks like the ticket being hard rather than the ticket being mis-filed, so nobody escalates, and a p55 slot in ready --track A guarantees the next arrival repeats the pass. That is the failure this lane exists to catch.

The measured facts, so the decision needs no re-derivation

All rows measured 2026-09-03 beside fpc -Mobjfpc -O2, pinned in test/test_open_array_param_aliasing.pas (which runs unmodified under FPC and is identical there on every row, plus i386/aarch64/arm32/riscv32):

shape pxx FPC
@a[0] = @caller[0], DYNAMIC argument, var and const TRUE TRUE
@a[0] = @caller[0], STATIC argument, var and const FALSE TRUE
@a[0] = @caller[0], by VALUE FALSE FALSE
element read, write-through, Length, High, stride correct correct

The by-value row is not a divergence — a by-value open array is a copy by definition, and asserting = there would be asserting the language is something else. The dynamic rows already alias. The whole of the question is the static row, and its consequence is bounded: the temp is written back on return, so an address that stays INSIDE the call (Move(a[0], …), handing @a[0] to a C routine for the duration) behaves correctly. It bites only when the address escapes — stored in a field, returned, retained past the call.

The arms

A — the descriptor. Pass (pointer, high), FPC's shape. Correct for every row, and it is the answer the language's own idiom expects. Cost: the [ptr-8] convention is shared with dynamic arrays and managed strings, so this is not a local change to open arrays — it is a wire-format change touching 633 IsArray sites across 27 files, 6 of them backends. Weeks, and it serialises anyone working in the backends while it lands.

B — prefix the argument's own storage with 8 bytes. Then a static array carries the header and can be passed by reference. Works for a local or a global, whose slot layout the compiler owns outright. Impossible for a record field or a 2-D row, whose offsets are observable. Leaves two behaviours for one construct with the second still broken and looking fixed. Rejected on that, not on difficulty — and worth keeping visible precisely because it is the arm that looks cheap.

C — keep one word, record the divergence as chosen. The one-word convention is a genuine simplification, not an accident: one representation serves open arrays, dynamic arrays and AnsiString handles. The cost is the escaping-address row, and the correct rows are already pinned by a test that deliberately does NOT assert today's wrong answer, so a future change cannot silently take the right ones away.

Recommendation — C, revisit on real source

CLAUDE.md's test is what the source MEANT, and the evidence that settles a compat question is real source that wants the behaviour. Nobody has produced any. Every use of @a[i] on an open-array parameter found so far is confined to the call, where pxx is already correct. Against that, arm A is weeks of work across every backend for one observable that no known program reaches, and it would also spend the simplification that makes three different array-ish things one representation.

So: keep the convention, mark the divergence chosen rather than tolerated, and reopen this decision on a program that needs the address to outlive the call — which is a cheap trigger to state and a clear one to recognise.

What I am NOT recommending: filing it to known-incompat/ unilaterally. That folder means "true, reproducible, and ours is chosen", which is exactly what arm C asserts — but the assertion costs a real FPC-compatible idiom, and choosing to spend that is above a worker's line. That is why this is here.

What each answer does