Skip to content

Commit e40fdc7

Browse files
committed
Handle NullOp::DebugAssertions and remove the MIR pass
1 parent 7b3b87e commit e40fdc7

10 files changed

+433
-403
lines changed

compiler/rustc_middle/src/mir/mod.rs

+77-20
Original file line numberDiff line numberDiff line change
@@ -654,22 +654,13 @@ impl<'tcx> Body<'tcx> {
654654
/// exactly match the number of blocks in the body so that `contains`
655655
/// checks can be done without worrying about panicking.
656656
///
657-
/// The main case this supports is filtering out `if <T as Trait>::CONST`
658-
/// bodies that can't be removed in generic MIR, but *can* be removed once
659-
/// the specific `T` is known.
660-
///
661-
/// This is used in the monomorphization collector as well as in codegen.
657+
/// This is mostly useful because it lets us skip lowering the `false` side
658+
/// of `if <T as Trait>::CONST`, as well as [`std::intrinsics::debug_assertions`].
662659
pub fn reachable_blocks_in_mono(
663660
&self,
664661
tcx: TyCtxt<'tcx>,
665662
instance: Instance<'tcx>,
666663
) -> BitSet<BasicBlock> {
667-
if instance.args.non_erasable_generics(tcx, instance.def_id()).next().is_none() {
668-
// If it's non-generic, then mir-opt const prop has already run, meaning it's
669-
// probably not worth doing any further filtering. So call everything reachable.
670-
return BitSet::new_filled(self.basic_blocks.len());
671-
}
672-
673664
let mut set = BitSet::new_empty(self.basic_blocks.len());
674665
self.reachable_blocks_in_mono_from(tcx, instance, &mut set, START_BLOCK);
675666
set
@@ -688,25 +679,91 @@ impl<'tcx> Body<'tcx> {
688679

689680
let data = &self.basic_blocks[bb];
690681

691-
if let TerminatorKind::SwitchInt { discr: Operand::Constant(constant), targets } =
692-
&data.terminator().kind
693-
{
682+
if let Some((bits, targets)) = Self::try_const_mono_switchint(tcx, instance, data) {
683+
let target = targets.target_for_value(bits);
684+
return self.reachable_blocks_in_mono_from(tcx, instance, set, target);
685+
}
686+
687+
for target in data.terminator().successors() {
688+
self.reachable_blocks_in_mono_from(tcx, instance, set, target);
689+
}
690+
}
691+
692+
/// If this basic block ends with a [`TerminatorKind::SwitchInt`] for which we can evaluate the
693+
/// dimscriminant in monomorphization, we return the discriminant bits and the
694+
/// [`SwitchTargets`], just so the caller doesn't also have to match on the terminator.
695+
fn try_const_mono_switchint<'a>(
696+
tcx: TyCtxt<'tcx>,
697+
instance: Instance<'tcx>,
698+
block: &'a BasicBlockData<'tcx>,
699+
) -> Option<(u128, &'a SwitchTargets)> {
700+
// There are two places here we need to evaluate a constant.
701+
let eval_mono_const = |constant: &ConstOperand<'tcx>| {
694702
let env = ty::ParamEnv::reveal_all();
695703
let mono_literal = instance.instantiate_mir_and_normalize_erasing_regions(
696704
tcx,
697705
env,
698706
crate::ty::EarlyBinder::bind(constant.const_),
699707
);
700-
if let Some(bits) = mono_literal.try_eval_bits(tcx, env) {
701-
let target = targets.target_for_value(bits);
702-
return self.reachable_blocks_in_mono_from(tcx, instance, set, target);
703-
} else {
708+
let Some(bits) = mono_literal.try_eval_bits(tcx, env) else {
704709
bug!("Couldn't evaluate constant {:?} in mono {:?}", constant, instance);
710+
};
711+
bits
712+
};
713+
714+
let TerminatorKind::SwitchInt { discr, targets } = &block.terminator().kind else {
715+
return None;
716+
};
717+
718+
// If this is a SwitchInt(const _), then we can just evaluate the constant and return.
719+
let discr = match discr {
720+
Operand::Constant(constant) => {
721+
let bits = eval_mono_const(constant);
722+
return Some((bits, targets));
705723
}
724+
Operand::Move(place) | Operand::Copy(place) => place,
725+
};
726+
727+
// MIR for `if false` actually looks like this:
728+
// _1 = const _
729+
// SwitchInt(_1)
730+
//
731+
// And MIR for if intrinsics::debug_assertions() looks like this:
732+
// _1 = cfg!(debug_assertions)
733+
// SwitchInt(_1)
734+
//
735+
// So we're going to try to recognize this pattern.
736+
//
737+
// If we have a SwitchInt on a non-const place, we find the most recent statement that
738+
// isn't a storage marker. If that statement is an assignment of a const to our
739+
// discriminant place, we evaluate and return the const, as if we've const-propagated it
740+
// into the SwitchInt.
741+
742+
let last_stmt = block
743+
.statements
744+
.iter()
745+
.rev()
746+
.skip_while(|stmt| {
747+
matches!(stmt.kind, StatementKind::StorageDead(_) | StatementKind::StorageLive(_))
748+
})
749+
.next()?;
750+
let StatementKind::Assign(box (place, rvalue)) = &last_stmt.kind else {
751+
return None;
752+
};
753+
754+
if discr != place {
755+
return None;
706756
}
707757

708-
for target in data.terminator().successors() {
709-
self.reachable_blocks_in_mono_from(tcx, instance, set, target);
758+
match rvalue {
759+
Rvalue::NullaryOp(NullOp::DebugAssertions, _) => {
760+
Some((tcx.sess.opts.debug_assertions as u128, targets))
761+
}
762+
Rvalue::Use(Operand::Constant(constant)) => {
763+
let bits = eval_mono_const(constant);
764+
Some((bits, targets))
765+
}
766+
_ => None,
710767
}
711768
}
712769

compiler/rustc_mir_transform/src/lib.rs

-2
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ mod check_alignment;
108108
pub mod simplify;
109109
mod simplify_branches;
110110
mod simplify_comparison_integral;
111-
mod simplify_if_const;
112111
mod sroa;
113112
mod uninhabited_enum_branching;
114113
mod unreachable_prop;
@@ -617,7 +616,6 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
617616
&large_enums::EnumSizeOpt { discrepancy: 128 },
618617
// Some cleanup necessary at least for LLVM and potentially other codegen backends.
619618
&add_call_guards::CriticalCallEdges,
620-
&simplify_if_const::SimplifyIfConst,
621619
// Cleanup for human readability, off by default.
622620
&prettify::ReorderBasicBlocks,
623621
&prettify::ReorderLocals,

compiler/rustc_mir_transform/src/simplify_if_const.rs

-75
This file was deleted.

tests/codegen/precondition-checks.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// compile-flags: -Copt-level=0 -Cdebug-assertions=no
2+
3+
// This test ensures that in a debug build which turns off debug assertions, we do not monomorphize
4+
// any of the standard library's unsafe precondition checks.
5+
// The naive codegen of those checks contains the actual check underneath an `if false`, which
6+
// could be optimized out if optimizations are enabled. But if we rely on optimizations to remove
7+
// panic branches, then we can't link compiler_builtins without optimizing it, which means that
8+
// -Zbuild-std doesn't work with -Copt-level=0.
9+
//
10+
// In other words, this tests for a mandatory optimization.
11+
12+
#![crate_type = "lib"]
13+
14+
use std::ptr::NonNull;
15+
16+
// CHECK-LABEL: ; core::ptr::non_null::NonNull<T>::new_unchecked
17+
// CHECK-NOT: call
18+
19+
// CHECK-LABEL: @nonnull_new
20+
#[no_mangle]
21+
pub unsafe fn nonnull_new(ptr: *mut u8) -> NonNull<u8> {
22+
// CHECK: ; call core::ptr::non_null::NonNull<T>::new_unchecked
23+
unsafe {
24+
NonNull::new_unchecked(ptr)
25+
}
26+
}

0 commit comments

Comments
 (0)