https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126190
Bug ID: 126190
Summary: [pdp11] variable logical right shift by runtime count
0 clears bit 0 (HImode/SImode)
Product: gcc
Version: 13.3.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: target
Assignee: unassigned at gcc dot gnu.org
Reporter: migraineman33 at gmail dot com
Target Milestone: ---
config/pdp11/pdp11.md, lshr<mode>3 define_expand, HImode/SImode branch
with a NON-CONSTANT shift count. The expansion is:
r = op1 >>logical 1; // lshiftrt_sc by 1, emitted unconditionally
n = count - 1;
result = r >>arith n; // ash/ashc
The one-bit logical pre-shift clears the MSB so the following
arithmetic shift behaves logically. The identity
x >>logical n == (x >>logical 1) >>arith (n - 1)
holds only for n >= 1. For a RUNTIME count of 0, n-1 = -1 makes the
ash/ashc a LEFT shift by 1, so the sequence computes
(x >> 1) << 1 = x & ~1: bit 0 is silently lost. Shift-by-zero is
well-defined C and must return x unchanged.
Because the flaw is in the identity rather than in any single emitted
insn, no operand-level correction exists; the expansion needs a
compare-and-branch around the shift pair when the count is not a
compile-time constant.
Test case:
unsigned int hshift(unsigned int x, int n) { return x >> n; }
/* hshift(19, 0) returns 18 (expected 19) */
unsigned long sshift(unsigned long x, int n) { return x >> n; }
/* same defect via ashc */
Generated code (gcc 13.3.0, -Os, pdp11-aout):
_hshift:
mov 02(sp),r0
clc
ror r0 ; bit 0 gone
mov $01,r1
sub 04(sp),r1 ; 1 - n
ash r1,r0 ; n=0 -> LEFT shift by 1
rts pc
Constant counts are unaffected (separate CONST_INT path, and gcc never
emits a constant shift by 0). ashl<mode>3/ashr<mode>3 are unaffected
(ash/ashc take the count directly; 0 is a hardware no-op).
The expansion is unchanged in current master (checked 2026-07-09).
How it was found: Fuzix (PDP-11 port) computes indirect-block indices
in bmap() as i = (bn >> sh) & 0xff where sh is a variable: 0 for
single-indirect, 8 for double-indirect lookups. With sh=0, every odd
block number indexes the predecessor slot, so every odd 512-byte block
in a file's single-indirect range silently reads as the block before
it. Any binary larger than 18 direct blocks (9216 bytes) had a
corrupted tail; manifested as a wild jump through a clobbered atexit
table -> MMU fault, only when a shell's fork child exec-failed and
exited.
Related but distinct from PR125058 (QImode branch of the same
expander fails to negate the shift count): that is a missing operand
transformation; this is the expansion strategy itself being invalid
at count 0.
A patch against master (compare the count against 0 and branch around
the shift pair, with a testsuite scan-assembler test) is attached.
Fix verified on a self-hosted Fuzix system: emitted code gains a
tst/beq guard, hshift(x,0)==x for HI and SI, filesystem regression
clean.