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.

Reply via email to