Labels-as-values is the whole of the lua regression
Two reds — regression-test-lua-compiler-srchash and
regression-test-lua-cross-compiler-srchash-2 — are one construct, and it is
the same root as the five test_c_gtk* reds
([[regression-test-core-test-c-gtk-2]]): 00ab464bf feat(C,B): the C frontend announces GNU C 2.7. Seven jobs, one cause.
The construct
library_candidates/lua/src/lvm.c:38:
#if !defined(LUA_USE_JUMPTABLE)
#if defined(__GNUC__)
#define LUA_USE_JUMPTABLE 1
#else
#define LUA_USE_JUMPTABLE 0
#endif
#endif
A bare defined(__GNUC__) with no version test — the exact shape
00ab464bf's own header names as the cost of the claim, one construct over from
the inline asm it predicted. It selects ljumptab.h:
#define vmdispatch(x) goto *disptab[x];
static const void *const disptab[NUM_OPCODES] = { &&L_OP_MOVE, ... };
Reproduced at binary 7ef59bc560b4b9fc, the recipe's own flags:
$ ./compiler/pascal26 -g -Ilib/crtl/include -Ilib/crtl/src -Ilibrary_candidates/lua/src \
test/lua/runner.c /tmp/luarunner
pascal26:28: error: expected C expression
in: library_candidates/lua/src/lvm.c
near: >>> L_OP_MOVE
Byte-identical to the log tail on both tickets.
It is the ONLY blocker, proved in both directions
Not "the first error we hit" — the only one. Two independent counter-tests, each
of which builds cleanly (rc=0, warnings only):
| counter-test | what it isolates | result |
|---|---|---|
-DLUA_USE_JUMPTABLE=0 |
the CONSTRUCT: force lua's portable switch arm |
builds |
-U__GNUC__ |
the CAUSE: undo 00ab464bf's claim for this TU |
builds |
Both proofs matter because they fail differently: the first says the jumptable is what pxx cannot compile, the second says the GNU C claim is what turns it on.
And the resulting binary is not merely a binary — it runs the corpus:
PASS closures.lua PASS coroutines.lua PASS files.lua
PASS numeric.lua PASS oop.lua PASS strings.lua 6/6
So there is nothing behind this gap. Implementing labels-as-values turns both jobs green; nothing else in lua is waiting.
What it needs
&&label as a void* value, and goto *expr as an indirect jump — an
address-of-label relocation into a read-only data table, plus an indirect branch
the backend and the IR's control-flow handling must both accept. That is a real
C-frontend feature, not a session-side fix, which is why this is a ticket and
not a commit.
What NOT to do
Do not add -DLUA_USE_JUMPTABLE=0 to the Makefile recipe. It makes the job
green today and hides the gap the tier exists to find; lua's default on a
GNU-announcing compiler IS the jumptable, so the flag would also stop testing
what real builds do. Same for lowering the __GNUC__ claim: 00ab464bf fixed a
SILENT wrong layout (__attribute__ expanding to nothing, so PACKED/ALIGNED did
nothing and a gzip header union came out 12 bytes where gcc makes 8), and its
trade — loud failures naming the construct, in exchange for no quiet wrong
answers — is the right one. Its own words: "the answer there is inline-asm
support, not a smaller version claim."
The number that commit did not have
00ab464bf measured "busybox's 265 translation units at this config: 1 fixed,
0 broken." The real cost, measured 2026-09-02 across the tier, is 7 jobs
broken: five test_c_gtk* on __builtin_constant_p (fixed, see
[[regression-test-core-test-c-gtk-2]]) and these two on labels-as-values. That
is not an argument against the commit — it is the number nobody had, and the
busybox corpus was simply the wrong place to look for it. Routed to frankD, who
owns the claim and the lane.
Taken by frankD (2026-09-02), and costed before started
frankD owns 00ab464bf and the lane, and has taken this. Their scoping, kept
here so the next reader does not re-derive it:
- address-of-label needs a relocation the object writer does not emit yet;
goto *pneeds an indirect branch the IR's control flow has no node for,- and therefore every backend's reachability walk has to stop treating a computed jump as a fall-through.
Not started immediately — a large batch lands first — and deliberately costed
before being touched rather than started and stalled. Nothing here is blocked on
me; the two lua tickets are blocked-by this one and will follow it.
The scoping above is WRONG, measured 2026-09-02 — it is much smaller
Both the ticket's estimate and frankD's own three bullets were reasoned, not measured. Measuring them changes the shape of the job. Three corrections, each from a probe rather than from reading:
1. Address-of-label needs NO object-writer relocation. The claim was that a
&&label in a static initialiser wants a link-time relocation into .rodata.
It does not, because pxx does not emit link-time relocations for address
initialisers at all. A static initialiser naming an address is lowered to a
runtime store: cparser.inc's PendingInit* arrays become AST assignments
(AN_PROCADDR, AN_ADDR) in pasparser_prog.inc:302, executed as ordinary
code. Probe: a file-scope static int (*const tab[2])(void) = { a, b }; builds
and runs under pxx today and matches gcc.
2. And the store lands in the RIGHT BODY. This was the part that looked
expensive: LabelPositions is reset per body (ir_codegen.inc:11626) and label
ids are per body, so a fill running in the program's init code could not name a
label inside luaV_execute. Measured — it does not run there. For a
FUNCTION-LOCAL static, the address stores are emitted inside the declaring
body:
$ PXXDBG=a.ir:main pascal26 localstatic.c # static tab[] inside main()
0: procaddr a=806 ...
6: procaddr a=807 ...
$ PXXDBG=a.ir:PXXMAIN pascal26 localstatic.c # the init body: none
0
lua's disptab is exactly that shape — ljumptab.h is #included inside
luaV_execute's body, so its table is a function-local static and its labels
are in the same body as the stores that fill it. So the existing per-body
LabelPositions + LabelFixupPos/Target machinery covers the whole feature,
and no module-scoped (proc, label) table is needed. CodeLen is already an
absolute offset into one module-wide code buffer, so the lea is a RIP-relative
displacement patched by the same fixup pass that patches a forward jmp.
3. The reachability point stands, and needs sharpening. A label whose
address is taken must be marked reachable UNCONDITIONALLY, because
IRMarkReachableLabels walks control-flow edges and there is no edge to see —
the jump goes through a value. Reading the label set off IRVerify's
label-operand kinds (the discipline that function's comment describes) keeps
this honest: adding IR_LABELADDR as a label-consuming kind is what makes the
walk mark it, and the verifier is what forces the arm to exist.
What it actually needs, then
clexer.inc: nothing.&&already lexes astkAnd(clexer.inc:834).cparser.inc: prefixtkAndbefore an identifier in a unary expression -> a label-address node;goto *expr-> an indirect-goto statement; one more arm in the static-initialiser element parser besidearrKind = 3.ir.inc: two node kinds (IR_LABELADDR,IR_JUMP_INDIRECT), theirIRVerifyandIROpNamearms,IRIsUncondTransferextended, andIRMarkReachableLabelsmarking address-taken labels.ir_codegen.inc(x86-64):lea rax, [rip+disp32]reusing the label fixup, andjmp rax(FF /4).- The other six backends: a clear refusal is honest to land first, but
regression-test-lua-cross-compiler-srchash-2is a CROSS job, so aarch64 is needed before both tickets go green. Do not claim the cross one on the x86-64 arm alone.
Landed, 2026-09-02 — x86-64 6eea46f7c, aarch64 in the commit after it
Both halves of the construct, on both targets the lua jobs need.
x86-64: &&label -> AN_LABELADDR -> IR_LABELADDR -> lea rax,[rip+disp32]
on the existing label fixup list; goto *expr -> AN_GOTO_INDIRECT ->
IR_JUMP_INDIRECT -> jmp rax. LabelFixupCount had to move from a local of
the body loop to unit scope: &&label is a VALUE node, emitted from
IREmitNode, which is a different procedure.
aarch64: adr x0, #imm21 — PC-relative, so unlike the IR_PROCADDR arm
directly above it, it needs neither a relocation nor an 8-byte literal. Forward
references share the branch fixup list and the patch loop recognises the
placeholder by its top byte ($10), which no other fixup shape there emits.
EncodeAdrX0A64 errors above +/-1MB rather than wrapping — that guard is
UNTESTED (a >1MB span is expensive to construct); it is a refusal, not a claim.
Results, both measured here:
| build | 6 lua programs | |
|---|---|---|
| native x86-64 | ok | 6/6 |
| cross aarch64 (qemu) | ok | 6/6 |
The control that matters. The lua suite does NOT discriminate the two
interpreter paths — a -DLUA_USE_JUMPTABLE=0 build passes all six as well. What
proves the computed-goto arm is the one that compiled is a binary comparison, on
each target: the default build is byte-identical to -DLUA_USE_JUMPTABLE=1 and
differs from -DLUA_USE_JUMPTABLE=0. Quoted without that, "6/6" would be a true
sentence about the wrong claim.
test/c_labels_as_values.c is 8 rows diffed against gcc, wired into test-core
and test-aarch64. Rows 2 and 3 are backward (immediate) and forward (fixup)
&&label separately, because those are different lines of codegen. Rows 5/6 are
the address-identity control, so an implementation returning a plausible
constant fails rather than adding up to the right total by luck.
Still open here: i386, arm32 and riscv32 refuse IR_LABELADDR by name —
[[feature-c-labels-as-values-on-i386-arm32-riscv32]], prio 30. Nothing measured
is blocked on them: test-lua-cross's other three targets already build-fail on
their variadic ABI, so implementing this there moves the failure without moving
a verdict.
Log
- 2026-09-02 — resolved; this names the commit that carried the resolve, which is not always the one that carried the change — commit 45a871287.