1 //! The general point of the optimizations provided here is to simplify something like:
12 use crate::transform
::{MirPass, MirSource, simplify}
;
13 use rustc
::ty
::{TyCtxt, Ty}
;
15 use rustc_target
::abi
::VariantIdx
;
16 use itertools
::Itertools
as _
;
18 /// Simplifies arms of form `Variant(x) => Variant(x)` to just a move.
20 /// This is done by transforming basic blocks where the statements match:
23 /// _LOCAL_TMP = ((_LOCAL_1 as Variant ).FIELD: TY );
24 /// ((_LOCAL_0 as Variant).FIELD: TY) = move _LOCAL_TMP;
25 /// discriminant(_LOCAL_0) = VAR_IDX;
31 /// _LOCAL_0 = move _LOCAL_1
33 pub struct SimplifyArmIdentity
;
35 impl<'tcx
> MirPass
<'tcx
> for SimplifyArmIdentity
{
36 fn run_pass(&self, _
: TyCtxt
<'tcx
>, _
: MirSource
<'tcx
>, body
: &mut BodyAndCache
<'tcx
>) {
37 let (basic_blocks
, local_decls
) = body
.basic_blocks_and_local_decls_mut();
38 for bb
in basic_blocks
{
40 let (s0
, s1
, s2
) = match &mut *bb
.statements
{
41 [s0
, s1
, s2
] => (s0
, s1
, s2
),
45 // Pattern match on the form we want:
46 let (local_tmp_s0
, local_1
, vf_s0
) = match match_get_variant_field(s0
) {
50 let (local_tmp_s1
, local_0
, vf_s1
) = match match_set_variant_field(s1
) {
54 if local_tmp_s0
!= local_tmp_s1
55 // The field-and-variant information match up.
57 // Source and target locals have the same type.
58 // FIXME(Centril | oli-obk): possibly relax to same layout?
59 || local_decls
[local_0
].ty
!= local_decls
[local_1
].ty
60 // We're setting the discriminant of `local_0` to this variant.
61 || Some((local_0
, vf_s0
.var_idx
)) != match_set_discr(s2
)
66 // Right shape; transform!
68 StatementKind
::Assign(box (place
, rvalue
)) => {
69 *place
= local_0
.into();
70 *rvalue
= Rvalue
::Use(Operand
::Move(local_1
.into()));
82 /// _LOCAL_INTO = ((_LOCAL_FROM as Variant).FIELD: TY);
84 fn match_get_variant_field
<'tcx
>(stmt
: &Statement
<'tcx
>) -> Option
<(Local
, Local
, VarField
<'tcx
>)> {
86 StatementKind
::Assign(box (place_into
, rvalue_from
)) => match rvalue_from
{
87 Rvalue
::Use(Operand
::Copy(pf
)) | Rvalue
::Use(Operand
::Move(pf
)) => {
88 let local_into
= place_into
.as_local()?
;
89 let (local_from
, vf
) = match_variant_field_place(&pf
)?
;
90 Some((local_into
, local_from
, vf
))
100 /// ((_LOCAL_FROM as Variant).FIELD: TY) = move _LOCAL_INTO;
102 fn match_set_variant_field
<'tcx
>(stmt
: &Statement
<'tcx
>) -> Option
<(Local
, Local
, VarField
<'tcx
>)> {
104 StatementKind
::Assign(box (place_from
, rvalue_into
)) => match rvalue_into
{
105 Rvalue
::Use(Operand
::Move(place_into
)) => {
106 let local_into
= place_into
.as_local()?
;
107 let (local_from
, vf
) = match_variant_field_place(&place_from
)?
;
108 Some((local_into
, local_from
, vf
))
118 /// discriminant(_LOCAL_TO_SET) = VAR_IDX;
120 fn match_set_discr
<'tcx
>(stmt
: &Statement
<'tcx
>) -> Option
<(Local
, VariantIdx
)> {
122 StatementKind
::SetDiscriminant { place, variant_index }
=> Some((
131 struct VarField
<'tcx
> {
137 /// Match on `((_LOCAL as Variant).FIELD: TY)`.
138 fn match_variant_field_place
<'tcx
>(place
: &Place
<'tcx
>) -> Option
<(Local
, VarField
<'tcx
>)> {
139 match place
.as_ref() {
141 base
: &PlaceBase
::Local(local
),
142 projection
: &[ProjectionElem
::Downcast(_
, var_idx
), ProjectionElem
::Field(field
, ty
)],
143 } => Some((local
, VarField { field, field_ty: ty, var_idx }
)),
148 /// Simplifies `SwitchInt(_) -> [targets]`,
149 /// where all the `targets` have the same form,
150 /// into `goto -> target_first`.
151 pub struct SimplifyBranchSame
;
153 impl<'tcx
> MirPass
<'tcx
> for SimplifyBranchSame
{
154 fn run_pass(&self, _
: TyCtxt
<'tcx
>, _
: MirSource
<'tcx
>, body
: &mut BodyAndCache
<'tcx
>) {
155 let mut did_remove_blocks
= false;
156 let bbs
= body
.basic_blocks_mut();
157 for bb_idx
in bbs
.indices() {
158 let targets
= match &bbs
[bb_idx
].terminator().kind
{
159 TerminatorKind
::SwitchInt { targets, .. }
=> targets
,
163 let mut iter_bbs_reachable
= targets
165 .map(|idx
| (*idx
, &bbs
[*idx
]))
167 // Reaching `unreachable` is UB so assume it doesn't happen.
168 bb
.terminator().kind
!= TerminatorKind
::Unreachable
169 // But `asm!(...)` could abort the program,
170 // so we cannot assume that the `unreachable` terminator itself is reachable.
171 // FIXME(Centril): use a normalization pass instead of a check.
172 || bb
.statements
.iter().any(|stmt
| match stmt
.kind
{
173 StatementKind
::InlineAsm(..) => true,
179 // We want to `goto -> bb_first`.
180 let bb_first
= iter_bbs_reachable
182 .map(|(idx
, _
)| *idx
)
183 .unwrap_or(targets
[0]);
185 // All successor basic blocks should have the exact same form.
186 let all_successors_equivalent
= iter_bbs_reachable
189 .all(|(bb_l
, bb_r
)| {
190 bb_l
.is_cleanup
== bb_r
.is_cleanup
191 && bb_l
.terminator().kind
== bb_r
.terminator().kind
192 && bb_l
.statements
.iter().eq_by(&bb_r
.statements
, |x
, y
| x
.kind
== y
.kind
)
195 if all_successors_equivalent
{
196 // Replace `SwitchInt(..) -> [bb_first, ..];` with a `goto -> bb_first;`.
197 bbs
[bb_idx
].terminator_mut().kind
= TerminatorKind
::Goto { target: bb_first }
;
198 did_remove_blocks
= true;
202 if did_remove_blocks
{
203 // We have dead blocks now, so remove those.
204 simplify
::remove_dead_blocks(body
);