Skip to content

Commit bd2d904

Browse files
Rollup merge of rust-lang#156429 - scottmcm:raw-eq-transmute, r=oli-obk
Simplify `intrinsic::raw_eq` in MIR when possible After rust-lang#150945 things can inline enough to have this end up specific enough that we can remove it, for example changing `raw_eq::<[u8; 4]>(a, b)` → `Transmute(a) == Transmute(b)`. The LLVM backend can also do this (and in more cases too) but we might as well do it in MIR instead when we can so it applies to all backends and other MIR optimizations can apply afterwards.
2 parents 275284b + c468ee3 commit bd2d904

10 files changed

Lines changed: 275 additions & 5 deletions

compiler/rustc_mir_transform/src/instsimplify.rs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
//! Performs various peephole optimizations.
22
3-
use rustc_abi::ExternAbi;
3+
use rustc_abi::{ExternAbi, Integer};
44
use rustc_hir::{LangItem, find_attr};
5+
use rustc_index::IndexVec;
56
use rustc_middle::bug;
67
use rustc_middle::mir::visit::MutVisitor;
78
use rustc_middle::mir::*;
8-
use rustc_middle::ty::layout::ValidityRequirement;
9+
use rustc_middle::ty::layout::{IntegerExt, ValidityRequirement};
910
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, layout};
1011
use rustc_span::{Symbol, sym};
1112

@@ -33,10 +34,10 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify {
3334
if !preserve_ub_checks {
3435
SimplifyUbCheck { tcx }.visit_body(body);
3536
}
36-
let ctx = InstSimplifyContext {
37+
let mut ctx = InstSimplifyContext {
3738
tcx,
38-
local_decls: &body.local_decls,
3939
typing_env: body.typing_env(tcx),
40+
local_decls: &mut body.local_decls,
4041
};
4142
for block in body.basic_blocks.as_mut() {
4243
for statement in block.statements.iter_mut() {
@@ -55,6 +56,7 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify {
5556
let terminator = block.terminator.as_mut().unwrap();
5657
ctx.simplify_primitive_clone(terminator, &mut block.statements);
5758
ctx.simplify_size_or_align_of_val(terminator, &mut block.statements);
59+
ctx.simplify_raw_eq(terminator, &mut block.statements);
5860
ctx.simplify_intrinsic_assert(terminator);
5961
ctx.simplify_nounwind_call(terminator);
6062
simplify_duplicate_switch_targets(terminator);
@@ -68,7 +70,7 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify {
6870

6971
struct InstSimplifyContext<'a, 'tcx> {
7072
tcx: TyCtxt<'tcx>,
71-
local_decls: &'a LocalDecls<'tcx>,
73+
local_decls: &'a mut IndexVec<Local, LocalDecl<'tcx>>,
7274
typing_env: ty::TypingEnv<'tcx>,
7375
}
7476

@@ -318,6 +320,63 @@ impl<'tcx> InstSimplifyContext<'_, 'tcx> {
318320
}
319321
}
320322

323+
/// Simplify `raw_eq` intrinsic calls to `Eq` when the type has the size of a primitive.
324+
///
325+
/// For example, replace `raw_eq::<[u8; 4]>(a, b)` with `Eq(Transmute(a), Transmute(b))`.
326+
fn simplify_raw_eq(
327+
&mut self,
328+
terminator: &mut Terminator<'tcx>,
329+
statements: &mut Vec<Statement<'tcx>>,
330+
) {
331+
let tcx = self.tcx;
332+
let source_info = terminator.source_info;
333+
let span = source_info.span;
334+
if let TerminatorKind::Call {
335+
func, args, destination, target: Some(destination_block), ..
336+
} = &terminator.kind
337+
&& args.len() == 2
338+
&& let Some((fn_def_id, generics)) = func.const_fn_def()
339+
&& tcx.is_intrinsic(fn_def_id, sym::raw_eq)
340+
&& let generic_ty = generics.type_at(0)
341+
&& let Ok(layout) = tcx.layout_of(self.typing_env.as_query_input(generic_ty))
342+
&& let Ok(integer) = Integer::from_size(layout.size)
343+
{
344+
let ref_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, generic_ty);
345+
let uint_ty = integer.to_ty(tcx, false);
346+
347+
let mut transmute_operand = |op: &Operand<'tcx>| -> Operand<'tcx> {
348+
let ref_local = self.local_decls.push(LocalDecl::new(ref_ty, span));
349+
statements.push(Statement::new(
350+
source_info,
351+
StatementKind::Assign(Box::new((
352+
Place::from(ref_local),
353+
Rvalue::Use(op.clone(), WithRetag::Yes),
354+
))),
355+
));
356+
let place = Place::from(ref_local).project_deeper(&[ProjectionElem::Deref], tcx);
357+
let int_local = self.local_decls.push(LocalDecl::new(uint_ty, span));
358+
statements.push(Statement::new(
359+
source_info,
360+
StatementKind::Assign(Box::new((
361+
Place::from(int_local),
362+
Rvalue::Cast(CastKind::Transmute, Operand::Copy(place), uint_ty),
363+
))),
364+
));
365+
Operand::Move(Place::from(int_local))
366+
};
367+
let lhs_op = transmute_operand(&args[0].node);
368+
let rhs_op = transmute_operand(&args[1].node);
369+
statements.push(Statement::new(
370+
source_info,
371+
StatementKind::Assign(Box::new((
372+
*destination,
373+
Rvalue::BinaryOp(BinOp::Eq, Box::new((lhs_op, rhs_op))),
374+
))),
375+
));
376+
terminator.kind = TerminatorKind::Goto { target: *destination_block };
377+
}
378+
}
379+
321380
fn simplify_nounwind_call(&self, terminator: &mut Terminator<'tcx>) {
322381
let TerminatorKind::Call { ref func, ref mut unwind, .. } = terminator.kind else {
323382
return;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
- // MIR for `inner_array` before InstSimplify-after-simplifycfg
2+
+ // MIR for `inner_array` after InstSimplify-after-simplifycfg
3+
4+
fn inner_array(_1: &&[i32; 2], _2: &&[i32; 2]) -> bool {
5+
let mut _0: bool;
6+
+ let mut _3: &[i32; 2];
7+
+ let mut _4: u64;
8+
+ let mut _5: &[i32; 2];
9+
+ let mut _6: u64;
10+
11+
bb0: {
12+
- _0 = raw_eq::<[i32; 2]>(copy (*_1), copy (*_2)) -> [return: bb1, unwind unreachable];
13+
+ _3 = copy (*_1);
14+
+ _4 = copy (*_3) as u64 (Transmute);
15+
+ _5 = copy (*_2);
16+
+ _6 = copy (*_5) as u64 (Transmute);
17+
+ _0 = Eq(move _4, move _6);
18+
+ goto -> bb1;
19+
}
20+
21+
bb1: {
22+
return;
23+
}
24+
}
25+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//@ test-mir-pass: InstSimplify-after-simplifycfg
2+
#![crate_type = "lib"]
3+
#![feature(core_intrinsics)]
4+
#![feature(custom_mir)]
5+
6+
// Custom MIR so we can get an argument that's not just a local directly
7+
use std::intrinsics::mir::*;
8+
use std::intrinsics::raw_eq;
9+
10+
// EMIT_MIR raw_eq.inner_array.InstSimplify-after-simplifycfg.diff
11+
#[custom_mir(dialect = "runtime")]
12+
pub fn inner_array(a: &&[i32; 2], b: &&[i32; 2]) -> bool {
13+
// CHECK-LABEL: fn inner_array(_1: &&[i32; 2], _2: &&[i32; 2]) -> bool
14+
// CHECK: [[AREF:_.+]] = copy (*_1);
15+
// CHECK: [[AINT:_.+]] = copy (*[[AREF]]) as u64 (Transmute);
16+
// CHECK: [[BREF:_.+]] = copy (*_2);
17+
// CHECK: [[BINT:_.+]] = copy (*[[BREF]]) as u64 (Transmute);
18+
// CHECK: _0 = Eq(move [[AINT]], move [[BINT]]);
19+
mir! {
20+
{
21+
Call(RET = raw_eq(*a, *b), ReturnTo(ret), UnwindUnreachable())
22+
}
23+
ret = {
24+
Return()
25+
}
26+
}
27+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// MIR for `eq_ipv4` after PreCodegen
2+
3+
fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool {
4+
debug a => _1;
5+
debug b => _2;
6+
let mut _0: bool;
7+
let mut _3: u32;
8+
let mut _4: u32;
9+
scope 1 (inlined std::cmp::impls::<impl PartialEq for &[u8; 4]>::eq) {
10+
scope 2 (inlined array::equality::<impl PartialEq for [u8; 4]>::eq) {
11+
scope 3 (inlined <u8 as array::equality::SpecArrayEq<u8, 4>>::spec_eq) {
12+
}
13+
}
14+
}
15+
16+
bb0: {
17+
_3 = copy (*_1) as u32 (Transmute);
18+
_4 = copy (*_2) as u32 (Transmute);
19+
_0 = Eq(move _3, move _4);
20+
return;
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// MIR for `eq_ipv4` after PreCodegen
2+
3+
fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool {
4+
debug a => _1;
5+
debug b => _2;
6+
let mut _0: bool;
7+
let mut _3: u32;
8+
let mut _4: u32;
9+
scope 1 (inlined std::cmp::impls::<impl PartialEq for &[u8; 4]>::eq) {
10+
scope 2 (inlined array::equality::<impl PartialEq for [u8; 4]>::eq) {
11+
scope 3 (inlined <u8 as array::equality::SpecArrayEq<u8, 4>>::spec_eq) {
12+
}
13+
}
14+
}
15+
16+
bb0: {
17+
_3 = copy (*_1) as u32 (Transmute);
18+
_4 = copy (*_2) as u32 (Transmute);
19+
_0 = Eq(move _3, move _4);
20+
return;
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// MIR for `eq_ipv6` after PreCodegen
2+
3+
fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool {
4+
debug a => _1;
5+
debug b => _2;
6+
let mut _0: bool;
7+
let mut _3: u128;
8+
let mut _4: u128;
9+
scope 1 (inlined std::cmp::impls::<impl PartialEq for &[u16; 8]>::eq) {
10+
scope 2 (inlined array::equality::<impl PartialEq for [u16; 8]>::eq) {
11+
scope 3 (inlined <u16 as array::equality::SpecArrayEq<u16, 8>>::spec_eq) {
12+
}
13+
}
14+
}
15+
16+
bb0: {
17+
_3 = copy (*_1) as u128 (Transmute);
18+
_4 = copy (*_2) as u128 (Transmute);
19+
_0 = Eq(move _3, move _4);
20+
return;
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// MIR for `eq_ipv6` after PreCodegen
2+
3+
fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool {
4+
debug a => _1;
5+
debug b => _2;
6+
let mut _0: bool;
7+
let mut _3: u128;
8+
let mut _4: u128;
9+
scope 1 (inlined std::cmp::impls::<impl PartialEq for &[u16; 8]>::eq) {
10+
scope 2 (inlined array::equality::<impl PartialEq for [u16; 8]>::eq) {
11+
scope 3 (inlined <u16 as array::equality::SpecArrayEq<u16, 8>>::spec_eq) {
12+
}
13+
}
14+
}
15+
16+
bb0: {
17+
_3 = copy (*_1) as u128 (Transmute);
18+
_4 = copy (*_2) as u128 (Transmute);
19+
_0 = Eq(move _3, move _4);
20+
return;
21+
}
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// MIR for `eq_odd_length` after PreCodegen
2+
3+
fn eq_odd_length(_1: &[u8; 3], _2: &[u8; 3]) -> bool {
4+
debug a => _1;
5+
debug b => _2;
6+
let mut _0: bool;
7+
scope 1 (inlined std::cmp::impls::<impl PartialEq for &[u8; 3]>::eq) {
8+
scope 2 (inlined array::equality::<impl PartialEq for [u8; 3]>::eq) {
9+
scope 3 (inlined <u8 as array::equality::SpecArrayEq<u8, 3>>::spec_eq) {
10+
}
11+
}
12+
}
13+
14+
bb0: {
15+
_0 = raw_eq::<[u8; 3]>(move _1, move _2) -> [return: bb1, unwind unreachable];
16+
}
17+
18+
bb1: {
19+
return;
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// MIR for `eq_odd_length` after PreCodegen
2+
3+
fn eq_odd_length(_1: &[u8; 3], _2: &[u8; 3]) -> bool {
4+
debug a => _1;
5+
debug b => _2;
6+
let mut _0: bool;
7+
scope 1 (inlined std::cmp::impls::<impl PartialEq for &[u8; 3]>::eq) {
8+
scope 2 (inlined array::equality::<impl PartialEq for [u8; 3]>::eq) {
9+
scope 3 (inlined <u8 as array::equality::SpecArrayEq<u8, 3>>::spec_eq) {
10+
}
11+
}
12+
}
13+
14+
bb0: {
15+
_0 = raw_eq::<[u8; 3]>(move _1, move _2) -> [return: bb1, unwind unreachable];
16+
}
17+
18+
bb1: {
19+
return;
20+
}
21+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//@ compile-flags: -O -Zmir-opt-level=2
2+
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
3+
4+
#![crate_type = "lib"]
5+
6+
// EMIT_MIR array_eq.eq_odd_length.PreCodegen.after.mir
7+
pub unsafe fn eq_odd_length<T: Copy>(a: &[u8; 3], b: &[u8; 3]) -> bool {
8+
// CHECK-LABEL: fn eq_odd_length(_1: &[u8; 3], _2: &[u8; 3]) -> bool
9+
// CHECK: _0 = raw_eq::<[u8; 3]>(move _1, move _2)
10+
a == b
11+
}
12+
13+
// EMIT_MIR array_eq.eq_ipv4.PreCodegen.after.mir
14+
pub unsafe fn eq_ipv4<T: Copy>(a: &[u8; 4], b: &[u8; 4]) -> bool {
15+
// CHECK-LABEL: fn eq_ipv4(_1: &[u8; 4], _2: &[u8; 4]) -> bool
16+
// CHECK: [[A:_.+]] = copy (*_1) as u32 (Transmute);
17+
// CHECK: [[B:_.+]] = copy (*_2) as u32 (Transmute);
18+
// CHECK: _0 = Eq(move [[A]], move [[B]]);
19+
a == b
20+
}
21+
22+
// EMIT_MIR array_eq.eq_ipv6.PreCodegen.after.mir
23+
pub unsafe fn eq_ipv6<T: Copy>(a: &[u16; 8], b: &[u16; 8]) -> bool {
24+
// CHECK-LABEL: fn eq_ipv6(_1: &[u16; 8], _2: &[u16; 8]) -> bool
25+
// CHECK: [[A:_.+]] = copy (*_1) as u128 (Transmute);
26+
// CHECK: [[B:_.+]] = copy (*_2) as u128 (Transmute);
27+
// CHECK: _0 = Eq(move [[A]], move [[B]]);
28+
a == b
29+
}

0 commit comments

Comments
 (0)