Skip to content

Commit c5e50d7

Browse files
committed
Auto merge of rust-lang#139088 - spastorino:ergonomic-ref-counting-2, r=<try>
Ergonomic ref counting: optimize away clones when possible This PR build on top of rust-lang#134797. It optimizes codegen of ergonomic ref-counting when the type being `use`d is only known to be copy after monomorphization. We avoid codening a clone and generate bitwise copy instead. RFC: rust-lang/rfcs#3680 Tracking issue: rust-lang#132290 Project goal: rust-lang/rust-project-goals#107 r? `@nikomatsakis` This PR could better sit on top of rust-lang#131650 but as it did not land yet I've decided to just do minimal changes. It may be the case that doing what I'm doing regress the performance and we may need to go the full route of rust-lang#131650. cc `@saethlin` in this regard.
2 parents 8c35f4a + da1df90 commit c5e50d7

File tree

7 files changed

+187
-60
lines changed

7 files changed

+187
-60
lines changed

compiler/rustc_codegen_ssa/src/mir/mod.rs

+70-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::iter;
33
use rustc_index::IndexVec;
44
use rustc_index::bit_set::DenseBitSet;
55
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
6-
use rustc_middle::mir::{Local, UnwindTerminateReason, traversal};
6+
use rustc_middle::mir::{Body, Local, UnwindTerminateReason, traversal};
77
use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, HasTypingEnv, TyAndLayout};
88
use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeFoldable, TypeVisitableExt};
99
use rustc_middle::{bug, mir, span_bug};
@@ -170,19 +170,25 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
170170
) {
171171
assert!(!instance.args.has_infer());
172172

173+
let tcx = cx.tcx();
173174
let llfn = cx.get_fn(instance);
174175

175-
let mir = cx.tcx().instance_mir(instance.def);
176+
let mir = tcx.instance_mir(instance.def);
177+
let mir = instance.instantiate_mir_and_normalize_erasing_regions(
178+
tcx,
179+
ty::TypingEnv::fully_monomorphized(),
180+
ty::EarlyBinder::bind(mir.clone()),
181+
);
176182

177183
let fn_abi = cx.fn_abi_of_instance(instance, ty::List::empty());
178184
debug!("fn_abi: {:?}", fn_abi);
179185

180-
if cx.tcx().codegen_fn_attrs(instance.def_id()).flags.contains(CodegenFnAttrFlags::NAKED) {
186+
if tcx.codegen_fn_attrs(instance.def_id()).flags.contains(CodegenFnAttrFlags::NAKED) {
181187
crate::mir::naked_asm::codegen_naked_asm::<Bx>(cx, &mir, instance);
182188
return;
183189
}
184190

185-
let debug_context = cx.create_function_debug_context(instance, fn_abi, llfn, mir);
191+
let debug_context = cx.create_function_debug_context(instance, fn_abi, llfn, &mir);
186192

187193
let start_llbb = Bx::append_block(cx, llfn, "start");
188194
let mut start_bx = Bx::build(cx, start_llbb);
@@ -194,7 +200,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
194200
}
195201

196202
let cleanup_kinds =
197-
base::wants_new_eh_instructions(cx.tcx().sess).then(|| analyze::cleanup_kinds(mir));
203+
base::wants_new_eh_instructions(tcx.sess).then(|| analyze::cleanup_kinds(&mir));
198204

199205
let cached_llbbs: IndexVec<mir::BasicBlock, CachedLlbb<Bx::BasicBlock>> =
200206
mir.basic_blocks
@@ -204,6 +210,8 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
204210
})
205211
.collect();
206212

213+
let mir = tcx.arena.alloc(optimize_use_clone::<Bx>(cx, mir));
214+
207215
let mut fx = FunctionCx {
208216
instance,
209217
mir,
@@ -217,7 +225,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
217225
cleanup_kinds,
218226
landing_pads: IndexVec::from_elem(None, &mir.basic_blocks),
219227
funclets: IndexVec::from_fn_n(|_| None, mir.basic_blocks.len()),
220-
cold_blocks: find_cold_blocks(cx.tcx(), mir),
228+
cold_blocks: find_cold_blocks(tcx, mir),
221229
locals: locals::Locals::empty(),
222230
debug_context,
223231
per_local_var_debug_info: None,
@@ -233,7 +241,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
233241
fx.compute_per_local_var_debug_info(&mut start_bx).unzip();
234242
fx.per_local_var_debug_info = per_local_var_debug_info;
235243

236-
let traversal_order = traversal::mono_reachable_reverse_postorder(mir, cx.tcx(), instance);
244+
let traversal_order = traversal::mono_reachable_reverse_postorder(mir, tcx, instance);
237245
let memory_locals = analyze::non_ssa_locals(&fx, &traversal_order);
238246

239247
// Allocate variable and temp allocas
@@ -310,6 +318,61 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
310318
}
311319
}
312320

321+
// FIXME: Move this function to mir::transform when post-mono MIR passes land.
322+
fn optimize_use_clone<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
323+
cx: &'a Bx::CodegenCx,
324+
mut mir: Body<'tcx>,
325+
) -> Body<'tcx> {
326+
let tcx = cx.tcx();
327+
328+
if tcx.features().ergonomic_clones() {
329+
for bb in mir.basic_blocks.as_mut() {
330+
let mir::TerminatorKind::Call {
331+
args,
332+
destination,
333+
target,
334+
call_source: mir::CallSource::Use,
335+
..
336+
} = &bb.terminator().kind
337+
else {
338+
continue;
339+
};
340+
341+
// CallSource::Use calls always use 1 argument.
342+
assert_eq!(args.len(), 1);
343+
let arg = &args[0];
344+
345+
// These types are easily available from locals, so check that before
346+
// doing DefId lookups to figure out what we're actually calling.
347+
let arg_ty = arg.node.ty(&mir.local_decls, tcx);
348+
349+
let ty::Ref(_region, inner_ty, mir::Mutability::Not) = *arg_ty.kind() else { continue };
350+
351+
if !tcx.type_is_copy_modulo_regions(cx.typing_env(), inner_ty) {
352+
continue;
353+
}
354+
355+
let Some(arg_place) = arg.node.place() else { continue };
356+
357+
let destination_block = target.unwrap();
358+
359+
bb.statements.push(mir::Statement {
360+
source_info: bb.terminator().source_info,
361+
kind: mir::StatementKind::Assign(Box::new((
362+
*destination,
363+
mir::Rvalue::Use(mir::Operand::Copy(
364+
arg_place.project_deeper(&[mir::ProjectionElem::Deref], tcx),
365+
)),
366+
))),
367+
});
368+
369+
bb.terminator_mut().kind = mir::TerminatorKind::Goto { target: destination_block };
370+
}
371+
}
372+
373+
mir
374+
}
375+
313376
/// Produces, for each argument, a `Value` pointing at the
314377
/// argument's value. As arguments are places, these are always
315378
/// indirect.

compiler/rustc_middle/src/mir/syntax.rs

+2
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,8 @@ pub enum CallSource {
652652
/// Other types of desugaring that did not come from the HIR, but we don't care about
653653
/// for diagnostics (yet).
654654
Misc,
655+
/// Use of value, generating a clone function call
656+
Use,
655657
/// Normal function call, no special source
656658
Normal,
657659
}

compiler/rustc_mir_build/src/builder/expr/into.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
328328
destination,
329329
target: Some(success),
330330
unwind: UnwindAction::Unreachable,
331-
call_source: CallSource::Misc,
331+
call_source: CallSource::Use,
332332
fn_span: expr_span,
333333
},
334334
);

rustfmt.toml

+4
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,8 @@ ignore = [
5454
# Code automatically generated and included.
5555
"compiler/rustc_codegen_gcc/src/intrinsic/archs.rs",
5656
"compiler/rustc_codegen_gcc/example",
57+
58+
# Rustfmt doesn't support use closures yet
59+
"tests/mir-opt/ergonomic-clones/closure.rs",
60+
"tests/codegen/ergonomic-clones/closure.rs",
5761
]
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//@ compile-flags: -C no-prepopulate-passes -Copt-level=0 -Zmir-opt-level=0
2+
3+
#![crate_type = "lib"]
4+
5+
#![feature(ergonomic_clones)]
6+
#![allow(incomplete_features)]
7+
8+
use std::clone::UseCloned;
9+
10+
pub fn ergonomic_clone_closure_move() -> String {
11+
let s = String::from("hi");
12+
13+
// CHECK-NOT: ; call core::clone::impls::<impl core::clone::Clone for String>::clone
14+
let cl = use || s;
15+
cl()
16+
}
17+
18+
#[derive(Clone)]
19+
struct Foo;
20+
21+
impl UseCloned for Foo {}
22+
23+
pub fn ergonomic_clone_closure_use_cloned() -> Foo {
24+
let f = Foo;
25+
26+
// CHECK: ; call <closure::Foo as core::clone::Clone>::clone
27+
let f1 = use || f;
28+
29+
// CHECK: ; call <closure::Foo as core::clone::Clone>::clone
30+
let f2 = use || f;
31+
32+
f
33+
}
34+
35+
pub fn ergonomic_clone_closure_copy() -> i32 {
36+
let i = 1;
37+
38+
// CHECK-NOT: ; call core::clone::impls::<impl core::clone::Clone for i32>::clone
39+
let i1 = use || i;
40+
41+
// CHECK-NOT: ; call core::clone::impls::<impl core::clone::Clone for i32>::clone
42+
let i2 = use || i;
43+
44+
i
45+
}
46+
47+
pub fn ergonomic_clone_closure_use_cloned_generics<T: UseCloned>(f: T) -> T {
48+
// CHECK-NOT: ; call core::clone::impls::<impl core::clone::Clone for i32>::clone
49+
let f1 = use || f;
50+
51+
// CHECK-NOT: ; call core::clone::impls::<impl core::clone::Clone for i32>::clone
52+
let f2 = use || f;
53+
54+
f
55+
}

tests/crashes/129372.rs

-52
This file was deleted.
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#![crate_type = "lib"]
2+
#![feature(ergonomic_clones)]
3+
#![allow(incomplete_features)]
4+
5+
use std::clone::UseCloned;
6+
7+
pub fn ergonomic_clone_closure_move() -> String {
8+
// CHECK-LABEL: fn ergonomic_clone_closure_move(
9+
// CHECK: _0 = move (_1.0: std::string::String);
10+
// CHECK-NOT: <String as Clone>::clone
11+
let s = String::from("hi");
12+
13+
let cl = use || s;
14+
cl()
15+
}
16+
17+
#[derive(Clone)]
18+
struct Foo;
19+
20+
impl UseCloned for Foo {}
21+
22+
pub fn ergonomic_clone_closure_use_cloned() -> Foo {
23+
// CHECK-LABEL: fn ergonomic_clone_closure_use_cloned(
24+
// CHECK: <Foo as Clone>::clone
25+
let f = Foo;
26+
27+
let f1 = use || f;
28+
29+
let f2 = use || f;
30+
31+
f
32+
}
33+
34+
pub fn ergonomic_clone_closure_copy() -> i32 {
35+
// CHECK-LABEL: fn ergonomic_clone_closure_copy(
36+
// CHECK: _0 = copy ((*_1).0: i32);
37+
// CHECK-NOT: <i32 as Clone>::clone
38+
let i = 1;
39+
40+
let i1 = use || i;
41+
42+
let i2 = use || i;
43+
44+
i
45+
}
46+
47+
pub fn ergonomic_clone_closure_use_cloned_generics<T: UseCloned>(f: T) -> T {
48+
// CHECK-LABEL: fn ergonomic_clone_closure_use_cloned_generics(
49+
// CHECK: <T as Clone>::clone
50+
let f1 = use || f;
51+
52+
let f2 = use || f;
53+
54+
f
55+
}

0 commit comments

Comments
 (0)