diff --git a/compiler/rustc_mir_transform/src/instsimplify.rs b/compiler/rustc_mir_transform/src/instsimplify.rs index 1a65affe8121a..927345a7dbb24 100644 --- a/compiler/rustc_mir_transform/src/instsimplify.rs +++ b/compiler/rustc_mir_transform/src/instsimplify.rs @@ -48,6 +48,7 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify { ctx.simplify_ref_deref(rvalue); ctx.simplify_ptr_aggregate(rvalue); ctx.simplify_cast(rvalue); + ctx.simplify_repeated_aggregate(rvalue); } _ => {} } @@ -68,6 +69,35 @@ struct InstSimplifyContext<'a, 'tcx> { } impl<'tcx> InstSimplifyContext<'_, 'tcx> { + /// Transform aggregates like [0, 0, 0, 0, 0] into [0; 5]. + /// GVN can also do this optimization, but GVN is only run at mir-opt-level 2 so having this in + /// InstSimplify helps unoptimized builds. + fn simplify_repeated_aggregate(&self, rvalue: &mut Rvalue<'tcx>) { + let Rvalue::Aggregate(box AggregateKind::Array(_), fields) = rvalue else { + return; + }; + if fields.len() < 5 { + return; + } + let first = &fields[rustc_abi::FieldIdx::ZERO]; + let Operand::Constant(first) = first else { + return; + }; + let Ok(first_val) = first.const_.eval(self.tcx, self.typing_env, first.span) else { + return; + }; + if fields.iter().all(|field| { + let Operand::Constant(field) = field else { + return false; + }; + let field = field.const_.eval(self.tcx, self.typing_env, field.span); + field == Ok(first_val) + }) { + let len = ty::Const::from_target_usize(self.tcx, fields.len().try_into().unwrap()); + *rvalue = Rvalue::Repeat(Operand::Constant(first.clone()), len); + } + } + /// Transform boolean comparisons into logical operations. fn simplify_bool_cmp(&self, rvalue: &mut Rvalue<'tcx>) { match rvalue { diff --git a/tests/mir-opt/instsimplify/aggregate_array.const_items.InstSimplify-after-simplifycfg.diff b/tests/mir-opt/instsimplify/aggregate_array.const_items.InstSimplify-after-simplifycfg.diff new file mode 100644 index 0000000000000..bdeabee2e46f8 --- /dev/null +++ b/tests/mir-opt/instsimplify/aggregate_array.const_items.InstSimplify-after-simplifycfg.diff @@ -0,0 +1,13 @@ +- // MIR for `const_items` before InstSimplify-after-simplifycfg ++ // MIR for `const_items` after InstSimplify-after-simplifycfg + + fn const_items() -> [u8; 5] { + let mut _0: [u8; 5]; + + bb0: { +- _0 = [const const_items::A, const const_items::B, const const_items::C, const const_items::D, const const_items::E]; ++ _0 = [const const_items::A; 5]; + return; + } + } + diff --git a/tests/mir-opt/instsimplify/aggregate_array.equal_referents.InstSimplify-after-simplifycfg.diff b/tests/mir-opt/instsimplify/aggregate_array.equal_referents.InstSimplify-after-simplifycfg.diff new file mode 100644 index 0000000000000..86e0860ccfa29 --- /dev/null +++ b/tests/mir-opt/instsimplify/aggregate_array.equal_referents.InstSimplify-after-simplifycfg.diff @@ -0,0 +1,12 @@ +- // MIR for `equal_referents` before InstSimplify-after-simplifycfg ++ // MIR for `equal_referents` after InstSimplify-after-simplifycfg + + fn equal_referents() -> [&u8; 5] { + let mut _0: [&u8; 5]; + + bb0: { + _0 = [const equal_referents::A, const equal_referents::B, const equal_referents::C, const equal_referents::D, const equal_referents::E]; + return; + } + } + diff --git a/tests/mir-opt/instsimplify/aggregate_array.literals.InstSimplify-after-simplifycfg.diff b/tests/mir-opt/instsimplify/aggregate_array.literals.InstSimplify-after-simplifycfg.diff new file mode 100644 index 0000000000000..2f6963ad016dd --- /dev/null +++ b/tests/mir-opt/instsimplify/aggregate_array.literals.InstSimplify-after-simplifycfg.diff @@ -0,0 +1,13 @@ +- // MIR for `literals` before InstSimplify-after-simplifycfg ++ // MIR for `literals` after InstSimplify-after-simplifycfg + + fn literals() -> [u8; 5] { + let mut _0: [u8; 5]; + + bb0: { +- _0 = [const 0_u8, const 0_u8, const 0_u8, const 0_u8, const 0_u8]; ++ _0 = [const 0_u8; 5]; + return; + } + } + diff --git a/tests/mir-opt/instsimplify/aggregate_array.local.InstSimplify-after-simplifycfg.diff b/tests/mir-opt/instsimplify/aggregate_array.local.InstSimplify-after-simplifycfg.diff new file mode 100644 index 0000000000000..2943aa540619a --- /dev/null +++ b/tests/mir-opt/instsimplify/aggregate_array.local.InstSimplify-after-simplifycfg.diff @@ -0,0 +1,39 @@ +- // MIR for `local` before InstSimplify-after-simplifycfg ++ // MIR for `local` after InstSimplify-after-simplifycfg + + fn local() -> [u8; 5] { + let mut _0: [u8; 5]; + let _1: u8; + let mut _2: u8; + let mut _3: u8; + let mut _4: u8; + let mut _5: u8; + let mut _6: u8; + scope 1 { + debug val => _1; + } + + bb0: { + StorageLive(_1); + _1 = const 0_u8; + StorageLive(_2); + _2 = copy _1; + StorageLive(_3); + _3 = copy _1; + StorageLive(_4); + _4 = copy _1; + StorageLive(_5); + _5 = copy _1; + StorageLive(_6); + _6 = copy _1; + _0 = [move _2, move _3, move _4, move _5, move _6]; + StorageDead(_6); + StorageDead(_5); + StorageDead(_4); + StorageDead(_3); + StorageDead(_2); + StorageDead(_1); + return; + } + } + diff --git a/tests/mir-opt/instsimplify/aggregate_array.rs b/tests/mir-opt/instsimplify/aggregate_array.rs new file mode 100644 index 0000000000000..8dd0d80b459a8 --- /dev/null +++ b/tests/mir-opt/instsimplify/aggregate_array.rs @@ -0,0 +1,56 @@ +//@ test-mir-pass: InstSimplify-after-simplifycfg +#![crate_type = "lib"] + +// This is the easy case, and the most plausible to run into in real code. +// EMIT_MIR aggregate_array.literals.InstSimplify-after-simplifycfg.diff +pub fn literals() -> [u8; 5] { + // CHECK-LABEL: fn literals( + // CHECK: _0 = [const 0_u8; 5]; + [0, 0, 0, 0, 0] +} + +// Check that hiding the const value behind a const item doesn't prevent the optimization +// EMIT_MIR aggregate_array.const_items.InstSimplify-after-simplifycfg.diff +pub fn const_items() -> [u8; 5] { + const A: u8 = 0; + const B: u8 = 0; + const C: u8 = 0; + const D: u8 = 0; + const E: u8 = 0; + + // CHECK-LABEL: fn const_items( + // CHECK: _0 = [const const_items::A; 5]; + [A, B, C, D, E] +} + +// EMIT_MIR aggregate_array.strs.InstSimplify-after-simplifycfg.diff +pub fn strs() -> [&'static str; 5] { + // CHECK-LABEL: fn strs( + // CHECK: _0 = [const "a"; 5]; + ["a", "a", "a", "a", "a"] +} + +// InstSimplify isn't able to see through the move operands, but GVN can. +// EMIT_MIR aggregate_array.local.InstSimplify-after-simplifycfg.diff +pub fn local() -> [u8; 5] { + // CHECK-LABEL: fn local( + // CHECK: _0 = [move _2, move _3, move _4, move _5, move _6]; + let val = 0; + [val, val, val, val, val] +} + +// All of these consts refer to the same value, but the addresses are all different. +// It would be wrong to apply the optimization here. +// EMIT_MIR aggregate_array.equal_referents.InstSimplify-after-simplifycfg.diff +pub fn equal_referents() -> [&'static u8; 5] { + const DATA: &[u8] = &[0, 0, 0, 0, 0]; + const A: &u8 = &DATA[0]; + const B: &u8 = &DATA[1]; + const C: &u8 = &DATA[2]; + const D: &u8 = &DATA[3]; + const E: &u8 = &DATA[4]; + + // CHECK-LABEL: fn equal_referents( + // CHECK: _0 = [const equal_referents::A, const equal_referents::B, const equal_referents::C, const equal_referents::D, const equal_referents::E]; + [A, B, C, D, E] +} diff --git a/tests/mir-opt/instsimplify/aggregate_array.strs.InstSimplify-after-simplifycfg.diff b/tests/mir-opt/instsimplify/aggregate_array.strs.InstSimplify-after-simplifycfg.diff new file mode 100644 index 0000000000000..f888488175603 --- /dev/null +++ b/tests/mir-opt/instsimplify/aggregate_array.strs.InstSimplify-after-simplifycfg.diff @@ -0,0 +1,13 @@ +- // MIR for `strs` before InstSimplify-after-simplifycfg ++ // MIR for `strs` after InstSimplify-after-simplifycfg + + fn strs() -> [&str; 5] { + let mut _0: [&str; 5]; + + bb0: { +- _0 = [const "a", const "a", const "a", const "a", const "a"]; ++ _0 = [const "a"; 5]; + return; + } + } +