Skip to content

Commit 5bc4a16

Browse files
committed
interpret: simplify handling of shifts by no longer trying to handle signed and unsigned shift amounts in the same branch
1 parent a04d56b commit 5bc4a16

File tree

6 files changed

+58
-36
lines changed

6 files changed

+58
-36
lines changed

compiler/rustc_codegen_ssa/src/base.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,13 @@ pub fn cast_shift_expr_rhs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
322322
if lhs_sz < rhs_sz {
323323
bx.trunc(rhs, lhs_llty)
324324
} else if lhs_sz > rhs_sz {
325-
// FIXME (#1877: If in the future shifting by negative
326-
// values is no longer undefined then this is wrong.
325+
// We zero-extend even if the RHS is signed. So e.g. `(x: i32) << -1i8` will zero-extend the
326+
// RHS to `255i32`. But then we mask the shift amount to be within the size of the LHS
327+
// anyway so the result is `31` as it should be. All the extra bits introduced by zext
328+
// are masked off so their value does not matter.
329+
// FIXME: if we ever support 512bit integers, this will be wrong! For such large integers,
330+
// the extra bits introduced by zext are *not* all masked away any more.
331+
assert!(lhs_sz <= 256);
327332
bx.zext(rhs, lhs_llty)
328333
} else {
329334
rhs

compiler/rustc_const_eval/src/interpret/operator.rs

+20-28
Original file line numberDiff line numberDiff line change
@@ -157,41 +157,33 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
157157

158158
// Shift ops can have an RHS with a different numeric type.
159159
if matches!(bin_op, Shl | ShlUnchecked | Shr | ShrUnchecked) {
160-
let size = u128::from(left_layout.size.bits());
161-
// Even if `r` is signed, we treat it as if it was unsigned (i.e., we use its
162-
// zero-extended form). This matches the codegen backend:
163-
// <https://github.com/rust-lang/rust/blob/c274e4969f058b1c644243181ece9f829efa7594/compiler/rustc_codegen_ssa/src/base.rs#L315-L317>.
164-
// The overflow check is also ignorant to the sign:
165-
// <https://github.com/rust-lang/rust/blob/c274e4969f058b1c644243181ece9f829efa7594/compiler/rustc_codegen_ssa/src/mir/rvalue.rs#L728>.
166-
// This would behave rather strangely if we had integer types of size 256: a shift by
167-
// -1i8 would actually shift by 255, but that would *not* be considered overflowing. A
168-
// shift by -1i16 though would be considered overflowing. If we had integers of size
169-
// 512, then a shift by -1i8 would even produce a different result than one by -1i16:
170-
// the first shifts by 255, the latter by u16::MAX % 512 = 511. Lucky enough, our
171-
// integers are maximally 128bits wide, so negative shifts *always* overflow and we have
172-
// consistent results for the same value represented at different bit widths.
173-
assert!(size <= 128);
174-
let original_r = r;
175-
let overflow = r >= size;
176-
// The shift offset is implicitly masked to the type size, to make sure this operation
177-
// is always defined. This is the one MIR operator that does *not* directly map to a
178-
// single LLVM operation. See
179-
// <https://github.com/rust-lang/rust/blob/c274e4969f058b1c644243181ece9f829efa7594/compiler/rustc_codegen_ssa/src/common.rs#L131-L158>
180-
// for the corresponding truncation in our codegen backends.
181-
let r = r % size;
182-
let r = u32::try_from(r).unwrap(); // we masked so this will always fit
160+
let size = left_layout.size.bits();
161+
// Compute how much we actually shift and whether there was an overflow due to shifting
162+
// too much.
163+
let (shift_amount, overflow) = if right_layout.abi.is_signed() {
164+
let shift_amount = self.sign_extend(r, right_layout) as i128;
165+
let overflow = shift_amount < 0 || shift_amount >= i128::from(size);
166+
let masked_amount = (shift_amount as u128) % u128::from(size);
167+
debug_assert_eq!(overflow, shift_amount != (masked_amount as i128));
168+
(masked_amount, overflow)
169+
} else {
170+
let shift_amount = r;
171+
let masked_amount = shift_amount % u128::from(size);
172+
(masked_amount, shift_amount != masked_amount)
173+
};
174+
let shift_amount = u32::try_from(shift_amount).unwrap(); // we masked so this will always fit
183175
let result = if left_layout.abi.is_signed() {
184176
let l = self.sign_extend(l, left_layout) as i128;
185177
let result = match bin_op {
186-
Shl | ShlUnchecked => l.checked_shl(r).unwrap(),
187-
Shr | ShrUnchecked => l.checked_shr(r).unwrap(),
178+
Shl | ShlUnchecked => l.checked_shl(shift_amount).unwrap(),
179+
Shr | ShrUnchecked => l.checked_shr(shift_amount).unwrap(),
188180
_ => bug!(),
189181
};
190182
result as u128
191183
} else {
192184
match bin_op {
193-
Shl | ShlUnchecked => l.checked_shl(r).unwrap(),
194-
Shr | ShrUnchecked => l.checked_shr(r).unwrap(),
185+
Shl | ShlUnchecked => l.checked_shl(shift_amount).unwrap(),
186+
Shr | ShrUnchecked => l.checked_shr(shift_amount).unwrap(),
195187
_ => bug!(),
196188
}
197189
};
@@ -200,7 +192,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
200192
if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
201193
throw_ub_custom!(
202194
fluent::const_eval_overflow_shift,
203-
val = original_r,
195+
val = if right_layout.abi.is_signed() { (self.sign_extend(r, right_layout) as i128).to_string() } else { r.to_string() },
204196
name = intrinsic_name
205197
);
206198
}

compiler/rustc_middle/src/mir/syntax.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,7 @@ pub enum BinOp {
14061406
///
14071407
/// The offset is truncated to the size of the first operand before shifting.
14081408
Shl,
1409-
/// Like `Shl`, but is UB if the RHS >= LHS::BITS
1409+
/// Like `Shl`, but is UB if the RHS >= LHS::BITS or RHS < 0
14101410
ShlUnchecked,
14111411
/// The `>>` operator (shift right)
14121412
///
@@ -1415,7 +1415,7 @@ pub enum BinOp {
14151415
/// This is an arithmetic shift if the LHS is signed
14161416
/// and a logical shift if the LHS is unsigned.
14171417
Shr,
1418-
/// Like `Shl`, but is UB if the RHS >= LHS::BITS
1418+
/// Like `Shl`, but is UB if the RHS >= LHS::BITS or RHS < 0
14191419
ShrUnchecked,
14201420
/// The `==` operator (equality)
14211421
Eq,

compiler/rustc_mir_build/src/build/expr/as_rvalue.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -600,10 +600,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
600600
BinOp::Shl | BinOp::Shr if self.check_overflow && ty.is_integral() => {
601601
// For an unsigned RHS, the shift is in-range for `rhs < bits`.
602602
// For a signed RHS, `IntToInt` cast to the equivalent unsigned
603-
// type and do that same comparison. Because the type is the
604-
// same size, there's no negative shift amount that ends up
605-
// overlapping with valid ones, thus it catches negatives too.
603+
// type and do that same comparison.
604+
// A negative value will be *at least* 128 after the cast (that's i8::MIN),
605+
// and 128 is an overflowing shift amount for all our currently existing types,
606+
// so this cast can never make us miss an overflow.
606607
let (lhs_size, _) = ty.int_size_and_signed(self.tcx);
608+
assert!(lhs_size.bits() <= 128);
607609
let rhs_ty = rhs.ty(&self.local_decls, self.tcx);
608610
let (rhs_size, _) = rhs_ty.int_size_and_signed(self.tcx);
609611

@@ -625,7 +627,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
625627

626628
// This can't overflow because the largest shiftable types are 128-bit,
627629
// which fits in `u8`, the smallest possible `unsigned_ty`.
628-
// (And `from_uint` will `bug!` if that's ever no longer true.)
629630
let lhs_bits = Operand::const_from_scalar(
630631
self.tcx,
631632
unsigned_ty,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![feature(core_intrinsics)]
2+
use std::intrinsics;
3+
4+
fn main() {
5+
unsafe {
6+
let _n = intrinsics::unchecked_shl(1i8, -1);
7+
//~^ ERROR: overflowing shift by -1 in `unchecked_shl`
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: overflowing shift by -1 in `unchecked_shl`
2+
--> $DIR/unchecked_shl2.rs:LL:CC
3+
|
4+
LL | let _n = intrinsics::unchecked_shl(1i8, -1);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shl`
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at $DIR/unchecked_shl2.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to previous error
15+

0 commit comments

Comments
 (0)