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());