Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make codegen choose whether to emit overflow checks #107921

Merged
merged 19 commits into from
Feb 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions compiler/rustc_codegen_cranelift/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,12 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
}
TerminatorKind::Assert { cond, expected, msg, target, cleanup: _ } => {
if !fx.tcx.sess.overflow_checks() {
if let mir::AssertKind::OverflowNeg(_) = *msg {
let overflow_not_to_check = match msg {
AssertKind::OverflowNeg(..) => true,
AssertKind::Overflow(op, ..) => op.is_checkable(),
_ => false,
};
if overflow_not_to_check {
let target = fx.get_block(*target);
fx.bcx.ins().jump(target, &[]);
continue;
Expand Down Expand Up @@ -567,15 +572,7 @@ fn codegen_stmt<'tcx>(
let lhs = codegen_operand(fx, &lhs_rhs.0);
let rhs = codegen_operand(fx, &lhs_rhs.1);

let res = if !fx.tcx.sess.overflow_checks() {
let val =
crate::num::codegen_int_binop(fx, bin_op, lhs, rhs).load_scalar(fx);
let is_overflow = fx.bcx.ins().iconst(types::I8, 0);
CValue::by_val_pair(val, is_overflow, lval.layout())
} else {
crate::num::codegen_checked_int_binop(fx, bin_op, lhs, rhs)
};

let res = crate::num::codegen_checked_int_binop(fx, bin_op, lhs, rhs);
lval.write_cvalue(fx, res);
}
Rvalue::UnaryOp(un_op, ref operand) => {
Expand Down
14 changes: 0 additions & 14 deletions compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,20 +493,6 @@ fn codegen_regular_intrinsic_call<'tcx>(
let res = crate::num::codegen_int_binop(fx, bin_op, x, y);
ret.write_cvalue(fx, res);
}
sym::add_with_overflow | sym::sub_with_overflow | sym::mul_with_overflow => {
intrinsic_args!(fx, args => (x, y); intrinsic);

assert_eq!(x.layout().ty, y.layout().ty);
let bin_op = match intrinsic {
sym::add_with_overflow => BinOp::Add,
sym::sub_with_overflow => BinOp::Sub,
sym::mul_with_overflow => BinOp::Mul,
_ => unreachable!(),
};

let res = crate::num::codegen_checked_int_binop(fx, bin_op, x, y);
ret.write_cvalue(fx, res);
}
sym::saturating_add | sym::saturating_sub => {
intrinsic_args!(fx, args => (lhs, rhs); intrinsic);

Expand Down
12 changes: 7 additions & 5 deletions compiler/rustc_codegen_ssa/src/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,11 +565,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
// with #[rustc_inherit_overflow_checks] and inlined from
// another crate (mostly core::num generic/#[inline] fns),
// while the current crate doesn't use overflow checks.
// NOTE: Unlike binops, negation doesn't have its own
// checked operation, just a comparison with the minimum
// value, so we have to check for the assert message.
if !bx.check_overflow() {
if let AssertKind::OverflowNeg(_) = *msg {
if !bx.cx().check_overflow() {
let overflow_not_to_check = match msg {
AssertKind::OverflowNeg(..) => true,
AssertKind::Overflow(op, ..) => op.is_checkable(),
bjorn3 marked this conversation as resolved.
Show resolved Hide resolved
_ => false,
};
if overflow_not_to_check {
const_cond = Some(expected);
}
}
Expand Down
25 changes: 0 additions & 25 deletions compiler/rustc_codegen_ssa/src/mir/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
args[1].val.unaligned_volatile_store(bx, dst);
return;
}
sym::add_with_overflow
| sym::sub_with_overflow
| sym::mul_with_overflow
| sym::unchecked_div
| sym::unchecked_rem
| sym::unchecked_shl
Expand All @@ -232,28 +229,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let ty = arg_tys[0];
match int_type_width_signed(ty, bx.tcx()) {
Some((_width, signed)) => match name {
sym::add_with_overflow
| sym::sub_with_overflow
| sym::mul_with_overflow => {
let op = match name {
sym::add_with_overflow => OverflowOp::Add,
sym::sub_with_overflow => OverflowOp::Sub,
sym::mul_with_overflow => OverflowOp::Mul,
_ => bug!(),
};
let (val, overflow) =
bx.checked_binop(op, ty, args[0].immediate(), args[1].immediate());
// Convert `i1` to a `bool`, and write it to the out parameter
let val = bx.from_immediate(val);
let overflow = bx.from_immediate(overflow);

let dest = result.project_field(bx, 0);
bx.store(val, dest.llval, dest.align);
let dest = result.project_field(bx, 1);
bx.store(overflow, dest.llval, dest.align);

return;
}
sym::exact_div => {
if signed {
bx.exactsdiv(args[0].immediate(), args[1].immediate())
Expand Down
9 changes: 0 additions & 9 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,15 +652,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
rhs: Bx::Value,
input_ty: Ty<'tcx>,
) -> OperandValue<Bx::Value> {
// This case can currently arise only from functions marked
// with #[rustc_inherit_overflow_checks] and inlined from
// another crate (mostly core::num generic/#[inline] fns),
// while the current crate doesn't use overflow checks.
if !bx.cx().check_overflow() {
let val = self.codegen_scalar_binop(bx, op, lhs, rhs, input_ty);
return OperandValue::Pair(val, bx.cx().const_bool(false));
}

let (val, of) = match op {
// These are checked using intrinsics
mir::BinOp::Add | mir::BinOp::Sub | mir::BinOp::Mul => {
Expand Down
13 changes: 0 additions & 13 deletions compiler/rustc_const_eval/src/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let out_val = numeric_intrinsic(intrinsic_name, bits, kind);
self.write_scalar(out_val, dest)?;
}
sym::add_with_overflow | sym::sub_with_overflow | sym::mul_with_overflow => {
let lhs = self.read_immediate(&args[0])?;
let rhs = self.read_immediate(&args[1])?;
let bin_op = match intrinsic_name {
sym::add_with_overflow => BinOp::Add,
sym::sub_with_overflow => BinOp::Sub,
sym::mul_with_overflow => BinOp::Mul,
_ => bug!(),
};
self.binop_with_overflow(
bin_op, /*force_overflow_checks*/ true, &lhs, &rhs, dest,
)?;
}
sym::saturating_add | sym::saturating_sub => {
let l = self.read_immediate(&args[0])?;
let r = self.read_immediate(&args[1])?;
Expand Down
9 changes: 5 additions & 4 deletions compiler/rustc_const_eval/src/interpret/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ pub trait Machine<'mir, 'tcx>: Sized {
true
}

/// Whether CheckedBinOp MIR statements should actually check for overflow.
fn checked_binop_checks_overflow(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
/// Whether Assert(OverflowNeg) and Assert(Overflow) MIR terminators should actually
/// check for overflow.
fn ignore_checkable_overflow_assertions(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;

/// Entry point for obtaining the MIR of anything that should get evaluated.
/// So not just functions and shims, but also const/static initializers, anonymous
Expand Down Expand Up @@ -466,8 +467,8 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
}

#[inline(always)]
fn checked_binop_checks_overflow(_ecx: &InterpCx<$mir, $tcx, Self>) -> bool {
true
fn ignore_checkable_overflow_assertions(_ecx: &InterpCx<$mir, $tcx, Self>) -> bool {
false
}

#[inline(always)]
Expand Down
8 changes: 0 additions & 8 deletions compiler/rustc_const_eval/src/interpret/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@ use super::{ImmTy, Immediate, InterpCx, Machine, PlaceTy};
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.
///
/// `force_overflow_checks` indicates whether overflow checks should be done even when
/// `tcx.sess.overflow_checks()` is `false`.
pub fn binop_with_overflow(
&mut self,
op: mir::BinOp,
force_overflow_checks: bool,
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
left: &ImmTy<'tcx, M::Provenance>,
right: &ImmTy<'tcx, M::Provenance>,
dest: &PlaceTy<'tcx, M::Provenance>,
Expand All @@ -28,10 +24,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
"type mismatch for result of {:?}",
op,
);
// As per https://github.com/rust-lang/rust/pull/98738, we always return `false` in the 2nd
// component when overflow checking is disabled.
let overflowed =
overflowed && (force_overflow_checks || M::checked_binop_checks_overflow(self));
// Write the result to `dest`.
if let Abi::ScalarPair(..) = dest.layout.abi {
// We can use the optimized path and avoid `place_field` (which might do
Expand Down
4 changes: 1 addition & 3 deletions compiler/rustc_const_eval/src/interpret/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let left = self.read_immediate(&self.eval_operand(left, None)?)?;
let layout = binop_right_homogeneous(bin_op).then_some(left.layout);
let right = self.read_immediate(&self.eval_operand(right, layout)?)?;
self.binop_with_overflow(
bin_op, /*force_overflow_checks*/ false, &left, &right, &dest,
)?;
self.binop_with_overflow(bin_op, &left, &right, &dest)?;
}

UnaryOp(un_op, ref operand) => {
Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_const_eval/src/interpret/terminator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}

Assert { ref cond, expected, ref msg, target, cleanup } => {
let ignored = M::ignore_checkable_overflow_assertions(self)
&& match msg {
mir::AssertKind::OverflowNeg(..) => true,
mir::AssertKind::Overflow(op, ..) => op.is_checkable(),
_ => false,
};
let cond_val = self.read_scalar(&self.eval_operand(cond, None)?)?.to_bool()?;
if expected == cond_val {
if ignored || expected == cond_val {
self.go_to_block(target);
} else {
M::assert_panic(self, msg, cleanup)?;
Expand Down
10 changes: 6 additions & 4 deletions compiler/rustc_middle/src/mir/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,12 @@ pub enum TerminatorKind<'tcx> {
/// as parameters, and `None` for the destination. Keep in mind that the `cleanup` path is not
/// necessarily executed even in the case of a panic, for example in `-C panic=abort`. If the
/// assertion does not fail, execution continues at the specified basic block.
///
/// When overflow checking is disabled and this is run-time MIR (as opposed to compile-time MIR
/// that is used for CTFE), the following variants of this terminator behave as `goto target`:
/// - `OverflowNeg(..)`,
/// - `Overflow(op, ..)` if op is a "checkable" operation (add, sub, mul, shl, shr, but NOT
/// div or rem).
Assert {
cond: Operand<'tcx>,
expected: bool,
Expand Down Expand Up @@ -1103,10 +1109,6 @@ pub enum Rvalue<'tcx> {

/// Same as `BinaryOp`, but yields `(T, bool)` with a `bool` indicating an error condition.
///
/// When overflow checking is disabled and we are generating run-time code, the error condition
/// is false. Otherwise, and always during CTFE, the error condition is determined as described
/// below.
///
/// For addition, subtraction, and multiplication on integers the error condition is set when
/// the infinite precision result would be unequal to the actual result.
///
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
111 changes: 15 additions & 96 deletions compiler/rustc_mir_transform/src/const_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use rustc_middle::mir::visit::{
};
use rustc_middle::mir::{
BasicBlock, BinOp, Body, Constant, ConstantKind, Local, LocalDecl, LocalKind, Location,
Operand, Place, Rvalue, SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UnOp,
Operand, Place, Rvalue, SourceInfo, Statement, StatementKind, Terminator, TerminatorKind,
RETURN_PLACE,
};
use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
Expand Down Expand Up @@ -503,55 +503,6 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
}
}

fn check_unary_op(&mut self, op: UnOp, arg: &Operand<'tcx>) -> Option<()> {
if self.use_ecx(|this| {
let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?;
let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?;
Ok(overflow)
})? {
// `AssertKind` only has an `OverflowNeg` variant, so make sure that is
// appropriate to use.
assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow");
return None;
}

Some(())
}

fn check_binary_op(
&mut self,
op: BinOp,
left: &Operand<'tcx>,
right: &Operand<'tcx>,
) -> Option<()> {
let r = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?));
let l = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?));
// Check for exceeding shifts *even if* we cannot evaluate the LHS.
if matches!(op, BinOp::Shr | BinOp::Shl) {
let r = r.clone()?;
// We need the type of the LHS. We cannot use `place_layout` as that is the type
// of the result, which for checked binops is not the same!
let left_ty = left.ty(self.local_decls, self.tcx);
let left_size = self.ecx.layout_of(left_ty).ok()?.size;
let right_size = r.layout.size;
let r_bits = r.to_scalar().to_bits(right_size).ok();
if r_bits.map_or(false, |b| b >= left_size.bits() as u128) {
return None;
}
}

if let (Some(l), Some(r)) = (&l, &r) {
// The remaining operators are handled through `overflowing_binary_op`.
if self.use_ecx(|this| {
let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
Ok(overflow)
})? {
return None;
}
}
Some(())
}

fn propagate_operand(&mut self, operand: &mut Operand<'tcx>) {
match *operand {
Operand::Copy(l) | Operand::Move(l) => {
Expand Down Expand Up @@ -587,28 +538,6 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
// 2. Working around bugs in other parts of the compiler
// - In this case, we'll return `None` from this function to stop evaluation.
match rvalue {
// Additional checking: give lints to the user if an overflow would occur.
// We do this here and not in the `Assert` terminator as that terminator is
// only sometimes emitted (overflow checks can be disabled), but we want to always
// lint.
Rvalue::UnaryOp(op, arg) => {
trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
self.check_unary_op(*op, arg)?;
}
Rvalue::BinaryOp(op, box (left, right)) => {
trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);
self.check_binary_op(*op, left, right)?;
}
Rvalue::CheckedBinaryOp(op, box (left, right)) => {
trace!(
"checking CheckedBinaryOp(op = {:?}, left = {:?}, right = {:?})",
op,
left,
right
);
self.check_binary_op(*op, left, right)?;
}

// Do not try creating references (#67862)
Rvalue::AddressOf(_, place) | Rvalue::Ref(_, _, place) => {
trace!("skipping AddressOf | Ref for {:?}", place);
Expand Down Expand Up @@ -638,7 +567,10 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
| Rvalue::Cast(..)
| Rvalue::ShallowInitBox(..)
| Rvalue::Discriminant(..)
| Rvalue::NullaryOp(..) => {}
| Rvalue::NullaryOp(..)
| Rvalue::UnaryOp(..)
| Rvalue::BinaryOp(..)
| Rvalue::CheckedBinaryOp(..) => {}
}

// FIXME we need to revisit this for #67176
Expand Down Expand Up @@ -1079,31 +1011,18 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
// Do NOT early return in this function, it does some crucial fixup of the state at the end!
match &mut terminator.kind {
TerminatorKind::Assert { expected, ref mut cond, .. } => {
if let Some(ref value) = self.eval_operand(&cond) {
trace!("assertion on {:?} should be {:?}", value, expected);
let expected = Scalar::from_bool(*expected);
if let Some(ref value) = self.eval_operand(&cond)
// FIXME should be used use_ecx rather than a local match... but we have
// quite a few of these read_scalar/read_immediate that need fixing.
if let Ok(value_const) = self.ecx.read_scalar(&value) {
if expected != value_const {
// Poison all places this operand references so that further code
// doesn't use the invalid value
match cond {
Operand::Move(ref place) | Operand::Copy(ref place) => {
Self::remove_const(&mut self.ecx, place.local);
}
Operand::Constant(_) => {}
}
} else {
if self.should_const_prop(value) {
*cond = self.operand_from_scalar(
value_const,
self.tcx.types.bool,
source_info.span,
);
}
}
}
&& let Ok(value_const) = self.ecx.read_scalar(&value)
&& self.should_const_prop(value)
{
trace!("assertion on {:?} should be {:?}", value, expected);
*cond = self.operand_from_scalar(
value_const,
self.tcx.types.bool,
source_info.span,
);
}
}
TerminatorKind::SwitchInt { ref mut discr, .. } => {
Expand Down
Loading