diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index bbb5daccfd639..1b650e8485344 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -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); diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 9bc7b57c53745..02b3463722eb6 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -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), diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index d91a118bc71a3..97c727c007b39 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -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) } }; diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 3ded81b90ffc1..42e4ef0cf8050 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -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(_, _) => {} diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 211a7b2300222..d78159adf9a98 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -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)?; } diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 4b45ced30c579..9cc9a22caa46c 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -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 @@ -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 diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 46c4d586f6ad9..d2b90f720ced3 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -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)) diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index f2d8781413096..44335f81c9f87 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -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| { diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 1119ff6ff3d60..aba076f60512e 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -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)] diff --git a/compiler/rustc_middle/src/mir/tcx.rs b/compiler/rustc_middle/src/mir/tcx.rs index 1075344dc00f3..5a91e1e3744c9 100644 --- a/compiler/rustc_middle/src/mir/tcx.rs +++ b/compiler/rustc_middle/src/mir/tcx.rs @@ -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 => { diff --git a/compiler/rustc_middle/src/mir/traversal.rs b/compiler/rustc_middle/src/mir/traversal.rs index 245e9096bad47..aa7c6ba7edcc9 100644 --- a/compiler/rustc_middle/src/mir/traversal.rs +++ b/compiler/rustc_middle/src/mir/traversal.rs @@ -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 ::CONST`, as well as [`NullOp::UbChecks`]. -/// -/// [`NullOp::UbChecks`]: rustc_middle::mir::NullOp::UbChecks +/// of `if ::CONST`, as well as [`NullOp::RuntimeChecks`]. pub fn mono_reachable<'a, 'tcx>( body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index c26a72e454382..31c97fdb922aa 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -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(_), _, ) => {} } diff --git a/compiler/rustc_mir_transform/src/cost_checker.rs b/compiler/rustc_mir_transform/src/cost_checker.rs index a1c1422912ee3..be2095e86b71f 100644 --- a/compiler/rustc_mir_transform/src/cost_checker.rs +++ b/compiler/rustc_mir_transform/src/cost_checker.rs @@ -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 diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index e16911d79c378..e01efb5bbdb04 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -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); diff --git a/compiler/rustc_mir_transform/src/instsimplify.rs b/compiler/rustc_mir_transform/src/instsimplify.rs index 2fc5f7e536ba8..b29d092d8792d 100644 --- a/compiler/rustc_mir_transform/src/instsimplify.rs +++ b/compiler/rustc_mir_transform/src/instsimplify.rs @@ -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))); diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 7202cc2d0427e..2126f6e7b2be8 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -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() } diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index a9bdff95fe5ac..a4bcd0d223359 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -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 }; diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index dc8e50ac8cd28..5618decda9a0d 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -448,7 +448,7 @@ impl<'tcx> Validator<'_, 'tcx> { NullOp::SizeOf => {} NullOp::AlignOf => {} NullOp::OffsetOf(_) => {} - NullOp::UbChecks => {} + NullOp::RuntimeChecks(_) => {} }, Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable), diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index 746d423b7a98c..1beb95c16d96b 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -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); diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs index da705e6f9598a..0ee7f3452dd01 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -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, + }), } } } diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs index 7c09fe1a08541..102623c84f819 100644 --- a/compiler/stable_mir/src/mir/body.rs +++ b/compiler/stable_mir/src/mir/body.rs @@ -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( @@ -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 { diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index e9eacbcd25a0a..6600e3dc00767 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -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. /// diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index f206b2ceebcb1..4ef16e4adc571 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -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); diff --git a/tests/codegen/auxiliary/overflow_checks_add.rs b/tests/codegen/auxiliary/overflow_checks_add.rs new file mode 100644 index 0000000000000..3402f59f94692 --- /dev/null +++ b/tests/codegen/auxiliary/overflow_checks_add.rs @@ -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) } +} diff --git a/tests/codegen/overflow-checks.rs b/tests/codegen/overflow-checks.rs new file mode 100644 index 0000000000000..fb6090e9195b3 --- /dev/null +++ b/tests/codegen/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 +//@ [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) +}