From bbcf44fbfaf9f48dfd7fd235152d0c33adbfe161 Mon Sep 17 00:00:00 2001 From: joboet Date: Sun, 14 Sep 2025 20:32:49 +0200 Subject: [PATCH] add an `Rvalue` for `is_val_statically_known` and use it in MIR optimization --- compiler/rustc_borrowck/src/lib.rs | 3 +- .../src/polonius/legacy/loan_invalidations.rs | 3 +- compiler/rustc_borrowck/src/type_check/mod.rs | 6 ++- compiler/rustc_codegen_cranelift/src/base.rs | 9 +++++ compiler/rustc_codegen_gcc/src/builder.rs | 6 +++ .../rustc_codegen_gcc/src/intrinsic/mod.rs | 6 --- compiler/rustc_codegen_llvm/src/builder.rs | 4 ++ compiler/rustc_codegen_llvm/src/intrinsic.rs | 11 ------ compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 21 ++++++++++ .../rustc_codegen_ssa/src/traits/builder.rs | 9 +++++ .../src/check_consts/check.rs | 4 ++ .../src/check_consts/qualifs.rs | 3 +- .../src/check_consts/resolver.rs | 3 +- .../src/const_eval/machine.rs | 6 --- .../rustc_const_eval/src/interpret/machine.rs | 12 ++++++ .../rustc_const_eval/src/interpret/step.rs | 6 +++ compiler/rustc_middle/src/mir/pretty.rs | 4 ++ compiler/rustc_middle/src/mir/statement.rs | 4 +- compiler/rustc_middle/src/mir/syntax.rs | 7 ++++ compiler/rustc_middle/src/mir/visit.rs | 4 ++ .../src/impls/borrowed_locals.rs | 3 +- .../src/move_paths/builder.rs | 3 +- .../src/dataflow_const_prop.rs | 10 +++++ compiler/rustc_mir_transform/src/gvn.rs | 23 +++++++++++ .../src/known_panics_lint.rs | 5 ++- .../src/lower_intrinsics.rs | 17 +++++++++ .../rustc_mir_transform/src/promote_consts.rs | 4 ++ compiler/rustc_mir_transform/src/validate.rs | 3 ++ .../src/unstable/convert/stable/mir.rs | 1 + library/core/src/intrinsics/mod.rs | 7 +--- src/tools/miri/src/intrinsics/mod.rs | 14 ------- src/tools/miri/src/machine.rs | 9 +++++ .../miri/tests/pass/intrinsics/intrinsics.rs | 30 +++++++-------- tests/codegen-llvm/is_val_statically_known.rs | 38 ------------------- tests/mir-opt/lower_intrinsics.rs | 6 +++ 35 files changed, 198 insertions(+), 106 deletions(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 5d2dda8b0e7cc..5b39cbe6f0d55 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -1535,7 +1535,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> { | Rvalue::Repeat(operand, _) | Rvalue::UnaryOp(_ /*un_op*/, operand) | Rvalue::Cast(_ /*cast_kind*/, operand, _ /*ty*/) - | Rvalue::ShallowInitBox(operand, _ /*ty*/) => { + | Rvalue::ShallowInitBox(operand, _ /*ty*/) + | Rvalue::StaticallyKnown(operand) => { self.consume_operand(location, (operand, span), state) } diff --git a/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs b/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs index 99dd0b2dd4664..47b9f6d58eca1 100644 --- a/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs +++ b/compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs @@ -299,7 +299,8 @@ impl<'a, 'tcx> LoanInvalidationsGenerator<'a, 'tcx> { | Rvalue::Repeat(operand, _) | Rvalue::UnaryOp(_ /*un_op*/, operand) | Rvalue::Cast(_ /*cast_kind*/, operand, _ /*ty*/) - | Rvalue::ShallowInitBox(operand, _ /*ty*/) => self.consume_operand(location, operand), + | Rvalue::ShallowInitBox(operand, _ /*ty*/) + | Rvalue::StaticallyKnown(operand) => self.consume_operand(location, operand), &Rvalue::CopyForDeref(place) => { let op = &Operand::Copy(place); diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 8c5447fe1e9e7..a13261141f25a 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1633,7 +1633,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { | Rvalue::ThreadLocalRef(..) | Rvalue::Len(..) | Rvalue::Discriminant(..) - | Rvalue::NullaryOp(NullOp::OffsetOf(..), _) => {} + | Rvalue::NullaryOp(NullOp::OffsetOf(..), _) + | Rvalue::StaticallyKnown(_) => {} } } @@ -2209,7 +2210,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { | Rvalue::CopyForDeref(..) | Rvalue::UnaryOp(..) | Rvalue::Discriminant(..) - | Rvalue::WrapUnsafeBinder(..) => None, + | Rvalue::WrapUnsafeBinder(..) + | Rvalue::StaticallyKnown(..) => None, Rvalue::Aggregate(aggregate, _) => match **aggregate { AggregateKind::Adt(_, _, _, user_ty, _) => user_ty, diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 3a28dd7e73cb3..4fbad22314364 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -934,6 +934,15 @@ fn codegen_stmt<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, cur_block: Block, stmt: let operand = codegen_operand(fx, operand); lval.write_cvalue_transmute(fx, operand); } + Rvalue::StaticallyKnown(_) => { + // Cranelift doesn't have an exquivalent for this. Just + // return `false` unconditionally. + let val = CValue::by_val( + fx.bcx.ins().iconst(types::I8, i64::from(false)), + fx.layout_of(fx.tcx.types.bool), + ); + lval.write_cvalue(fx, val); + } } } StatementKind::StorageLive(_) diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index f7a7a3f8c7e35..a0bb4e748db2a 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -925,6 +925,12 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { self.gcc_checked_binop(oop, typ, lhs, rhs) } + fn is_constant(&mut self, val: Self::Value) -> Self::Value { + let builtin = self.context.get_builtin_function("__builtin_constant_p"); + let res = self.context.new_call(None, builtin, &[val]); + self.icmp(IntPredicate::IntEQ, res, self.const_i32(0)) + } + fn alloca(&mut self, size: Size, align: Align) -> RValue<'gcc> { let ty = self.cx.type_array(self.cx.type_i8(), size.bytes()).get_aligned(align.bytes()); // TODO(antoyo): It might be better to return a LValue, but fixing the rustc API is non-trivial. diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index eb0a5336a1f13..d9d35b72f7174 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -381,12 +381,6 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc &args.iter().map(|arg| arg.immediate()).collect::>(), ) } - sym::is_val_statically_known => { - let a = args[0].immediate(); - let builtin = self.context.get_builtin_function("__builtin_constant_p"); - let res = self.context.new_call(None, builtin, &[a]); - self.icmp(IntPredicate::IntEQ, res, self.const_i32(0)) - } sym::catch_unwind => { try_intrinsic( self, diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 7d0691366e602..aeecc15807a21 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -1103,6 +1103,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { Some(self.call_intrinsic(name, &[self.type_i8(), self.type_ix(size.bits())], &[lhs, rhs])) } + fn is_constant(&mut self, val: Self::Value) -> Self::Value { + self.call_intrinsic("llvm.is.constant", &[self.cx.val_ty(val)], &[val]) + } + /* Miscellaneous instructions */ fn memcpy( &mut self, diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 85f71f331a491..c2fe301a1e406 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -198,17 +198,6 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { codegen_autodiff(self, tcx, instance, args, result); return Ok(()); } - sym::is_val_statically_known => { - if let OperandValue::Immediate(imm) = args[0].val { - self.call_intrinsic( - "llvm.is.constant", - &[args[0].layout.immediate_llvm_type(self.cx)], - &[imm], - ) - } else { - self.const_bool(false) - } - } sym::select_unpredictable => { let cond = args[0].immediate(); assert_eq!(args[1].layout, args[2].layout); diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index f6f2e3f2a3a3c..d6519652f8a6e 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -737,6 +737,27 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let layout = bx.cx().layout_of(binder_ty); OperandRef { val: operand.val, layout } } + mir::Rvalue::StaticallyKnown(ref operand) => { + let operand = self.codegen_operand(bx, operand); + let val = match operand.val { + // This would need to insert some loads for the type. That's + // unlikely to be profitable, so just collapse to `false`. + OperandValue::Ref(..) => bx.const_bool(false), + OperandValue::Immediate(val) => bx.is_constant(val), + OperandValue::Pair(lval, rval) => { + let l = bx.is_constant(lval); + let r = bx.is_constant(rval); + bx.and(l, r) + } + // A zero-sized value is always constant, so the backend + // knows everything there is to know about it. + OperandValue::ZeroSized => bx.const_bool(true), + }; + OperandRef { + val: OperandValue::Immediate(val), + layout: bx.cx().layout_of(bx.tcx().types.bool), + } + } } } diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index f417d1a7bf724..7ab02ed283347 100644 --- a/compiler/rustc_codegen_ssa/src/traits/builder.rs +++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs @@ -293,6 +293,15 @@ pub trait BuilderMethods<'a, 'tcx>: self.assume(is_null); } + /// Returns whether `val` has a value known at compile-time. + /// + /// Defaults to always returning `false`, since returning `true` might trick + /// the program into performing costly checks that only make sense at + /// compile-time. + fn is_constant(&mut self, _val: Self::Value) -> Self::Value { + self.const_bool(false) + } + fn range_metadata(&mut self, load: Self::Value, range: WrappingRange); fn nonnull_metadata(&mut self, load: Self::Value); diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index ca707b50d50d9..8361f97bd48be 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -708,6 +708,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { Rvalue::WrapUnsafeBinder(..) => { // Unsafe binders are always trivial to create. } + + Rvalue::StaticallyKnown(..) => { + // We can always choose a value for this. + } } } diff --git a/compiler/rustc_const_eval/src/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/check_consts/qualifs.rs index faf41f1658b70..f1c630a6b002f 100644 --- a/compiler/rustc_const_eval/src/check_consts/qualifs.rs +++ b/compiler/rustc_const_eval/src/check_consts/qualifs.rs @@ -242,7 +242,8 @@ where | Rvalue::Repeat(operand, _) | Rvalue::UnaryOp(_, operand) | Rvalue::Cast(_, operand, _) - | Rvalue::ShallowInitBox(operand, _) => in_operand::(cx, in_local, operand), + | Rvalue::ShallowInitBox(operand, _) + | Rvalue::StaticallyKnown(operand) => in_operand::(cx, in_local, operand), Rvalue::BinaryOp(_, box (lhs, rhs)) => { in_operand::(cx, in_local, lhs) || in_operand::(cx, in_local, rhs) diff --git a/compiler/rustc_const_eval/src/check_consts/resolver.rs b/compiler/rustc_const_eval/src/check_consts/resolver.rs index d98e5027e4d6b..c86819ad4f109 100644 --- a/compiler/rustc_const_eval/src/check_consts/resolver.rs +++ b/compiler/rustc_const_eval/src/check_consts/resolver.rs @@ -203,7 +203,8 @@ where | mir::Rvalue::UnaryOp(..) | mir::Rvalue::Discriminant(..) | mir::Rvalue::Aggregate(..) - | mir::Rvalue::WrapUnsafeBinder(..) => {} + | mir::Rvalue::WrapUnsafeBinder(..) + | mir::Rvalue::StaticallyKnown(..) => {} } } diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index fccb6b171b1c2..d278372a5f282 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -548,12 +548,6 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { ecx.write_pointer(ptr, dest)?; } - // The intrinsic represents whether the value is known to the optimizer (LLVM). - // We're not doing any optimizations here, so there is no optimizer that could know the value. - // (We know the value here in the machine of course, but this is the runtime of that code, - // not the optimization stage.) - sym::is_val_statically_known => ecx.write_scalar(Scalar::from_bool(false), dest)?, - // We handle these here since Miri does not want to have them. sym::assert_inhabited | sym::assert_zero_valid diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index e22629993fba7..eab54ba41a4d9 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -289,6 +289,18 @@ pub trait Machine<'tcx>: Sized { a } + /// Computes the result of `is_val_statically_known`. + /// + /// The default implementation always returns `false`, but this could also + /// non-deterministically choose a value (miri does that to fully explore + /// the possible AM executions). + fn is_val_statically_known( + _ecx: &InterpCx<'tcx, Self>, + _val: &OpTy<'tcx, Self::Provenance>, + ) -> InterpResult<'tcx, bool> { + interp_ok(false) + } + /// Called before a basic block terminator is executed. #[inline] fn before_terminator(_ecx: &mut InterpCx<'tcx, Self>) -> InterpResult<'tcx> { diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 23d362de308f3..af56dd51ee717 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -295,6 +295,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let op = self.eval_operand(op, None)?; self.copy_op_allow_transmute(&op, &dest)?; } + + StaticallyKnown(ref op) => { + let op = self.eval_operand(op, None)?; + let known = M::is_val_statically_known(self, &op)?; + self.write_scalar(Scalar::from_bool(known), &dest)?; + } } trace!("{:?}", self.dump_place(&dest)); diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index a7d99f513a12a..a3b57ce7384f6 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1224,6 +1224,10 @@ impl<'tcx> Debug for Rvalue<'tcx> { WrapUnsafeBinder(ref op, ty) => { with_no_trimmed_paths!(write!(fmt, "wrap_binder!({op:?}; {ty})")) } + + StaticallyKnown(ref op) => { + write!(fmt, "StaticallyKnown({op:?})") + } } } } diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index ec2a8e86077d0..a74573f3e7000 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -717,7 +717,8 @@ impl<'tcx> Rvalue<'tcx> { | Rvalue::Discriminant(_) | Rvalue::Aggregate(_, _) | Rvalue::ShallowInitBox(_, _) - | Rvalue::WrapUnsafeBinder(_, _) => true, + | Rvalue::WrapUnsafeBinder(_, _) + | Rvalue::StaticallyKnown(_) => true, } } @@ -772,6 +773,7 @@ impl<'tcx> Rvalue<'tcx> { Rvalue::ShallowInitBox(_, ty) => Ty::new_box(tcx, ty), Rvalue::CopyForDeref(ref place) => place.ty(local_decls, tcx).ty, Rvalue::WrapUnsafeBinder(_, ty) => ty, + Rvalue::StaticallyKnown(_) => tcx.types.bool, } } diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 3b8def67f92d7..498129cb7dfa9 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1489,6 +1489,13 @@ pub enum Rvalue<'tcx> { /// Wraps a value in an unsafe binder. WrapUnsafeBinder(Operand<'tcx>, Ty<'tcx>), + + /// Yields a boolean that indicates whether the value of the operand is + /// known during optimization. + /// + /// From an operational semantics perspective, the result is + /// non-deterministically chosen. + StaticallyKnown(Operand<'tcx>), } #[derive(Clone, Copy, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index b498b7b8912cc..9706eb4255053 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -795,6 +795,10 @@ macro_rules! make_mir_visitor { self.visit_operand(op, location); self.visit_ty($(& $mutability)? *ty, TyContext::Location(location)); } + + Rvalue::StaticallyKnown(op) => { + self.visit_operand(op, location); + } } } diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs index 9abb83434321d..34934032c4459 100644 --- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs +++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs @@ -98,7 +98,8 @@ where | Rvalue::Discriminant(..) | Rvalue::Aggregate(..) | Rvalue::CopyForDeref(..) - | Rvalue::WrapUnsafeBinder(..) => {} + | Rvalue::WrapUnsafeBinder(..) + | Rvalue::StaticallyKnown(..) => {} } } diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 48718cad597a8..20b957c5712ad 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -399,7 +399,8 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> { | Rvalue::Cast(_, ref operand, _) | Rvalue::ShallowInitBox(ref operand, _) | Rvalue::UnaryOp(_, ref operand) - | Rvalue::WrapUnsafeBinder(ref operand, _) => self.gather_operand(operand), + | Rvalue::WrapUnsafeBinder(ref operand, _) + | Rvalue::StaticallyKnown(ref operand) => self.gather_operand(operand), Rvalue::BinaryOp(ref _binop, box (ref lhs, ref rhs)) => { self.gather_operand(lhs); self.gather_operand(rhs); diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index fe53de31f7583..8964836b5b11b 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -491,6 +491,16 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { FlatSet::Elem(Scalar::from_target_usize(val, &self.tcx)) } Rvalue::Discriminant(place) => state.get_discr(place.as_ref(), &self.map), + Rvalue::StaticallyKnown(op) => { + match self.eval_operand(op, state) { + // The constant value of the argument is known to this pass + // and can be used for further optimizations. Collapsing to + // `true` is thus likely to be profitable. + FlatSet::Elem(_) => FlatSet::Elem(Scalar::from_bool(true)), + FlatSet::Bottom => FlatSet::Bottom, + FlatSet::Top => FlatSet::Top, + } + } Rvalue::Use(operand) => return self.handle_operand(operand, state), Rvalue::CopyForDeref(place) => { return self.handle_operand(&Operand::Copy(*place), state); diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index bf6aa800d20f4..c2faf89d63ee9 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -858,6 +858,9 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { } Value::Discriminant(place) } + Rvalue::StaticallyKnown(ref mut arg_op) => { + return self.simplify_statically_known(arg_op, location); + } // Unsupported values. Rvalue::ThreadLocalRef(..) | Rvalue::ShallowInitBox(..) => return None, @@ -1440,6 +1443,26 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { Some(self.insert(self.tcx.types.usize, Value::Len(inner))) } + fn simplify_statically_known( + &mut self, + arg_op: &mut Operand<'tcx>, + location: Location, + ) -> Option { + let value = self.simplify_operand(arg_op, location)?; + match self.evaluated[value] { + // `Rvalue::StaticallyKnown` intentionally does not have a + // corresponding `Value` variant in order to preserve as many + // distinct `StaticallyKnown`s as possible – other passes may + // learn more information that allows them to optimize patterns + // that this pass does not recognize. + None => None, + // This pass knows the constant value of the argument and can use + // that for further optimizations. Thus collapsing this to `true` + // is likely to be profitable. + Some(_) => Some(self.insert_bool(true)), + } + } + fn pointers_have_same_metadata(&self, left_ptr_ty: Ty<'tcx>, right_ptr_ty: Ty<'tcx>) -> bool { let left_meta_ty = left_ptr_ty.pointee_metadata_ty_or_projection(self.tcx); let right_meta_ty = right_ptr_ty.pointee_metadata_ty_or_projection(self.tcx); diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 481c794190925..0078be043bb32 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -446,7 +446,8 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { | Rvalue::ShallowInitBox(..) | Rvalue::Discriminant(..) | Rvalue::NullaryOp(..) - | Rvalue::WrapUnsafeBinder(..) => {} + | Rvalue::WrapUnsafeBinder(..) + | Rvalue::StaticallyKnown(..) => {} } // FIXME we need to revisit this for #67176 @@ -685,6 +686,8 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { })?; imm.into() } + + StaticallyKnown(..) => return None, }; trace!(?val); diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index 8dadce0d448d3..67c294c3bdf90 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -374,6 +374,23 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics { )); terminator.kind = TerminatorKind::Goto { target }; } + sym::is_val_statically_known => { + let Ok([arg]) = take_array(args) else { + span_bug!( + terminator.source_info.span, + "Wrong number of arguments for is_val_statically_known intrinsic", + ); + }; + let target = target.unwrap(); + block.statements.push(Statement::new( + terminator.source_info, + StatementKind::Assign(Box::new(( + *destination, + Rvalue::StaticallyKnown(arg.node), + ))), + )); + 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 9ea2eb4f25d91..e2ff77f21f53c 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -602,6 +602,10 @@ impl<'tcx> Validator<'_, 'tcx> { self.validate_operand(o)?; } } + + Rvalue::StaticallyKnown(operand) => { + self.validate_operand(operand)?; + } } Ok(()) diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index 99e4782e4700c..d3ff5470f151f 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -1410,6 +1410,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ); } } + Rvalue::StaticallyKnown(_) => { + // All operand types are allowed. + } } self.super_rvalue(rvalue, location); } diff --git a/compiler/rustc_public/src/unstable/convert/stable/mir.rs b/compiler/rustc_public/src/unstable/convert/stable/mir.rs index be8ee80bed3c0..fe04ddfd3ca6e 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mir.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mir.rs @@ -252,6 +252,7 @@ impl<'tcx> Stable<'tcx> for mir::Rvalue<'tcx> { } CopyForDeref(place) => crate::mir::Rvalue::CopyForDeref(place.stable(tables, cx)), WrapUnsafeBinder(..) => todo!("FIXME(unsafe_binders):"), + StaticallyKnown(..) => todo!(), } } } diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index bffffbc29c1eb..32d9eb8cbaaaf 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2527,13 +2527,10 @@ pub(crate) macro const_eval_select { /// # _ = foo(&5_i32); /// # _ = bar(&5_i32); /// ``` -#[rustc_const_stable_indirect] #[rustc_nounwind] -#[unstable(feature = "core_intrinsics", issue = "none")] #[rustc_intrinsic] -pub const fn is_val_statically_known(_arg: T) -> bool { - false -} +#[rustc_intrinsic_const_stable_indirect] +pub const fn is_val_statically_known(arg: T) -> bool; /// Non-overlapping *typed* swap of a single value. /// diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 4628c30e2dfbe..21d079a62e5c1 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -162,20 +162,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_pointer(Pointer::new(ptr.provenance, masked_addr), dest)?; } - // We want to return either `true` or `false` at random, or else something like - // ``` - // if !is_val_statically_known(0) { unreachable_unchecked(); } - // ``` - // Would not be considered UB, or the other way around (`is_val_statically_known(0)`). - "is_val_statically_known" => { - let [_arg] = check_intrinsic_arg_count(args)?; - // FIXME: should we check for validity here? It's tricky because we do not have a - // place. Codegen does not seem to set any attributes like `noundef` for intrinsic - // calls, so we don't *have* to do anything. - let branch: bool = this.machine.rng.get_mut().random(); - this.write_scalar(Scalar::from_bool(branch), dest)?; - } - "sqrtf32" => { let [f] = check_intrinsic_arg_count(args)?; let f = this.read_scalar(f)?.to_f32()?; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index e4540fb9bc58b..1fd9bf14768ec 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1291,6 +1291,15 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { ecx.equal_float_min_max(a, b) } + // We want to return either `true` or `false` at random, or else something like + // ``` + // if !is_val_statically_known(0) { unreachable_unchecked(); } + // ``` + // Would not be considered UB, or the other way around (`is_val_statically_known(0)`). + fn is_val_statically_known(ecx: &InterpCx<'tcx, Self>, _val: &OpTy<'tcx>) -> InterpResult<'tcx, bool> { + interp_ok(ecx.machine.rng.borrow_mut().random()) + } + #[inline(always)] fn ub_checks(ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool> { interp_ok(ecx.tcx.sess.ub_checks()) diff --git a/src/tools/miri/tests/pass/intrinsics/intrinsics.rs b/src/tools/miri/tests/pass/intrinsics/intrinsics.rs index 913c3cde272d8..718839d3dadaf 100644 --- a/src/tools/miri/tests/pass/intrinsics/intrinsics.rs +++ b/src/tools/miri/tests/pass/intrinsics/intrinsics.rs @@ -33,24 +33,22 @@ fn main() { assert_eq!(intrinsics::likely(false), false); assert_eq!(intrinsics::unlikely(true), true); - // Skip this test when we use the fallback bodies, as that one is deterministic. - // (CI sets `--cfg force_intrinsic_fallback` together with `-Zmiri-force-intrinsic-fallback`.) - if !cfg!(force_intrinsic_fallback) { - let mut saw_true = false; - let mut saw_false = false; - - for _ in 0..50 { - if intrinsics::is_val_statically_known(0) { - saw_true = true; - } else { - saw_false = true; - } + let mut saw_true = false; + let mut saw_false = false; + for _ in 0..50 { + // Use `black_box` to make sure that the non-determinism of + // `is_val_statically_known` isn't lost to optimization (and to + // test that intrinsic as well...). + if intrinsics::is_val_statically_known(intrinsics::black_box(0)) { + saw_true = true; + } else { + saw_false = true; } - assert!( - saw_true && saw_false, - "`is_val_statically_known` failed to return both true and false. Congrats, you won the lottery!" - ); } + assert!( + saw_true && saw_false, + "`is_val_statically_known` failed to return both true and false. Congrats, you won the lottery!" + ); intrinsics::forget(Bomb); diff --git a/tests/codegen-llvm/is_val_statically_known.rs b/tests/codegen-llvm/is_val_statically_known.rs index 8119d3a3bf678..fe23d8b58267d 100644 --- a/tests/codegen-llvm/is_val_statically_known.rs +++ b/tests/codegen-llvm/is_val_statically_known.rs @@ -48,44 +48,6 @@ pub fn _bool_false(b: bool) -> i32 { _bool(b) } -#[inline] -pub fn _iref(a: &u8) -> i32 { - if is_val_statically_known(a) { 5 } else { 4 } -} - -// CHECK-LABEL: @_iref_borrow( -#[no_mangle] -pub fn _iref_borrow() -> i32 { - // CHECK: ret i32 4 - _iref(&0) -} - -// CHECK-LABEL: @_iref_arg( -#[no_mangle] -pub fn _iref_arg(a: &u8) -> i32 { - // CHECK: ret i32 4 - _iref(a) -} - -#[inline] -pub fn _slice_ref(a: &[u8]) -> i32 { - if is_val_statically_known(a) { 7 } else { 6 } -} - -// CHECK-LABEL: @_slice_ref_borrow( -#[no_mangle] -pub fn _slice_ref_borrow() -> i32 { - // CHECK: ret i32 6 - _slice_ref(&[0; 3]) -} - -// CHECK-LABEL: @_slice_ref_arg( -#[no_mangle] -pub fn _slice_ref_arg(a: &[u8]) -> i32 { - // CHECK: ret i32 6 - _slice_ref(a) -} - #[inline] pub fn _f16(a: f16) -> i32 { if is_val_statically_known(a) { 1 } else { 0 } diff --git a/tests/mir-opt/lower_intrinsics.rs b/tests/mir-opt/lower_intrinsics.rs index 7729e502ad6b9..6167d0d4bfd38 100644 --- a/tests/mir-opt/lower_intrinsics.rs +++ b/tests/mir-opt/lower_intrinsics.rs @@ -284,3 +284,9 @@ pub unsafe fn slice_get<'a, 'b>( slice_get_unchecked(pm, i), ) } + +pub fn statically_known_primitive(i: i32) -> bool { + // CHECK-LABEL: fn statically_known_primitive( + // CHECK: = StaticallyKnown( + std::intrinsics::is_val_statically_known(i) +}