Implement checked Shl/Shr at MIR building.
This commit is contained in:
parent
43ee4d15bf
commit
dd582bd7db
7 changed files with 109 additions and 107 deletions
|
|
@ -170,14 +170,6 @@ pub(crate) fn codegen_checked_int_binop<'tcx>(
|
|||
in_lhs: CValue<'tcx>,
|
||||
in_rhs: CValue<'tcx>,
|
||||
) -> CValue<'tcx> {
|
||||
if bin_op != BinOp::Shl && bin_op != BinOp::Shr {
|
||||
assert_eq!(
|
||||
in_lhs.layout().ty,
|
||||
in_rhs.layout().ty,
|
||||
"checked int binop requires lhs and rhs of same type"
|
||||
);
|
||||
}
|
||||
|
||||
let lhs = in_lhs.load_scalar(fx);
|
||||
let rhs = in_rhs.load_scalar(fx);
|
||||
|
||||
|
|
@ -271,21 +263,6 @@ pub(crate) fn codegen_checked_int_binop<'tcx>(
|
|||
_ => unreachable!("invalid non-integer type {}", ty),
|
||||
}
|
||||
}
|
||||
BinOp::Shl => {
|
||||
let val = fx.bcx.ins().ishl(lhs, rhs);
|
||||
let ty = fx.bcx.func.dfg.value_type(val);
|
||||
let max_shift = i64::from(ty.bits()) - 1;
|
||||
let has_overflow = fx.bcx.ins().icmp_imm(IntCC::UnsignedGreaterThan, rhs, max_shift);
|
||||
(val, has_overflow)
|
||||
}
|
||||
BinOp::Shr => {
|
||||
let val =
|
||||
if !signed { fx.bcx.ins().ushr(lhs, rhs) } else { fx.bcx.ins().sshr(lhs, rhs) };
|
||||
let ty = fx.bcx.func.dfg.value_type(val);
|
||||
let max_shift = i64::from(ty.bits()) - 1;
|
||||
let has_overflow = fx.bcx.ins().icmp_imm(IntCC::UnsignedGreaterThan, rhs, max_shift);
|
||||
(val, has_overflow)
|
||||
}
|
||||
_ => bug!("binop {:?} on checked int/uint lhs: {:?} rhs: {:?}", bin_op, in_lhs, in_rhs),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -663,17 +663,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
};
|
||||
bx.checked_binop(oop, input_ty, lhs, rhs)
|
||||
}
|
||||
mir::BinOp::Shl | mir::BinOp::Shr => {
|
||||
let lhs_llty = bx.cx().val_ty(lhs);
|
||||
let rhs_llty = bx.cx().val_ty(rhs);
|
||||
let invert_mask = common::shift_mask_val(bx, lhs_llty, rhs_llty, true);
|
||||
let outer_bits = bx.and(rhs, invert_mask);
|
||||
|
||||
let of = bx.icmp(IntPredicate::IntNE, outer_bits, bx.cx().const_null(rhs_llty));
|
||||
let val = self.codegen_scalar_binop(bx, op, lhs, rhs, input_ty);
|
||||
|
||||
(val, of)
|
||||
}
|
||||
_ => bug!("Operator `{:?}` is not a checkable operator", op),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -553,15 +553,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
);
|
||||
}
|
||||
}
|
||||
Shl | Shr => {
|
||||
for x in [a, b] {
|
||||
check_kinds!(
|
||||
x,
|
||||
"Cannot perform checked shift on non-integer type {:?}",
|
||||
ty::Uint(..) | ty::Int(..)
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => self.fail(location, format!("There is no checked version of {:?}", op)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,22 +59,13 @@ impl<'tcx> fmt::Display for Discr<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn int_size_and_signed<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> (Size, bool) {
|
||||
let (int, signed) = match *ty.kind() {
|
||||
ty::Int(ity) => (Integer::from_int_ty(&tcx, ity), true),
|
||||
ty::Uint(uty) => (Integer::from_uint_ty(&tcx, uty), false),
|
||||
_ => bug!("non integer discriminant"),
|
||||
};
|
||||
(int.size(), signed)
|
||||
}
|
||||
|
||||
impl<'tcx> Discr<'tcx> {
|
||||
/// Adds `1` to the value and wraps around if the maximum for the type is reached.
|
||||
pub fn wrap_incr(self, tcx: TyCtxt<'tcx>) -> Self {
|
||||
self.checked_add(tcx, 1).0
|
||||
}
|
||||
pub fn checked_add(self, tcx: TyCtxt<'tcx>, n: u128) -> (Self, bool) {
|
||||
let (size, signed) = int_size_and_signed(tcx, self.ty);
|
||||
let (size, signed) = self.ty.int_size_and_signed(tcx);
|
||||
let (val, oflo) = if signed {
|
||||
let min = size.signed_int_min();
|
||||
let max = size.signed_int_max();
|
||||
|
|
@ -922,12 +913,21 @@ impl<'tcx> TypeFolder<TyCtxt<'tcx>> for OpaqueTypeExpander<'tcx> {
|
|||
}
|
||||
|
||||
impl<'tcx> Ty<'tcx> {
|
||||
pub fn int_size_and_signed(self, tcx: TyCtxt<'tcx>) -> (Size, bool) {
|
||||
let (int, signed) = match *self.kind() {
|
||||
ty::Int(ity) => (Integer::from_int_ty(&tcx, ity), true),
|
||||
ty::Uint(uty) => (Integer::from_uint_ty(&tcx, uty), false),
|
||||
_ => bug!("non integer discriminant"),
|
||||
};
|
||||
(int.size(), signed)
|
||||
}
|
||||
|
||||
/// Returns the maximum value for the given numeric type (including `char`s)
|
||||
/// or returns `None` if the type is not numeric.
|
||||
pub fn numeric_max_val(self, tcx: TyCtxt<'tcx>) -> Option<ty::Const<'tcx>> {
|
||||
let val = match self.kind() {
|
||||
ty::Int(_) | ty::Uint(_) => {
|
||||
let (size, signed) = int_size_and_signed(tcx, self);
|
||||
let (size, signed) = self.int_size_and_signed(tcx);
|
||||
let val =
|
||||
if signed { size.signed_int_max() as u128 } else { size.unsigned_int_max() };
|
||||
Some(val)
|
||||
|
|
@ -948,7 +948,7 @@ impl<'tcx> Ty<'tcx> {
|
|||
pub fn numeric_min_val(self, tcx: TyCtxt<'tcx>) -> Option<ty::Const<'tcx>> {
|
||||
let val = match self.kind() {
|
||||
ty::Int(_) | ty::Uint(_) => {
|
||||
let (size, signed) = int_size_and_signed(tcx, self);
|
||||
let (size, signed) = self.int_size_and_signed(tcx);
|
||||
let val = if signed { size.truncate(size.signed_int_min() as u128) } else { 0 };
|
||||
Some(val)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use crate::build::expr::category::{Category, RvalueFunc};
|
|||
use crate::build::{BlockAnd, BlockAndExtension, Builder, NeedsTemporary};
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_middle::middle::region;
|
||||
use rustc_middle::mir::interpret::Scalar;
|
||||
use rustc_middle::mir::AssertKind;
|
||||
use rustc_middle::mir::Place;
|
||||
use rustc_middle::mir::*;
|
||||
|
|
@ -519,30 +520,68 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
) -> BlockAnd<Rvalue<'tcx>> {
|
||||
let source_info = self.source_info(span);
|
||||
let bool_ty = self.tcx.types.bool;
|
||||
if self.check_overflow && op.is_checkable() && ty.is_integral() {
|
||||
let result_tup = self.tcx.mk_tup(&[ty, bool_ty]);
|
||||
let result_value = self.temp(result_tup, span);
|
||||
let rvalue = match op {
|
||||
BinOp::Add | BinOp::Sub | BinOp::Mul if self.check_overflow && ty.is_integral() => {
|
||||
let result_tup = self.tcx.mk_tup(&[ty, bool_ty]);
|
||||
let result_value = self.temp(result_tup, span);
|
||||
|
||||
self.cfg.push_assign(
|
||||
block,
|
||||
source_info,
|
||||
result_value,
|
||||
Rvalue::CheckedBinaryOp(op, Box::new((lhs.to_copy(), rhs.to_copy()))),
|
||||
);
|
||||
let val_fld = Field::new(0);
|
||||
let of_fld = Field::new(1);
|
||||
self.cfg.push_assign(
|
||||
block,
|
||||
source_info,
|
||||
result_value,
|
||||
Rvalue::CheckedBinaryOp(op, Box::new((lhs.to_copy(), rhs.to_copy()))),
|
||||
);
|
||||
let val_fld = Field::new(0);
|
||||
let of_fld = Field::new(1);
|
||||
|
||||
let tcx = self.tcx;
|
||||
let val = tcx.mk_place_field(result_value, val_fld, ty);
|
||||
let of = tcx.mk_place_field(result_value, of_fld, bool_ty);
|
||||
let tcx = self.tcx;
|
||||
let val = tcx.mk_place_field(result_value, val_fld, ty);
|
||||
let of = tcx.mk_place_field(result_value, of_fld, bool_ty);
|
||||
|
||||
let err = AssertKind::Overflow(op, lhs, rhs);
|
||||
let err = AssertKind::Overflow(op, lhs, rhs);
|
||||
block = self.assert(block, Operand::Move(of), false, err, span);
|
||||
|
||||
block = self.assert(block, Operand::Move(of), false, err, span);
|
||||
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);
|
||||
|
||||
block.and(Rvalue::Use(Operand::Move(val)))
|
||||
} else {
|
||||
if ty.is_integral() && (op == BinOp::Div || op == BinOp::Rem) {
|
||||
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(
|
||||
self.tcx,
|
||||
rhs_ty,
|
||||
Scalar::from_uint(rhs_size.truncate(mask), rhs_size),
|
||||
span,
|
||||
);
|
||||
|
||||
let outer_bits = self.temp(rhs_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))),
|
||||
);
|
||||
|
||||
let overflow_err = AssertKind::Overflow(op, lhs.to_copy(), rhs.to_copy());
|
||||
block = self.assert(block, Operand::Move(overflows), false, overflow_err, span);
|
||||
Rvalue::BinaryOp(op, Box::new((lhs, rhs)))
|
||||
}
|
||||
BinOp::Div | BinOp::Rem if ty.is_integral() => {
|
||||
// Checking division and remainder is more complex, since we 1. always check
|
||||
// and 2. there are two possible failure cases, divide-by-zero and overflow.
|
||||
|
||||
|
|
@ -601,10 +640,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
|
||||
block = self.assert(block, Operand::Move(of), false, overflow_err, span);
|
||||
}
|
||||
}
|
||||
|
||||
block.and(Rvalue::BinaryOp(op, Box::new((lhs, rhs))))
|
||||
}
|
||||
Rvalue::BinaryOp(op, Box::new((lhs, rhs)))
|
||||
}
|
||||
_ => Rvalue::BinaryOp(op, Box::new((lhs, rhs))),
|
||||
};
|
||||
block.and(rvalue)
|
||||
}
|
||||
|
||||
fn build_zero_repeat(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue