← board

-O3 (x86-64): W1 slice 10 fused the sign-extend and skipped the zero-extend

Found by porting slice 10 to aarch64, where both flavours were handled in one helper and the asymmetry on the x86-64 side became visible by comparison.

The gap

A shift's leading widen on a narrow operand has two flavours:

operand x86-64 emits slice 10
signed, native-width result cdqe fused into movsxd rax, rNd
unsigned (or a narrow result) mov eax, eax not fused

For a register-resident operand the unfused form is mov rax, rN (3 bytes) + mov eax, eax (2) = 5 bytes, two instructions. It should be mov eax, rNd44 89 C0 for r8-r15, three bytes, one instruction — which zero-extends into the full 64-bit register by construction, exactly as the 32-bit mov always does.

W1LeadingCdqe in ir_codegen.inc currently answers False for this case, so the load is not skipped and the arm falls through to the zero-extend. The aarch64 twin (W1LeadingExtA64) returns 1 for sxtw and 2 for mov w0, w0 and fuses both, which is what made the omission obvious.

Why it was missed

Slice 10's test drives the widen through LongInt shr k, and this dialect promotes that to native width — so every row in it takes the signed arm. The unsigned rows in test_shr_resident_widen.pas are marked as controls for a different property (u shr 1 on a LongWord proves the fold does not claim the zero-extend site) and they passed, which is correct and also exactly why nobody looked again: a control that proves "this pass does not fire here" reads identically whether not-firing is right or is a missed opportunity.

Gate

Its own -O0/-O3 control pair with band rows, an unsigned narrow operand whose value is above 2^31 (where a sign-extension would give a different answer), and a deliberate break verified to change the emitted bytes.

Log