add overflow_checks intrinsic

This commit is contained in:
Peter Jaszkowiak 2024-08-03 22:57:10 -06:00
parent 2e5d395f2b
commit cc8b95cc54
12 changed files with 81 additions and 10 deletions

View file

@ -645,10 +645,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
Rvalue::Cast(_, _, _) => {} Rvalue::Cast(_, _, _) => {}
Rvalue::NullaryOp( Rvalue::NullaryOp(NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_), _) => {}
NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_),
_,
) => {}
Rvalue::ShallowInitBox(_, _) => {} Rvalue::ShallowInitBox(_, _) => {}
Rvalue::UnaryOp(op, operand) => { Rvalue::UnaryOp(op, operand) => {

View file

@ -163,6 +163,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
| sym::minnumf128 | sym::minnumf128
| sym::mul_with_overflow | sym::mul_with_overflow
| sym::needs_drop | sym::needs_drop
| sym::overflow_checks
| sym::powf16 | sym::powf16
| sym::powf32 | sym::powf32
| sym::powf64 | sym::powf64
@ -643,7 +644,7 @@ pub(crate) fn check_intrinsic_type(
sym::aggregate_raw_ptr => (3, 0, vec![param(1), param(2)], param(0)), 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::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::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))), sym::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))),

View file

@ -1097,6 +1097,9 @@ impl<'tcx> Debug for Rvalue<'tcx> {
NullOp::RuntimeChecks(RuntimeChecks::ContractChecks) => { NullOp::RuntimeChecks(RuntimeChecks::ContractChecks) => {
write!(fmt, "ContractChecks()") write!(fmt, "ContractChecks()")
} }
NullOp::RuntimeChecks(RuntimeChecks::OverflowChecks) => {
write!(fmt, "OverflowChecks()")
}
} }
} }
ThreadLocalRef(did) => ty::tls::with(|tcx| { ThreadLocalRef(did) => ty::tls::with(|tcx| {

View file

@ -1577,6 +1577,9 @@ pub enum RuntimeChecks {
/// Returns whether we should perform contract-checking at runtime. /// Returns whether we should perform contract-checking at runtime.
/// See the `contract_checks` intrinsic docs for details. /// See the `contract_checks` intrinsic docs for details.
ContractChecks, ContractChecks,
/// Returns whether we should perform some overflow-checking at runtime.
/// See the `overflow_checks` intrinsic docs for details.
OverflowChecks,
} }
impl RuntimeChecks { impl RuntimeChecks {
@ -1584,6 +1587,7 @@ impl RuntimeChecks {
match self { match self {
Self::UbChecks => sess.ub_checks(), Self::UbChecks => sess.ub_checks(),
Self::ContractChecks => sess.contract_checks(), Self::ContractChecks => sess.contract_checks(),
Self::OverflowChecks => sess.overflow_checks(),
} }
} }
} }

View file

@ -451,10 +451,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
Rvalue::Ref(..) Rvalue::Ref(..)
| Rvalue::RawPtr(..) | Rvalue::RawPtr(..)
| Rvalue::Discriminant(..) | Rvalue::Discriminant(..)
| Rvalue::NullaryOp( | Rvalue::NullaryOp(NullOp::OffsetOf(..) | NullOp::RuntimeChecks(_), _) => {}
NullOp::OffsetOf(..) | NullOp::RuntimeChecks(_),
_,
) => {}
} }
} }

View file

@ -23,10 +23,11 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics {
sym::unreachable => { sym::unreachable => {
terminator.kind = TerminatorKind::Unreachable; terminator.kind = TerminatorKind::Unreachable;
} }
sym::ub_checks | sym::contract_checks => { sym::ub_checks | sym::overflow_checks | sym::contract_checks => {
let op = match intrinsic.name { let op = match intrinsic.name {
sym::ub_checks => RuntimeChecks::UbChecks, sym::ub_checks => RuntimeChecks::UbChecks,
sym::contract_checks => RuntimeChecks::ContractChecks, sym::contract_checks => RuntimeChecks::ContractChecks,
sym::overflow_checks => RuntimeChecks::OverflowChecks,
_ => unreachable!(), _ => unreachable!(),
}; };
let target = target.unwrap(); let target = target.unwrap();

View file

@ -1033,6 +1033,8 @@ pub enum RuntimeChecks {
UbChecks, UbChecks,
/// cfg!(contract_checks), but at codegen time /// cfg!(contract_checks), but at codegen time
ContractChecks, ContractChecks,
/// cfg!(overflow_checks), but at codegen time
OverflowChecks,
} }
impl Operand { impl Operand {

View file

@ -330,6 +330,7 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> {
RuntimeChecks(op) => crate::mir::NullOp::RuntimeChecks(match op { RuntimeChecks(op) => crate::mir::NullOp::RuntimeChecks(match op {
UbChecks => crate::mir::RuntimeChecks::UbChecks, UbChecks => crate::mir::RuntimeChecks::UbChecks,
ContractChecks => crate::mir::RuntimeChecks::ContractChecks, ContractChecks => crate::mir::RuntimeChecks::ContractChecks,
OverflowChecks => crate::mir::RuntimeChecks::OverflowChecks,
}), }),
} }
} }

View file

@ -2585,6 +2585,24 @@ pub const fn ub_checks() -> bool {
cfg!(ub_checks) cfg!(ub_checks)
} }
/// 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 overflow checks enabled or carries the `#[rustc_inherit_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_inherit_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 overflow checks enabled. However if the
/// user has overflow checks disabled, the checks will still get optimized out.
#[inline(always)]
#[rustc_intrinsic]
pub const fn overflow_checks() -> bool {
cfg!(debug_assertions)
}
/// Allocates a block of memory at compile time. /// Allocates a block of memory at compile time.
/// At runtime, just returns a null pointer. /// At runtime, just returns a null pointer.
/// ///

View file

@ -0,0 +1,10 @@
//@ compile-flags: -Cdebug-assertions=yes
#![crate_type = "lib"]
#![feature(core_intrinsics)]
/// Emulates the default behavior of `+` using `intrinsics::overflow_checks()`.
#[inline]
pub fn add(a: u8, b: u8) -> u8 {
if core::intrinsics::overflow_checks() { a.strict_add(b) } else { a.wrapping_add(b) }
}

View file

@ -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
//@ compile-flags: -O -Cdebug-assertions=yes
//@ [NOCHECKS] compile-flags: -Coverflow-checks=no
#![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
// CHECK: add i8 %b, %a
// DEBUG: icmp ult i8 [[zero:[^,]+]], %a
// DEBUG: call core::num::overflow_panic::add
// DEBUG: unreachable
// NOCHECKS-NOT: unreachable
// NOCHECKS: ret i8 %0
overflow_checks_add::add(a, b)
}

View file

@ -0,0 +1,8 @@
//@ build-pass
//@ compile-flags: -O -C overflow-checks=no
#![crate_type = "lib"]
#![feature(core_intrinsics)]
// Always returns true during CTFE, even if overflow checks are disabled.
const _: () = assert!(core::intrinsics::overflow_checks());