Skip to content

Commit 94d9b7e

Browse files
Rollup merge of #117832 - RalfJung:interpret-shift, r=cjgillot
interpret: simplify handling of shifts by no longer trying to handle signed and unsigned shift amounts in the same branch While we're at it, also update comments in codegen and MIR building related to shifts, and fix the overflow error printed by Miri on negative shift amounts.
2 parents b39791a + 31493c7 commit 94d9b7e

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
@@ -156,41 +156,35 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
156156

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

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)