Skip to content

Commit 7edf381

Browse files
Auto merge of #147907 - RalfJung:box_new, r=<try>
perf experiment: try to get rid of box_new
2 parents 23fced0 + 8fca418 commit 7edf381

34 files changed

+452
-575
lines changed

compiler/rustc_borrowck/src/type_check/mod.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,15 +1537,17 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
15371537
}
15381538
}
15391539
CastKind::Transmute => {
1540-
let ty_from = op.ty(self.body, tcx);
1541-
match ty_from.kind() {
1542-
ty::Pat(base, _) if base == ty => {}
1543-
_ => span_mirbug!(
1544-
self,
1545-
rvalue,
1546-
"Unexpected CastKind::Transmute {ty_from:?} -> {ty:?}, which is not permitted in Analysis MIR",
1547-
),
1548-
}
1540+
// FIXME: `init_box_via_move` lowering really wants to use this.
1541+
// What do we have to do here?
1542+
// let ty_from = op.ty(self.body, tcx);
1543+
// match ty_from.kind() {
1544+
// ty::Pat(base, _) if base == ty => {}
1545+
// _ => span_mirbug!(
1546+
// self,
1547+
// rvalue,
1548+
// "Unexpected CastKind::Transmute {ty_from:?} -> {ty:?}, which is not permitted in Analysis MIR",
1549+
// ),
1550+
// }
15491551
}
15501552
CastKind::Subtype => {
15511553
bug!("CastKind::Subtype shouldn't exist in borrowck")

compiler/rustc_hir_analysis/src/check/intrinsic.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
7676
| sym::autodiff
7777
| sym::bitreverse
7878
| sym::black_box
79-
| sym::box_new
8079
| sym::breakpoint
8180
| sym::bswap
8281
| sym::caller_location
@@ -132,6 +131,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
132131
| sym::forget
133132
| sym::frem_algebraic
134133
| sym::fsub_algebraic
134+
| sym::init_box_via_move
135135
| sym::is_val_statically_known
136136
| sym::log2f16
137137
| sym::log2f32
@@ -553,6 +553,12 @@ pub(crate) fn check_intrinsic_type(
553553
sym::write_via_move => {
554554
(1, 0, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], tcx.types.unit)
555555
}
556+
sym::init_box_via_move => {
557+
let t = param(0);
558+
let maybe_uninit_t = Ty::new_maybe_uninit(tcx, t);
559+
560+
(1, 0, vec![Ty::new_box(tcx, maybe_uninit_t), param(0)], Ty::new_box(tcx, t))
561+
}
556562

557563
sym::typed_swap_nonoverlapping => {
558564
(1, 0, vec![Ty::new_mut_ptr(tcx, param(0)); 2], tcx.types.unit)
@@ -645,8 +651,6 @@ pub(crate) fn check_intrinsic_type(
645651

646652
sym::ub_checks => (0, 0, Vec::new(), tcx.types.bool),
647653

648-
sym::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))),
649-
650654
// contract_check_requires::<C>(C) -> bool, where C: impl Fn() -> bool
651655
sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit),
652656
sym::contract_check_ensures => {

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3024,6 +3024,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
30243024
{
30253025
let deref_kind = if checked_ty.is_box() {
30263026
// detect Box::new(..)
3027+
// FIXME: use `box_new` diagnostic item instead?
30273028
if let ExprKind::Call(box_new, [_]) = expr.kind
30283029
&& let ExprKind::Path(qpath) = &box_new.kind
30293030
&& let Res::Def(DefKind::AssocFn, fn_id) =

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

Lines changed: 111 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,116 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
365365
None
366366
})
367367
}
368+
// Some intrinsics are handled here because they desperately want to avoid introducing
369+
// unnecessary copies.
370+
ExprKind::Call { ty, fun, ref args, .. }
371+
if let ty::FnDef(def_id, generic_args) = ty.kind()
372+
&& let Some(intrinsic) = this.tcx.intrinsic(def_id)
373+
&& matches!(intrinsic.name, sym::write_via_move | sym::init_box_via_move) =>
374+
{
375+
// We still have to evaluate the callee expression as normal (but we don't care
376+
// about its result).
377+
let _fun = unpack!(block = this.as_local_operand(block, fun));
378+
379+
match intrinsic.name {
380+
sym::write_via_move => {
381+
// `write_via_move(ptr, val)` becomes `*ptr = val` but without any dropping.
382+
383+
// The destination must have unit type (so we don't actually have to store anything
384+
// into it).
385+
assert!(destination.ty(&this.local_decls, this.tcx).ty.is_unit());
386+
387+
// Compile this to an assignment of the argument into the destination.
388+
let [ptr, val] = **args else {
389+
span_bug!(expr_span, "invalid write_via_move call")
390+
};
391+
let Some(ptr) = unpack!(block = this.as_local_operand(block, ptr)).place()
392+
else {
393+
span_bug!(expr_span, "invalid write_via_move call")
394+
};
395+
let ptr_deref = ptr.project_deeper(&[ProjectionElem::Deref], this.tcx);
396+
this.expr_into_dest(ptr_deref, block, val)
397+
}
398+
sym::init_box_via_move => {
399+
// `write_via_move(b, val)` becomes
400+
// ```
401+
// *transmute::<_, *mut T>(b) = val;
402+
// transmute::<_, Box<T>>(b)
403+
// ```
404+
let t = generic_args.type_at(0);
405+
let [b, val] = **args else {
406+
span_bug!(expr_span, "invalid init_box_via_move call")
407+
};
408+
let Some(b) = unpack!(block = this.as_local_operand(block, b)).place()
409+
else {
410+
span_bug!(expr_span, "invalid init_box_via_move call")
411+
};
412+
// Project to the pointer inside `b`. We have to keep `b` in scope to ensure
413+
// it gets dropped. After the first projection we can transmute which is
414+
// easier.
415+
let ty::Adt(box_adt_def, box_adt_args) =
416+
b.ty(&this.local_decls, this.tcx).ty.kind()
417+
else {
418+
span_bug!(expr_span, "invalid init_box_via_move call")
419+
};
420+
let unique_field =
421+
this.tcx.adt_def(box_adt_def.did()).non_enum_variant().fields
422+
[rustc_abi::FieldIdx::ZERO]
423+
.did;
424+
let Some(unique_def) =
425+
this.tcx.type_of(unique_field).instantiate_identity().ty_adt_def()
426+
else {
427+
span_bug!(
428+
this.tcx.def_span(unique_field),
429+
"expected Box to contain Unique"
430+
)
431+
};
432+
let unique_ty =
433+
Ty::new_adt(this.tcx, unique_def, this.tcx.mk_args(&[box_adt_args[0]]));
434+
let b_field = b.project_deeper(
435+
&[ProjectionElem::Field(rustc_abi::FieldIdx::ZERO, unique_ty)],
436+
this.tcx,
437+
);
438+
// `ptr` is `b` transmuted to `*mut T`.
439+
let ptr_ty = Ty::new_mut_ptr(this.tcx, t);
440+
let ptr = this.local_decls.push(LocalDecl::new(ptr_ty, expr_span));
441+
this.cfg.push(
442+
block,
443+
Statement::new(source_info, StatementKind::StorageLive(ptr)),
444+
);
445+
this.cfg.push_assign(
446+
block,
447+
source_info,
448+
Place::from(ptr),
449+
// Needs to be a `Copy` so that `b` still gets dropped if `val` panics.
450+
Rvalue::Cast(CastKind::Transmute, Operand::Copy(b_field), ptr_ty),
451+
);
452+
// Store `val` into `ptr`.
453+
let ptr_deref =
454+
Place::from(ptr).project_deeper(&[ProjectionElem::Deref], this.tcx);
455+
unpack!(block = this.expr_into_dest(ptr_deref, block, val));
456+
// Return `ptr` transmuted to `Box<T>`.
457+
this.cfg.push_assign(
458+
block,
459+
source_info,
460+
destination,
461+
Rvalue::Cast(
462+
CastKind::Transmute,
463+
// Move from `b` so that does not get dropped any more.
464+
Operand::Move(b),
465+
Ty::new_box(this.tcx, t),
466+
),
467+
);
468+
// We don't need `ptr` any more.
469+
this.cfg.push(
470+
block,
471+
Statement::new(source_info, StatementKind::StorageDead(ptr)),
472+
);
473+
block.unit()
474+
}
475+
_ => rustc_middle::bug!(),
476+
}
477+
}
368478
ExprKind::Call { ty: _, fun, ref args, from_hir_call, fn_span } => {
369479
let fun = unpack!(block = this.as_local_operand(block, fun));
370480
let args: Box<[_]> = args

compiler/rustc_mir_build/src/thir/cx/expr.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use rustc_middle::ty::{
2020
self, AdtKind, GenericArgs, InlineConstArgs, InlineConstArgsParts, ScalarInt, Ty, UpvarArgs,
2121
};
2222
use rustc_middle::{bug, span_bug};
23-
use rustc_span::{Span, sym};
23+
use rustc_span::Span;
2424
use tracing::{debug, info, instrument, trace};
2525

2626
use crate::errors::*;
@@ -385,24 +385,6 @@ impl<'tcx> ThirBuildCx<'tcx> {
385385
from_hir_call: true,
386386
fn_span: expr.span,
387387
}
388-
} else if let ty::FnDef(def_id, _) = self.typeck_results.expr_ty(fun).kind()
389-
&& let Some(intrinsic) = self.tcx.intrinsic(def_id)
390-
&& intrinsic.name == sym::box_new
391-
{
392-
// We don't actually evaluate `fun` here, so make sure that doesn't miss any side-effects.
393-
if !matches!(fun.kind, hir::ExprKind::Path(_)) {
394-
span_bug!(
395-
expr.span,
396-
"`box_new` intrinsic can only be called via path expression"
397-
);
398-
}
399-
let value = &args[0];
400-
return Expr {
401-
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
402-
ty: expr_ty,
403-
span: expr.span,
404-
kind: ExprKind::Box { value: self.mirror_expr(value) },
405-
};
406388
} else {
407389
// Tuple-like ADTs are represented as ExprKind::Call. We convert them here.
408390
let adt_data = if let hir::ExprKind::Path(ref qpath) = fun.kind

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 {

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,7 @@ symbols! {
12331233
infer_static_outlives_requirements,
12341234
inherent_associated_types,
12351235
inherit,
1236+
init_box_via_move,
12361237
initial,
12371238
inlateout,
12381239
inline,

library/alloc/src/alloc.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -342,19 +342,6 @@ unsafe impl Allocator for Global {
342342
}
343343
}
344344

345-
/// The allocator for `Box`.
346-
#[cfg(not(no_global_oom_handling))]
347-
#[lang = "exchange_malloc"]
348-
#[inline]
349-
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
350-
unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
351-
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
352-
match Global.allocate(layout) {
353-
Ok(ptr) => ptr.as_mut_ptr(),
354-
Err(_) => handle_alloc_error(layout),
355-
}
356-
}
357-
358345
// # Allocation error handler
359346

360347
#[cfg(not(no_global_oom_handling))]

library/alloc/src/boxed.rs

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ use core::fmt;
192192
use core::future::Future;
193193
use core::hash::{Hash, Hasher};
194194
use core::marker::{Tuple, Unsize};
195-
use core::mem::{self, SizedTypeProperties};
195+
use core::mem::{self, MaybeUninit, SizedTypeProperties};
196196
use core::ops::{
197197
AsyncFn, AsyncFnMut, AsyncFnOnce, CoerceUnsized, Coroutine, CoroutineState, Deref, DerefMut,
198198
DerefPure, DispatchFromDyn, LegacyReceiver,
@@ -233,14 +233,24 @@ pub struct Box<
233233
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
234234
>(Unique<T>, A);
235235

236-
/// Constructs a `Box<T>` by calling the `exchange_malloc` lang item and moving the argument into
237-
/// the newly allocated memory. This is an intrinsic to avoid unnecessary copies.
236+
/// Writes `x` into `b`, then returns `b` at its new type`.
238237
///
239-
/// This is the surface syntax for `box <expr>` expressions.
240-
#[doc(hidden)]
238+
/// This is needed for `vec!`, which can't afford any extra copies of the argument (or else debug
239+
/// builds regress), has to be written fully as a call chain without `let` (or else the temporary
240+
/// lifetimes of the arguments change), and can't use an `unsafe` block as that would then also
241+
/// include the user-provided `$x`.
241242
#[rustc_intrinsic]
242243
#[unstable(feature = "liballoc_internals", issue = "none")]
243-
pub fn box_new<T>(x: T) -> Box<T>;
244+
pub fn init_box_via_move<T>(b: Box<MaybeUninit<T>>, x: T) -> Box<T>;
245+
246+
/// Helper for `vec!` to ensure type inferences work correctly (which it wouldn't if we
247+
/// inlined the `as` cast).
248+
#[doc(hidden)]
249+
#[unstable(feature = "liballoc_internals", issue = "none")]
250+
#[inline(always)]
251+
pub fn box_array_into_vec<T, const N: usize>(b: Box<[T; N]>) -> crate::vec::Vec<T> {
252+
(b as Box<[T]>).into_vec()
253+
}
244254

245255
impl<T> Box<T> {
246256
/// Allocates memory on the heap and then places `x` into it.
@@ -259,7 +269,9 @@ impl<T> Box<T> {
259269
#[rustc_diagnostic_item = "box_new"]
260270
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
261271
pub fn new(x: T) -> Self {
262-
return box_new(x);
272+
let b = Box::new_uninit();
273+
// We could do this with `write_via_move`, but may as well use `init_box_via_move`.
274+
init_box_via_move(b, x)
263275
}
264276

265277
/// Constructs a new box with uninitialized contents.
@@ -277,9 +289,34 @@ impl<T> Box<T> {
277289
#[cfg(not(no_global_oom_handling))]
278290
#[stable(feature = "new_uninit", since = "1.82.0")]
279291
#[must_use]
280-
#[inline]
292+
#[inline(always)]
293+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
281294
pub fn new_uninit() -> Box<mem::MaybeUninit<T>> {
282-
Self::new_uninit_in(Global)
295+
// This is the same as `Self::new_uninit_in(Global)`, but manually inlined and polymorphized
296+
// to help with build performance.
297+
298+
/// Put the bulk of the code in a monomorphic function.
299+
///
300+
/// Safety: size and align need to be safe for `Layout::from_size_align_unchecked`.
301+
#[inline]
302+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
303+
unsafe fn alloc_box(size: usize, align: usize) -> *mut u8 {
304+
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
305+
match Global.allocate(layout) {
306+
Ok(ptr) => ptr.as_mut_ptr(),
307+
Err(_) => handle_alloc_error(layout),
308+
}
309+
}
310+
311+
// SAFETY:
312+
// - The size and align of a valid type `T` are always valid for `Layout`.
313+
// - If `allocate` succeeds, the returned pointer exactly matches what `Box` needs.
314+
unsafe {
315+
mem::transmute(alloc_box(
316+
<T as SizedTypeProperties>::SIZE,
317+
<T as SizedTypeProperties>::ALIGN,
318+
))
319+
}
283320
}
284321

285322
/// Constructs a new `Box` with uninitialized contents, with the memory

library/alloc/src/macros.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,15 @@ macro_rules! vec {
4747
$crate::vec::from_elem($elem, $n)
4848
);
4949
($($x:expr),+ $(,)?) => (
50-
<[_]>::into_vec(
51-
// Using the intrinsic produces a dramatic improvement in stack usage for
50+
$crate::boxed::box_array_into_vec(
51+
// Using `init_box_via_move` produces a dramatic improvement in stack usage for
5252
// unoptimized programs using this code path to construct large Vecs.
53-
$crate::boxed::box_new([$($x),+])
53+
// We can't use `write_via_move` because this entire invocation has to remain a call
54+
// chain without `let` bindings, or else the temporary scopes change and things break;
55+
// it also has to all be safe since `safe { ... }` blocks sadly are not a thing and we
56+
// must not wrap `$x` in `unsafe` (also, wrapping `$x` in a safe block has a good chance
57+
// of introducing extra moves so might not be a good call either).
58+
$crate::boxed::init_box_via_move($crate::boxed::Box::new_uninit(), [$($x),+])
5459
)
5560
);
5661
}

0 commit comments

Comments
 (0)