Skip to content

Commit 31493c7

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 31493c7

File tree

7 files changed

+86
-58
lines changed

7 files changed

+86
-58
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

+26-28
Original file line numberDiff line numberDiff line change
@@ -157,41 +157,35 @@ 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+
// The shift offset is implicitly masked to the type size. (This is the one MIR operator
162+
// that does *not* directly map to a single LLVM operation.) Compute how much we
163+
// actually shift and whether there was an overflow due to shifting too much.
164+
let (shift_amount, overflow) = if right_layout.abi.is_signed() {
165+
let shift_amount = self.sign_extend(r, right_layout) as i128;
166+
let overflow = shift_amount < 0 || shift_amount >= i128::from(size);
167+
let masked_amount = (shift_amount as u128) % u128::from(size);
168+
debug_assert_eq!(overflow, shift_amount != (masked_amount as i128));
169+
(masked_amount, overflow)
170+
} else {
171+
let shift_amount = r;
172+
let masked_amount = shift_amount % u128::from(size);
173+
(masked_amount, shift_amount != masked_amount)
174+
};
175+
let shift_amount = u32::try_from(shift_amount).unwrap(); // we masked so this will always fit
176+
// Compute the shifted result.
183177
let result = if left_layout.abi.is_signed() {
184178
let l = self.sign_extend(l, left_layout) as i128;
185179
let result = match bin_op {
186-
Shl | ShlUnchecked => l.checked_shl(r).unwrap(),
187-
Shr | ShrUnchecked => l.checked_shr(r).unwrap(),
180+
Shl | ShlUnchecked => l.checked_shl(shift_amount).unwrap(),
181+
Shr | ShrUnchecked => l.checked_shr(shift_amount).unwrap(),
188182
_ => bug!(),
189183
};
190184
result as u128
191185
} else {
192186
match bin_op {
193-
Shl | ShlUnchecked => l.checked_shl(r).unwrap(),
194-
Shr | ShrUnchecked => l.checked_shr(r).unwrap(),
187+
Shl | ShlUnchecked => l.checked_shl(shift_amount).unwrap(),
188+
Shr | ShrUnchecked => l.checked_shr(shift_amount).unwrap(),
195189
_ => bug!(),
196190
}
197191
};
@@ -200,7 +194,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
200194
if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
201195
throw_ub_custom!(
202196
fluent::const_eval_overflow_shift,
203-
val = original_r,
197+
val = if right_layout.abi.is_signed() {
198+
(self.sign_extend(r, right_layout) as i128).to_string()
199+
} else {
200+
r.to_string()
201+
},
204202
name = intrinsic_name
205203
);
206204
}

compiler/rustc_middle/src/mir/syntax.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1404,18 +1404,18 @@ pub enum BinOp {
14041404
BitOr,
14051405
/// The `<<` operator (shift left)
14061406
///
1407-
/// The offset is truncated to the size of the first operand before shifting.
1407+
/// The offset is truncated to the size of the first operand and made unsigned 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
///
1413-
/// The offset is truncated to the size of the first operand before shifting.
1413+
/// The offset is truncated to the size of the first operand and made unsigned before shifting.
14141414
///
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+

tests/ui/consts/const-int-unchecked.stderr

+20-20
Original file line numberDiff line numberDiff line change
@@ -62,61 +62,61 @@ error[E0080]: evaluation of constant value failed
6262
--> $DIR/const-int-unchecked.rs:41:33
6363
|
6464
LL | const SHL_I8_NEG: i8 = unsafe { intrinsics::unchecked_shl(5_i8, -1) };
65-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 255 in `unchecked_shl`
65+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shl`
6666

6767
error[E0080]: evaluation of constant value failed
6868
--> $DIR/const-int-unchecked.rs:43:35
6969
|
7070
LL | const SHL_I16_NEG: i16 = unsafe { intrinsics::unchecked_shl(5_16, -1) };
71-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 65535 in `unchecked_shl`
71+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shl`
7272

7373
error[E0080]: evaluation of constant value failed
7474
--> $DIR/const-int-unchecked.rs:45:35
7575
|
7676
LL | const SHL_I32_NEG: i32 = unsafe { intrinsics::unchecked_shl(5_i32, -1) };
77-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 4294967295 in `unchecked_shl`
77+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shl`
7878

7979
error[E0080]: evaluation of constant value failed
8080
--> $DIR/const-int-unchecked.rs:47:35
8181
|
8282
LL | const SHL_I64_NEG: i64 = unsafe { intrinsics::unchecked_shl(5_i64, -1) };
83-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 18446744073709551615 in `unchecked_shl`
83+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shl`
8484

8585
error[E0080]: evaluation of constant value failed
8686
--> $DIR/const-int-unchecked.rs:49:37
8787
|
8888
LL | const SHL_I128_NEG: i128 = unsafe { intrinsics::unchecked_shl(5_i128, -1) };
89-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 340282366920938463463374607431768211455 in `unchecked_shl`
89+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shl`
9090

9191
error[E0080]: evaluation of constant value failed
9292
--> $DIR/const-int-unchecked.rs:55:40
9393
|
9494
LL | const SHL_I8_NEG_RANDOM: i8 = unsafe { intrinsics::unchecked_shl(5_i8, -6) };
95-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 250 in `unchecked_shl`
95+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -6 in `unchecked_shl`
9696

9797
error[E0080]: evaluation of constant value failed
9898
--> $DIR/const-int-unchecked.rs:57:42
9999
|
100100
LL | const SHL_I16_NEG_RANDOM: i16 = unsafe { intrinsics::unchecked_shl(5_16, -13) };
101-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 65523 in `unchecked_shl`
101+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -13 in `unchecked_shl`
102102

103103
error[E0080]: evaluation of constant value failed
104104
--> $DIR/const-int-unchecked.rs:59:42
105105
|
106106
LL | const SHL_I32_NEG_RANDOM: i32 = unsafe { intrinsics::unchecked_shl(5_i32, -25) };
107-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 4294967271 in `unchecked_shl`
107+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -25 in `unchecked_shl`
108108

109109
error[E0080]: evaluation of constant value failed
110110
--> $DIR/const-int-unchecked.rs:61:42
111111
|
112112
LL | const SHL_I64_NEG_RANDOM: i64 = unsafe { intrinsics::unchecked_shl(5_i64, -30) };
113-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 18446744073709551586 in `unchecked_shl`
113+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -30 in `unchecked_shl`
114114

115115
error[E0080]: evaluation of constant value failed
116116
--> $DIR/const-int-unchecked.rs:63:44
117117
|
118118
LL | const SHL_I128_NEG_RANDOM: i128 = unsafe { intrinsics::unchecked_shl(5_i128, -93) };
119-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 340282366920938463463374607431768211363 in `unchecked_shl`
119+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -93 in `unchecked_shl`
120120

121121
error[E0080]: evaluation of constant value failed
122122
--> $DIR/const-int-unchecked.rs:70:29
@@ -182,61 +182,61 @@ error[E0080]: evaluation of constant value failed
182182
--> $DIR/const-int-unchecked.rs:96:33
183183
|
184184
LL | const SHR_I8_NEG: i8 = unsafe { intrinsics::unchecked_shr(5_i8, -1) };
185-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 255 in `unchecked_shr`
185+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shr`
186186

187187
error[E0080]: evaluation of constant value failed
188188
--> $DIR/const-int-unchecked.rs:98:35
189189
|
190190
LL | const SHR_I16_NEG: i16 = unsafe { intrinsics::unchecked_shr(5_16, -1) };
191-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 65535 in `unchecked_shr`
191+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shr`
192192

193193
error[E0080]: evaluation of constant value failed
194194
--> $DIR/const-int-unchecked.rs:100:35
195195
|
196196
LL | const SHR_I32_NEG: i32 = unsafe { intrinsics::unchecked_shr(5_i32, -1) };
197-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 4294967295 in `unchecked_shr`
197+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shr`
198198

199199
error[E0080]: evaluation of constant value failed
200200
--> $DIR/const-int-unchecked.rs:102:35
201201
|
202202
LL | const SHR_I64_NEG: i64 = unsafe { intrinsics::unchecked_shr(5_i64, -1) };
203-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 18446744073709551615 in `unchecked_shr`
203+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shr`
204204

205205
error[E0080]: evaluation of constant value failed
206206
--> $DIR/const-int-unchecked.rs:104:37
207207
|
208208
LL | const SHR_I128_NEG: i128 = unsafe { intrinsics::unchecked_shr(5_i128, -1) };
209-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 340282366920938463463374607431768211455 in `unchecked_shr`
209+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -1 in `unchecked_shr`
210210

211211
error[E0080]: evaluation of constant value failed
212212
--> $DIR/const-int-unchecked.rs:110:40
213213
|
214214
LL | const SHR_I8_NEG_RANDOM: i8 = unsafe { intrinsics::unchecked_shr(5_i8, -6) };
215-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 250 in `unchecked_shr`
215+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -6 in `unchecked_shr`
216216

217217
error[E0080]: evaluation of constant value failed
218218
--> $DIR/const-int-unchecked.rs:112:42
219219
|
220220
LL | const SHR_I16_NEG_RANDOM: i16 = unsafe { intrinsics::unchecked_shr(5_16, -13) };
221-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 65523 in `unchecked_shr`
221+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -13 in `unchecked_shr`
222222

223223
error[E0080]: evaluation of constant value failed
224224
--> $DIR/const-int-unchecked.rs:114:42
225225
|
226226
LL | const SHR_I32_NEG_RANDOM: i32 = unsafe { intrinsics::unchecked_shr(5_i32, -25) };
227-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 4294967271 in `unchecked_shr`
227+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -25 in `unchecked_shr`
228228

229229
error[E0080]: evaluation of constant value failed
230230
--> $DIR/const-int-unchecked.rs:116:42
231231
|
232232
LL | const SHR_I64_NEG_RANDOM: i64 = unsafe { intrinsics::unchecked_shr(5_i64, -30) };
233-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 18446744073709551586 in `unchecked_shr`
233+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -30 in `unchecked_shr`
234234

235235
error[E0080]: evaluation of constant value failed
236236
--> $DIR/const-int-unchecked.rs:118:44
237237
|
238238
LL | const SHR_I128_NEG_RANDOM: i128 = unsafe { intrinsics::unchecked_shr(5_i128, -93) };
239-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 340282366920938463463374607431768211363 in `unchecked_shr`
239+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by -93 in `unchecked_shr`
240240

241241
error[E0080]: evaluation of constant value failed
242242
--> $DIR/const-int-unchecked.rs:123:25

0 commit comments

Comments
 (0)