Skip to content

Commit 7e4077d

Browse files
committed
Auto merge of rust-lang#135274 - saethlin:array-repeats, r=compiler-errors
Add an InstSimplify for repetitive array expressions I noticed in rust-lang#135068 (comment) that GVN's implementation of this same transform was quite profitable on the deep-vector benchmark. But of course GVN doesn't run in unoptimized builds, so this is my attempt to write a version of this transform that benefits the deep-vector case and is fast enough to run in InstSimplify. The benchmark suite indicates that this is effective.
2 parents a2d7c81 + a285d20 commit 7e4077d

7 files changed

+176
-0
lines changed

compiler/rustc_mir_transform/src/instsimplify.rs

+30
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify {
4848
ctx.simplify_ref_deref(rvalue);
4949
ctx.simplify_ptr_aggregate(rvalue);
5050
ctx.simplify_cast(rvalue);
51+
ctx.simplify_repeated_aggregate(rvalue);
5152
}
5253
_ => {}
5354
}
@@ -68,6 +69,35 @@ struct InstSimplifyContext<'a, 'tcx> {
6869
}
6970

7071
impl<'tcx> InstSimplifyContext<'_, 'tcx> {
72+
/// Transform aggregates like [0, 0, 0, 0, 0] into [0; 5].
73+
/// GVN can also do this optimization, but GVN is only run at mir-opt-level 2 so having this in
74+
/// InstSimplify helps unoptimized builds.
75+
fn simplify_repeated_aggregate(&self, rvalue: &mut Rvalue<'tcx>) {
76+
let Rvalue::Aggregate(box AggregateKind::Array(_), fields) = rvalue else {
77+
return;
78+
};
79+
if fields.len() < 5 {
80+
return;
81+
}
82+
let first = &fields[rustc_abi::FieldIdx::ZERO];
83+
let Operand::Constant(first) = first else {
84+
return;
85+
};
86+
let Ok(first_val) = first.const_.eval(self.tcx, self.typing_env, first.span) else {
87+
return;
88+
};
89+
if fields.iter().all(|field| {
90+
let Operand::Constant(field) = field else {
91+
return false;
92+
};
93+
let field = field.const_.eval(self.tcx, self.typing_env, field.span);
94+
field == Ok(first_val)
95+
}) {
96+
let len = ty::Const::from_target_usize(self.tcx, fields.len().try_into().unwrap());
97+
*rvalue = Rvalue::Repeat(Operand::Constant(first.clone()), len);
98+
}
99+
}
100+
71101
/// Transform boolean comparisons into logical operations.
72102
fn simplify_bool_cmp(&self, rvalue: &mut Rvalue<'tcx>) {
73103
match rvalue {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
- // MIR for `const_items` before InstSimplify-after-simplifycfg
2+
+ // MIR for `const_items` after InstSimplify-after-simplifycfg
3+
4+
fn const_items() -> [u8; 5] {
5+
let mut _0: [u8; 5];
6+
7+
bb0: {
8+
- _0 = [const const_items::A, const const_items::B, const const_items::C, const const_items::D, const const_items::E];
9+
+ _0 = [const const_items::A; 5];
10+
return;
11+
}
12+
}
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
- // MIR for `equal_referents` before InstSimplify-after-simplifycfg
2+
+ // MIR for `equal_referents` after InstSimplify-after-simplifycfg
3+
4+
fn equal_referents() -> [&u8; 5] {
5+
let mut _0: [&u8; 5];
6+
7+
bb0: {
8+
_0 = [const equal_referents::A, const equal_referents::B, const equal_referents::C, const equal_referents::D, const equal_referents::E];
9+
return;
10+
}
11+
}
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
- // MIR for `literals` before InstSimplify-after-simplifycfg
2+
+ // MIR for `literals` after InstSimplify-after-simplifycfg
3+
4+
fn literals() -> [u8; 5] {
5+
let mut _0: [u8; 5];
6+
7+
bb0: {
8+
- _0 = [const 0_u8, const 0_u8, const 0_u8, const 0_u8, const 0_u8];
9+
+ _0 = [const 0_u8; 5];
10+
return;
11+
}
12+
}
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
- // MIR for `local` before InstSimplify-after-simplifycfg
2+
+ // MIR for `local` after InstSimplify-after-simplifycfg
3+
4+
fn local() -> [u8; 5] {
5+
let mut _0: [u8; 5];
6+
let _1: u8;
7+
let mut _2: u8;
8+
let mut _3: u8;
9+
let mut _4: u8;
10+
let mut _5: u8;
11+
let mut _6: u8;
12+
scope 1 {
13+
debug val => _1;
14+
}
15+
16+
bb0: {
17+
StorageLive(_1);
18+
_1 = const 0_u8;
19+
StorageLive(_2);
20+
_2 = copy _1;
21+
StorageLive(_3);
22+
_3 = copy _1;
23+
StorageLive(_4);
24+
_4 = copy _1;
25+
StorageLive(_5);
26+
_5 = copy _1;
27+
StorageLive(_6);
28+
_6 = copy _1;
29+
_0 = [move _2, move _3, move _4, move _5, move _6];
30+
StorageDead(_6);
31+
StorageDead(_5);
32+
StorageDead(_4);
33+
StorageDead(_3);
34+
StorageDead(_2);
35+
StorageDead(_1);
36+
return;
37+
}
38+
}
39+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//@ test-mir-pass: InstSimplify-after-simplifycfg
2+
#![crate_type = "lib"]
3+
4+
// This is the easy case, and the most plausible to run into in real code.
5+
// EMIT_MIR aggregate_array.literals.InstSimplify-after-simplifycfg.diff
6+
pub fn literals() -> [u8; 5] {
7+
// CHECK-LABEL: fn literals(
8+
// CHECK: _0 = [const 0_u8; 5];
9+
[0, 0, 0, 0, 0]
10+
}
11+
12+
// Check that hiding the const value behind a const item doesn't prevent the optimization
13+
// EMIT_MIR aggregate_array.const_items.InstSimplify-after-simplifycfg.diff
14+
pub fn const_items() -> [u8; 5] {
15+
const A: u8 = 0;
16+
const B: u8 = 0;
17+
const C: u8 = 0;
18+
const D: u8 = 0;
19+
const E: u8 = 0;
20+
21+
// CHECK-LABEL: fn const_items(
22+
// CHECK: _0 = [const const_items::A; 5];
23+
[A, B, C, D, E]
24+
}
25+
26+
// EMIT_MIR aggregate_array.strs.InstSimplify-after-simplifycfg.diff
27+
pub fn strs() -> [&'static str; 5] {
28+
// CHECK-LABEL: fn strs(
29+
// CHECK: _0 = [const "a"; 5];
30+
["a", "a", "a", "a", "a"]
31+
}
32+
33+
// InstSimplify isn't able to see through the move operands, but GVN can.
34+
// EMIT_MIR aggregate_array.local.InstSimplify-after-simplifycfg.diff
35+
pub fn local() -> [u8; 5] {
36+
// CHECK-LABEL: fn local(
37+
// CHECK: _0 = [move _2, move _3, move _4, move _5, move _6];
38+
let val = 0;
39+
[val, val, val, val, val]
40+
}
41+
42+
// All of these consts refer to the same value, but the addresses are all different.
43+
// It would be wrong to apply the optimization here.
44+
// EMIT_MIR aggregate_array.equal_referents.InstSimplify-after-simplifycfg.diff
45+
pub fn equal_referents() -> [&'static u8; 5] {
46+
const DATA: &[u8] = &[0, 0, 0, 0, 0];
47+
const A: &u8 = &DATA[0];
48+
const B: &u8 = &DATA[1];
49+
const C: &u8 = &DATA[2];
50+
const D: &u8 = &DATA[3];
51+
const E: &u8 = &DATA[4];
52+
53+
// CHECK-LABEL: fn equal_referents(
54+
// CHECK: _0 = [const equal_referents::A, const equal_referents::B, const equal_referents::C, const equal_referents::D, const equal_referents::E];
55+
[A, B, C, D, E]
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
- // MIR for `strs` before InstSimplify-after-simplifycfg
2+
+ // MIR for `strs` after InstSimplify-after-simplifycfg
3+
4+
fn strs() -> [&str; 5] {
5+
let mut _0: [&str; 5];
6+
7+
bb0: {
8+
- _0 = [const "a", const "a", const "a", const "a", const "a"];
9+
+ _0 = [const "a"; 5];
10+
return;
11+
}
12+
}
13+

0 commit comments

Comments
 (0)