← board

A function-pointer TABLE is not callable: local array, and struct field

The measurement

gcc -O0 vs pxx at d2eaf3f18. Both failures are parse errors (error: unexpected token), not wrong values.

shape gcc pxx
binop tab[2] = {add,mul}; inside a function, then tab[0](5,6) 11 parse error
the same declaration at file scope 11 11
int (*tab[2])(int,int) (raw spelling), either scope 11 11
binop g = tab[0]; g(5,6) (via a temp) 11 11
h.f[0](5,6) — struct field array, local struct 11 parse error
gh.f[0](5,6) — global struct 11 parse error
p->f[1](5,6) 30 parse error
(*h.f[0])(5,6) 11 parse error

Five ways to spell one call; the two that real code writes were the broken ones. That is why this survived: every alternative worked, so any single test would have passed.

Two root causes

1. The local array declaration never recorded the element signature. The scalar local branch sets SymProcSig[idx] := declProcSig for a plain fn-pointer variable, and the file-scope path sets SymElemProcSig for an array — but the LOCAL array branch set only the pointer metadata (PtrElemTk, PtrDepth, …). With no SymElemProcSig, CalleeSig's arr[i](args) arm had no signature to bind and the ( was unexpected.

The channel matters: SymElemProcSig, never SymProcSig. The file-scope arm already says why — SymProcSig marks the array VARIABLE as a proc value, so codegen resolves tab to a code address and the indexing is corrupt.

2. s.f[i](args) was missing from CalleeSig entirely. It has an arm for AN_INDEX over an AN_IDENT (a plain array) and an arm for AN_FIELD (a scalar fn-pointer field), but nothing for an AN_INDEX over an AN_FIELD. A struct holding a dispatch table — how C spells a vtable, and what every plugin/driver interface in C looks like — could not be called at all. The new arm reads the field's per-element signature from RecFieldElemProcSig, the same channel (*s.pf)(args) already uses, falling back to the field's own sig.

Test

test/cfnptr_array_callable.c, gcc-oracled: the local table and the struct field at both scopes and through ->, a runtime index and a loop over one, an explicit deref of the element, a table declared with no initializer and filled afterwards, and every spelling that already worked (file scope, raw declarator, temp, direct call, an ordinary non-pointer field) so the fix is proved not to have moved them. The pinned binary cannot compile it.

Not affected

The rest of the C probe matches gcc line for line: bitfields (including truncation on overflow and signed narrow fields), unions and their byte/short overlays, struct layout and offsets with and without __attribute__((packed)), designated initializers (flat, nested, and array-index), the comma operator, ternary result types, integer promotions and signed/unsigned comparison, and switch fallthrough.

__builtin_offsetof is not recognised (the &((T*)0)->m form works); noted, not filed — nothing depends on it here.

Gate

make compiler/pascal26 fixedpoint converged after 1 round; tools/gate.sh quick GREEN.