Skip to content

Commit 9044961

Browse files
committed
replace box_new in Box::new with write_via_move
requires lowering write_via_move during MIR building to make it just like an assignment
1 parent 79966ae commit 9044961

11 files changed

+154
-109
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: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// MIR for `box_new` after CleanupPostBorrowck
2+
3+
fn box_new(_1: T) -> Box<[T; 1024]> {
4+
debug x => _1;
5+
let mut _0: std::boxed::Box<[T; 1024]>;
6+
let mut _2: std::boxed::Box<std::mem::MaybeUninit<[T; 1024]>>;
7+
let mut _4: &mut std::mem::MaybeUninit<[T; 1024]>;
8+
let mut _5: &mut std::mem::MaybeUninit<[T; 1024]>;
9+
let _6: ();
10+
let mut _7: *mut [T; 1024];
11+
let mut _8: T;
12+
let mut _9: std::boxed::Box<std::mem::MaybeUninit<[T; 1024]>>;
13+
scope 1 {
14+
debug b => _2;
15+
let _3: *mut [T; 1024];
16+
scope 2 {
17+
debug ptr => _3;
18+
}
19+
}
20+
21+
bb0: {
22+
StorageLive(_2);
23+
_2 = Box::<[T; 1024]>::new_uninit() -> [return: bb1, unwind: bb7];
24+
}
25+
26+
bb1: {
27+
nop;
28+
StorageLive(_3);
29+
StorageLive(_4);
30+
StorageLive(_5);
31+
_5 = &mut (*_2);
32+
_4 = &mut (*_5);
33+
_3 = MaybeUninit::<[T; 1024]>::as_mut_ptr(move _4) -> [return: bb2, unwind: bb6];
34+
}
35+
36+
bb2: {
37+
StorageDead(_4);
38+
nop;
39+
StorageDead(_5);
40+
StorageLive(_6);
41+
StorageLive(_7);
42+
_7 = copy _3;
43+
StorageLive(_8);
44+
_8 = copy _1;
45+
(*_7) = [move _8; 1024];
46+
StorageDead(_8);
47+
StorageDead(_7);
48+
StorageDead(_6);
49+
StorageLive(_9);
50+
_9 = move _2;
51+
_0 = Box::<MaybeUninit<[T; 1024]>>::assume_init(move _9) -> [return: bb3, unwind: bb5];
52+
}
53+
54+
bb3: {
55+
StorageDead(_9);
56+
StorageDead(_3);
57+
drop(_2) -> [return: bb4, unwind: bb7];
58+
}
59+
60+
bb4: {
61+
StorageDead(_2);
62+
return;
63+
}
64+
65+
bb5 (cleanup): {
66+
drop(_9) -> [return: bb6, unwind terminate(cleanup)];
67+
}
68+
69+
bb6 (cleanup): {
70+
drop(_2) -> [return: bb7, unwind terminate(cleanup)];
71+
}
72+
73+
bb7 (cleanup): {
74+
resume;
75+
}
76+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
// Can't emit `built.after` here as that contains user type annotations which contain DefId that
8+
// change all the time.
9+
// EMIT_MIR write_via_move.box_new.CleanupPostBorrowck.after.mir
10+
// CHECK-LABEL: fn box_new
11+
#[inline(never)]
12+
fn box_new<T: Copy>(x: T) -> Box<[T; 1024]> {
13+
let mut b = Box::new_uninit();
14+
let ptr = mem::MaybeUninit::as_mut_ptr(&mut *b);
15+
// Ensure the array gets constructed directly into the deref'd pointer.
16+
// CHECK: (*[[TEMP1:_.+]]) = [{{(move|copy) _.+}}; 1024];
17+
unsafe { std::intrinsics::write_via_move(ptr, [x; 1024]) };
18+
unsafe { b.assume_init() }
19+
}
20+
21+
fn main() {
22+
box_new(0);
23+
}

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)