Skip to content

Commit

Permalink
Handle NullOp::DebugAssertions and remove the MIR pass
Browse files Browse the repository at this point in the history
  • Loading branch information
saethlin committed Feb 24, 2024
1 parent 7b3b87e commit e40fdc7
Show file tree
Hide file tree
Showing 10 changed files with 433 additions and 403 deletions.
97 changes: 77 additions & 20 deletions compiler/rustc_middle/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,22 +654,13 @@ impl<'tcx> Body<'tcx> {
/// exactly match the number of blocks in the body so that `contains`
/// checks can be done without worrying about panicking.
///
/// The main case this supports is filtering out `if <T as Trait>::CONST`
/// bodies that can't be removed in generic MIR, but *can* be removed once
/// the specific `T` is known.
///
/// This is used in the monomorphization collector as well as in codegen.
/// This is mostly useful because it lets us skip lowering the `false` side
/// of `if <T as Trait>::CONST`, as well as [`std::intrinsics::debug_assertions`].
pub fn reachable_blocks_in_mono(
&self,
tcx: TyCtxt<'tcx>,
instance: Instance<'tcx>,
) -> BitSet<BasicBlock> {
if instance.args.non_erasable_generics(tcx, instance.def_id()).next().is_none() {
// If it's non-generic, then mir-opt const prop has already run, meaning it's
// probably not worth doing any further filtering. So call everything reachable.
return BitSet::new_filled(self.basic_blocks.len());
}

let mut set = BitSet::new_empty(self.basic_blocks.len());
self.reachable_blocks_in_mono_from(tcx, instance, &mut set, START_BLOCK);
set
Expand All @@ -688,25 +679,91 @@ impl<'tcx> Body<'tcx> {

let data = &self.basic_blocks[bb];

if let TerminatorKind::SwitchInt { discr: Operand::Constant(constant), targets } =
&data.terminator().kind
{
if let Some((bits, targets)) = Self::try_const_mono_switchint(tcx, instance, data) {
let target = targets.target_for_value(bits);
return self.reachable_blocks_in_mono_from(tcx, instance, set, target);
}

for target in data.terminator().successors() {
self.reachable_blocks_in_mono_from(tcx, instance, set, target);
}
}

/// If this basic block ends with a [`TerminatorKind::SwitchInt`] for which we can evaluate the
/// dimscriminant in monomorphization, we return the discriminant bits and the
/// [`SwitchTargets`], just so the caller doesn't also have to match on the terminator.
fn try_const_mono_switchint<'a>(
tcx: TyCtxt<'tcx>,
instance: Instance<'tcx>,
block: &'a BasicBlockData<'tcx>,
) -> Option<(u128, &'a SwitchTargets)> {
// There are two places here we need to evaluate a constant.
let eval_mono_const = |constant: &ConstOperand<'tcx>| {
let env = ty::ParamEnv::reveal_all();
let mono_literal = instance.instantiate_mir_and_normalize_erasing_regions(
tcx,
env,
crate::ty::EarlyBinder::bind(constant.const_),
);
if let Some(bits) = mono_literal.try_eval_bits(tcx, env) {
let target = targets.target_for_value(bits);
return self.reachable_blocks_in_mono_from(tcx, instance, set, target);
} else {
let Some(bits) = mono_literal.try_eval_bits(tcx, env) else {
bug!("Couldn't evaluate constant {:?} in mono {:?}", constant, instance);
};
bits
};

let TerminatorKind::SwitchInt { discr, targets } = &block.terminator().kind else {
return None;
};

// If this is a SwitchInt(const _), then we can just evaluate the constant and return.
let discr = match discr {
Operand::Constant(constant) => {
let bits = eval_mono_const(constant);
return Some((bits, targets));
}
Operand::Move(place) | Operand::Copy(place) => place,
};

// MIR for `if false` actually looks like this:
// _1 = const _
// SwitchInt(_1)
//
// And MIR for if intrinsics::debug_assertions() looks like this:
// _1 = cfg!(debug_assertions)
// SwitchInt(_1)
//
// So we're going to try to recognize this pattern.
//
// If we have a SwitchInt on a non-const place, we find the most recent statement that
// isn't a storage marker. If that statement is an assignment of a const to our
// discriminant place, we evaluate and return the const, as if we've const-propagated it
// into the SwitchInt.

let last_stmt = block
.statements
.iter()
.rev()
.skip_while(|stmt| {
matches!(stmt.kind, StatementKind::StorageDead(_) | StatementKind::StorageLive(_))
})
.next()?;
let StatementKind::Assign(box (place, rvalue)) = &last_stmt.kind else {
return None;
};

if discr != place {
return None;
}

for target in data.terminator().successors() {
self.reachable_blocks_in_mono_from(tcx, instance, set, target);
match rvalue {
Rvalue::NullaryOp(NullOp::DebugAssertions, _) => {
Some((tcx.sess.opts.debug_assertions as u128, targets))
}
Rvalue::Use(Operand::Constant(constant)) => {
let bits = eval_mono_const(constant);
Some((bits, targets))
}
_ => None,
}
}

Expand Down
2 changes: 0 additions & 2 deletions compiler/rustc_mir_transform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ mod check_alignment;
pub mod simplify;
mod simplify_branches;
mod simplify_comparison_integral;
mod simplify_if_const;
mod sroa;
mod uninhabited_enum_branching;
mod unreachable_prop;
Expand Down Expand Up @@ -617,7 +616,6 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
&large_enums::EnumSizeOpt { discrepancy: 128 },
// Some cleanup necessary at least for LLVM and potentially other codegen backends.
&add_call_guards::CriticalCallEdges,
&simplify_if_const::SimplifyIfConst,
// Cleanup for human readability, off by default.
&prettify::ReorderBasicBlocks,
&prettify::ReorderLocals,
Expand Down
75 changes: 0 additions & 75 deletions compiler/rustc_mir_transform/src/simplify_if_const.rs

This file was deleted.

26 changes: 26 additions & 0 deletions tests/codegen/precondition-checks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// compile-flags: -Copt-level=0 -Cdebug-assertions=no

// This test ensures that in a debug build which turns off debug assertions, we do not monomorphize
// any of the standard library's unsafe precondition checks.
// The naive codegen of those checks contains the actual check underneath an `if false`, which
// could be optimized out if optimizations are enabled. But if we rely on optimizations to remove
// panic branches, then we can't link compiler_builtins without optimizing it, which means that
// -Zbuild-std doesn't work with -Copt-level=0.
//
// In other words, this tests for a mandatory optimization.

#![crate_type = "lib"]

use std::ptr::NonNull;

// CHECK-LABEL: ; core::ptr::non_null::NonNull<T>::new_unchecked
// CHECK-NOT: call

// CHECK-LABEL: @nonnull_new
#[no_mangle]
pub unsafe fn nonnull_new(ptr: *mut u8) -> NonNull<u8> {
// CHECK: ; call core::ptr::non_null::NonNull<T>::new_unchecked
unsafe {
NonNull::new_unchecked(ptr)
}
}
Loading

0 comments on commit e40fdc7

Please sign in to comment.