Skip to content

Commit 00a684a

Browse files
committed
add init_box_via_move intrinsic in preparation for using it in vec!
1 parent 16641f8 commit 00a684a

File tree

25 files changed

+337
-472
lines changed

25 files changed

+337
-472
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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
132132
| sym::forget
133133
| sym::frem_algebraic
134134
| sym::fsub_algebraic
135+
| sym::init_box_via_move
135136
| sym::is_val_statically_known
136137
| sym::log2f16
137138
| sym::log2f32
@@ -553,6 +554,12 @@ pub(crate) fn check_intrinsic_type(
553554
sym::write_via_move => {
554555
(1, 0, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], tcx.types.unit)
555556
}
557+
sym::init_box_via_move => {
558+
let t = param(0);
559+
let maybe_uninit_t = Ty::new_maybe_uninit(tcx, t);
560+
561+
(1, 0, vec![Ty::new_box(tcx, maybe_uninit_t), param(0)], Ty::new_box(tcx, t))
562+
}
556563

557564
sym::typed_swap_nonoverlapping => {
558565
(1, 0, vec![Ty::new_mut_ptr(tcx, param(0)); 2], tcx.types.unit)

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

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

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

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -365,30 +365,115 @@ 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.
368+
// Some intrinsics are handled here because they desperately want to avoid introducing
369+
// unnecessary copies.
371370
ExprKind::Call { ty, fun, ref args, .. }
372-
if let ty::FnDef(def_id, _generic_args) = ty.kind()
371+
if let ty::FnDef(def_id, generic_args) = ty.kind()
373372
&& let Some(intrinsic) = this.tcx.intrinsic(def_id)
374-
&& intrinsic.name == sym::write_via_move =>
373+
&& matches!(intrinsic.name, sym::write_via_move | sym::init_box_via_move) =>
375374
{
376375
// We still have to evaluate the callee expression as normal (but we don't care
377376
// about its result).
378377
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());
382378

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)
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+
}
392477
}
393478
ExprKind::Call { ty: _, fun, ref args, from_hir_call, fn_span } => {
394479
let fun = unpack!(block = this.as_local_operand(block, fun));

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 & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -342,23 +342,6 @@ unsafe impl Allocator for Global {
342342
}
343343
}
344344

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

364347
#[cfg(not(no_global_oom_handling))]

library/alloc/src/boxed.rs

Lines changed: 52 additions & 14 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,
@@ -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, exchange_malloc};
206+
use crate::alloc::{AllocError, Allocator, Global, Layout};
207207
use crate::raw_vec::RawVec;
208208
#[cfg(not(no_global_oom_handling))]
209209
use crate::str::from_boxed_utf8_unchecked;
@@ -237,11 +237,29 @@ pub struct Box<
237237
/// the newly allocated memory. This is an intrinsic to avoid unnecessary copies.
238238
///
239239
/// This is the surface syntax for `box <expr>` expressions.
240-
#[doc(hidden)]
241240
#[rustc_intrinsic]
242241
#[unstable(feature = "liballoc_internals", issue = "none")]
243242
pub fn box_new<T>(x: T) -> Box<T>;
244243

244+
/// Writes `x` into `b`, then returns `b` at its new type`.
245+
///
246+
/// This is needed for `vec!`, which can't afford any extra copies of the argument (or else debug
247+
/// builds regress), has to be written fully as a call chain without `let` (or else the temporary
248+
/// lifetimes of the arguments change), and can't use an `unsafe` block as that would then also
249+
/// include the user-provided `$x`.
250+
#[rustc_intrinsic]
251+
#[unstable(feature = "liballoc_internals", issue = "none")]
252+
pub fn init_box_via_move<T>(b: Box<MaybeUninit<T>>, x: T) -> Box<T>;
253+
254+
/// Helper for `vec!` to ensure type inferences work correctly (which it wouldn't if we
255+
/// inlined the `as` cast).
256+
#[doc(hidden)]
257+
#[unstable(feature = "liballoc_internals", issue = "none")]
258+
#[inline(always)]
259+
pub fn box_array_into_vec<T, const N: usize>(b: Box<[T; N]>) -> crate::vec::Vec<T> {
260+
(b as Box<[T]>).into_vec()
261+
}
262+
245263
impl<T> Box<T> {
246264
/// Allocates memory on the heap and then places `x` into it.
247265
///
@@ -259,15 +277,9 @@ impl<T> Box<T> {
259277
#[rustc_diagnostic_item = "box_new"]
260278
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
261279
pub fn new(x: T) -> Self {
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) }
280+
let b = Box::new_uninit();
281+
// We could do this with `write_via_move`, but may as well use `init_box_via_move`.
282+
init_box_via_move(b, x)
271283
}
272284

273285
/// Constructs a new box with uninitialized contents.
@@ -285,9 +297,35 @@ impl<T> Box<T> {
285297
#[cfg(not(no_global_oom_handling))]
286298
#[stable(feature = "new_uninit", since = "1.82.0")]
287299
#[must_use]
288-
#[inline]
300+
#[inline(always)]
301+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
289302
pub fn new_uninit() -> Box<mem::MaybeUninit<T>> {
290-
Self::new_uninit_in(Global)
303+
// This is the same as `Self::new_uninit_in(Global)`, but manually inlined and polymorphized
304+
// to help with build performance.
305+
306+
/// Put the bulk of the code in a monomorphic function.
307+
///
308+
/// Safety: size and align need to be safe for `Layout::from_size_align_unchecked`.
309+
#[inline]
310+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
311+
#[lang = "exchange_malloc"]
312+
unsafe fn alloc_box(size: usize, align: usize) -> *mut u8 {
313+
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
314+
match Global.allocate(layout) {
315+
Ok(ptr) => ptr.as_mut_ptr(),
316+
Err(_) => handle_alloc_error(layout),
317+
}
318+
}
319+
320+
// SAFETY:
321+
// - The size and align of a valid type `T` are always valid for `Layout`.
322+
// - If `allocate` succeeds, the returned pointer exactly matches what `Box` needs.
323+
unsafe {
324+
mem::transmute(alloc_box(
325+
<T as SizedTypeProperties>::SIZE,
326+
<T as SizedTypeProperties>::ALIGN,
327+
))
328+
}
291329
}
292330

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

library/alloc/src/macros.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ macro_rules! vec {
4848
);
4949
($($x:expr),+ $(,)?) => (
5050
<[_]>::into_vec(
51-
// Using the intrinsic produces a dramatic improvement in stack usage for
52-
// unoptimized programs using this code path to construct large Vecs.
5351
$crate::boxed::box_new([$($x),+])
5452
)
5553
);

src/tools/miri/tests/pass/vec.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ fn extract_if() {
178178
}
179179
}
180180

181+
fn vec_macro_cleanup() {
182+
fn panic<T>() -> T {
183+
panic!()
184+
}
185+
// Ensure all memory gets deallocated on a panic: the `Box` we construct, and the `Box`
186+
// constructed inside `vec!` to eventually turn into a `Vec`.
187+
std::panic::catch_unwind(|| {
188+
let _v = vec![Box::new(0), panic()];
189+
})
190+
.unwrap_err();
191+
}
192+
181193
fn main() {
182194
assert_eq!(vec_reallocate().len(), 5);
183195

@@ -209,4 +221,5 @@ fn main() {
209221
reverse();
210222
miri_issue_2759();
211223
extract_if();
224+
vec_macro_cleanup();
212225
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
thread 'main' ($TID) panicked at tests/pass/vec.rs:LL:CC:
3+
explicit panic
4+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
5+
note: in Miri, you may have to set `MIRIFLAGS=-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect

0 commit comments

Comments
 (0)