Rollup merge of #109475 - scottmcm:simpler-shifts, r=WaffleLapkin
Simpler checked shifts in MIR building Doing masking to check unsigned shift amounts is overcomplicated; just comparing the shift directly saves a statement and a temporary, as well as is much easier to read as a human. And shifting by unsigned is the canonical case -- notably, all the library shifting methods (that don't support every type) take shift RHSs as `u32` -- so we might as well make that simpler since it's easy to do so. This PR also changes *signed* shift amounts to `IntToInt` casts and then uses the same check as for unsigned. The bit-masking is a nice trick, but for example LLVM actually canonicalizes it to an unsigned comparison anyway <https://rust.godbolt.org/z/8h59fMGT4> so I don't think it's worth the effort and the extra `Constant`. (If MIR's `assert` was `assert_nz` then the masking might make sense, but when the `!=` uses another statement I think the comparison is better.) To review, I suggest looking at https://github.com/rust-lang/rust/pull/109475/commits/2ee0468c49be9f8ea68b7b935ea1f4c2555849e9 first -- that's the interesting code change and has a MIR diff. My favourite part of the diff: ```diff - _20 = BitAnd(_19, const 340282366920938463463374607431768211448_u128); // scope 0 at $DIR/shifts.rs:+2:34: +2:44 - _21 = Ne(move _20, const 0_u128); // scope 0 at $DIR/shifts.rs:+2:34: +2:44 - assert(!move _21, "attempt to shift right by `{}`, which would overflow", _19) -> [success: bb3, unwind: bb7]; // scope 0 at $DIR/shifts.rs:+2:34: +2:44 + _18 = Lt(_17, const 8_u128); // scope 0 at $DIR/shifts.rs:+2:34: +2:44 + assert(move _18, "attempt to shift right by `{}`, which would overflow", _17) -> [success: bb3, unwind: bb7]; // scope 0 at $DIR/shifts.rs:+2:34: +2:44 ```
This commit is contained in:
commit
7afed92749
6 changed files with 371 additions and 37 deletions
|
@ -566,41 +566,51 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
Rvalue::Use(Operand::Move(val))
|
||||
}
|
||||
BinOp::Shl | BinOp::Shr if self.check_overflow && ty.is_integral() => {
|
||||
// Consider that the shift overflows if `rhs < 0` or `rhs >= bits`.
|
||||
// This can be encoded as a single operation as `(rhs & -bits) != 0`.
|
||||
let (size, _) = ty.int_size_and_signed(self.tcx);
|
||||
let bits = size.bits();
|
||||
debug_assert!(bits.is_power_of_two());
|
||||
let mask = !((bits - 1) as u128);
|
||||
|
||||
// For an unsigned RHS, the shift is in-range for `rhs < bits`.
|
||||
// For a signed RHS, `IntToInt` cast to the equivalent unsigned
|
||||
// type and do that same comparison. Because the type is the
|
||||
// same size, there's no negative shift amount that ends up
|
||||
// overlapping with valid ones, thus it catches negatives too.
|
||||
let (lhs_size, _) = ty.int_size_and_signed(self.tcx);
|
||||
let rhs_ty = rhs.ty(&self.local_decls, self.tcx);
|
||||
let (rhs_size, _) = rhs_ty.int_size_and_signed(self.tcx);
|
||||
let mask = Operand::const_from_scalar(
|
||||
|
||||
let (unsigned_rhs, unsigned_ty) = match rhs_ty.kind() {
|
||||
ty::Uint(_) => (rhs.to_copy(), rhs_ty),
|
||||
ty::Int(int_width) => {
|
||||
let uint_ty = self.tcx.mk_mach_uint(int_width.to_unsigned());
|
||||
let rhs_temp = self.temp(uint_ty, span);
|
||||
self.cfg.push_assign(
|
||||
block,
|
||||
source_info,
|
||||
rhs_temp,
|
||||
Rvalue::Cast(CastKind::IntToInt, rhs.to_copy(), uint_ty),
|
||||
);
|
||||
(Operand::Move(rhs_temp), uint_ty)
|
||||
}
|
||||
_ => unreachable!("only integers are shiftable"),
|
||||
};
|
||||
|
||||
// This can't overflow because the largest shiftable types are 128-bit,
|
||||
// which fits in `u8`, the smallest possible `unsigned_ty`.
|
||||
// (And `from_uint` will `bug!` if that's ever no longer true.)
|
||||
let lhs_bits = Operand::const_from_scalar(
|
||||
self.tcx,
|
||||
rhs_ty,
|
||||
Scalar::from_uint(rhs_size.truncate(mask), rhs_size),
|
||||
unsigned_ty,
|
||||
Scalar::from_uint(lhs_size.bits(), rhs_size),
|
||||
span,
|
||||
);
|
||||
|
||||
let outer_bits = self.temp(rhs_ty, span);
|
||||
let inbounds = self.temp(bool_ty, span);
|
||||
self.cfg.push_assign(
|
||||
block,
|
||||
source_info,
|
||||
outer_bits,
|
||||
Rvalue::BinaryOp(BinOp::BitAnd, Box::new((rhs.to_copy(), mask))),
|
||||
);
|
||||
|
||||
let overflows = self.temp(bool_ty, span);
|
||||
let zero = self.zero_literal(span, rhs_ty);
|
||||
self.cfg.push_assign(
|
||||
block,
|
||||
source_info,
|
||||
overflows,
|
||||
Rvalue::BinaryOp(BinOp::Ne, Box::new((Operand::Move(outer_bits), zero))),
|
||||
inbounds,
|
||||
Rvalue::BinaryOp(BinOp::Lt, Box::new((unsigned_rhs, lhs_bits))),
|
||||
);
|
||||
|
||||
let overflow_err = AssertKind::Overflow(op, lhs.to_copy(), rhs.to_copy());
|
||||
block = self.assert(block, Operand::Move(overflows), false, overflow_err, span);
|
||||
block = self.assert(block, Operand::Move(inbounds), true, overflow_err, span);
|
||||
Rvalue::BinaryOp(op, Box::new((lhs, rhs)))
|
||||
}
|
||||
BinOp::Div | BinOp::Rem if ty.is_integral() => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue