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

Add overflow_checks intrinsic #128666

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1955,7 +1955,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
ConstraintCategory::SizedBound,
);
}
&Rvalue::NullaryOp(NullOp::UbChecks, _) => {}
&Rvalue::NullaryOp(NullOp::RuntimeChecks(_), _) => {}

Rvalue::ShallowInitBox(operand, ty) => {
self.check_operand(operand, location);
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_codegen_cranelift/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,8 +839,8 @@ fn codegen_stmt<'tcx>(
.tcx
.offset_of_subfield(ParamEnv::reveal_all(), layout, fields.iter())
.bytes(),
NullOp::UbChecks => {
let val = fx.tcx.sess.ub_checks();
NullOp::RuntimeChecks(kind) => {
let val = kind.value(fx.tcx.sess);
let val = CValue::by_val(
fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()),
fx.layout_of(fx.tcx.types.bool),
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,8 +706,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
.bytes();
bx.cx().const_usize(val)
}
mir::NullOp::UbChecks => {
let val = bx.tcx().sess.ub_checks();
mir::NullOp::RuntimeChecks(kind) => {
let val = kind.value(bx.tcx().sess);
bx.cx().const_bool(val)
}
};
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/check_consts/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
Rvalue::Cast(_, _, _) => {}

Rvalue::NullaryOp(
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks,
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_),
_,
) => {}
Rvalue::ShallowInitBox(_, _) => {}
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_const_eval/src/interpret/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
.bytes();
Scalar::from_target_usize(val, self)
}
mir::NullOp::UbChecks => Scalar::from_bool(self.tcx.sess.ub_checks()),
mir::NullOp::RuntimeChecks(kind) => {
Scalar::from_bool(kind.value(self.tcx.sess))
}
};
self.write_scalar(val, &dest)?;
}
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_hir_analysis/src/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -
| sym::aggregate_raw_ptr
| sym::ptr_metadata
| sym::ub_checks
| sym::overflow_checks
| sym::fadd_algebraic
| sym::fsub_algebraic
| sym::fmul_algebraic
Expand Down Expand Up @@ -592,7 +593,7 @@ pub fn check_intrinsic_type(
sym::aggregate_raw_ptr => (3, 0, vec![param(1), param(2)], param(0)),
sym::ptr_metadata => (2, 0, vec![Ty::new_imm_ptr(tcx, param(0))], param(1)),

sym::ub_checks => (0, 0, Vec::new(), tcx.types.bool),
sym::ub_checks | sym::overflow_checks => (0, 0, Vec::new(), tcx.types.bool),

sym::simd_eq
| sym::simd_ne
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_middle/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,9 @@ impl<'tcx> Body<'tcx> {
}

match rvalue {
Rvalue::NullaryOp(NullOp::UbChecks, _) => Some((tcx.sess.ub_checks() as u128, targets)),
Rvalue::NullaryOp(NullOp::RuntimeChecks(kind), _) => {
Some((kind.value(tcx.sess) as u128, targets))
}
Rvalue::Use(Operand::Constant(constant)) => {
let bits = eval_mono_const(constant)?;
Some((bits, targets))
Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_middle/src/mir/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,10 @@ impl<'tcx> Debug for Rvalue<'tcx> {
NullOp::SizeOf => write!(fmt, "SizeOf({t})"),
NullOp::AlignOf => write!(fmt, "AlignOf({t})"),
NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"),
NullOp::UbChecks => write!(fmt, "UbChecks()"),
NullOp::RuntimeChecks(RuntimeChecks::UbChecks) => write!(fmt, "UbChecks()"),
NullOp::RuntimeChecks(RuntimeChecks::OverflowChecks) => {
write!(fmt, "OverflowChecks()")
}
}
}
ThreadLocalRef(did) => ty::tls::with(|tcx| {
Expand Down
18 changes: 18 additions & 0 deletions compiler/rustc_middle/src/mir/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1460,9 +1460,27 @@ pub enum NullOp<'tcx> {
AlignOf,
/// Returns the offset of a field
OffsetOf(&'tcx List<(VariantIdx, FieldIdx)>),
/// Returns whether we should perform some checking at runtime.
RuntimeChecks(RuntimeChecks),
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
pub enum RuntimeChecks {
/// Returns whether we should perform some UB-checking at runtime.
/// See the `ub_checks` intrinsic docs for details.
UbChecks,
/// Returns whether we should perform some overflow-checking at runtime.
/// See the `overflow_checks` intrinsic docs for details.
OverflowChecks,
}

impl RuntimeChecks {
pub fn value(self, sess: &rustc_session::Session) -> bool {
match self {
Self::UbChecks => sess.ub_checks(),
Self::OverflowChecks => sess.overflow_checks(),
}
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/mir/tcx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ impl<'tcx> Rvalue<'tcx> {
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
tcx.types.usize
}
Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool,
Rvalue::NullaryOp(NullOp::RuntimeChecks(_), _) => tcx.types.bool,
Rvalue::Aggregate(ref ak, ref ops) => match **ak {
AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64),
AggregateKind::Tuple => {
Expand Down
4 changes: 1 addition & 3 deletions compiler/rustc_middle/src/mir/traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,7 @@ pub fn reverse_postorder<'a, 'tcx>(
/// reachable.
///
/// Such a traversal is mostly useful because it lets us skip lowering the `false` side
/// of `if <T as Trait>::CONST`, as well as [`NullOp::UbChecks`].
///
/// [`NullOp::UbChecks`]: rustc_middle::mir::NullOp::UbChecks
/// of `if <T as Trait>::CONST`, as well as [`NullOp::RuntimeChecks`].
pub fn mono_reachable<'a, 'tcx>(
body: &'a Body<'tcx>,
tcx: TyCtxt<'tcx>,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_dataflow/src/move_paths/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ impl<'b, 'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> Gatherer<'b, 'a, 'tcx, F> {
| Rvalue::Discriminant(..)
| Rvalue::Len(..)
| Rvalue::NullaryOp(
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbChecks,
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::RuntimeChecks(_),
_,
) => {}
}
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_mir_transform/src/cost_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> {

fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, _location: Location) {
match rvalue {
Rvalue::NullaryOp(NullOp::UbChecks, ..)
// FIXME: Should we do the same for `OverflowChecks`?
Rvalue::NullaryOp(NullOp::RuntimeChecks(RuntimeChecks::UbChecks), ..)
if !self
.tcx
.sess
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/gvn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
.tcx
.offset_of_subfield(self.ecx.param_env(), layout, fields.iter())
.bytes(),
NullOp::UbChecks => return None,
NullOp::RuntimeChecks(_) => return None,
};
let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
let imm = ImmTy::from_uint(val, usize_layout);
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_mir_transform/src/instsimplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> {
}

fn simplify_ub_check(&self, source_info: &SourceInfo, rvalue: &mut Rvalue<'tcx>) {
if let Rvalue::NullaryOp(NullOp::UbChecks, _) = *rvalue {
// FIXME: Should we do the same for overflow checks?
if let Rvalue::NullaryOp(NullOp::RuntimeChecks(RuntimeChecks::UbChecks), _) = *rvalue {
let const_ = Const::from_bool(self.tcx, self.tcx.sess.ub_checks());
let constant = ConstOperand { span: source_info.span, const_, user_ty: None };
*rvalue = Rvalue::Use(Operand::Constant(Box::new(constant)));
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/known_panics_lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
.tcx
.offset_of_subfield(self.param_env, op_layout, fields.iter())
.bytes(),
NullOp::UbChecks => return None,
NullOp::RuntimeChecks(_) => return None,
};
ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into()
}
Expand Down
9 changes: 7 additions & 2 deletions compiler/rustc_mir_transform/src/lower_intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
sym::unreachable => {
terminator.kind = TerminatorKind::Unreachable;
}
sym::ub_checks => {
sym::ub_checks | sym::overflow_checks => {
let op = match intrinsic.name {
sym::ub_checks => RuntimeChecks::UbChecks,
sym::overflow_checks => RuntimeChecks::OverflowChecks,
_ => unreachable!(),
};
let target = target.unwrap();
block.statements.push(Statement {
source_info: terminator.source_info,
kind: StatementKind::Assign(Box::new((
*destination,
Rvalue::NullaryOp(NullOp::UbChecks, tcx.types.bool),
Rvalue::NullaryOp(NullOp::RuntimeChecks(op), tcx.types.bool),
))),
});
terminator.kind = TerminatorKind::Goto { target };
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/promote_consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ impl<'tcx> Validator<'_, 'tcx> {
NullOp::SizeOf => {}
NullOp::AlignOf => {}
NullOp::OffsetOf(_) => {}
NullOp::UbChecks => {}
NullOp::RuntimeChecks(_) => {}
},

Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1346,7 +1346,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
Rvalue::Repeat(_, _)
| Rvalue::ThreadLocalRef(_)
| Rvalue::AddressOf(_, _)
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks, _)
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::RuntimeChecks(_), _)
| Rvalue::Discriminant(_) => {}
}
self.super_rvalue(rvalue, location);
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_smir/src/rustc_smir/convert/mir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,17 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> {
type T = stable_mir::mir::NullOp;
fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
use rustc_middle::mir::NullOp::*;
use rustc_middle::mir::RuntimeChecks::*;
match self {
SizeOf => stable_mir::mir::NullOp::SizeOf,
AlignOf => stable_mir::mir::NullOp::AlignOf,
OffsetOf(indices) => stable_mir::mir::NullOp::OffsetOf(
indices.iter().map(|idx| idx.stable(tables)).collect(),
),
UbChecks => stable_mir::mir::NullOp::UbChecks,
RuntimeChecks(kind) => stable_mir::mir::NullOp::RuntimeChecks(match kind {
UbChecks => stable_mir::mir::RuntimeChecks::UbChecks,
OverflowChecks => stable_mir::mir::RuntimeChecks::OverflowChecks,
}),
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion compiler/stable_mir/src/mir/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ impl Rvalue {
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
Ok(Ty::usize_ty())
}
Rvalue::NullaryOp(NullOp::UbChecks, _) => Ok(Ty::bool_ty()),
Rvalue::NullaryOp(NullOp::RuntimeChecks(_), _) => Ok(Ty::bool_ty()),
Rvalue::Aggregate(ak, ops) => match *ak {
AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64),
AggregateKind::Tuple => Ok(Ty::new_tuple(
Expand Down Expand Up @@ -978,8 +978,16 @@ pub enum NullOp {
AlignOf,
/// Returns the offset of a field.
OffsetOf(Vec<(VariantIdx, FieldIdx)>),
/// Codegen conditions for runtime checks.
RuntimeChecks(RuntimeChecks),
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub enum RuntimeChecks {
/// cfg!(ub_checks), but at codegen time
UbChecks,
/// cfg!(overflow_checks), but at codegen time
OverflowChecks,
}

impl Operand {
Expand Down
21 changes: 21 additions & 0 deletions library/core/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2671,6 +2671,27 @@ pub const fn ub_checks() -> bool {
cfg!(debug_assertions)
}

/// Returns whether we should perform some overflow-checking at runtime. This eventually evaluates to
/// `cfg!(overflow_checks)`, but behaves different from `cfg!` when mixing crates built with different
/// flags: if the crate has UB checks enabled or carries the `#[rustc_preserve_overflow_checks]`
/// attribute, evaluation is delayed until monomorphization (or until the call gets inlined into
/// a crate that does not delay evaluation further); otherwise it can happen any time.
///
/// The common case here is a user program built with overflow_checks linked against the distributed
/// sysroot which is built without overflow_checks but with `#[rustc_preserve_overflow_checks]`.
/// For code that gets monomorphized in the user crate (i.e., generic functions and functions with
/// `#[inline]`), gating assertions on `overflow_checks()` rather than `cfg!(overflow_checks)` means that
/// assertions are enabled whenever the *user crate* has UB checks enabled. However if the
/// user has overflow checks disabled, the checks will still get optimized out.
#[cfg(not(bootstrap))]
#[rustc_const_unstable(feature = "const_overflow_checks", issue = "none")]
#[unstable(feature = "core_intrinsics", issue = "none")]
#[inline(always)]
#[rustc_intrinsic]
pub const fn overflow_checks() -> bool {
cfg!(debug_assertions)
}

/// Allocates a block of memory at compile time.
/// At runtime, just returns a null pointer.
///
Expand Down
2 changes: 1 addition & 1 deletion src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ fn check_rvalue<'tcx>(
))
}
},
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, _)
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_), _)
| Rvalue::ShallowInitBox(_, _) => Ok(()),
Rvalue::UnaryOp(_, operand) => {
let ty = operand.ty(body, tcx);
Expand Down
14 changes: 14 additions & 0 deletions tests/codegen/auxiliary/overflow_checks_add.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//@ compile-flags: -Cdebug-assertions=yes

#![crate_type = "lib"]
#![feature(strict_overflow_ops)]
#![feature(rustc_attrs)]
#![feature(core_intrinsics)]

/// Emulates the default behavior of `+` using
/// `intrinsics::overflow_checks()`.
#[inline]
#[rustc_inherit_overflow_checks]
pub fn add(a: u8, b: u8) -> u8 {
if core::intrinsics::overflow_checks() { a.strict_add(b) } else { a.wrapping_add(b) }
}
29 changes: 29 additions & 0 deletions tests/codegen/overflow-checks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// With -Coverflow-checks=yes (enabled by default by -Cdebug-assertions=yes) we will produce a
// runtime check that panics when an operation would result in integer overflow.
//
// This test ensures that such a runtime check is *not* emitted when debug-assertions are enabled,
// but overflow-checks are explicitly disabled. It also ensures that even if a dependency is
// compiled with overflow checks, `intrinsics::overflow_checks()` will be treated with the
// overflow-checks setting of the current crate (when `#[rustc_inherit_overflow_checks]`) is used.

//@ aux-build:overflow_checks_add.rs
//@ revisions: DEBUG NOCHECKS
//@ [DEBUG] compile-flags:
//@ [NOCHECKS] compile-flags: -Coverflow-checks=no
//@ compile-flags: -O -Cdebug-assertions=yes

#![crate_type = "lib"]

extern crate overflow_checks_add;

// CHECK-LABEL: @add(
#[no_mangle]
pub unsafe fn add(a: u8, b: u8) -> u8 {
// CHECK: i8 noundef %a, i8 noundef %b
// DEBUG: @llvm.uadd.with.overflow.i8
// DEBUG: call core::num::overflow_panic::add
// DEBUG: unreachable
// NOCHECKS: %0 = add i8 %b, %a
// NOCHECKS: ret i8 %0
overflow_checks_add::add(a, b)
}
Loading