← board

What broke

Before, a ^TDyn held the array's HANDLE — @la and p^[i] both worked on the data pointer. bug-p-address-of-a-dynamic-array-captures-the-handle-not-the-variable made @dy yield the VARIABLE's slot, and p^[i] moved with it: it now uniques through p's value as a handle SLOT address.

Three places kept building the old shape.

  1. The parallel for worker's capture accessor. For a dyn-array capture it synthesized

    capj := Pointer(PNativeInt(NativeInt(__pfctx) + off)^);   { the DATA pointer }
    

    so capj^[i] read the array's first ELEMENT as a handle. Segfault, on all four targets that run parallel for.

  2. p^ to a var/out dyn-array parameter (ir.inc). It materialised a hidden slot — temp := handle; pass &temp — to give the callee something to deref. With p already holding the slot address that is one indirection too many: the callee derefed to the slot ADDRESS and read that as the handle.

  3. p^ to a by-value dyn-array parameter. It passed p's value as the handle; p's value is now the slot, so it needs a load through it.

Found by

The Track T watcher: test-aarch64 / test-arm32 / test-i386 / test-threads #src:test/test_parallel_for_capture_aggr.pas, STILL-RED at d3c1e87dce5b, all four qemu: uncaught target signal 11. Reproduced natively in eight lines — a parallel for over a captured fixed array passes, the same loop over a captured dynamic array segfaults.

Fix

One convention, applied to all three.

The two argument arms were written as mirrors of each other and the comment on each said so; they moved together, which is what made the second one findable from the first.

Verification

Lesson

The convention change was gated, and the gate was green, because the quick tier does not run --threadsafe cross-target jobs. A change to what a POINTER MEANS has a blast radius the size of every consumer of that pointer, and the way to find them is to grep for the consumers, not to trust the tier that happens to run. The comment I wrote at the p^[i] site even said "reachable only since @dy began yielding the slot" — the right next question was "what else was".