Skip to content

Commit 662199f

Browse files
committed
InstCombine away intrinsic validity assertions
1 parent b8f9cb3 commit 662199f

5 files changed

+239
-2
lines changed

compiler/rustc_mir_transform/src/instcombine.rs

+77-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use rustc_middle::mir::{
66
BinOp, Body, Constant, ConstantKind, LocalDecls, Operand, Place, ProjectionElem, Rvalue,
77
SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UnOp,
88
};
9-
use rustc_middle::ty::{self, TyCtxt};
9+
use rustc_middle::ty::{self, layout::TyAndLayout, ParamEnv, SubstsRef, Ty, TyCtxt};
10+
use rustc_span::symbol::{sym, Symbol};
1011

1112
pub struct InstCombine;
1213

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

1819
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
19-
let ctx = InstCombineContext { tcx, local_decls: &body.local_decls };
20+
let ctx = InstCombineContext {
21+
tcx,
22+
local_decls: &body.local_decls,
23+
param_env: tcx.param_env_reveal_all_normalized(body.source.def_id()),
24+
};
2025
for block in body.basic_blocks.as_mut() {
2126
for statement in block.statements.iter_mut() {
2227
match statement.kind {
@@ -33,13 +38,18 @@ impl<'tcx> MirPass<'tcx> for InstCombine {
3338
&mut block.terminator.as_mut().unwrap(),
3439
&mut block.statements,
3540
);
41+
ctx.combine_intrinsic_assert(
42+
&mut block.terminator.as_mut().unwrap(),
43+
&mut block.statements,
44+
);
3645
}
3746
}
3847
}
3948

4049
struct InstCombineContext<'tcx, 'a> {
4150
tcx: TyCtxt<'tcx>,
4251
local_decls: &'a LocalDecls<'tcx>,
52+
param_env: ParamEnv<'tcx>,
4353
}
4454

4555
impl<'tcx> InstCombineContext<'tcx, '_> {
@@ -200,4 +210,69 @@ impl<'tcx> InstCombineContext<'tcx, '_> {
200210
});
201211
terminator.kind = TerminatorKind::Goto { target: destination_block };
202212
}
213+
214+
fn combine_intrinsic_assert(
215+
&self,
216+
terminator: &mut Terminator<'tcx>,
217+
_statements: &mut Vec<Statement<'tcx>>,
218+
) {
219+
let TerminatorKind::Call { func, target, .. } = &mut terminator.kind else { return; };
220+
let Some(target_block) = target else { return; };
221+
let func_ty = func.ty(self.local_decls, self.tcx);
222+
let Some((intrinsic_name, substs)) = resolve_rust_intrinsic(self.tcx, func_ty) else {
223+
return;
224+
};
225+
// The intrinsics we are interested in have one generic parameter
226+
if substs.is_empty() {
227+
return;
228+
}
229+
let ty = substs.type_at(0);
230+
231+
// Check this is a foldable intrinsic before we query the layout of our generic parameter
232+
let Some(assert_panics) = intrinsic_assert_panics(intrinsic_name) else { return; };
233+
let Ok(layout) = self.tcx.layout_of(self.param_env.and(ty)) else { return; };
234+
if assert_panics(self.tcx, layout) {
235+
// If we know the assert panics, indicate to later opts that the call diverges
236+
*target = None;
237+
} else {
238+
// If we know the assert does not panic, turn the call into a Goto
239+
terminator.kind = TerminatorKind::Goto { target: *target_block };
240+
}
241+
}
242+
}
243+
244+
fn intrinsic_assert_panics<'tcx>(
245+
intrinsic_name: Symbol,
246+
) -> Option<fn(TyCtxt<'tcx>, TyAndLayout<'tcx>) -> bool> {
247+
fn inhabited_predicate<'tcx>(_tcx: TyCtxt<'tcx>, layout: TyAndLayout<'tcx>) -> bool {
248+
layout.abi.is_uninhabited()
249+
}
250+
fn zero_valid_predicate<'tcx>(tcx: TyCtxt<'tcx>, layout: TyAndLayout<'tcx>) -> bool {
251+
!tcx.permits_zero_init(layout)
252+
}
253+
fn mem_uninitialized_valid_predicate<'tcx>(
254+
tcx: TyCtxt<'tcx>,
255+
layout: TyAndLayout<'tcx>,
256+
) -> bool {
257+
!tcx.permits_uninit_init(layout)
258+
}
259+
260+
match intrinsic_name {
261+
sym::assert_inhabited => Some(inhabited_predicate),
262+
sym::assert_zero_valid => Some(zero_valid_predicate),
263+
sym::assert_mem_uninitialized_valid => Some(mem_uninitialized_valid_predicate),
264+
_ => None,
265+
}
266+
}
267+
268+
fn resolve_rust_intrinsic<'tcx>(
269+
tcx: TyCtxt<'tcx>,
270+
func_ty: Ty<'tcx>,
271+
) -> Option<(Symbol, SubstsRef<'tcx>)> {
272+
if let ty::FnDef(def_id, substs) = *func_ty.kind() {
273+
if tcx.is_intrinsic(def_id) {
274+
return Some((tcx.item_name(def_id), substs));
275+
}
276+
}
277+
None
203278
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
- // MIR for `generic` before InstCombine
2+
+ // MIR for `generic` after InstCombine
3+
4+
fn generic() -> () {
5+
let mut _0: (); // return place in scope 0 at $DIR/intrinsic_asserts.rs:+0:21: +0:21
6+
let _1: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:46
7+
let _2: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:47
8+
let _3: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:60
9+
10+
bb0: {
11+
StorageLive(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:46
12+
_1 = assert_inhabited::<T>() -> bb1; // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:46
13+
// mir::Constant
14+
// + span: $DIR/intrinsic_asserts.rs:25:5: 25:44
15+
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_inhabited::<T>}, val: Value(<ZST>) }
16+
}
17+
18+
bb1: {
19+
StorageDead(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:46: +1:47
20+
StorageLive(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:47
21+
_2 = assert_zero_valid::<T>() -> bb2; // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:47
22+
// mir::Constant
23+
// + span: $DIR/intrinsic_asserts.rs:26:5: 26:45
24+
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_zero_valid::<T>}, val: Value(<ZST>) }
25+
}
26+
27+
bb2: {
28+
StorageDead(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:47: +2:48
29+
StorageLive(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:60
30+
_3 = assert_mem_uninitialized_valid::<T>() -> bb3; // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:60
31+
// mir::Constant
32+
// + span: $DIR/intrinsic_asserts.rs:27:5: 27:58
33+
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_mem_uninitialized_valid::<T>}, val: Value(<ZST>) }
34+
}
35+
36+
bb3: {
37+
StorageDead(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:60: +3:61
38+
nop; // scope 0 at $DIR/intrinsic_asserts.rs:+0:21: +4:2
39+
return; // scope 0 at $DIR/intrinsic_asserts.rs:+4:2: +4:2
40+
}
41+
}
42+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
- // MIR for `panics` before InstCombine
2+
+ // MIR for `panics` after InstCombine
3+
4+
fn panics() -> () {
5+
let mut _0: (); // return place in scope 0 at $DIR/intrinsic_asserts.rs:+0:17: +0:17
6+
let _1: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:50
7+
let _2: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:49
8+
let _3: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:62
9+
10+
bb0: {
11+
StorageLive(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:50
12+
- _1 = assert_inhabited::<Never>() -> bb1; // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:50
13+
+ _1 = assert_inhabited::<Never>(); // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:50
14+
// mir::Constant
15+
// + span: $DIR/intrinsic_asserts.rs:17:5: 17:48
16+
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_inhabited::<Never>}, val: Value(<ZST>) }
17+
}
18+
19+
bb1: {
20+
StorageDead(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:50: +1:51
21+
StorageLive(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:49
22+
- _2 = assert_zero_valid::<&u8>() -> bb2; // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:49
23+
+ _2 = assert_zero_valid::<&u8>(); // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:49
24+
// mir::Constant
25+
// + span: $DIR/intrinsic_asserts.rs:18:5: 18:47
26+
// + user_ty: UserType(0)
27+
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_zero_valid::<&u8>}, val: Value(<ZST>) }
28+
}
29+
30+
bb2: {
31+
StorageDead(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:49: +2:50
32+
StorageLive(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:62
33+
- _3 = assert_mem_uninitialized_valid::<&u8>() -> bb3; // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:62
34+
+ _3 = assert_mem_uninitialized_valid::<&u8>(); // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:62
35+
// mir::Constant
36+
// + span: $DIR/intrinsic_asserts.rs:19:5: 19:60
37+
// + user_ty: UserType(1)
38+
// + literal: Const { ty: extern "rust-intrinsic" fn() {assert_mem_uninitialized_valid::<&u8>}, val: Value(<ZST>) }
39+
}
40+
41+
bb3: {
42+
StorageDead(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:62: +3:63
43+
nop; // scope 0 at $DIR/intrinsic_asserts.rs:+0:17: +4:2
44+
return; // scope 0 at $DIR/intrinsic_asserts.rs:+4:2: +4:2
45+
}
46+
}
47+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
- // MIR for `removable` before InstCombine
2+
+ // MIR for `removable` after InstCombine
3+
4+
fn removable() -> () {
5+
let mut _0: (); // return place in scope 0 at $DIR/intrinsic_asserts.rs:+0:20: +0:20
6+
let _1: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:47
7+
let _2: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:48
8+
let _3: (); // in scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:61
9+
10+
bb0: {
11+
StorageLive(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:47
12+
- _1 = assert_inhabited::<()>() -> bb1; // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:47
13+
- // mir::Constant
14+
- // + span: $DIR/intrinsic_asserts.rs:7:5: 7:45
15+
- // + literal: Const { ty: extern "rust-intrinsic" fn() {assert_inhabited::<()>}, val: Value(<ZST>) }
16+
+ goto -> bb1; // scope 0 at $DIR/intrinsic_asserts.rs:+1:5: +1:47
17+
}
18+
19+
bb1: {
20+
StorageDead(_1); // scope 0 at $DIR/intrinsic_asserts.rs:+1:47: +1:48
21+
StorageLive(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:48
22+
- _2 = assert_zero_valid::<u8>() -> bb2; // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:48
23+
- // mir::Constant
24+
- // + span: $DIR/intrinsic_asserts.rs:8:5: 8:46
25+
- // + literal: Const { ty: extern "rust-intrinsic" fn() {assert_zero_valid::<u8>}, val: Value(<ZST>) }
26+
+ goto -> bb2; // scope 0 at $DIR/intrinsic_asserts.rs:+2:5: +2:48
27+
}
28+
29+
bb2: {
30+
StorageDead(_2); // scope 0 at $DIR/intrinsic_asserts.rs:+2:48: +2:49
31+
StorageLive(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:61
32+
- _3 = assert_mem_uninitialized_valid::<u8>() -> bb3; // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:61
33+
- // mir::Constant
34+
- // + span: $DIR/intrinsic_asserts.rs:9:5: 9:59
35+
- // + literal: Const { ty: extern "rust-intrinsic" fn() {assert_mem_uninitialized_valid::<u8>}, val: Value(<ZST>) }
36+
+ goto -> bb3; // scope 0 at $DIR/intrinsic_asserts.rs:+3:5: +3:61
37+
}
38+
39+
bb3: {
40+
StorageDead(_3); // scope 0 at $DIR/intrinsic_asserts.rs:+3:61: +3:62
41+
nop; // scope 0 at $DIR/intrinsic_asserts.rs:+0:20: +4:2
42+
return; // scope 0 at $DIR/intrinsic_asserts.rs:+4:2: +4:2
43+
}
44+
}
45+

tests/mir-opt/intrinsic_asserts.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![crate_type = "lib"]
2+
#![feature(core_intrinsics)]
3+
4+
// All these assertions pass, so all the intrinsic calls should be deleted.
5+
// EMIT_MIR intrinsic_asserts.removable.InstCombine.diff
6+
pub fn removable() {
7+
core::intrinsics::assert_inhabited::<()>();
8+
core::intrinsics::assert_zero_valid::<u8>();
9+
core::intrinsics::assert_mem_uninitialized_valid::<u8>();
10+
}
11+
12+
enum Never {}
13+
14+
// These assertions all diverge, so their target blocks should become None.
15+
// EMIT_MIR intrinsic_asserts.panics.InstCombine.diff
16+
pub fn panics() {
17+
core::intrinsics::assert_inhabited::<Never>();
18+
core::intrinsics::assert_zero_valid::<&u8>();
19+
core::intrinsics::assert_mem_uninitialized_valid::<&u8>();
20+
}
21+
22+
// Whether or not these asserts pass isn't known, so they shouldn't be modified.
23+
// EMIT_MIR intrinsic_asserts.generic.InstCombine.diff
24+
pub fn generic<T>() {
25+
core::intrinsics::assert_inhabited::<T>();
26+
core::intrinsics::assert_zero_valid::<T>();
27+
core::intrinsics::assert_mem_uninitialized_valid::<T>();
28+
}

0 commit comments

Comments
 (0)