← board

SPECIAL_IN has no arm in the two 32-bit cross backends

The double case

ParseSetMembershipAST (pasparser_lval.inc) forks:

Two shapes for one construct, which is what [[devdocs/dev/normalise-dont-special-case]] is about — and the parser's own comment records that these two paths had already diverged once: writeln(e in [a,b]) printed 1 instead of TRUE, because only the binop arm was typed tyBoolean. That fix typed the node. Nobody checked whether every backend implemented both arms.

grep SPECIAL_IN compiler/: ir_codegen.inc, ir_codegen_aarch64.inc, ir_codegen_arm32.inc, ir_codegen386.inc. Not riscv32. Not xtensa.

How it was found, and the part I have to own

I added the tkIn arm to ir_codegen_xtensa.inc earlier the same session, measured it green against the oracle, and closed the ticket. in has two shapes and I fixed one. The rule that says to look for the other is the rule this repo wrote down, in a lane that had spent the night finding exactly this shape in other people's code. Nothing failed; the ticket closed green; the sibling sat two programs away.

What surfaced it was not a hunch but classifying the tail instead of guessing at it — partitioning all 23 remaining xtensa compile failures into seven named categories, which is what made a "builtin with no arm" bucket exist at all. Builtin -999 is SPECIAL_IN (defs.inc).

Both backends, one change

riscv32 was fixed in the same commit rather than filed. Fixing one arm of a double case and filing the other is the same defect one level up: it leaves riscv32 as the next lane's surprise. A repair for a fixed-on-one-arm-only bug that is itself applied to one arm is not a partial fix.

ir_codegen_riscv32.inc is Track A's file; scope granted for this arm only, with the collision check recorded (grant-ir-codegen-riscv32-to-track-s-for-the-special-in-arm).

Same rule, each backend's own idiom — which is what normalising across targets should look like:

backend how the membership accumulates
arm32 (model) conditional execution — moveq r4, #1
riscv32 (new) branchless: slt / sltiu / xori, no labels, no patching
xtensa (new) branches over a movi — no conditional execution, same reason its tkIn arm had to branch for want of sltiu

Xtensa constants go through EmitLoadConstXtensa, never a bare movi: xtensa_movi masks to 12 bits with no range check, so a set element above 2047 would silently encode as something else. That is why each xtensa item is its own EmitAsmXtensa block — the literal-pool fallback can emit a jump, and labels are scoped per block, so nothing branches across one. Registers avoid a7 (frame pointer under the windowed ABI) and a15 (frame pointer under Call0).

Both arms also refuse a non-constant element rather than reading IRIVal off whatever node is there. The parser cannot deliver one today; arm32 would produce a wrong value if it ever did.

Measured — two independent sweeps, both against a rebuilt baseline

129 sources, each target against the x86-64 oracle:

xtensa    MATCH  97 -> 99    CFAIL 23 -> 21    regressions: NONE
riscv32   MATCH 107 -> 109   CFAIL 18 -> 16    regressions: NONE

Exactly two rows moved on each, both off CFAIL: test_cross_in_operator and test_cross_string_cow. The riscv32 baseline was produced by stashing the change and rebuilding the compiler at HEAD (a60f92ba830a), not by reusing an older binary — the same discipline that caught a confounded measurement earlier in this session.

Four rows wired: two into test-xtensa (now 100), two into test-riscv32, where they replace explicit # SKIP … backend feature gap comments. Those comments named the right cause and nobody had priced it.

A note for whoever reads riscv32's diagnostic next

riscv32's message for a missing builtin arm reads "standard builtin calls not supported in bare-metal stage 1" — and it is reached under an ordinary hosted cross compile where there is no bare-metal stage. It does append (builtin id 999), so the subject is named; it is the sentence that is stale and sends the reader to a profile question that is not the problem. Same defect class as the xtensa messages fixed earlier tonight ([[bug-a-iropname-has-no-entry-for-seven-ir-ops-so-a-missing-arm-reports-unknown]]). Not fixed here — chasing it would widen an A-file grant scoped to one arm.

Log