Skip to content

Commit e7386b3

Browse files
committed
Auto merge of #128299 - DianQK:clone-copy, r=cjgillot
Simplify the canonical clone method and the copy-like forms to copy Fixes #128081. The optimized clone method ends up as the following MIR: ``` _2 = copy ((*_1).0: i32); _3 = copy ((*_1).1: u64); _4 = copy ((*_1).2: [i8; 3]); _0 = Foo { a: move _2, b: move _3, c: move _4 }; ``` We can transform this to: ``` _0 = copy (*_1); ``` r? `@cjgillot`
2 parents 02b1be1 + 25d434b commit e7386b3

28 files changed

+1648
-53
lines changed

compiler/rustc_mir_transform/src/gvn.rs

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

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

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

@@ -1485,7 +1581,7 @@ impl<'tcx> VnState<'_, 'tcx> {
14851581
}
14861582

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

tests/codegen/clone_as_copy.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@ revisions: DEBUGINFO NODEBUGINFO
2+
//@ compile-flags: -O -Cno-prepopulate-passes
3+
//@ [DEBUGINFO] compile-flags: -Cdebuginfo=full
4+
5+
// From https://github.com/rust-lang/rust/issues/128081.
6+
// Ensure that we only generate a memcpy instruction.
7+
8+
#![crate_type = "lib"]
9+
10+
#[derive(Clone)]
11+
struct SubCloneAndCopy {
12+
v1: u32,
13+
v2: u32,
14+
}
15+
16+
#[derive(Clone)]
17+
struct CloneOnly {
18+
v1: u8,
19+
v2: u8,
20+
v3: u8,
21+
v4: u8,
22+
v5: u8,
23+
v6: u8,
24+
v7: u8,
25+
v8: u8,
26+
v9: u8,
27+
v_sub: SubCloneAndCopy,
28+
v_large: [u8; 256],
29+
}
30+
31+
// CHECK-LABEL: define {{.*}}@clone_only(
32+
#[no_mangle]
33+
pub fn clone_only(v: &CloneOnly) -> CloneOnly {
34+
// CHECK-NOT: call {{.*}}clone
35+
// CHECK-NOT: store i8
36+
// CHECK-NOT: store i32
37+
// CHECK: call void @llvm.memcpy
38+
// CHECK-NEXT: ret void
39+
v.clone()
40+
}

tests/codegen/enum/unreachable_enum_default_branch.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ pub fn implicit_match(x: Int) -> bool {
2828
// The code is from https://github.com/rust-lang/rust/issues/110097.
2929
// We expect it to generate the same optimized code as a full match.
3030
// CHECK-LABEL: @if_let(
31-
// CHECK-NEXT: start:
31+
// CHECK: start:
32+
// CHECK-NOT: zext
33+
// CHECK: select
3234
// CHECK-NEXT: insertvalue
3335
// CHECK-NEXT: insertvalue
3436
// CHECK-NEXT: ret
3537
#[no_mangle]
3638
pub fn if_let(val: Result<i32, ()>) -> Result<i32, ()> {
37-
if let Ok(x) = val { Ok(x) } else { Err(()) }
39+
if let Ok(x) = val { Ok(x * 2) } else { Err(()) }
3840
}

tests/codegen/try_question_mark_nop.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
//@ compile-flags: -O -Z merge-functions=disabled --edition=2021
22
//@ only-x86_64
3+
// FIXME: Remove the `min-llvm-version`.
4+
//@ min-llvm-version: 19
35

46
#![crate_type = "lib"]
57
#![feature(try_blocks)]
68

79
use std::ops::ControlFlow::{self, Break, Continue};
810
use std::ptr::NonNull;
911

12+
// FIXME: The `trunc` and `select` instructions can be eliminated.
1013
// CHECK-LABEL: @option_nop_match_32
1114
#[no_mangle]
1215
pub fn option_nop_match_32(x: Option<u32>) -> Option<u32> {
1316
// CHECK: start:
14-
// CHECK-NEXT: insertvalue { i32, i32 }
17+
// CHECK-NEXT: [[TRUNC:%.*]] = trunc nuw i32 %0 to i1
18+
// CHECK-NEXT: [[FIRST:%.*]] = select i1 [[TRUNC]], i32 %0
19+
// CHECK-NEXT: insertvalue { i32, i32 } poison, i32 [[FIRST]]
1520
// CHECK-NEXT: insertvalue { i32, i32 }
1621
// CHECK-NEXT: ret { i32, i32 }
1722
match x {

tests/mir-opt/gvn_clone.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//@ test-mir-pass: GVN
2+
//@ compile-flags: -Zmir-enable-passes=+InstSimplify-before-inline
3+
4+
// Check if we have transformed the default clone to copy in the specific pipeline.
5+
6+
// EMIT_MIR gvn_clone.{impl#0}-clone.GVN.diff
7+
8+
// CHECK-LABEL: ::clone(
9+
// CHECK-NOT: = AllCopy { {{.*}} };
10+
// CHECK: _0 = copy (*_1);
11+
// CHECK: return;
12+
#[derive(Clone)]
13+
struct AllCopy {
14+
a: i32,
15+
b: u64,
16+
c: [i8; 3],
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
- // MIR for `<impl at $DIR/gvn_clone.rs:12:10: 12:15>::clone` before GVN
2+
+ // MIR for `<impl at $DIR/gvn_clone.rs:12:10: 12:15>::clone` after GVN
3+
4+
fn <impl at $DIR/gvn_clone.rs:12:10: 12:15>::clone(_1: &AllCopy) -> AllCopy {
5+
debug self => _1;
6+
let mut _0: AllCopy;
7+
let mut _2: i32;
8+
let mut _3: &i32;
9+
let _4: &i32;
10+
let mut _5: u64;
11+
let mut _6: &u64;
12+
let _7: &u64;
13+
let mut _8: [i8; 3];
14+
let mut _9: &[i8; 3];
15+
let _10: &[i8; 3];
16+
17+
bb0: {
18+
StorageLive(_2);
19+
StorageLive(_3);
20+
- StorageLive(_4);
21+
+ nop;
22+
_4 = &((*_1).0: i32);
23+
_3 = copy _4;
24+
- _2 = copy (*_3);
25+
+ _2 = copy ((*_1).0: i32);
26+
goto -> bb1;
27+
}
28+
29+
bb1: {
30+
StorageDead(_3);
31+
StorageLive(_5);
32+
StorageLive(_6);
33+
- StorageLive(_7);
34+
+ nop;
35+
_7 = &((*_1).1: u64);
36+
_6 = copy _7;
37+
- _5 = copy (*_6);
38+
+ _5 = copy ((*_1).1: u64);
39+
goto -> bb2;
40+
}
41+
42+
bb2: {
43+
StorageDead(_6);
44+
StorageLive(_8);
45+
StorageLive(_9);
46+
- StorageLive(_10);
47+
+ nop;
48+
_10 = &((*_1).2: [i8; 3]);
49+
_9 = copy _10;
50+
- _8 = copy (*_9);
51+
+ _8 = copy ((*_1).2: [i8; 3]);
52+
goto -> bb3;
53+
}
54+
55+
bb3: {
56+
StorageDead(_9);
57+
- _0 = AllCopy { a: move _2, b: move _5, c: move _8 };
58+
+ _0 = copy (*_1);
59+
StorageDead(_8);
60+
StorageDead(_5);
61+
StorageDead(_2);
62+
- StorageDead(_10);
63+
- StorageDead(_7);
64+
- StorageDead(_4);
65+
+ nop;
66+
+ nop;
67+
+ nop;
68+
return;
69+
}
70+
}
71+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
- // MIR for `all_copy` before GVN
2+
+ // MIR for `all_copy` after GVN
3+
4+
fn all_copy(_1: &AllCopy) -> AllCopy {
5+
debug v => _1;
6+
let mut _0: AllCopy;
7+
let _2: i32;
8+
let mut _5: i32;
9+
let mut _6: u64;
10+
let mut _7: [i8; 3];
11+
scope 1 {
12+
debug a => _2;
13+
let _3: u64;
14+
scope 2 {
15+
debug b => _3;
16+
let _4: [i8; 3];
17+
scope 3 {
18+
debug c => _4;
19+
}
20+
}
21+
}
22+
23+
bb0: {
24+
- StorageLive(_2);
25+
+ nop;
26+
_2 = copy ((*_1).0: i32);
27+
- StorageLive(_3);
28+
+ nop;
29+
_3 = copy ((*_1).1: u64);
30+
- StorageLive(_4);
31+
+ nop;
32+
_4 = copy ((*_1).2: [i8; 3]);
33+
StorageLive(_5);
34+
_5 = copy _2;
35+
StorageLive(_6);
36+
_6 = copy _3;
37+
StorageLive(_7);
38+
_7 = copy _4;
39+
- _0 = AllCopy { a: move _5, b: move _6, c: move _7 };
40+
+ _0 = copy (*_1);
41+
StorageDead(_7);
42+
StorageDead(_6);
43+
StorageDead(_5);
44+
- StorageDead(_4);
45+
- StorageDead(_3);
46+
- StorageDead(_2);
47+
+ nop;
48+
+ nop;
49+
+ nop;
50+
return;
51+
}
52+
}
53+

0 commit comments

Comments
 (0)