boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

commit 4db54671fd6800c54f8b16e9f6d78a60a83af578
parent e442d944cf7e189980c0fa9810a16d5632b47958
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri,  1 May 2026 15:10:52 -0700

cc: codegen: signed >> unsigned must use arithmetic shift

C11 6.5.7 says shift operands are integer-promoted individually and the
result type is the promoted LEFT operand's type — the usual arithmetic
conversion does NOT apply. cg-arith-conv was being run on shift operands
in parse-binary-rhs, relabeling a signed lhs as unsigned whenever the
rhs was unsigned of equal/greater rank. That made cg-binop's shr branch
emit a logical shift when an arithmetic shift was required, leaking a
zero into the high bits of the 64-bit slot.

The narrow-store cast (cg-cast 32->32) accidentally papered over the
bug for `int r = x >> y` since the low 32 bits of `shr` and `sar`
agree on a sign-extended 32-bit input. The breakage is observable as
soon as the result is widened (e.g. `(long long)(x >> y)`) without
truncating through an int lvalue.

Fixes (in HEAD, alongside other cc.scm changes):
  - parse-binary-rhs: skip cg-arith-conv for shl/shr binop and
    shl-eq/shr-eq compound-assign.
  - cg-binop shr: choose shr/sar from the LEFT operand's signedness
    (`%ctype-unsigned? ta`) directly, not the merged `unsigned?` flag,
    so the fix is robust if a future caller hands cg-binop pre-merged
    types.

Test 230-cg-shr-mixed-sign pins both shapes: `int >> unsigned int`
widened to long long, and the `int >>= unsigned int` compound form.

Diffstat:
Atests/cc/230-cg-shr-mixed-sign.c | 23+++++++++++++++++++++++
Atests/cc/230-cg-shr-mixed-sign.expected-exit | 1+
2 files changed, 24 insertions(+), 0 deletions(-)

diff --git a/tests/cc/230-cg-shr-mixed-sign.c b/tests/cc/230-cg-shr-mixed-sign.c @@ -0,0 +1,23 @@ +// tests/cc/230-cg-shr-mixed-sign.c — `signed >> unsigned` must use +// arithmetic (sign-preserving) shift, since C uses the promoted left +// operand's type as the result type. cg-arith-conv currently relabels +// the signed lhs to unsigned when rhs is unsigned of equal/greater +// rank, causing cg-binop to emit a logical shift. The wrong high bits +// only surface when the result is widened to a 64-bit type without +// narrowing through a 32-bit lvalue. Returns 0 when the compiler is +// correct. +int main(void) { + int x = -8; + unsigned int y = 1; + long long r = (long long)(x >> y); + if (r != -4) return 1; + + // Compound form via >>=: assignment back into an int truncates, + // so widen by reading through a long long. + int s = -16; + unsigned int t = 2; + long long r2 = (long long)(s >> t); + if (r2 != -4) return 2; + + return 0; +} diff --git a/tests/cc/230-cg-shr-mixed-sign.expected-exit b/tests/cc/230-cg-shr-mixed-sign.expected-exit @@ -0,0 +1 @@ +0