Introduce Operand::RuntimeChecks.

This commit is contained in:
Camille Gillot 2025-11-16 19:21:17 +00:00
parent 1a227bd47f
commit 6319bee585
45 changed files with 167 additions and 148 deletions

View file

@ -764,6 +764,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
{
// Just point to the function, to reduce the chance of overlapping spans.
let function_span = match func {
Operand::RuntimeChecks(_) => span,
Operand::Constant(c) => c.span,
Operand::Copy(place) | Operand::Move(place) => {
if let Some(l) = place.as_local() {
@ -809,6 +810,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
{
// Just point to the function, to reduce the chance of overlapping spans.
let function_span = match func {
Operand::RuntimeChecks(_) => span,
Operand::Constant(c) => c.span,
Operand::Copy(place) | Operand::Move(place) => {
if let Some(l) = place.as_local() {

View file

@ -1696,7 +1696,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
_ => propagate_closure_used_mut_place(self, place),
}
}
Operand::Constant(..) => {}
Operand::Constant(..) | Operand::RuntimeChecks(_) => {}
}
}
@ -1747,7 +1747,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
state,
);
}
Operand::Constant(_) => {}
Operand::Constant(_) | Operand::RuntimeChecks(_) => {}
}
}

View file

@ -247,7 +247,7 @@ impl<'a, 'tcx> LoanInvalidationsGenerator<'a, 'tcx> {
LocalMutationIsAllowed::Yes,
);
}
Operand::Constant(_) => {}
Operand::Constant(_) | Operand::RuntimeChecks(_) => {}
}
}

View file

@ -1023,7 +1023,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
// element, so we require the `Copy` trait.
if len.try_to_target_usize(tcx).is_none_or(|len| len > 1) {
match operand {
Operand::Copy(..) | Operand::Constant(..) => {
Operand::Copy(..) | Operand::Constant(..) | Operand::RuntimeChecks(_) => {
// These are always okay: direct use of a const, or a value that can
// evidently be copied.
}

View file

@ -8,10 +8,10 @@ use rustc_ast::InlineAsmOptions;
use rustc_codegen_ssa::base::is_call_from_compiler_builtins_to_upstream_monomorphization;
use rustc_data_structures::profiling::SelfProfilerRef;
use rustc_index::IndexVec;
use rustc_middle::ty::TypeVisitableExt;
use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::layout::FnAbiOf;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{ScalarInt, TypeVisitableExt};
use rustc_session::config::OutputFilenames;
use rustc_span::Symbol;
@ -1039,6 +1039,12 @@ pub(crate) fn codegen_operand<'tcx>(
cplace.to_cvalue(fx)
}
Operand::Constant(const_) => crate::constant::codegen_constant_operand(fx, const_),
Operand::RuntimeChecks(checks) => {
let int = checks.value(fx.tcx.sess);
let int = ScalarInt::try_from_uint(int, Size::from_bits(1)).unwrap();
let layout = fx.layout_of(fx.tcx.types.bool);
return CValue::const_val(fx, layout, int);
}
}
}

View file

@ -215,11 +215,6 @@ pub(crate) fn codegen_const_value<'tcx>(
CValue::by_val(val, layout)
}
},
ConstValue::RuntimeChecks(checks) => {
let int = checks.value(fx.tcx.sess);
let int = ScalarInt::try_from_uint(int, Size::from_bits(1)).unwrap();
return CValue::const_val(fx, layout, int);
}
ConstValue::Indirect { alloc_id, offset } => CValue::by_ref(
Pointer::new(pointer_for_allocation(fx, alloc_id))
.offset_i64(fx, i64::try_from(offset.bytes()).unwrap()),
@ -545,6 +540,10 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
operand: &Operand<'tcx>,
) -> Option<ScalarInt> {
match operand {
Operand::RuntimeChecks(checks) => {
let int = checks.value(fx.tcx.sess);
ScalarInt::try_from_uint(int, Size::from_bits(1))
}
Operand::Constant(const_) => eval_mir_constant(fx, const_).0.try_to_scalar_int(),
// FIXME(rust-lang/rust#85105): Casts like `IMM8 as u32` result in the const being stored
// inside a temporary before being passed to the intrinsic requiring the const argument.

View file

@ -165,14 +165,6 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
let llval = bx.scalar_to_backend(x, scalar, bx.immediate_backend_type(layout));
OperandValue::Immediate(llval)
}
mir::ConstValue::RuntimeChecks(checks) => {
let BackendRepr::Scalar(scalar) = layout.backend_repr else {
bug!("from_const: invalid ByVal layout: {:#?}", layout);
};
let x = Scalar::from_bool(checks.value(bx.tcx().sess));
let llval = bx.scalar_to_backend(x, scalar, bx.immediate_backend_type(layout));
OperandValue::Immediate(llval)
}
ConstValue::ZeroSized => return OperandRef::zero_sized(layout),
ConstValue::Slice { alloc_id, meta } => {
let BackendRepr::ScalarPair(a_scalar, _) = layout.backend_repr else {
@ -1060,6 +1052,17 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
OperandRef { move_annotation, ..self.codegen_consume(bx, place.as_ref()) }
}
mir::Operand::RuntimeChecks(checks) => {
let layout = bx.layout_of(bx.tcx().types.bool);
let BackendRepr::Scalar(scalar) = layout.backend_repr else {
bug!("from_const: invalid ByVal layout: {:#?}", layout);
};
let x = Scalar::from_bool(checks.value(bx.tcx().sess));
let llval = bx.scalar_to_backend(x, scalar, bx.immediate_backend_type(layout));
let val = OperandValue::Immediate(llval);
OperandRef { val, layout, move_annotation: None }
}
mir::Operand::Constant(ref constant) => {
let constant_ty = self.monomorphize(constant.ty());
// Most SIMD vector constants should be passed as immediates.

View file

@ -338,6 +338,7 @@ where
Operand::Copy(place) | Operand::Move(place) => {
return in_place::<Q, _>(cx, in_local, place.as_ref());
}
Operand::RuntimeChecks(_) => return Q::in_any_value_of_ty(cx, cx.tcx.types.bool),
Operand::Constant(c) => c,
};

View file

@ -122,6 +122,13 @@ impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
unimplemented!()
}
#[inline(always)]
fn runtime_checks(_ecx: &InterpCx<'tcx, Self>, r: 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.
panic!("compiletime machine evaluated {r:?}")
}
fn binary_ptr_op(
ecx: &InterpCx<'tcx, Self>,
bin_op: BinOp,

View file

@ -637,6 +637,16 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
Err(ConstEvalErrKind::AssertFailure(err)).into()
}
#[inline(always)]
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)
}
fn binary_ptr_op(
_ecx: &InterpCx<'tcx, Self>,
_bin_op: mir::BinOp,

View file

@ -298,7 +298,7 @@ pub trait Machine<'tcx>: Sized {
interp_ok(())
}
/// Determines the result of a `NullaryOp::RuntimeChecks` invocation.
/// Determines the result of a `Operand::RuntimeChecks` invocation.
fn runtime_checks(
_ecx: &InterpCx<'tcx, Self>,
r: mir::RuntimeChecks,
@ -680,16 +680,6 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
true
}
#[inline(always)]
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)
}
#[inline(always)]
fn adjust_global_allocation<'b>(
_ecx: &InterpCx<$tcx, Self>,

View file

@ -845,6 +845,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
// FIXME: do some more logic on `move` to invalidate the old location
&Copy(place) | &Move(place) => self.eval_place_to_op(place, layout)?,
&RuntimeChecks(checks) => {
let val = M::runtime_checks(self, checks)?;
ImmTy::from_bool(val, self.tcx()).into()
}
Constant(constant) => {
let c = self.instantiate_from_current_frame_and_normalize_erasing_regions(
constant.const_,
@ -892,10 +897,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
let ptr = Pointer::new(CtfeProvenance::from(alloc_id).as_immutable(), Size::ZERO);
Immediate::new_slice(self.global_root_pointer(ptr)?.into(), meta, self)
}
mir::ConstValue::RuntimeChecks(checks) => {
let val = M::runtime_checks(self, checks)?;
Scalar::from_bool(val).into()
}
};
interp_ok(OpTy { op: Operand::Immediate(imm), layout })
}

View file

@ -387,7 +387,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
move_definitely_disjoint: bool,
) -> InterpResult<'tcx, FnArg<'tcx, M::Provenance>> {
interp_ok(match op {
mir::Operand::Copy(_) | mir::Operand::Constant(_) => {
mir::Operand::Copy(_) | mir::Operand::Constant(_) | mir::Operand::RuntimeChecks(_) => {
// Make a regular copy.
let op = self.eval_operand(op, None)?;
FnArg::Copy(op)

View file

@ -68,10 +68,6 @@ pub enum ConstValue {
/// Offset into `alloc`
offset: Size,
},
/// Special constants whose value depends on the evaluation context. Their value depends on a
/// flag on the crate being codegenned.
RuntimeChecks(RuntimeChecks),
}
#[cfg(target_pointer_width = "64")]
@ -81,10 +77,7 @@ impl ConstValue {
#[inline]
pub fn try_to_scalar(&self) -> Option<Scalar> {
match *self {
ConstValue::Indirect { .. }
| ConstValue::Slice { .. }
| ConstValue::ZeroSized
| ConstValue::RuntimeChecks(_) => None,
ConstValue::Indirect { .. } | ConstValue::Slice { .. } | ConstValue::ZeroSized => None,
ConstValue::Scalar(val) => Some(val),
}
}
@ -140,7 +133,7 @@ impl ConstValue {
tcx: TyCtxt<'tcx>,
) -> Option<&'tcx [u8]> {
let (alloc_id, start, len) = match self {
ConstValue::Scalar(_) | ConstValue::ZeroSized | ConstValue::RuntimeChecks(_) => {
ConstValue::Scalar(_) | ConstValue::ZeroSized => {
bug!("`try_get_slice_bytes` on non-slice constant")
}
&ConstValue::Slice { alloc_id, meta } => (alloc_id, 0, meta),
@ -192,9 +185,7 @@ impl ConstValue {
/// Can return `true` even if there is no provenance.
pub fn may_have_provenance(&self, tcx: TyCtxt<'_>, size: Size) -> bool {
match *self {
ConstValue::ZeroSized
| ConstValue::Scalar(Scalar::Int(_))
| ConstValue::RuntimeChecks(_) => return false,
ConstValue::ZeroSized | ConstValue::Scalar(Scalar::Int(_)) => return false,
ConstValue::Scalar(Scalar::Ptr(..)) => return true,
// It's hard to find out the part of the allocation we point to;
// just conservatively check everything.
@ -233,29 +224,6 @@ impl ConstValue {
}
}
#[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(),
}
}
}
///////////////////////////////////////////////////////////////////////////
/// Constants
@ -549,7 +517,6 @@ impl<'tcx> Const<'tcx> {
ConstValue::Slice { .. }
| ConstValue::ZeroSized
| ConstValue::Scalar(_)
| ConstValue::RuntimeChecks(_)
| ConstValue::Indirect { .. },
_,
) => true,

View file

@ -607,9 +607,6 @@ impl<'tcx> Body<'tcx> {
typing_env,
crate::ty::EarlyBinder::bind(constant.const_),
);
if let Const::Val(ConstValue::RuntimeChecks(check), _) = mono_literal {
return Some(check.value(tcx.sess) as u128);
}
mono_literal.try_eval_bits(tcx, typing_env)
};
@ -623,6 +620,10 @@ impl<'tcx> Body<'tcx> {
let bits = eval_mono_const(constant)?;
return Some((bits, targets));
}
Operand::RuntimeChecks(check) => {
let bits = check.value(tcx.sess) as u128;
return Some((bits, targets));
}
Operand::Move(place) | Operand::Copy(place) => place,
};

View file

@ -1255,6 +1255,7 @@ impl<'tcx> Debug for Operand<'tcx> {
Constant(ref a) => write!(fmt, "{a:?}"),
Copy(ref place) => write!(fmt, "copy {place:?}"),
Move(ref place) => write!(fmt, "move {place:?}"),
RuntimeChecks(checks) => write!(fmt, "const {checks:?}"),
}
}
}
@ -1518,7 +1519,6 @@ pub fn write_allocations<'tcx>(
match val {
ConstValue::Scalar(interpret::Scalar::Ptr(ptr, _)) => Some(ptr.provenance.alloc_id()),
ConstValue::Scalar(interpret::Scalar::Int { .. }) => None,
ConstValue::RuntimeChecks(_) => None,
ConstValue::ZeroSized => None,
ConstValue::Slice { alloc_id, .. } | ConstValue::Indirect { alloc_id, .. } => {
// FIXME: we don't actually want to print all of these, since some are printed nicely directly as values inline in MIR.
@ -1969,7 +1969,6 @@ fn pretty_print_const_value_tcx<'tcx>(
fmt.write_str(&p.into_buffer())?;
return Ok(());
}
(ConstValue::RuntimeChecks(checks), _) => return write!(fmt, "{checks:?}"),
(ConstValue::ZeroSized, ty::FnDef(d, s)) => {
let mut p = FmtPrinter::new(tcx, Namespace::ValueNS);
p.print_alloc_ids = true;

View file

@ -642,7 +642,7 @@ impl<'tcx> Operand<'tcx> {
pub fn to_copy(&self) -> Self {
match *self {
Operand::Copy(_) | Operand::Constant(_) => self.clone(),
Operand::Copy(_) | Operand::Constant(_) | Operand::RuntimeChecks(_) => self.clone(),
Operand::Move(place) => Operand::Copy(place),
}
}
@ -652,7 +652,7 @@ impl<'tcx> Operand<'tcx> {
pub fn place(&self) -> Option<Place<'tcx>> {
match self {
Operand::Copy(place) | Operand::Move(place) => Some(*place),
Operand::Constant(_) => None,
Operand::Constant(_) | Operand::RuntimeChecks(_) => None,
}
}
@ -661,7 +661,7 @@ impl<'tcx> Operand<'tcx> {
pub fn constant(&self) -> Option<&ConstOperand<'tcx>> {
match self {
Operand::Constant(x) => Some(&**x),
Operand::Copy(_) | Operand::Move(_) => None,
Operand::Copy(_) | Operand::Move(_) | Operand::RuntimeChecks(_) => None,
}
}
@ -681,6 +681,7 @@ impl<'tcx> Operand<'tcx> {
match self {
&Operand::Copy(ref l) | &Operand::Move(ref l) => l.ty(local_decls, tcx).ty,
Operand::Constant(c) => c.const_.ty(),
Operand::RuntimeChecks(_) => tcx.types.bool,
}
}
@ -693,6 +694,7 @@ impl<'tcx> Operand<'tcx> {
local_decls.local_decls()[l.local].source_info.span
}
Operand::Constant(c) => c.span,
Operand::RuntimeChecks(_) => DUMMY_SP,
}
}
}

View file

@ -1327,6 +1327,10 @@ pub enum Operand<'tcx> {
/// Constants are already semantically values, and remain unchanged.
Constant(Box<ConstOperand<'tcx>>),
/// Special constants whose value depends on the evaluation context. Their value depends on a
/// flag on the crate being codegenned.
RuntimeChecks(RuntimeChecks),
}
#[derive(Clone, Copy, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]
@ -1558,6 +1562,29 @@ pub enum AggregateKind<'tcx> {
RawPtr(Ty<'tcx>, Mutability),
}
#[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)]
#[derive(HashStable, TyEncodable, TyDecodable, TypeFoldable, TypeVisitable)]
pub enum UnOp {

View file

@ -845,6 +845,7 @@ macro_rules! make_mir_visitor {
Operand::Constant(constant) => {
self.visit_const_operand(constant, location);
}
Operand::RuntimeChecks(_) => {}
}
}

View file

@ -261,6 +261,7 @@ impl<'a, 'tcx> ParseCtxt<'a, 'tcx> {
let value = match operand {
Operand::Constant(c) => VarDebugInfoContents::Const(*c),
Operand::Copy(p) | Operand::Move(p) => VarDebugInfoContents::Place(p),
Operand::RuntimeChecks(_) => unreachable!(),
};
let dbginfo = VarDebugInfo {
name,

View file

@ -1099,7 +1099,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
Some(DropData { source_info, local, kind: DropKind::Value })
}
Operand::Constant(_) => None,
Operand::Constant(_) | Operand::RuntimeChecks(_) => None,
})
.collect();
@ -1563,7 +1563,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// look for moves of a local variable, like `MOVE(_X)`
let locals_moved = operands.iter().flat_map(|operand| match operand.node {
Operand::Copy(_) | Operand::Constant(_) => None,
Operand::Copy(_) | Operand::Constant(_) | Operand::RuntimeChecks(_) => None,
Operand::Move(place) => place.as_local(),
});

View file

@ -546,9 +546,10 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
fn gather_operand(&mut self, operand: &Operand<'tcx>) {
match *operand {
Operand::Constant(..) | Operand::Copy(..) => {} // not-a-move
// not-a-move
Operand::Constant(..) | Operand::Copy(..) | Operand::RuntimeChecks(_) => {}
// a move
Operand::Move(place) => {
// a move
self.gather_move(place);
}
}

View file

@ -103,7 +103,7 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> {
self.penalty += CALL_PENALTY;
}
TerminatorKind::SwitchInt { discr, targets } => {
if discr.constant().is_some() {
if matches!(discr, Operand::Constant(_) | Operand::RuntimeChecks(_)) {
// Not only will this become a `Goto`, but likely other
// things will be removable as unreachable.
self.bonus += CONST_SWITCH_BONUS;

View file

@ -211,6 +211,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
state: &mut State<FlatSet<Scalar>>,
) -> ValueOrPlace<FlatSet<Scalar>> {
match operand {
Operand::RuntimeChecks(_) => ValueOrPlace::TOP,
Operand::Constant(box constant) => {
ValueOrPlace::Value(self.handle_constant(constant, state))
}
@ -530,6 +531,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
operand: &Operand<'tcx>,
) {
match operand {
Operand::RuntimeChecks(_) => {}
Operand::Copy(rhs) | Operand::Move(rhs) => {
if let Some(rhs) = self.map.find(rhs.as_ref()) {
state.insert_place_idx(place, rhs, &self.map);
@ -1036,7 +1038,7 @@ impl<'tcx> MutVisitor<'tcx> for Patch<'tcx> {
self.super_operand(operand, location)
}
}
Operand::Constant(_) => {}
Operand::Constant(_) | Operand::RuntimeChecks(_) => {}
}
}

View file

@ -117,11 +117,7 @@ impl<'tcx> crate::MirPass<'tcx> for EarlyOtherwiseBranch {
unreachable!()
};
// Always correct since we can only switch on `Copy` types
let parent_op = match parent_op {
Operand::Move(x) => Operand::Copy(*x),
Operand::Copy(x) => Operand::Copy(*x),
Operand::Constant(x) => Operand::Constant(x.clone()),
};
let parent_op = parent_op.to_copy();
let parent_ty = parent_op.ty(body.local_decls(), tcx);
let statements_before = bbs[parent].statements.len();
let parent_end = Location { block: parent, statement_index: statements_before };

View file

@ -136,12 +136,7 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> {
}
fn nth_arg_span(&self, args: &[Spanned<Operand<'tcx>>], n: usize) -> Span {
match &args[n].node {
Operand::Copy(place) | Operand::Move(place) => {
self.body.local_decls[place.local].source_info.span
}
Operand::Constant(constant) => constant.span,
}
args[n].node.span(&self.body.local_decls)
}
fn emit_lint(

View file

@ -248,6 +248,7 @@ enum Value<'a, 'tcx> {
Discriminant(VnIndex),
// Operations.
RuntimeChecks(RuntimeChecks),
UnaryOp(UnOp, VnIndex),
BinaryOp(BinOp, VnIndex, VnIndex),
Cast {
@ -569,6 +570,8 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
_ if ty.is_zst() => ImmTy::uninit(ty).into(),
Opaque(_) => return None,
// Keep runtime check constants as symbolic.
RuntimeChecks(..) => return None,
// In general, evaluating repeat expressions just consumes a lot of memory.
// But in the special case that the element is just Immediate::Uninit, we can evaluate
@ -1005,11 +1008,16 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
location: Location,
) -> Option<VnIndex> {
match *operand {
Operand::RuntimeChecks(c) => {
Some(self.insert(self.tcx.types.bool, Value::RuntimeChecks(c)))
}
Operand::Constant(ref constant) => Some(self.insert_constant(constant.const_)),
Operand::Copy(ref mut place) | Operand::Move(ref mut place) => {
let value = self.simplify_place_value(place, location)?;
if let Some(const_) = self.try_as_constant(value) {
*operand = Operand::Constant(Box::new(const_));
} else if let Value::RuntimeChecks(c) = self.get(value) {
*operand = Operand::RuntimeChecks(c);
}
Some(value)
}
@ -1777,6 +1785,8 @@ impl<'tcx> VnState<'_, '_, 'tcx> {
fn try_as_operand(&mut self, index: VnIndex, location: Location) -> Option<Operand<'tcx>> {
if let Some(const_) = self.try_as_constant(index) {
Some(Operand::Constant(Box::new(const_)))
} else if let Value::RuntimeChecks(c) = self.get(index) {
Some(Operand::RuntimeChecks(c))
} else if let Some(place) = self.try_as_place(index, location, false) {
self.reused_locals.insert(place.local);
Some(Operand::Copy(place))

View file

@ -363,11 +363,15 @@ impl<'tcx> MutVisitor<'tcx> for SimplifyUbCheck<'tcx> {
}
fn visit_operand(&mut self, operand: &mut Operand<'tcx>, _: Location) {
if let Operand::Constant(c) = operand
&& let Const::Val(c, _) = &mut c.const_
&& let ConstValue::RuntimeChecks(RuntimeChecks::UbChecks) = c
{
*c = ConstValue::from_bool(self.tcx.sess.ub_checks());
if let Operand::RuntimeChecks(RuntimeChecks::UbChecks) = operand {
*operand = Operand::Constant(Box::new(ConstOperand {
span: rustc_span::DUMMY_SP,
user_ty: None,
const_: Const::Val(
ConstValue::from_bool(self.tcx.sess.ub_checks()),
self.tcx.types.bool,
),
}));
}
}
}

View file

@ -477,6 +477,7 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
let Some(rhs) = self.map.find(rhs.as_ref()) else { return };
self.process_copy(lhs, rhs, state)
}
Operand::RuntimeChecks(_) => {}
}
}

View file

@ -282,6 +282,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
/// or `eval_place`, depending on the variant of `Operand` used.
fn eval_operand(&mut self, op: &Operand<'tcx>) -> Option<ImmTy<'tcx>> {
match *op {
Operand::RuntimeChecks(_) => None,
Operand::Constant(ref c) => self.eval_constant(c),
Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
}

View file

@ -261,7 +261,7 @@ fn remap_mir_for_const_eval_select<'tcx>(
if context == hir::Constness::Const { called_in_const } else { called_at_rt };
let (method, place): (fn(Place<'tcx>) -> Operand<'tcx>, Place<'tcx>) =
match tupled_args.node {
Operand::Constant(_) => {
Operand::Constant(_) | Operand::RuntimeChecks(_) => {
// There is no good way of extracting a tuple arg from a constant
// (const generic stuff) so we just create a temporary and deconstruct
// that.

View file

@ -35,14 +35,7 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics {
terminator.source_info,
StatementKind::Assign(Box::new((
*destination,
Rvalue::Use(Operand::Constant(Box::new(ConstOperand {
span: terminator.source_info.span,
user_ty: None,
const_: Const::Val(
ConstValue::RuntimeChecks(op),
tcx.types.bool,
),
}))),
Rvalue::Use(Operand::RuntimeChecks(op)),
))),
));
terminator.kind = TerminatorKind::Goto { target };

View file

@ -360,6 +360,9 @@ impl<'tcx> Validator<'_, 'tcx> {
match operand {
Operand::Copy(place) | Operand::Move(place) => self.validate_place(place.as_ref()),
// Promoting a runtime check would transform a runtime error into a compile-time error.
Operand::RuntimeChecks(_) => Err(Unpromotable),
// The qualifs for a constant (e.g. `HasMutInterior`) are checked in
// `validate_rvalue` upon access.
Operand::Constant(c) => {

View file

@ -41,7 +41,7 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyConstCondition {
{
Some(const_operand)
}
Operand::Copy(_) | Operand::Move(_) => None,
Operand::Copy(_) | Operand::Move(_) | Operand::RuntimeChecks(_) => None,
}
}

View file

@ -392,11 +392,14 @@ impl<'tcx, 'll> MutVisitor<'tcx> for ReplacementVisitor<'tcx, 'll> {
// a_1 = move? place.1
// ...
// ```
StatementKind::Assign(box (lhs, Rvalue::Use(ref op))) => {
let (rplace, copy) = match *op {
Operand::Copy(rplace) => (rplace, true),
Operand::Move(rplace) => (rplace, false),
Operand::Constant(_) => bug!(),
StatementKind::Assign(box (
lhs,
Rvalue::Use(ref op @ (Operand::Copy(rplace) | Operand::Move(rplace))),
)) => {
let copy = match *op {
Operand::Copy(_) => true,
Operand::Move(_) => false,
Operand::Constant(_) | Operand::RuntimeChecks(_) => bug!(),
};
if let Some(final_locals) = self.replacements.place_fragments(lhs) {
for (field, ty, new_local) in final_locals {

View file

@ -53,7 +53,6 @@ pub(crate) fn try_new_allocation<'tcx>(
ConstValue::Scalar(scalar) => {
alloc::try_new_scalar(layout, scalar, cx).map(|alloc| alloc.stable(tables, cx))
}
ConstValue::RuntimeChecks(_) => todo!(),
ConstValue::ZeroSized => Ok(new_empty_allocation(layout.align.abi)),
ConstValue::Slice { alloc_id, meta } => {
alloc::try_new_slice(layout, alloc_id, meta, cx).map(|alloc| alloc.stable(tables, cx))

View file

@ -673,6 +673,7 @@ pub enum Operand {
Copy(Place),
Move(Place),
Constant(ConstOperand),
RuntimeChecks(RuntimeChecks),
}
#[derive(Clone, Eq, PartialEq, Hash, Serialize)]
@ -695,6 +696,16 @@ pub struct ConstOperand {
pub const_: MirConst,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, 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,
}
/// Debug information pertaining to a user variable.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct VarDebugInfo {
@ -1025,6 +1036,7 @@ impl Operand {
match self {
Operand::Copy(place) | Operand::Move(place) => place.ty(locals),
Operand::Constant(c) => Ok(c.ty()),
Operand::RuntimeChecks(_) => Ok(Ty::bool_ty()),
}
}
}

View file

@ -332,6 +332,7 @@ fn pretty_operand(operand: &Operand) -> String {
format!("move {mv:?}")
}
Operand::Constant(cnst) => pretty_mir_const(&cnst.const_),
Operand::RuntimeChecks(checks) => format!("const {checks:?}"),
}
}

View file

@ -296,6 +296,7 @@ macro_rules! make_mir_visitor {
Operand::Constant(constant) => {
self.visit_const_operand(constant, location);
}
Operand::RuntimeChecks(_) => {}
}
}

View file

@ -1357,7 +1357,6 @@ pub enum ConstantKind {
Ty(TyConst),
Allocated(Allocation),
Unevaluated(UnevaluatedConst),
RuntimeChecks(RuntimeChecks),
Param(ParamConst),
/// Store ZST constants.
/// We have to special handle these constants since its type might be generic.
@ -1377,16 +1376,6 @@ pub struct UnevaluatedConst {
pub promoted: Option<Promoted>,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, 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,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub enum TraitSpecializationKind {
None,

View file

@ -312,7 +312,7 @@ impl<'tcx> Stable<'tcx> for mir::FakeBorrowKind {
}
impl<'tcx> Stable<'tcx> for mir::RuntimeChecks {
type T = crate::ty::RuntimeChecks;
type T = crate::mir::RuntimeChecks;
fn stable<'cx>(
&self,
_: &mut Tables<'cx, BridgeTys>,
@ -320,9 +320,9 @@ impl<'tcx> Stable<'tcx> for mir::RuntimeChecks {
) -> Self::T {
use rustc_middle::mir::RuntimeChecks::*;
match self {
UbChecks => crate::ty::RuntimeChecks::UbChecks,
ContractChecks => crate::ty::RuntimeChecks::ContractChecks,
OverflowChecks => crate::ty::RuntimeChecks::OverflowChecks,
UbChecks => crate::mir::RuntimeChecks::UbChecks,
ContractChecks => crate::mir::RuntimeChecks::ContractChecks,
OverflowChecks => crate::mir::RuntimeChecks::OverflowChecks,
}
}
}
@ -379,6 +379,7 @@ impl<'tcx> Stable<'tcx> for mir::Operand<'tcx> {
Copy(place) => crate::mir::Operand::Copy(place.stable(tables, cx)),
Move(place) => crate::mir::Operand::Move(place.stable(tables, cx)),
Constant(c) => crate::mir::Operand::Constant(c.stable(tables, cx)),
RuntimeChecks(c) => crate::mir::Operand::RuntimeChecks(c.stable(tables, cx)),
}
}
}
@ -886,13 +887,6 @@ impl<'tcx> Stable<'tcx> for rustc_middle::mir::Const<'tcx> {
let ty = ty.stable(tables, cx);
MirConst::new(ConstantKind::ZeroSized, ty, id)
}
mir::Const::Val(mir::ConstValue::RuntimeChecks(checks), ty) => {
let ty = cx.lift(ty).unwrap();
let checks = cx.lift(checks).unwrap();
let ty = ty.stable(tables, cx);
let kind = ConstantKind::RuntimeChecks(checks.stable(tables, cx));
MirConst::new(kind, ty, id)
}
mir::Const::Val(val, ty) => {
let ty = cx.lift(ty).unwrap();
let val = cx.lift(val).unwrap();

View file

@ -68,9 +68,7 @@ impl Visitable for MirConst {
super::ty::ConstantKind::Ty(ct) => ct.visit(visitor)?,
super::ty::ConstantKind::Allocated(alloc) => alloc.visit(visitor)?,
super::ty::ConstantKind::Unevaluated(uv) => uv.visit(visitor)?,
super::ty::ConstantKind::RuntimeChecks(_)
| super::ty::ConstantKind::Param(_)
| super::ty::ConstantKind::ZeroSized => {}
super::ty::ConstantKind::Param(_) | super::ty::ConstantKind::ZeroSized => {}
}
self.ty().visit(visitor)
}

View file

@ -110,7 +110,7 @@ impl<'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'_, '_, 'tcx> {
immutable_borrowers.push(p.local);
}
},
mir::Operand::Constant(..) => (),
mir::Operand::Constant(..) | mir::Operand::RuntimeChecks(..) => (),
}
}
@ -151,7 +151,7 @@ fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
let mut visit_op = |op: &mir::Operand<'_>| match op {
mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local),
mir::Operand::Constant(..) => (),
mir::Operand::Constant(..) | mir::Operand::RuntimeChecks(..) => (),
};
match rvalue {

View file

@ -277,6 +277,7 @@ fn check_operand<'tcx>(
Some(_) => Err((span, "cannot access `static` items in const fn".into())),
None => Ok(()),
},
Operand::RuntimeChecks(..) => Ok(()),
}
}

View file

@ -8,10 +8,8 @@ type Demo = [u8; 3];
#[no_mangle]
pub fn slice_iter_len_eq_zero(y: std::slice::Iter<'_, Demo>) -> bool {
// CHECK-NOT: sub
// CHECK: %2 = icmp ne ptr %1, null
// CHECK-NEXT: tail call void @llvm.assume(i1 %2)
// CHECK-NEXT: %[[RET:.+]] = icmp eq ptr {{%0, %1|%1, %0}}
// CHECK-NEXT: ret i1 %[[RET]]
// CHECK: %[[RET:.+]] = icmp eq ptr {{%y.0, %y.1|%y.1, %y.0}}
// CHECK: ret i1 %[[RET]]
y.len() == 0
}
@ -33,7 +31,7 @@ struct MyZST;
// CHECK-LABEL: @slice_zst_iter_len_eq_zero
#[no_mangle]
pub fn slice_zst_iter_len_eq_zero(y: std::slice::Iter<'_, MyZST>) -> bool {
// CHECK: %[[RET:.+]] = icmp eq ptr %1, null
// CHECK: %[[RET:.+]] = icmp eq ptr %y.1, null
// CHECK: ret i1 %[[RET]]
y.len() == 0
}