Skip to content

Add overflow_checks intrinsic #128666

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 1 addition & 2 deletions compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,8 +1040,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
ConstraintCategory::SizedBound,
);
}
&Rvalue::NullaryOp(NullOp::ContractChecks, _) => {}
&Rvalue::NullaryOp(NullOp::UbChecks, _) => {}
&Rvalue::NullaryOp(NullOp::RuntimeChecks(_), _) => {}

Rvalue::ShallowInitBox(_operand, ty) => {
let trait_ref = ty::TraitRef::new(
Expand Down
13 changes: 2 additions & 11 deletions compiler/rustc_codegen_cranelift/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -836,17 +836,8 @@ fn codegen_stmt<'tcx>(
fields.iter(),
)
.bytes(),
NullOp::UbChecks => {
let val = fx.tcx.sess.ub_checks();
let val = CValue::by_val(
fx.bcx.ins().iconst(types::I8, i64::from(val)),
fx.layout_of(fx.tcx.types.bool),
);
lval.write_cvalue(fx, val);
return;
}
NullOp::ContractChecks => {
let val = fx.tcx.sess.contract_checks();
NullOp::RuntimeChecks(kind) => {
let val = kind.value(fx.tcx.sess);
let val = CValue::by_val(
fx.bcx.ins().iconst(types::I8, i64::from(val)),
fx.layout_of(fx.tcx.types.bool),
Expand Down
8 changes: 2 additions & 6 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,12 +752,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();
bx.cx().const_bool(val)
}
mir::NullOp::ContractChecks => {
let val = bx.tcx().sess.contract_checks();
mir::NullOp::RuntimeChecks(kind) => {
let val = kind.value(bx.tcx().sess);
bx.cx().const_bool(val)
}
};
Expand Down
6 changes: 1 addition & 5 deletions compiler/rustc_const_eval/src/check_consts/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,11 +675,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
Rvalue::Cast(_, _, _) => {}

Rvalue::NullaryOp(
NullOp::SizeOf
| NullOp::AlignOf
| NullOp::OffsetOf(_)
| NullOp::UbChecks
| NullOp::ContractChecks,
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_),
_,
) => {}
Rvalue::ShallowInitBox(_, _) => {}
Expand Down
22 changes: 9 additions & 13 deletions compiler/rustc_const_eval/src/interpret/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,11 @@ pub trait Machine<'tcx>: Sized {
interp_ok(())
}

/// Determines the result of a `NullaryOp::UbChecks` invocation.
fn ub_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>;

/// Determines the result of a `NullaryOp::ContractChecks` invocation.
fn contract_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>;
/// Determines the result of a `NullaryOp::RuntimeChecks` invocation.
fn runtime_checks(
_ecx: &InterpCx<'tcx, Self>,
r: mir::RuntimeChecks,
) -> InterpResult<'tcx, bool>;

/// Called when the interpreter encounters a `StatementKind::ConstEvalCounter` instruction.
/// You can use this to detect long or endlessly running programs.
Expand Down Expand Up @@ -677,14 +677,10 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
}

#[inline(always)]
fn ub_checks(_ecx: &InterpCx<$tcx, Self>) -> InterpResult<$tcx, bool> {
// We can't look at `tcx.sess` here as that can differ across crates, which can lead to
// unsound differences in evaluating the same constant at different instantiation sites.
interp_ok(true)
}

#[inline(always)]
fn contract_checks(_ecx: &InterpCx<$tcx, Self>) -> InterpResult<$tcx, bool> {
fn runtime_checks(
_ecx: &InterpCx<$tcx, Self>,
_r: mir::RuntimeChecks,
) -> InterpResult<$tcx, bool> {
// We can't look at `tcx.sess` here as that can differ across crates, which can lead to
// unsound differences in evaluating the same constant at different instantiation sites.
interp_ok(true)
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_const_eval/src/interpret/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self.tcx.offset_of_subfield(self.typing_env, layout, fields.iter()).bytes();
ImmTy::from_uint(val, usize_layout())
}
UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx),
ContractChecks => ImmTy::from_bool(M::contract_checks(self)?, *self.tcx),
RuntimeChecks(r) => ImmTy::from_bool(M::runtime_checks(self, r)?, *self.tcx),
})
}
}
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 @@ -120,6 +120,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
| sym::contract_checks
| sym::contract_check_requires
| sym::contract_check_ensures
| sym::overflow_checks
| sym::fadd_algebraic
| sym::fsub_algebraic
| sym::fmul_algebraic
Expand Down Expand Up @@ -591,7 +592,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))),

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 @@ -692,7 +692,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
9 changes: 7 additions & 2 deletions compiler/rustc_middle/src/mir/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1134,8 +1134,13 @@ 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::ContractChecks => write!(fmt, "ContractChecks()"),
NullOp::RuntimeChecks(RuntimeChecks::UbChecks) => write!(fmt, "UbChecks()"),
NullOp::RuntimeChecks(RuntimeChecks::ContractChecks) => {
write!(fmt, "ContractChecks()")
}
NullOp::RuntimeChecks(RuntimeChecks::OverflowChecks) => {
write!(fmt, "OverflowChecks()")
}
}
}
ThreadLocalRef(did) => ty::tls::with(|tcx| {
Expand Down
5 changes: 2 additions & 3 deletions compiler/rustc_middle/src/mir/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,8 +709,7 @@ impl<'tcx> Rvalue<'tcx> {
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
tcx.types.usize
}
Rvalue::NullaryOp(NullOp::ContractChecks, _)
| 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 Expand Up @@ -778,7 +777,7 @@ impl<'tcx> NullOp<'tcx> {
pub fn ty(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
match self {
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) => tcx.types.usize,
NullOp::UbChecks | NullOp::ContractChecks => tcx.types.bool,
NullOp::RuntimeChecks(_) => tcx.types.bool,
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions compiler/rustc_middle/src/mir/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1588,12 +1588,31 @@ 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 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 {
pub fn value(self, sess: &rustc_session::Session) -> bool {
match self {
Self::UbChecks => sess.ub_checks(),
Self::ContractChecks => sess.contract_checks(),
Self::OverflowChecks => sess.overflow_checks(),
}
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_middle/src/mir/traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,9 @@ 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`].
/// of `if <T as Trait>::CONST`, as well as [`NullOp::RuntimeChecks`].
///
/// [`NullOp::UbChecks`]: rustc_middle::mir::NullOp::UbChecks
/// [`NullOp::RuntimeChecks`]: rustc_middle::mir::NullOp::RuntimeChecks
pub fn mono_reachable<'a, 'tcx>(
body: &'a Body<'tcx>,
tcx: TyCtxt<'tcx>,
Expand Down
6 changes: 1 addition & 5 deletions compiler/rustc_mir_dataflow/src/move_paths/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,11 +416,7 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
| Rvalue::Discriminant(..)
| Rvalue::Len(..)
| Rvalue::NullaryOp(
NullOp::SizeOf
| NullOp::AlignOf
| NullOp::OffsetOf(..)
| NullOp::UbChecks
| NullOp::ContractChecks,
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 @@ -75,7 +75,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
3 changes: 1 addition & 2 deletions compiler/rustc_mir_transform/src/gvn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,8 +538,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
.tcx
.offset_of_subfield(self.typing_env(), layout, fields.iter())
.bytes(),
NullOp::UbChecks => return None,
NullOp::ContractChecks => 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
5 changes: 4 additions & 1 deletion compiler/rustc_mir_transform/src/instsimplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,10 @@ impl<'tcx> InstSimplifyContext<'_, 'tcx> {
}

fn simplify_ub_check(&self, rvalue: &mut Rvalue<'tcx>) {
let Rvalue::NullaryOp(NullOp::UbChecks, _) = *rvalue else { return };
// FIXME: Should we do the same for overflow checks?
let Rvalue::NullaryOp(NullOp::RuntimeChecks(RuntimeChecks::UbChecks), _) = *rvalue else {
return;
};

let const_ = Const::from_bool(self.tcx, self.tcx.sess.ub_checks());
let constant = ConstOperand { span: DUMMY_SP, const_, user_ty: None };
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_mir_transform/src/known_panics_lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,8 +629,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
.tcx
.offset_of_subfield(self.typing_env, op_layout, fields.iter())
.bytes(),
NullOp::UbChecks => return None,
NullOp::ContractChecks => return None,
NullOp::RuntimeChecks(_) => return None,
};
ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into()
}
Expand Down
21 changes: 8 additions & 13 deletions compiler/rustc_mir_transform/src/lower_intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,19 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics {
sym::unreachable => {
terminator.kind = TerminatorKind::Unreachable;
}
sym::ub_checks => {
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),
))),
});
terminator.kind = TerminatorKind::Goto { target };
}
sym::contract_checks => {
sym::ub_checks | sym::overflow_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();
block.statements.push(Statement {
source_info: terminator.source_info,
kind: StatementKind::Assign(Box::new((
*destination,
Rvalue::NullaryOp(NullOp::ContractChecks, tcx.types.bool),
Rvalue::NullaryOp(NullOp::RuntimeChecks(op), tcx.types.bool),
))),
});
terminator.kind = TerminatorKind::Goto { target };
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_mir_transform/src/promote_consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,7 @@ impl<'tcx> Validator<'_, 'tcx> {
NullOp::SizeOf => {}
NullOp::AlignOf => {}
NullOp::OffsetOf(_) => {}
NullOp::UbChecks => {}
NullOp::ContractChecks => {}
NullOp::RuntimeChecks(_) => {}
},

Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),
Expand Down
5 changes: 1 addition & 4 deletions compiler/rustc_mir_transform/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1390,10 +1390,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
Rvalue::Repeat(_, _)
| Rvalue::ThreadLocalRef(_)
| Rvalue::RawPtr(_, _)
| Rvalue::NullaryOp(
NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks | NullOp::ContractChecks,
_,
)
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::RuntimeChecks(_), _)
| Rvalue::Discriminant(_) => {}

Rvalue::WrapUnsafeBinder(op, ty) => {
Expand Down
8 changes: 6 additions & 2 deletions compiler/rustc_smir/src/rustc_smir/convert/mir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,14 +285,18 @@ 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,
ContractChecks => stable_mir::mir::NullOp::ContractChecks,
RuntimeChecks(kind) => stable_mir::mir::NullOp::RuntimeChecks(match kind {
UbChecks => stable_mir::mir::RuntimeChecks::UbChecks,
ContractChecks => stable_mir::mir::RuntimeChecks::ContractChecks,
OverflowChecks => stable_mir::mir::RuntimeChecks::OverflowChecks,
}),
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions compiler/rustc_smir/src/stable_mir/mir/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,8 +642,7 @@ impl Rvalue {
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
Ok(Ty::usize_ty())
}
Rvalue::NullaryOp(NullOp::ContractChecks, _)
| 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 @@ -1040,10 +1039,18 @@ 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!(contract_checks), but at codegen time
ContractChecks,
/// cfg!(overflow_checks), but at codegen time
OverflowChecks,
}

impl Operand {
Expand Down
20 changes: 20 additions & 0 deletions library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3346,6 +3346,26 @@ pub const unsafe fn typed_swap_nonoverlapping<T>(x: *mut T, y: *mut T) {
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 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 = "const_overflow_checks", 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
Loading
Loading