Skip to content

Commit f4a2559

Browse files
committed
Simplify the canonical clone method to copy
The optimized clone method ends up as the following MIR: ``` _2 = ((*_1).0: i32); _3 = ((*_1).1: u64); _4 = ((*_1).2: [i8; 3]); _0 = Foo { a: move _2, b: move _3, c: move _4 }; ``` We can transform this to: ``` _0 = (*_1); ```
1 parent 95b83d7 commit f4a2559

File tree

8 files changed

+228
-6
lines changed

8 files changed

+228
-6
lines changed

compiler/rustc_index/src/vec.rs

+10
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,16 @@ impl<I: Idx, T> IndexVec<I, T> {
190190
let min_new_len = elem.index() + 1;
191191
self.raw.resize_with(min_new_len, fill_value);
192192
}
193+
194+
#[inline]
195+
pub fn reset_all(&mut self, elem: T)
196+
where
197+
T: Copy,
198+
{
199+
for e in self.raw.iter_mut() {
200+
*e = elem;
201+
}
202+
}
193203
}
194204

195205
/// `IndexVec` is often used as a map, so it provides some map-like APIs.

compiler/rustc_mir_transform/src/instsimplify.rs

+79-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::simplify::simplify_duplicate_switch_targets;
44
use crate::take_array;
55
use rustc_ast::attr;
66
use rustc_hir::LangItem;
7+
use rustc_index::IndexVec;
78
use rustc_middle::bug;
89
use rustc_middle::mir::*;
910
use rustc_middle::ty::layout;
@@ -60,8 +61,8 @@ impl<'tcx> MirPass<'tcx> for InstSimplify {
6061
_ => {}
6162
}
6263
}
63-
6464
ctx.simplify_primitive_clone(block.terminator.as_mut().unwrap(), &mut block.statements);
65+
ctx.simplify_copy_like(&mut block.statements);
6566
ctx.simplify_intrinsic_assert(block.terminator.as_mut().unwrap());
6667
ctx.simplify_nounwind_call(block.terminator.as_mut().unwrap());
6768
simplify_duplicate_switch_targets(block.terminator.as_mut().unwrap());
@@ -207,6 +208,83 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> {
207208
}
208209
}
209210

211+
/// Transform `Aggregate(Adt, [(*_1).0, (*_1).1])` ==> `Copy(*_1)`.
212+
/// This comes from the simplification of the clone method by `simplify_primitive_clone`.
213+
fn simplify_copy_like(&self, statements: &mut Vec<Statement<'tcx>>) {
214+
let mut assignments = IndexVec::from_elem(None::<Place<'tcx>>, self.local_decls);
215+
for statement in statements {
216+
match statement.kind {
217+
StatementKind::Assign(box (dest, ref mut rvalue)) => {
218+
if let Rvalue::Aggregate(_, fields) = rvalue {
219+
let mut from_local = None;
220+
if fields.iter_enumerated().all(|(index, field)| {
221+
let Some(from_place) = field
222+
.place()
223+
.and_then(|p| p.as_local())
224+
.and_then(|l| assignments[l])
225+
else {
226+
return false;
227+
};
228+
// All fields must come from the same local.
229+
if let Some(from_local) = from_local {
230+
if from_place.local != from_local {
231+
return false;
232+
}
233+
} else {
234+
// We can only copy the same type.
235+
let Some(from_ty) =
236+
self.local_decls[from_place.local].ty.builtin_deref(false)
237+
else {
238+
return false;
239+
};
240+
let dest_ty = dest.ty(self.local_decls, self.tcx).ty;
241+
if dest_ty != from_ty {
242+
return false;
243+
};
244+
from_local = Some(from_place.local);
245+
}
246+
// For more complex scenarios, we expect to get this simplified projection within a complete pipeline.
247+
let [ProjectionElem::Deref, ProjectionElem::Field(from_index, _)] =
248+
*from_place.projection.as_slice()
249+
else {
250+
return false;
251+
};
252+
from_index == index
253+
}) {
254+
if let Some(local) = from_local {
255+
if self.should_simplify(&statement.source_info, rvalue) {
256+
*rvalue = Rvalue::Use(Operand::Copy(Place {
257+
local,
258+
projection: self
259+
.tcx
260+
.mk_place_elems(&[ProjectionElem::Deref]),
261+
}));
262+
}
263+
}
264+
}
265+
}
266+
// Collect available assignments, including those transformed from `Aggregate`.
267+
if let Some(local) = dest.as_local() {
268+
assignments[local] = if let Rvalue::Use(Operand::Copy(place)) = rvalue {
269+
Some(*place)
270+
} else {
271+
// This assignment generally comes from debuginfo (e.g., Ref),
272+
// but we still need to check if a local is being overridden.
273+
None
274+
};
275+
} else {
276+
// We don't handle projection, so we drop all previous assignments.
277+
assignments.reset_all(None);
278+
}
279+
}
280+
StatementKind::StorageLive(_) | StatementKind::StorageDead(_) => {}
281+
_ => {
282+
assignments.reset_all(None);
283+
}
284+
}
285+
}
286+
}
287+
210288
fn simplify_cast(&self, rvalue: &mut Rvalue<'tcx>) {
211289
if let Rvalue::Cast(kind, operand, cast_ty) = rvalue {
212290
let operand_ty = operand.ty(self.local_decls, self.tcx);

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/mir-opt/instsimplify/clone.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//@ test-mir-pass: InstSimplify-after-simplifycfg
2+
//@ compile-flags: -Zmir-enable-passes=+InstSimplify-before-inline,+ReferencePropagation,+SimplifyCfg-after-unreachable-enum-branching
3+
4+
// Check if we have transformed the default clone to copy in the specific pipeline.
5+
6+
// EMIT_MIR clone.{impl#0}-clone.InstSimplify-after-simplifycfg.diff
7+
8+
// CHECK-LABEL: ::clone(
9+
// CHECK-NOT: = AllCopy { {{.*}} };
10+
// CHECK: _0 = (*_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,32 @@
1+
- // MIR for `<impl at $DIR/clone.rs:12:10: 12:15>::clone` before InstSimplify-after-simplifycfg
2+
+ // MIR for `<impl at $DIR/clone.rs:12:10: 12:15>::clone` after InstSimplify-after-simplifycfg
3+
4+
fn <impl at $DIR/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+
_2 = ((*_1).0: i32);
20+
StorageLive(_5);
21+
_5 = ((*_1).1: u64);
22+
StorageLive(_8);
23+
_8 = ((*_1).2: [i8; 3]);
24+
- _0 = AllCopy { a: move _2, b: move _5, c: move _8 };
25+
+ _0 = (*_1);
26+
StorageDead(_8);
27+
StorageDead(_5);
28+
StorageDead(_2);
29+
return;
30+
}
31+
}
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// MIR for `clone_as_copy` after PreCodegen
2+
3+
fn clone_as_copy(_1: &CopyLikeStruct) -> CopyLikeStruct {
4+
debug v => _1;
5+
let mut _0: CopyLikeStruct;
6+
scope 1 (inlined <CopyLikeStruct as Clone>::clone) {
7+
debug self => _1;
8+
let _2: &AllCopy;
9+
scope 2 (inlined <AllCopy as Clone>::clone) {
10+
debug self => _2;
11+
}
12+
}
13+
14+
bb0: {
15+
StorageLive(_2);
16+
_2 = &((*_1).1: AllCopy);
17+
_0 = (*_1);
18+
StorageDead(_2);
19+
return;
20+
}
21+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@ compile-flags: -Cdebuginfo=full
2+
3+
// Check if we have transformed the nested clone to the copy in the complete pipeline.
4+
5+
#[derive(Clone)]
6+
struct AllCopy {
7+
a: i32,
8+
b: u64,
9+
c: [i8; 3],
10+
}
11+
12+
#[derive(Clone)]
13+
struct CopyLikeStruct {
14+
a: i32,
15+
b: AllCopy,
16+
c: [i8; 3],
17+
}
18+
19+
// EMIT_MIR clone_as_copy.clone_as_copy.PreCodegen.after.mir
20+
#[inline(never)]
21+
fn clone_as_copy(v: &CopyLikeStruct) -> CopyLikeStruct {
22+
// CHECK-LABEL: fn clone_as_copy(
23+
// CHECK-NOT: = AllCopy { {{.*}} };
24+
// CHECK-NOT: = CopyLikeStruct { {{.*}} };
25+
// CHECK: _0 = (*_1);
26+
// CHECK: return;
27+
v.clone()
28+
}

tests/mir-opt/pre-codegen/no_inlined_clone.{impl#0}-clone.PreCodegen.after.mir

+1-5
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,9 @@
33
fn <impl at $DIR/no_inlined_clone.rs:9:10: 9:15>::clone(_1: &Foo) -> Foo {
44
debug self => _1;
55
let mut _0: Foo;
6-
let mut _2: i32;
76

87
bb0: {
9-
StorageLive(_2);
10-
_2 = ((*_1).0: i32);
11-
_0 = Foo { a: move _2 };
12-
StorageDead(_2);
8+
_0 = (*_1);
139
return;
1410
}
1511
}

0 commit comments

Comments
 (0)