Skip to content

Commit 59a9bb4

Browse files
committed
Auto merge of rust-lang#128299 - DianQK:clone-copy, r=<try>
Simplify the canonical clone method and the copy-like forms to copy Fixes rust-lang#128081. r? `@cjgillot`
2 parents 9b82580 + df81dee commit 59a9bb4

File tree

48 files changed

+2054
-293
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2054
-293
lines changed

compiler/rustc_mir_transform/src/gvn.rs

+97-1
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,95 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
877877
None
878878
}
879879

880+
fn try_as_place_elem(
881+
&mut self,
882+
proj: ProjectionElem<VnIndex, Ty<'tcx>>,
883+
loc: Location,
884+
) -> Option<PlaceElem<'tcx>> {
885+
Some(match proj {
886+
ProjectionElem::Deref => ProjectionElem::Deref,
887+
ProjectionElem::Field(idx, ty) => ProjectionElem::Field(idx, ty),
888+
ProjectionElem::Index(idx) => {
889+
let Some(local) = self.try_as_local(idx, loc) else {
890+
return None;
891+
};
892+
self.reused_locals.insert(local);
893+
ProjectionElem::Index(local)
894+
}
895+
ProjectionElem::ConstantIndex { offset, min_length, from_end } => {
896+
ProjectionElem::ConstantIndex { offset, min_length, from_end }
897+
}
898+
ProjectionElem::Subslice { from, to, from_end } => {
899+
ProjectionElem::Subslice { from, to, from_end }
900+
}
901+
ProjectionElem::Downcast(symbol, idx) => ProjectionElem::Downcast(symbol, idx),
902+
ProjectionElem::OpaqueCast(idx) => ProjectionElem::OpaqueCast(idx),
903+
ProjectionElem::Subtype(idx) => ProjectionElem::Subtype(idx),
904+
})
905+
}
906+
907+
fn simplify_aggregate_to_copy(
908+
&mut self,
909+
rvalue: &mut Rvalue<'tcx>,
910+
location: Location,
911+
fields: &[VnIndex],
912+
variant_index: VariantIdx,
913+
) -> Option<VnIndex> {
914+
let Some(&first_field) = fields.first() else {
915+
return None;
916+
};
917+
let Value::Projection(copy_from_value, _) = *self.get(first_field) else {
918+
return None;
919+
};
920+
// All fields must correspond one-to-one and come from the same aggregate value.
921+
if fields.iter().enumerate().any(|(index, &v)| {
922+
if let Value::Projection(pointer, ProjectionElem::Field(from_index, _)) = *self.get(v)
923+
&& copy_from_value == pointer
924+
&& from_index.index() == index
925+
{
926+
return false;
927+
}
928+
true
929+
}) {
930+
return None;
931+
}
932+
933+
let mut copy_from_local_value = copy_from_value;
934+
if let Value::Projection(pointer, proj) = *self.get(copy_from_value)
935+
&& let ProjectionElem::Downcast(_, read_variant) = proj
936+
{
937+
if variant_index == read_variant {
938+
// When copying a variant, there is no need to downcast.
939+
copy_from_local_value = pointer;
940+
} else {
941+
// The copied variant must be identical.
942+
return None;
943+
}
944+
}
945+
946+
let tcx = self.tcx;
947+
let mut projection = SmallVec::<[PlaceElem<'tcx>; 1]>::new();
948+
loop {
949+
if let Some(local) = self.try_as_local(copy_from_local_value, location) {
950+
projection.reverse();
951+
let place = Place { local, projection: tcx.mk_place_elems(projection.as_slice()) };
952+
if rvalue.ty(self.local_decls, tcx) == place.ty(self.local_decls, tcx).ty {
953+
self.reused_locals.insert(local);
954+
*rvalue = Rvalue::Use(Operand::Copy(place));
955+
return Some(copy_from_value);
956+
}
957+
return None;
958+
} else if let Value::Projection(pointer, proj) = *self.get(copy_from_local_value)
959+
&& let Some(proj) = self.try_as_place_elem(proj, location)
960+
{
961+
projection.push(proj);
962+
copy_from_local_value = pointer;
963+
} else {
964+
return None;
965+
}
966+
}
967+
}
968+
880969
fn simplify_aggregate(
881970
&mut self,
882971
rvalue: &mut Rvalue<'tcx>,
@@ -973,6 +1062,13 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
9731062
}
9741063
}
9751064

1065+
if let AggregateTy::Def(_, _) = ty
1066+
&& let Some(value) =
1067+
self.simplify_aggregate_to_copy(rvalue, location, &fields, variant_index)
1068+
{
1069+
return Some(value);
1070+
}
1071+
9761072
Some(self.insert(Value::Aggregate(ty, variant_index, fields)))
9771073
}
9781074

@@ -1486,7 +1582,7 @@ impl<'tcx> VnState<'_, 'tcx> {
14861582
}
14871583

14881584
/// If there is a local which is assigned `index`, and its assignment strictly dominates `loc`,
1489-
/// return it.
1585+
/// return it. If you used this local, add it to `reused_locals` to remove storage statements.
14901586
fn try_as_local(&mut self, index: VnIndex, loc: Location) -> Option<Local> {
14911587
let other = self.rev_locals.get(index)?;
14921588
other

compiler/rustc_mir_transform/src/lib.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -586,15 +586,14 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
586586
// Now, we need to shrink the generated MIR.
587587
&ref_prop::ReferencePropagation,
588588
&sroa::ScalarReplacementOfAggregates,
589-
&match_branches::MatchBranchSimplification,
590-
// inst combine is after MatchBranchSimplification to clean up Ne(_1, false)
591589
&multiple_return_terminators::MultipleReturnTerminators,
592590
// After simplifycfg, it allows us to discover new opportunities for peephole optimizations.
593591
&instsimplify::InstSimplify::AfterSimplifyCfg,
594592
&simplify::SimplifyLocals::BeforeConstProp,
595593
&dead_store_elimination::DeadStoreElimination::Initial,
596594
&gvn::GVN,
597595
&simplify::SimplifyLocals::AfterGVN,
596+
&match_branches::MatchBranchSimplification,
598597
&dataflow_const_prop::DataflowConstProp,
599598
&single_use_consts::SingleUseConsts,
600599
&o1(simplify_branches::SimplifyConstCondition::AfterConstProp),

compiler/rustc_mir_transform/src/match_branches.rs

+218-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
use std::iter;
1+
use std::{iter, usize};
22

3+
use rustc_const_eval::const_eval::mk_eval_cx_for_const_val;
4+
use rustc_index::bit_set::BitSet;
35
use rustc_index::IndexSlice;
46
use rustc_middle::mir::patch::MirPatch;
57
use rustc_middle::mir::*;
8+
use rustc_middle::ty;
69
use rustc_middle::ty::layout::{IntegerExt, TyAndLayout};
10+
use rustc_middle::ty::util::Discr;
711
use rustc_middle::ty::{ParamEnv, ScalarInt, Ty, TyCtxt};
12+
use rustc_mir_dataflow::impls::{borrowed_locals, MaybeTransitiveLiveLocals};
13+
use rustc_mir_dataflow::Analysis;
814
use rustc_target::abi::Integer;
915
use rustc_type_ir::TyKind::*;
1016

@@ -48,6 +54,10 @@ impl<'tcx> MirPass<'tcx> for MatchBranchSimplification {
4854
should_cleanup = true;
4955
continue;
5056
}
57+
if simplify_to_copy(tcx, body, bb_idx, param_env).is_some() {
58+
should_cleanup = true;
59+
continue;
60+
}
5161
}
5262

5363
if should_cleanup {
@@ -515,3 +525,210 @@ impl<'tcx> SimplifyMatch<'tcx> for SimplifyToExp {
515525
}
516526
}
517527
}
528+
529+
/// This is primarily used to merge these copy statements that simplified the canonical enum clone method by GVN.
530+
/// The GVN simplified
531+
/// ```ignore (syntax-highlighting-only)
532+
/// match a {
533+
/// Foo::A(x) => Foo::A(*x),
534+
/// Foo::B => Foo::B
535+
/// }
536+
/// ```
537+
/// to
538+
/// ```ignore (syntax-highlighting-only)
539+
/// match a {
540+
/// Foo::A(_x) => a, // copy a
541+
/// Foo::B => Foo::B
542+
/// }
543+
/// ```
544+
/// This function will simplify into a copy statement.
545+
fn simplify_to_copy<'tcx>(
546+
tcx: TyCtxt<'tcx>,
547+
body: &mut Body<'tcx>,
548+
switch_bb_idx: BasicBlock,
549+
param_env: ParamEnv<'tcx>,
550+
) -> Option<()> {
551+
if switch_bb_idx != START_BLOCK {
552+
return None;
553+
}
554+
let bbs = &body.basic_blocks;
555+
// Check if the copy source matches the following pattern.
556+
// _2 = discriminant(*_1); // "*_1" is the expected the copy source.
557+
// switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1];
558+
let &Statement {
559+
kind: StatementKind::Assign(box (discr_place, Rvalue::Discriminant(expected_src_place))),
560+
..
561+
} = bbs[switch_bb_idx].statements.last()?
562+
else {
563+
return None;
564+
};
565+
let expected_src_ty = expected_src_place.ty(body.local_decls(), tcx);
566+
if !expected_src_ty.ty.is_enum() || expected_src_ty.variant_index.is_some() {
567+
return None;
568+
}
569+
let expected_dest_place = Place::return_place();
570+
let expected_dest_ty = expected_dest_place.ty(body.local_decls(), tcx);
571+
if expected_dest_ty.ty != expected_src_ty.ty || expected_dest_ty.variant_index.is_some() {
572+
return None;
573+
}
574+
let targets = match bbs[switch_bb_idx].terminator().kind {
575+
TerminatorKind::SwitchInt { ref discr, ref targets, .. }
576+
if discr.place() == Some(discr_place) =>
577+
{
578+
targets
579+
}
580+
_ => return None,
581+
};
582+
// We require that the possible target blocks all be distinct.
583+
if !targets.is_distinct() {
584+
return None;
585+
}
586+
if !bbs[targets.otherwise()].is_empty_unreachable() {
587+
return None;
588+
}
589+
// Check that destinations are identical, and if not, then don't optimize this block.
590+
let mut target_iter = targets.iter();
591+
let first_terminator_kind = &bbs[target_iter.next().unwrap().1].terminator().kind;
592+
if !target_iter
593+
.all(|(_, other_target)| first_terminator_kind == &bbs[other_target].terminator().kind)
594+
{
595+
return None;
596+
}
597+
598+
let borrowed_locals = borrowed_locals(body);
599+
let mut live = None;
600+
601+
for (index, target_bb) in targets.iter() {
602+
let stmts = &bbs[target_bb].statements;
603+
if stmts.is_empty() {
604+
return None;
605+
}
606+
if let [Statement { kind: StatementKind::Assign(box (place, rvalue)), .. }] =
607+
bbs[target_bb].statements.as_slice()
608+
{
609+
let dest_ty = place.ty(body.local_decls(), tcx);
610+
if dest_ty.ty != expected_src_ty.ty || dest_ty.variant_index.is_some() {
611+
return None;
612+
}
613+
let ty::Adt(def, _) = dest_ty.ty.kind() else {
614+
return None;
615+
};
616+
if expected_dest_place != *place {
617+
return None;
618+
}
619+
match rvalue {
620+
// Check if `_3 = const Foo::B` can be transformed to `_3 = copy *_1`.
621+
Rvalue::Use(Operand::Constant(box constant))
622+
if let Const::Val(const_, ty) = constant.const_ =>
623+
{
624+
let (ecx, op) =
625+
mk_eval_cx_for_const_val(tcx.at(constant.span), param_env, const_, ty)?;
626+
let variant = ecx.read_discriminant(&op).ok()?;
627+
if !def.variants()[variant].fields.is_empty() {
628+
return None;
629+
}
630+
let Discr { val, .. } = ty.discriminant_for_variant(tcx, variant)?;
631+
if val != index {
632+
return None;
633+
}
634+
}
635+
Rvalue::Use(Operand::Copy(src_place)) if *src_place == expected_src_place => {}
636+
// Check if `_3 = Foo::B` can be transformed to `_3 = copy *_1`.
637+
Rvalue::Aggregate(box AggregateKind::Adt(_, variant_index, _, _, None), fields)
638+
if fields.is_empty()
639+
&& let Some(Discr { val, .. }) =
640+
expected_src_ty.ty.discriminant_for_variant(tcx, *variant_index)
641+
&& val == index => {}
642+
_ => return None,
643+
}
644+
} else {
645+
// If the BB contains more than one statement, we have to check if these statements can be ignored.
646+
let mut lived_stmts: BitSet<usize> =
647+
BitSet::new_filled(bbs[target_bb].statements.len());
648+
let mut expected_copy_stmt = None;
649+
for (statement_index, statement) in bbs[target_bb].statements.iter().enumerate().rev() {
650+
let loc = Location { block: target_bb, statement_index };
651+
if let StatementKind::Assign(assign) = &statement.kind {
652+
if !assign.1.is_safe_to_remove() {
653+
return None;
654+
}
655+
}
656+
match &statement.kind {
657+
StatementKind::Assign(box (place, _))
658+
| StatementKind::SetDiscriminant { place: box place, .. }
659+
| StatementKind::Deinit(box place) => {
660+
if place.is_indirect() || borrowed_locals.contains(place.local) {
661+
return None;
662+
}
663+
let live = live.get_or_insert_with(|| {
664+
MaybeTransitiveLiveLocals::new(&borrowed_locals)
665+
.into_engine(tcx, body)
666+
.iterate_to_fixpoint()
667+
.into_results_cursor(body)
668+
});
669+
live.seek_before_primary_effect(loc);
670+
if !live.get().contains(place.local) {
671+
lived_stmts.remove(statement_index);
672+
} else if let StatementKind::Assign(box (
673+
_,
674+
Rvalue::Use(Operand::Copy(src_place)),
675+
)) = statement.kind
676+
&& expected_copy_stmt.is_none()
677+
&& expected_src_place == src_place
678+
&& expected_dest_place == *place
679+
{
680+
// There is only one statement that cannot be ignored that can be used as an expected copy statement.
681+
expected_copy_stmt = Some(statement_index);
682+
} else {
683+
return None;
684+
}
685+
}
686+
StatementKind::StorageLive(_)
687+
| StatementKind::StorageDead(_)
688+
| StatementKind::Nop => (),
689+
690+
StatementKind::Retag(_, _)
691+
| StatementKind::Coverage(_)
692+
| StatementKind::Intrinsic(_)
693+
| StatementKind::ConstEvalCounter
694+
| StatementKind::PlaceMention(_)
695+
| StatementKind::FakeRead(_)
696+
| StatementKind::AscribeUserType(_, _) => {
697+
return None;
698+
}
699+
}
700+
}
701+
let expected_copy_stmt = expected_copy_stmt?;
702+
// We can ignore the paired StorageLive and StorageDead.
703+
let mut storage_live_locals: BitSet<Local> = BitSet::new_empty(body.local_decls.len());
704+
for stmt_index in lived_stmts.iter() {
705+
let statement = &bbs[target_bb].statements[stmt_index];
706+
match &statement.kind {
707+
StatementKind::Assign(_) if expected_copy_stmt == stmt_index => {}
708+
StatementKind::StorageLive(local)
709+
if *local != expected_dest_place.local
710+
&& storage_live_locals.insert(*local) => {}
711+
StatementKind::StorageDead(local)
712+
if *local != expected_dest_place.local
713+
&& storage_live_locals.remove(*local) => {}
714+
StatementKind::Nop => {}
715+
_ => return None,
716+
}
717+
}
718+
if !storage_live_locals.is_empty() {
719+
return None;
720+
}
721+
}
722+
}
723+
let statement_index = bbs[switch_bb_idx].statements.len();
724+
let parent_end = Location { block: switch_bb_idx, statement_index };
725+
let mut patch = MirPatch::new(body);
726+
patch.add_assign(
727+
parent_end,
728+
expected_dest_place,
729+
Rvalue::Use(Operand::Copy(expected_src_place)),
730+
);
731+
patch.patch_terminator(switch_bb_idx, first_terminator_kind.clone());
732+
patch.apply(body);
733+
Some(())
734+
}

0 commit comments

Comments
 (0)