From cc8b95cc5490552e7080ec4e98c19249b47dac3a Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Sat, 3 Aug 2024 22:57:10 -0600 Subject: [PATCH] add `overflow_checks` intrinsic --- .../src/check_consts/check.rs | 5 +--- .../rustc_hir_analysis/src/check/intrinsic.rs | 3 +- compiler/rustc_middle/src/mir/pretty.rs | 3 ++ compiler/rustc_middle/src/mir/syntax.rs | 4 +++ .../src/move_paths/builder.rs | 5 +--- .../src/lower_intrinsics.rs | 3 +- compiler/rustc_public/src/mir/body.rs | 2 ++ .../src/unstable/convert/stable/mir.rs | 1 + library/core/src/intrinsics/mod.rs | 18 ++++++++++++ .../auxiliary/overflow_checks_add.rs | 10 +++++++ tests/codegen-llvm/overflow-checks.rs | 29 +++++++++++++++++++ tests/ui/consts/const-eval/overflow_checks.rs | 8 +++++ 12 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 tests/codegen-llvm/auxiliary/overflow_checks_add.rs create mode 100644 tests/codegen-llvm/overflow-checks.rs create mode 100644 tests/ui/consts/const-eval/overflow_checks.rs diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index c0a9bd187c14..f515f5d751bc 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -645,10 +645,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { Rvalue::Cast(_, _, _) => {} - Rvalue::NullaryOp( - NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_), - _, - ) => {} + Rvalue::NullaryOp(NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_), _) => {} Rvalue::ShallowInitBox(_, _) => {} Rvalue::UnaryOp(op, operand) => { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index a6659912e3fb..39c26f4ea40d 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -163,6 +163,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::minnumf128 | sym::mul_with_overflow | sym::needs_drop + | sym::overflow_checks | sym::powf16 | sym::powf32 | 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::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))), diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index a7941290de2e..f881ff1067d9 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1097,6 +1097,9 @@ impl<'tcx> Debug for Rvalue<'tcx> { NullOp::RuntimeChecks(RuntimeChecks::ContractChecks) => { write!(fmt, "ContractChecks()") } + NullOp::RuntimeChecks(RuntimeChecks::OverflowChecks) => { + write!(fmt, "OverflowChecks()") + } } } ThreadLocalRef(did) => ty::tls::with(|tcx| { diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 865b91817b3c..3b48a68df126 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1577,6 +1577,9 @@ pub enum RuntimeChecks { /// Returns whether we should perform contract-checking at runtime. /// See the `contract_checks` intrinsic docs for details. ContractChecks, + /// Returns whether we should perform some overflow-checking at runtime. + /// See the `overflow_checks` intrinsic docs for details. + OverflowChecks, } impl RuntimeChecks { @@ -1584,6 +1587,7 @@ impl RuntimeChecks { match self { Self::UbChecks => sess.ub_checks(), Self::ContractChecks => sess.contract_checks(), + Self::OverflowChecks => sess.overflow_checks(), } } } diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 8801fa8d9fd3..b4ffeb782b59 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -451,10 +451,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { Rvalue::Ref(..) | Rvalue::RawPtr(..) | Rvalue::Discriminant(..) - | Rvalue::NullaryOp( - NullOp::OffsetOf(..) | NullOp::RuntimeChecks(_), - _, - ) => {} + | Rvalue::NullaryOp(NullOp::OffsetOf(..) | NullOp::RuntimeChecks(_), _) => {} } } diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index e6fa30a72b9b..1e874300e25e 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -23,10 +23,11 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics { sym::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 { sym::ub_checks => RuntimeChecks::UbChecks, sym::contract_checks => RuntimeChecks::ContractChecks, + sym::overflow_checks => RuntimeChecks::OverflowChecks, _ => unreachable!(), }; let target = target.unwrap(); diff --git a/compiler/rustc_public/src/mir/body.rs b/compiler/rustc_public/src/mir/body.rs index 551f666023b0..5f41b1063280 100644 --- a/compiler/rustc_public/src/mir/body.rs +++ b/compiler/rustc_public/src/mir/body.rs @@ -1033,6 +1033,8 @@ pub enum RuntimeChecks { UbChecks, /// cfg!(contract_checks), but at codegen time ContractChecks, + /// cfg!(overflow_checks), but at codegen time + OverflowChecks, } impl Operand { diff --git a/compiler/rustc_public/src/unstable/convert/stable/mir.rs b/compiler/rustc_public/src/unstable/convert/stable/mir.rs index 1de5a2b32779..d5896474d009 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mir.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mir.rs @@ -330,6 +330,7 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> { RuntimeChecks(op) => crate::mir::NullOp::RuntimeChecks(match op { UbChecks => crate::mir::RuntimeChecks::UbChecks, ContractChecks => crate::mir::RuntimeChecks::ContractChecks, + OverflowChecks => crate::mir::RuntimeChecks::OverflowChecks, }), } } diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index c397e762d558..41afb3694b91 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2585,6 +2585,24 @@ pub const fn ub_checks() -> bool { 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. /// At runtime, just returns a null pointer. /// diff --git a/tests/codegen-llvm/auxiliary/overflow_checks_add.rs b/tests/codegen-llvm/auxiliary/overflow_checks_add.rs new file mode 100644 index 000000000000..ea9db1e9837e --- /dev/null +++ b/tests/codegen-llvm/auxiliary/overflow_checks_add.rs @@ -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) } +} diff --git a/tests/codegen-llvm/overflow-checks.rs b/tests/codegen-llvm/overflow-checks.rs new file mode 100644 index 000000000000..c8b10df507b0 --- /dev/null +++ b/tests/codegen-llvm/overflow-checks.rs @@ -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) +} diff --git a/tests/ui/consts/const-eval/overflow_checks.rs b/tests/ui/consts/const-eval/overflow_checks.rs new file mode 100644 index 000000000000..7f6915777990 --- /dev/null +++ b/tests/ui/consts/const-eval/overflow_checks.rs @@ -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());