Skip to content

Commit

Permalink
InstCombine away intrinsic validity assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
saethlin committed Jan 15, 2023
1 parent b8f9cb3 commit 662199f
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 2 deletions.
79 changes: 77 additions & 2 deletions compiler/rustc_mir_transform/src/instcombine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use rustc_middle::mir::{
BinOp, Body, Constant, ConstantKind, LocalDecls, Operand, Place, ProjectionElem, Rvalue,
SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UnOp,
};
use rustc_middle::ty::{self, TyCtxt};
use rustc_middle::ty::{self, layout::TyAndLayout, ParamEnv, SubstsRef, Ty, TyCtxt};
use rustc_span::symbol::{sym, Symbol};

pub struct InstCombine;

Expand All @@ -16,7 +17,11 @@ impl<'tcx> MirPass<'tcx> for InstCombine {
}

fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let ctx = InstCombineContext { tcx, local_decls: &body.local_decls };
let ctx = InstCombineContext {
tcx,
local_decls: &body.local_decls,
param_env: tcx.param_env_reveal_all_normalized(body.source.def_id()),
};
for block in body.basic_blocks.as_mut() {
for statement in block.statements.iter_mut() {
match statement.kind {
Expand All @@ -33,13 +38,18 @@ impl<'tcx> MirPass<'tcx> for InstCombine {
&mut block.terminator.as_mut().unwrap(),
&mut block.statements,
);
ctx.combine_intrinsic_assert(
&mut block.terminator.as_mut().unwrap(),
&mut block.statements,
);
}
}
}

struct InstCombineContext<'tcx, 'a> {
tcx: TyCtxt<'tcx>,
local_decls: &'a LocalDecls<'tcx>,
param_env: ParamEnv<'tcx>,
}

impl<'tcx> InstCombineContext<'tcx, '_> {
Expand Down Expand Up @@ -200,4 +210,69 @@ impl<'tcx> InstCombineContext<'tcx, '_> {
});
terminator.kind = TerminatorKind::Goto { target: destination_block };
}

fn combine_intrinsic_assert(
&self,
terminator: &mut Terminator<'tcx>,
_statements: &mut Vec<Statement<'tcx>>,
) {
let TerminatorKind::Call { func, target, .. } = &mut terminator.kind else { return; };
let Some(target_block) = target else { return; };
let func_ty = func.ty(self.local_decls, self.tcx);
let Some((intrinsic_name, substs)) = resolve_rust_intrinsic(self.tcx, func_ty) else {
return;
};
// The intrinsics we are interested in have one generic parameter
if substs.is_empty() {
return;
}
let ty = substs.type_at(0);

// Check this is a foldable intrinsic before we query the layout of our generic parameter
let Some(assert_panics) = intrinsic_assert_panics(intrinsic_name) else { return; };
let Ok(layout) = self.tcx.layout_of(self.param_env.and(ty)) else { return; };
if assert_panics(self.tcx, layout) {
// If we know the assert panics, indicate to later opts that the call diverges
*target = None;
} else {
// If we know the assert does not panic, turn the call into a Goto
terminator.kind = TerminatorKind::Goto { target: *target_block };
}
}
}

fn intrinsic_assert_panics<'tcx>(
intrinsic_name: Symbol,
) -> Option<fn(TyCtxt<'tcx>, TyAndLayout<'tcx>) -> bool> {
fn inhabited_predicate<'tcx>(_tcx: TyCtxt<'tcx>, layout: TyAndLayout<'tcx>) -> bool {
layout.abi.is_uninhabited()
}
fn zero_valid_predicate<'tcx>(tcx: TyCtxt<'tcx>, layout: TyAndLayout<'tcx>) -> bool {
!tcx.permits_zero_init(layout)
}
fn mem_uninitialized_valid_predicate<'tcx>(
tcx: TyCtxt<'tcx>,
layout: TyAndLayout<'tcx>,
) -> bool {
!tcx.permits_uninit_init(layout)
}

match intrinsic_name {
sym::assert_inhabited => Some(inhabited_predicate),
sym::assert_zero_valid => Some(zero_valid_predicate),
sym::assert_mem_uninitialized_valid => Some(mem_uninitialized_valid_predicate),
_ => None,
}
}

fn resolve_rust_intrinsic<'tcx>(
tcx: TyCtxt<'tcx>,
func_ty: Ty<'tcx>,
) -> Option<(Symbol, SubstsRef<'tcx>)> {
if let ty::FnDef(def_id, substs) = *func_ty.kind() {
if tcx.is_intrinsic(def_id) {
return Some((tcx.item_name(def_id), substs));
}
}
None
}
42 changes: 42 additions & 0 deletions tests/mir-opt/intrinsic_asserts.generic.InstCombine.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
- // MIR for `generic` before InstCombine
+ // MIR for `generic` after InstCombine

fn generic() -> () {
let mut _0: (); // return place in scope 0 at $DIR/intrinsic_asserts.rs:+0:21: +0:21
let _1: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:46
let _2: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:47
let _3: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:60

bb0: {
StorageLive(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:46
_1 = assert_inhabited::<T>() -> bb1; // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:46
// mir::Constant
// + span: $DIR/intrinsic_asserts.rs:25:5: 25:44
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_inhabited::<T>}, val: Value(<ZST>) }
}

bb1: {
StorageDead(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:46: +1:47
StorageLive(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:47
_2 = assert_zero_valid::<T>() -> bb2; // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:47
// mir::Constant
// + span: $DIR/intrinsic_asserts.rs:26:5: 26:45
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_zero_valid::<T>}, val: Value(<ZST>) }
}

bb2: {
StorageDead(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:47: +2:48
StorageLive(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:60
_3 = assert_mem_uninitialized_valid::<T>() -> bb3; // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:60
// mir::Constant
// + span: $DIR/intrinsic_asserts.rs:27:5: 27:58
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_mem_uninitialized_valid::<T>}, val: Value(<ZST>) }
}

bb3: {
StorageDead(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:60: +3:61
nop; // scope 0 at $DIR/intrinsic_asserts.rs:+0:21: +4:2
return; // scope 0 at $DIR/intrinsic_asserts.rs:+4:2: +4:2
}
}

47 changes: 47 additions & 0 deletions tests/mir-opt/intrinsic_asserts.panics.InstCombine.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
- // MIR for `panics` before InstCombine
+ // MIR for `panics` after InstCombine

fn panics() -> () {
let mut _0: (); // return place in scope 0 at $DIR/intrinsic_asserts.rs:+0:17: +0:17
let _1: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:50
let _2: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:49
let _3: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:62

bb0: {
StorageLive(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:50
- _1 = assert_inhabited::<Never>() -> bb1; // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:50
+ _1 = assert_inhabited::<Never>(); // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:50
// mir::Constant
// + span: $DIR/intrinsic_asserts.rs:17:5: 17:48
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_inhabited::<Never>}, val: Value(<ZST>) }
}

bb1: {
StorageDead(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:50: +1:51
StorageLive(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:49
- _2 = assert_zero_valid::<&u8>() -> bb2; // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:49
+ _2 = assert_zero_valid::<&u8>(); // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:49
// mir::Constant
// + span: $DIR/intrinsic_asserts.rs:18:5: 18:47
// + user_ty: UserType(0)
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_zero_valid::<&u8>}, val: Value(<ZST>) }
}

bb2: {
StorageDead(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:49: +2:50
StorageLive(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:62
- _3 = assert_mem_uninitialized_valid::<&u8>() -> bb3; // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:62
+ _3 = assert_mem_uninitialized_valid::<&u8>(); // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:62
// mir::Constant
// + span: $DIR/intrinsic_asserts.rs:19:5: 19:60
// + user_ty: UserType(1)
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_mem_uninitialized_valid::<&u8>}, val: Value(<ZST>) }
}

bb3: {
StorageDead(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:62: +3:63
nop; // scope 0 at $DIR/intrinsic_asserts.rs:+0:17: +4:2
return; // scope 0 at $DIR/intrinsic_asserts.rs:+4:2: +4:2
}
}

45 changes: 45 additions & 0 deletions tests/mir-opt/intrinsic_asserts.removable.InstCombine.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
- // MIR for `removable` before InstCombine
+ // MIR for `removable` after InstCombine

fn removable() -> () {
let mut _0: (); // return place in scope 0 at $DIR/intrinsic_asserts.rs:+0:20: +0:20
let _1: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:47
let _2: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:48
let _3: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:61

bb0: {
StorageLive(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:47
- _1 = assert_inhabited::<()>() -> bb1; // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:47
- // mir::Constant
- // + span: $DIR/intrinsic_asserts.rs:7:5: 7:45
- // + literal: Const { ty: extern "rust-intrinsic" fn() {assert_inhabited::<()>}, val: Value(<ZST>) }
+ goto -> bb1; // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:47
}

bb1: {
StorageDead(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:47: +1:48
StorageLive(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:48
- _2 = assert_zero_valid::<u8>() -> bb2; // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:48
- // mir::Constant
- // + span: $DIR/intrinsic_asserts.rs:8:5: 8:46
- // + literal: Const { ty: extern "rust-intrinsic" fn() {assert_zero_valid::<u8>}, val: Value(<ZST>) }
+ goto -> bb2; // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:48
}

bb2: {
StorageDead(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:48: +2:49
StorageLive(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:61
- _3 = assert_mem_uninitialized_valid::<u8>() -> bb3; // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:61
- // mir::Constant
- // + span: $DIR/intrinsic_asserts.rs:9:5: 9:59
- // + literal: Const { ty: extern "rust-intrinsic" fn() {assert_mem_uninitialized_valid::<u8>}, val: Value(<ZST>) }
+ goto -> bb3; // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:61
}

bb3: {
StorageDead(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:61: +3:62
nop; // scope 0 at $DIR/intrinsic_asserts.rs:+0:20: +4:2
return; // scope 0 at $DIR/intrinsic_asserts.rs:+4:2: +4:2
}
}

28 changes: 28 additions & 0 deletions tests/mir-opt/intrinsic_asserts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![crate_type = "lib"]
#![feature(core_intrinsics)]

// All these assertions pass, so all the intrinsic calls should be deleted.
// EMIT_MIR intrinsic_asserts.removable.InstCombine.diff
pub fn removable() {
core::intrinsics::assert_inhabited::<()>();
core::intrinsics::assert_zero_valid::<u8>();
core::intrinsics::assert_mem_uninitialized_valid::<u8>();
}

enum Never {}

// These assertions all diverge, so their target blocks should become None.
// EMIT_MIR intrinsic_asserts.panics.InstCombine.diff
pub fn panics() {
core::intrinsics::assert_inhabited::<Never>();
core::intrinsics::assert_zero_valid::<&u8>();
core::intrinsics::assert_mem_uninitialized_valid::<&u8>();
}

// Whether or not these asserts pass isn't known, so they shouldn't be modified.
// EMIT_MIR intrinsic_asserts.generic.InstCombine.diff
pub fn generic<T>() {
core::intrinsics::assert_inhabited::<T>();
core::intrinsics::assert_zero_valid::<T>();
core::intrinsics::assert_mem_uninitialized_valid::<T>();
}

0 comments on commit 662199f

Please sign in to comment.