← board

The fact, measured on every runnable target

test/test_set_in_64bit_const.pas, run cross at 831919a7d:

x86_64   A FALSE B TRUE  C TRUE  D FALSE E TRUE F TRUE G TRUE   <- correct
aarch64  A FALSE B TRUE  C TRUE  D FALSE E TRUE F TRUE G TRUE   <- correct
i386     A TRUE  B FALSE C FALSE D FALSE E TRUE F TRUE G TRUE
arm32    A TRUE  B FALSE C FALSE D FALSE E TRUE F TRUE G TRUE
riscv32  A TRUE  B FALSE C FALSE D FALSE E TRUE F TRUE G TRUE

A is q=1; q in [4294967297] (must be FALSE), B is the same set with q=4294967297 (must be TRUE), C is 4294967296 in [4294967296..4294967300].

Why this is NOT the ticket that was just closed

831919a7d widened two narrowings on the shared path — loVal, hiVal in ParseSetMembershipAST and the Integer(IRIVal[...]) casts — and added CmpRcxImm so x86-64 could encode an immediate it physically could not hold. That is enough for the two 64-bit targets. It cannot be enough here, and the mechanism is a different one:

done/bug-a-set-membership-truncates-the-test-value-on-32-bit-backends installed the current 32-bit shape, which compares only the LOW word of the test value and then ANDs in a "the test value fits in a signed int32" flag (i386: sar eax,31; cmp eax,edx; sete al, arm32: the mov r2, r0, asr #31 block). That is correct while every constant fits in int32, which was true when it was written. With a constant above 2^31 it fails both ways:

What a fix has to do

Compare both words per item, rather than one word plus a whole-expression flag: an item K matches iff low32(q) = low32(K) and high32(q) = high32(K). Ranges are worse — a signed 64-bit </> on a 32-bit target is a two-word compare with a borrow — and the item loop's jump patch sites are hand-emitted per backend, which is why this is sized work and not a cast deletion.

The machinery already exists: done/bug-64bit-named-const-truncated-32bit-targets made 64-bit named constants materialise correctly on these three targets (EmitLoadConst64RISCV32, Is64Bit386, Is64BitArm32), so the constant can be got into registers at full width. What is missing is the compare.

wasm32 is already correct by construction — WasmEmitSetIn has a wide flag and PushOrd(x: Int64) — and is not affected. xtensa cannot be run on this host and was not measured; its SPECIAL_IN walk declares itemNode, hi: Integer like riscv32's, so it is a candidate rather than a finding.

Reachability — read before ranking

Same argument as the parent: real Pascal sets are 0..255 and a set item at or above 2^31 is close to nonexistent. Prio 20 because it is a silent wrong answer on three cross targets, not because anything is blocked on it. Do not rank it down on "x86-64 is fine" — that is the native-only blind spot CLAUDE.md names, and here the two 64-bit targets are green precisely because they are 64-bit.

Repro

make compiler/pascal26
for t in i386 arm32 riscv32; do
  ./compiler/pascal26 --target=$t test/test_set_in_64bit_const.pas /tmp/s_$t
done
qemu-i386 /tmp/s_i386
qemu-arm /tmp/s_arm32
qemu-riscv32 /tmp/s_riscv32

All three print SETIN64 FAILED 5, identical row for row, measured at 831919a7d:

FAIL A_LOW_MATCH: got TRUE want FALSE
FAIL B_EXACT: got FALSE want TRUE
FAIL C_RANGE: got FALSE want TRUE
FAIL H_INT32_MAX_PLUS1: got FALSE want TRUE
FAIL I_MIXED_WIDTH_SET: got FALSE want TRUE

aarch64 prints SETIN64 OK. That the three 32-bit targets fail the SAME five rows identically is the evidence they share one mechanism rather than three.

MEASURED UNDER AN EMULATOR, NOT ON HARDWARE, and the distinction is not pedantry here — a cross red seen under qemu and one seen on a real chip are different claims, and only the first is available on this box. All four runners are qemu 10.2.1 (Debian 1:10.2.1+ds-1ubuntu3.2), host kernel 7.0.0-30. The failure is a wrong constant in emitted code rather than anything the emulator interprets differently, so emulation is very unlikely to be the cause — but nobody has run it on hardware and this ticket does not claim otherwise. (regression-test-core-c-crtl-wait is the standing example on this repo of a cross red that WAS the emulator: seven's qemu 8.2.2 against plexus's 10.2.1, same compiler bytes, opposite answers.)

Blast radius is bounded to SPECIAL_IN — checked, not assumed

The obvious worry, raised by frankZ: cmp rcx, imm32 sign-extending is a fact about x86-64 comparisons in general, not about sets, so if the general comparison lowering shared the defect this ticket would be much larger than its title. It does not. Measured at 831919a7d and against the pre-fix pin, both CMPWIDE OK:

q := 4294967297;
if q =  4294967297 ... if q <> 4294967296 ... if q > 4294967296
if q >= 4294967297 ... if q <  4294967300 ... if q <= 4294967297
case q of 4294967297: ...        { also correct }

All ten rows right on both compilers, so the general path already materialises a wide immediate properly and only the hand-emitted SPECIAL_IN walk bypassed it. The same probe is worth re-running on the 32-bit targets before writing their fix: if plain if q = 4294967297 is already correct there, whatever it uses is the machinery the item loop should be reusing rather than a new two-word compare written from scratch.