Skip to content

Commit 0977786

Browse files
Auto merge of #147907 - RalfJung:box_new, r=<try>
perf experiment: try to replace box_new with write_via_move
2 parents 79966ae + 9933e87 commit 0977786

13 files changed

+169
-116
lines changed

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use rustc_middle::mir::*;
99
use rustc_middle::span_bug;
1010
use rustc_middle::thir::*;
1111
use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty};
12-
use rustc_span::DUMMY_SP;
1312
use rustc_span::source_map::Spanned;
13+
use rustc_span::{DUMMY_SP, sym};
1414
use rustc_trait_selection::infer::InferCtxtExt;
1515
use tracing::{debug, instrument};
1616

@@ -365,6 +365,31 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
365365
None
366366
})
367367
}
368+
// The `write_via_move` intrinsic needs to be special-cased very early to avoid
369+
// introducing unnecessary copies that can be hard to remove again later:
370+
// `write_via_move(ptr, val)` becomes `*ptr = val` but without any dropping.
371+
ExprKind::Call { ty, fun, ref args, .. }
372+
if let ty::FnDef(def_id, _generic_args) = ty.kind()
373+
&& let Some(intrinsic) = this.tcx.intrinsic(def_id)
374+
&& intrinsic.name == sym::write_via_move =>
375+
{
376+
// We still have to evaluate the callee expression as normal (but we don't care
377+
// about its result).
378+
let _fun = unpack!(block = this.as_local_operand(block, fun));
379+
// The destination must have unit type (so we don't actually have to store anything
380+
// into it).
381+
assert!(destination.ty(&this.local_decls, this.tcx).ty.is_unit());
382+
383+
// Compile this to an assignment of the argument into the destination.
384+
let [ptr, val] = **args else {
385+
span_bug!(expr_span, "invalid write_via_move call")
386+
};
387+
let Some(ptr) = unpack!(block = this.as_local_operand(block, ptr)).place() else {
388+
span_bug!(expr_span, "invalid write_via_move call")
389+
};
390+
let ptr_deref = ptr.project_deeper(&[ProjectionElem::Deref], this.tcx);
391+
this.expr_into_dest(ptr_deref, block, val)
392+
}
368393
ExprKind::Call { ty: _, fun, ref args, from_hir_call, fn_span } => {
369394
let fun = unpack!(block = this.as_local_operand(block, fun));
370395
let args: Box<[_]> = args

compiler/rustc_mir_transform/src/lower_intrinsics.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -171,30 +171,7 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics {
171171
Some(target) => TerminatorKind::Goto { target },
172172
}
173173
}
174-
sym::write_via_move => {
175-
let target = target.unwrap();
176-
let Ok([ptr, val]) = take_array(args) else {
177-
span_bug!(
178-
terminator.source_info.span,
179-
"Wrong number of arguments for write_via_move intrinsic",
180-
);
181-
};
182-
let derefed_place = if let Some(place) = ptr.node.place()
183-
&& let Some(local) = place.as_local()
184-
{
185-
tcx.mk_place_deref(local.into())
186-
} else {
187-
span_bug!(
188-
terminator.source_info.span,
189-
"Only passing a local is supported"
190-
);
191-
};
192-
block.statements.push(Statement::new(
193-
terminator.source_info,
194-
StatementKind::Assign(Box::new((derefed_place, Rvalue::Use(val.node)))),
195-
));
196-
terminator.kind = TerminatorKind::Goto { target };
197-
}
174+
// `write_via_move` is already lowered during MIR building.
198175
sym::discriminant_value => {
199176
let target = target.unwrap();
200177
let Ok([arg]) = take_array(args) else {

library/alloc/src/alloc.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,11 +343,15 @@ unsafe impl Allocator for Global {
343343
}
344344

345345
/// The allocator for `Box`.
346+
///
347+
/// # Safety
348+
///
349+
/// `size` and `align` must satisfy the conditions in [`Layout::from_size_align`].
346350
#[cfg(not(no_global_oom_handling))]
347351
#[lang = "exchange_malloc"]
348352
#[inline]
349353
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
350-
unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
354+
pub(crate) unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
351355
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
352356
match Global.allocate(layout) {
353357
Ok(ptr) => ptr.as_mut_ptr(),

library/alloc/src/boxed.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ use core::task::{Context, Poll};
203203

204204
#[cfg(not(no_global_oom_handling))]
205205
use crate::alloc::handle_alloc_error;
206-
use crate::alloc::{AllocError, Allocator, Global, Layout};
206+
use crate::alloc::{AllocError, Allocator, Global, Layout, exchange_malloc};
207207
use crate::raw_vec::RawVec;
208208
#[cfg(not(no_global_oom_handling))]
209209
use crate::str::from_boxed_utf8_unchecked;
@@ -259,7 +259,15 @@ impl<T> Box<T> {
259259
#[rustc_diagnostic_item = "box_new"]
260260
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
261261
pub fn new(x: T) -> Self {
262-
return box_new(x);
262+
// SAFETY: the size and align of a valid type `T` are always valid for `Layout`.
263+
let ptr = unsafe {
264+
exchange_malloc(<T as SizedTypeProperties>::SIZE, <T as SizedTypeProperties>::ALIGN)
265+
} as *mut T;
266+
// Nothing below can panic so we do not have to worry about deallocating `ptr`.
267+
// SAFETY: we just allocated the box to store `x`.
268+
unsafe { core::intrinsics::write_via_move(ptr, x) };
269+
// SAFETY: we just initialized `b`.
270+
unsafe { mem::transmute(ptr) }
263271
}
264272

265273
/// Constructs a new box with uninitialized contents.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// MIR for `box_new` after built
2+
3+
| User Type Annotations
4+
| 0: user_ty: Canonical { value: TypeOf(DefId(3:682 ~ alloc[78d6]::boxed::{impl#0}::new_uninit), UserArgs { args: [^c_0], user_self_ty: Some(UserSelfTy { impl_def_id: DefId(3:679 ~ alloc[78d6]::boxed::{impl#0}), self_ty: std::boxed::Box<^c_1, ^c_2> }) }), max_universe: U0, variables: [Ty { ui: U0, sub_root: 0 }, Ty { ui: U0, sub_root: 1 }, Ty { ui: U0, sub_root: 2 }] }, span: $DIR/write_via_move.rs:11:17: 11:32, inferred_ty: fn() -> std::boxed::Box<std::mem::MaybeUninit<[T; 1024]>> {std::boxed::Box::<[T; 1024]>::new_uninit}
5+
| 1: user_ty: Canonical { value: TypeOf(DefId(2:2175 ~ core[cfc2]::mem::maybe_uninit::{impl#2}::as_mut_ptr), UserArgs { args: [^c_0], user_self_ty: Some(UserSelfTy { impl_def_id: DefId(2:2168 ~ core[cfc2]::mem::maybe_uninit::{impl#2}), self_ty: std::mem::MaybeUninit<^c_1> }) }), max_universe: U0, variables: [Ty { ui: U0, sub_root: 0 }, Ty { ui: U0, sub_root: 1 }] }, span: $DIR/write_via_move.rs:12:15: 12:43, inferred_ty: for<'a> fn(&'a mut std::mem::MaybeUninit<[T; 1024]>) -> *mut [T; 1024] {std::mem::MaybeUninit::<[T; 1024]>::as_mut_ptr}
6+
|
7+
fn box_new(_1: T) -> Box<[T; 1024]> {
8+
debug x => _1;
9+
let mut _0: std::boxed::Box<[T; 1024]>;
10+
let mut _2: std::boxed::Box<std::mem::MaybeUninit<[T; 1024]>>;
11+
let mut _4: &mut std::mem::MaybeUninit<[T; 1024]>;
12+
let mut _5: &mut std::mem::MaybeUninit<[T; 1024]>;
13+
let _6: ();
14+
let mut _7: *mut [T; 1024];
15+
let mut _8: T;
16+
let mut _9: std::boxed::Box<std::mem::MaybeUninit<[T; 1024]>>;
17+
scope 1 {
18+
debug b => _2;
19+
let _3: *mut [T; 1024];
20+
scope 2 {
21+
debug ptr => _3;
22+
}
23+
}
24+
25+
bb0: {
26+
StorageLive(_2);
27+
_2 = Box::<[T; 1024]>::new_uninit() -> [return: bb1, unwind: bb7];
28+
}
29+
30+
bb1: {
31+
FakeRead(ForLet(None), _2);
32+
StorageLive(_3);
33+
StorageLive(_4);
34+
StorageLive(_5);
35+
_5 = &mut (*_2);
36+
_4 = &mut (*_5);
37+
_3 = MaybeUninit::<[T; 1024]>::as_mut_ptr(move _4) -> [return: bb2, unwind: bb6];
38+
}
39+
40+
bb2: {
41+
StorageDead(_4);
42+
FakeRead(ForLet(None), _3);
43+
StorageDead(_5);
44+
StorageLive(_6);
45+
StorageLive(_7);
46+
_7 = copy _3;
47+
StorageLive(_8);
48+
_8 = copy _1;
49+
(*_7) = [move _8; 1024];
50+
StorageDead(_8);
51+
StorageDead(_7);
52+
StorageDead(_6);
53+
StorageLive(_9);
54+
_9 = move _2;
55+
_0 = Box::<MaybeUninit<[T; 1024]>>::assume_init(move _9) -> [return: bb3, unwind: bb5];
56+
}
57+
58+
bb3: {
59+
StorageDead(_9);
60+
StorageDead(_3);
61+
drop(_2) -> [return: bb4, unwind: bb7];
62+
}
63+
64+
bb4: {
65+
StorageDead(_2);
66+
return;
67+
}
68+
69+
bb5 (cleanup): {
70+
drop(_9) -> [return: bb6, unwind terminate(cleanup)];
71+
}
72+
73+
bb6 (cleanup): {
74+
drop(_2) -> [return: bb7, unwind terminate(cleanup)];
75+
}
76+
77+
bb7 (cleanup): {
78+
resume;
79+
}
80+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//! Ensure we don't generate unnecessary copys for `write_via_move`.
2+
//@ compile-flags: -Zmir-opt-level=0
3+
#![feature(core_intrinsics)]
4+
5+
use std::mem;
6+
7+
// EMIT_MIR write_via_move.box_new.built.after.mir
8+
// CHECK-LABEL: fn box_new
9+
#[inline(never)]
10+
fn box_new<T: Copy>(x: T) -> Box<[T; 1024]> {
11+
let mut b = Box::new_uninit();
12+
let ptr = mem::MaybeUninit::as_mut_ptr(&mut *b);
13+
// Ensure the array gets constructed directly into the deref'd pointer.
14+
// CHECK: (*[[TEMP1:_.+]]) = [{{(move|copy) _.+}}; 1024];
15+
unsafe { std::intrinsics::write_via_move(ptr, [x; 1024]) };
16+
unsafe { b.assume_init() }
17+
}
18+
19+
fn main() {
20+
box_new(0);
21+
}

tests/mir-opt/inline_coroutine_body.run2-{closure#0}.Inline.panic-unwind.diff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@
213213
+ StorageLive(_42);
214214
+ _42 = Option::<()>::None;
215215
+ _35 = copy ((*_37).0: std::option::Option<()>);
216-
+ ((*_37).0: std::option::Option<()>) = copy _42;
216+
+ ((*_37).0: std::option::Option<()>) = move _42;
217217
+ StorageDead(_42);
218218
+ StorageLive(_43);
219219
+ _43 = discriminant(_35);

tests/mir-opt/lower_intrinsics.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -197,17 +197,6 @@ pub fn read_via_copy_uninhabited(r: &Never) -> Never {
197197
unsafe { core::intrinsics::read_via_copy(r) }
198198
}
199199

200-
// EMIT_MIR lower_intrinsics.write_via_move_string.LowerIntrinsics.diff
201-
pub fn write_via_move_string(r: &mut String, v: String) {
202-
// CHECK-LABEL: fn write_via_move_string(
203-
// CHECK: [[ptr:_.*]] = &raw mut (*_1);
204-
// CHECK: [[tmp:_.*]] = move _2;
205-
// CHECK: (*[[ptr]]) = move [[tmp]];
206-
// CHECK: return;
207-
208-
unsafe { core::intrinsics::write_via_move(r, v) }
209-
}
210-
211200
pub enum Never {}
212201

213202
// EMIT_MIR lower_intrinsics.ptr_offset.LowerIntrinsics.diff

tests/mir-opt/lower_intrinsics.write_via_move_string.LowerIntrinsics.panic-abort.diff

Lines changed: 0 additions & 31 deletions
This file was deleted.

tests/mir-opt/lower_intrinsics.write_via_move_string.LowerIntrinsics.panic-unwind.diff

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)