1
Fork 0

Promote unchecked_add/sub/mul/shl/shr to mir::BinOp

This commit is contained in:
Scott McMurray 2023-06-03 00:41:50 -07:00
parent 8d1fa473dd
commit 39788e07ba
36 changed files with 504 additions and 215 deletions

View file

@ -3,10 +3,13 @@ use rustc_middle::mir;
use rustc_middle::mir::interpret::{InterpResult, Scalar};
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_middle::ty::{self, FloatTy, Ty};
use rustc_span::symbol::sym;
use rustc_target::abi::Abi;
use super::{ImmTy, Immediate, InterpCx, Machine, PlaceTy};
use crate::fluent_generated as fluent;
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// Applies the binary operation `op` to the two operands and writes a tuple of the result
/// and a boolean signifying the potential overflow to the destination.
@ -139,8 +142,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
) -> InterpResult<'tcx, (Scalar<M::Provenance>, bool, Ty<'tcx>)> {
use rustc_middle::mir::BinOp::*;
let throw_ub_on_overflow = match bin_op {
AddUnchecked => Some(sym::unchecked_add),
SubUnchecked => Some(sym::unchecked_sub),
MulUnchecked => Some(sym::unchecked_mul),
ShlUnchecked => Some(sym::unchecked_shl),
ShrUnchecked => Some(sym::unchecked_shr),
_ => None,
};
// Shift ops can have an RHS with a different numeric type.
if bin_op == Shl || bin_op == Shr {
if matches!(bin_op, Shl | ShlUnchecked | Shr | ShrUnchecked) {
let size = u128::from(left_layout.size.bits());
// Even if `r` is signed, we treat it as if it was unsigned (i.e., we use its
// zero-extended form). This matches the codegen backend:
@ -155,6 +167,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// integers are maximally 128bits wide, so negative shifts *always* overflow and we have
// consistent results for the same value represented at different bit widths.
assert!(size <= 128);
let original_r = r;
let overflow = r >= size;
// The shift offset is implicitly masked to the type size, to make sure this operation
// is always defined. This is the one MIR operator that does *not* directly map to a
@ -166,19 +179,28 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let result = if left_layout.abi.is_signed() {
let l = self.sign_extend(l, left_layout) as i128;
let result = match bin_op {
Shl => l.checked_shl(r).unwrap(),
Shr => l.checked_shr(r).unwrap(),
Shl | ShlUnchecked => l.checked_shl(r).unwrap(),
Shr | ShrUnchecked => l.checked_shr(r).unwrap(),
_ => bug!(),
};
result as u128
} else {
match bin_op {
Shl => l.checked_shl(r).unwrap(),
Shr => l.checked_shr(r).unwrap(),
Shl | ShlUnchecked => l.checked_shl(r).unwrap(),
Shr | ShrUnchecked => l.checked_shr(r).unwrap(),
_ => bug!(),
}
};
let truncated = self.truncate(result, left_layout);
if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
throw_ub_custom!(
fluent::const_eval_overflow_shift,
val = original_r,
name = intrinsic_name
);
}
return Ok((Scalar::from_uint(truncated, left_layout.size), overflow, left_layout.ty));
}
@ -216,9 +238,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
Rem if r == 0 => throw_ub!(RemainderByZero),
Div => Some(i128::overflowing_div),
Rem => Some(i128::overflowing_rem),
Add => Some(i128::overflowing_add),
Sub => Some(i128::overflowing_sub),
Mul => Some(i128::overflowing_mul),
Add | AddUnchecked => Some(i128::overflowing_add),
Sub | SubUnchecked => Some(i128::overflowing_sub),
Mul | MulUnchecked => Some(i128::overflowing_mul),
_ => None,
};
if let Some(op) = op {
@ -242,11 +264,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// If that truncation loses any information, we have an overflow.
let result = result as u128;
let truncated = self.truncate(result, left_layout);
return Ok((
Scalar::from_uint(truncated, size),
oflo || self.sign_extend(truncated, left_layout) != result,
left_layout.ty,
));
let overflow = oflo || self.sign_extend(truncated, left_layout) != result;
if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name);
}
return Ok((Scalar::from_uint(truncated, size), overflow, left_layout.ty));
}
}
@ -263,12 +285,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
BitAnd => (Scalar::from_uint(l & r, size), left_layout.ty),
BitXor => (Scalar::from_uint(l ^ r, size), left_layout.ty),
Add | Sub | Mul | Rem | Div => {
Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Rem | Div => {
assert!(!left_layout.abi.is_signed());
let op: fn(u128, u128) -> (u128, bool) = match bin_op {
Add => u128::overflowing_add,
Sub => u128::overflowing_sub,
Mul => u128::overflowing_mul,
Add | AddUnchecked => u128::overflowing_add,
Sub | SubUnchecked => u128::overflowing_sub,
Mul | MulUnchecked => u128::overflowing_mul,
Div if r == 0 => throw_ub!(DivisionByZero),
Rem if r == 0 => throw_ub!(RemainderByZero),
Div => u128::overflowing_div,
@ -279,11 +301,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Truncate to target type.
// If that truncation loses any information, we have an overflow.
let truncated = self.truncate(result, left_layout);
return Ok((
Scalar::from_uint(truncated, size),
oflo || truncated != result,
left_layout.ty,
));
let overflow = oflo || truncated != result;
if overflow && let Some(intrinsic_name) = throw_ub_on_overflow {
throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name);
}
return Ok((Scalar::from_uint(truncated, size), overflow, left_layout.ty));
}
_ => span_bug!(

View file

@ -16,7 +16,8 @@ use super::{ImmTy, InterpCx, Machine};
fn binop_left_homogeneous(op: mir::BinOp) -> bool {
use rustc_middle::mir::BinOp::*;
match op {
Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Offset | Shl | Shr => true,
Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor
| BitAnd | BitOr | Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => true,
Eq | Ne | Lt | Le | Gt | Ge => false,
}
}
@ -26,8 +27,9 @@ fn binop_left_homogeneous(op: mir::BinOp) -> bool {
fn binop_right_homogeneous(op: mir::BinOp) -> bool {
use rustc_middle::mir::BinOp::*;
match op {
Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge => true,
Offset | Shl | Shr => false,
Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor
| BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge => true,
Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => false,
}
}

View file

@ -569,13 +569,18 @@ impl<'tcx> Validator<'_, 'tcx> {
| BinOp::Gt
| BinOp::Offset
| BinOp::Add
| BinOp::AddUnchecked
| BinOp::Sub
| BinOp::SubUnchecked
| BinOp::Mul
| BinOp::MulUnchecked
| BinOp::BitXor
| BinOp::BitAnd
| BinOp::BitOr
| BinOp::Shl
| BinOp::Shr => {}
| BinOp::ShlUnchecked
| BinOp::Shr
| BinOp::ShrUnchecked => {}
}
self.validate_operand(lhs)?;

View file

@ -556,7 +556,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
);
}
}
Shl | Shr => {
Shl | ShlUnchecked | Shr | ShrUnchecked => {
for x in [a, b] {
check_kinds!(
x,
@ -601,6 +601,24 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
);
}
}
AddUnchecked | SubUnchecked | MulUnchecked => {
for x in [a, b] {
check_kinds!(
x,
"Cannot perform unchecked arithmetic on type {:?}",
ty::Uint(..) | ty::Int(..)
)
}
if a != b {
self.fail(
location,
format!(
"Cannot perform unchecked arithmetic on unequal types {:?} and {:?}",
a, b
),
);
}
}
}
}
Rvalue::CheckedBinaryOp(op, vals) => {