Skip to content

Commit 3c9faa0

Browse files
committed
Auto merge of #148190 - RalfJung:box_new, r=RalfJung
replace box_new with lower-level intrinsics The `box_new` intrinsic is super special: during THIR construction it turns into an `ExprKind::Box` (formerly known as the `box` keyword), which then during MIR building turns into a special instruction sequence that invokes the exchange_malloc lang item (which has a name from a different time) and a special MIR statement to represent a shallowly-initialized `Box` (which raises [interesting opsem questions](#97270)). This PR is the n-th attempt to get rid of `box_new`. That's non-trivial because it usually causes a perf regression: replacing `box_new` by naive unsafe code will incur extra copies in debug builds, making the resulting binaries a lot slower, and will generate a lot more MIR, making compilation measurably slower. Furthermore, `vec!` is a macro, so the exact code it expands to is highly relevant for borrow checking, type inference, and temporary scopes. To avoid those problems, this PR does its best to make the MIR almost exactly the same as what it was before. `box_new` is used in two places, `Box::new` and `vec!`: - For `Box::new` that is fairly easy: the `move_by_value` intrinsic is basically all we need. However, to avoid the extra copy that would usually be generated for the argument of a function call, we need to special-case this intrinsic during MIR building. That's what the first commit does. - `vec!` is a lot more tricky. As a macro, its details leak to stable code, so almost every variant I tried broke either type inference or the lifetimes of temporaries in some ui test or ended up accepting unsound code due to the borrow checker not enforcing all the constraints I hoped it would enforce. I ended up with a variant that involves a new intrinsic, `fn write_box_via_move<T>(b: Box<MaybeUninit<T>>, x: T) -> Box<MaybeUninit<T>>`, that writes a value into a `Box<MaybeUninit<T>>` and returns that box again. In exchange we can get rid of somewhat similar code in the lowering for `ExprKind::Box`, and the `exchange_malloc` lang item. (We can also get rid of `Rvalue::ShallowInitBox`; I didn't include that in this PR -- I think @cjgillot has a commit for this somewhere [around here](https://github.com/rust-lang/rust/pull/147862/commits).) See [here](#148190 (comment)) for the latest perf numbers. Most of the regressions are in deep-vector which consists entirely of an invocation of `vec!`, so any change to that macro affects this benchmark disproportionally. This is my first time even looking at MIR building code, so I am very low confidence in that part of the patch, in particular when it comes to scopes and drops and things like that. I also had do nerf some clippy tests because clippy gets confused by the new expansion of `vec!` so it makes fewer suggestions when `vec!` is involved. ### `vec!` FAQ - Why does `write_box_via_move` return the `Box` again? Because we need to expand `vec!` to a bunch of method invocations without any blocks or let-statements, or else the temporary scopes (and type inference) don't work out. - Why is `box_assume_init_into_vec_unsafe` (unsoundly!) a safe function? Because we can't use an unsafe block in `vec!` as that would necessarily also include the `$x` (due to it all being one big method invocation) and therefore interpret the user's code as being inside `unsafe`, which would be bad (and 10 years later, we still don't have safe blocks for macros like this). - Why does `write_box_via_move` use `Box` as input/output type, and not, say, raw pointers? Because that is the only way to get the correct behavior when `$x` panics or has control effects: we need the `Box` to be dropped in that case. (As a nice side-effect this also makes the intrinsic safe, which is imported as explained in the previous bullet.) - Can't we make it safe by having `write_box_via_move` return `Box<T>`? Yes we could, but there's no easy way for the intrinsic to convert its `Box<MaybeUninit<T>>` to a `Box<T>`. Transmuting would be unsound as the borrow checker would no longer properly enforce that lifetimes involved in a `vec!` invocation behave correctly. - Is this macro truly cursed? Yes, yes it is.
2 parents 71e0027 + 0df1764 commit 3c9faa0

100 files changed

Lines changed: 1236 additions & 1488 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

compiler/rustc_codegen_cranelift/example/mini_core.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -622,11 +622,6 @@ impl<T: ?Sized> Deref for Box<T> {
622622
}
623623
}
624624

625-
#[lang = "exchange_malloc"]
626-
unsafe fn allocate(size: usize, _align: usize) -> *mut u8 {
627-
unsafe { libc::malloc(size) }
628-
}
629-
630625
#[lang = "drop"]
631626
pub trait Drop {
632627
fn drop(&mut self);

compiler/rustc_codegen_gcc/example/mini_core.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -628,11 +628,6 @@ impl<T: ?Sized, A: Allocator> Deref for Box<T, A> {
628628
}
629629
}
630630

631-
#[lang = "exchange_malloc"]
632-
unsafe fn allocate(size: usize, _align: usize) -> *mut u8 {
633-
libc::malloc(size)
634-
}
635-
636631
#[lang = "drop"]
637632
pub trait Drop {
638633
fn drop(&mut self);

compiler/rustc_const_eval/src/check_consts/check.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -850,13 +850,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
850850
return;
851851
}
852852

853-
// This can be called on stable via the `vec!` macro.
854-
if tcx.is_lang_item(callee, LangItem::ExchangeMalloc) {
855-
self.check_op(ops::HeapAllocation);
856-
// Allow this call, skip all the checks below.
857-
return;
858-
}
859-
860853
// Intrinsics are language primitives, not regular calls, so treat them separately.
861854
if let Some(intrinsic) = tcx.intrinsic(callee) {
862855
if !tcx.is_const_fn(callee) {

compiler/rustc_const_eval/src/check_consts/ops.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -561,18 +561,6 @@ impl<'tcx> NonConstOp<'tcx> for Coroutine {
561561
}
562562
}
563563

564-
#[derive(Debug)]
565-
pub(crate) struct HeapAllocation;
566-
impl<'tcx> NonConstOp<'tcx> for HeapAllocation {
567-
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
568-
ccx.dcx().create_err(errors::UnallowedHeapAllocations {
569-
span,
570-
kind: ccx.const_kind(),
571-
teach: ccx.tcx.sess.teach(E0010),
572-
})
573-
}
574-
}
575-
576564
#[derive(Debug)]
577565
pub(crate) struct InlineAsm;
578566
impl<'tcx> NonConstOp<'tcx> for InlineAsm {

compiler/rustc_const_eval/src/errors.rs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -289,31 +289,6 @@ pub(crate) struct UnallowedOpInConstContext {
289289
pub msg: String,
290290
}
291291

292-
#[derive(Diagnostic)]
293-
#[diag(r#"allocations are not allowed in {$kind ->
294-
[const] constant
295-
[static] static
296-
[const_fn] constant function
297-
*[other] {""}
298-
}s"#, code = E0010)]
299-
pub(crate) struct UnallowedHeapAllocations {
300-
#[primary_span]
301-
#[label(
302-
r#"allocation not allowed in {$kind ->
303-
[const] constant
304-
[static] static
305-
[const_fn] constant function
306-
*[other] {""}
307-
}s"#
308-
)]
309-
pub span: Span,
310-
pub kind: ConstContext,
311-
#[note(
312-
"the runtime heap is not yet available at compile-time, so no runtime heap allocations can be created"
313-
)]
314-
pub teach: bool,
315-
}
316-
317292
#[derive(Diagnostic)]
318293
#[diag(r#"inline assembly is not allowed in {$kind ->
319294
[const] constant
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
#### Note: this error code is no longer emitted by the compiler.
2+
13
The value of statics and constants must be known at compile time, and they live
24
for the entire lifetime of a program. Creating a boxed value allocates memory on
35
the heap at runtime, and therefore cannot be done at compile time.
46

57
Erroneous code example:
68

7-
```compile_fail,E0010
9+
```ignore (no longer emitted)
810
const CON : Vec<i32> = vec![1, 2, 3];
911
```

compiler/rustc_error_codes/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
//
2121
// Do *not* remove entries from this list. Instead, just add a note to the corresponding markdown
2222
// file saying that this error is not emitted by the compiler any more (see E0001.md for an
23-
// example), and remove all code examples that do not build any more.
23+
// example), and remove all code examples that do not build any more by marking them
24+
// with `ignore (no longer emitted)`.
2425
#[macro_export]
2526
#[rustfmt::skip]
2627
macro_rules! error_codes {

compiler/rustc_hir/src/lang_items.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,6 @@ language_item_table! {
320320
FormatArgument, sym::format_argument, format_argument, Target::Struct, GenericRequirement::None;
321321
FormatArguments, sym::format_arguments, format_arguments, Target::Struct, GenericRequirement::None;
322322

323-
ExchangeMalloc, sym::exchange_malloc, exchange_malloc_fn, Target::Fn, GenericRequirement::None;
324323
DropInPlace, sym::drop_in_place, drop_in_place_fn, Target::Fn, GenericRequirement::Minimum(1);
325324
AllocLayout, sym::alloc_layout, alloc_layout, Target::Struct, GenericRequirement::None;
326325

compiler/rustc_hir_analysis/src/check/intrinsic.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
7777
| sym::autodiff
7878
| sym::bitreverse
7979
| sym::black_box
80-
| sym::box_new
8180
| sym::breakpoint
8281
| sym::bswap
8382
| sym::caller_location
@@ -223,6 +222,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
223222
| sym::wrapping_add
224223
| sym::wrapping_mul
225224
| sym::wrapping_sub
225+
| sym::write_box_via_move
226226
// tidy-alphabetical-end
227227
=> hir::Safety::Safe,
228228
_ => hir::Safety::Unsafe,
@@ -584,6 +584,13 @@ pub(crate) fn check_intrinsic_type(
584584
sym::write_via_move => {
585585
(1, 0, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], tcx.types.unit)
586586
}
587+
sym::write_box_via_move => {
588+
let t = param(0);
589+
let maybe_uninit_t = Ty::new_maybe_uninit(tcx, t);
590+
let box_mu_t = Ty::new_box(tcx, maybe_uninit_t);
591+
592+
(1, 0, vec![box_mu_t, param(0)], box_mu_t)
593+
}
587594

588595
sym::typed_swap_nonoverlapping => {
589596
(1, 0, vec![Ty::new_mut_ptr(tcx, param(0)); 2], tcx.types.unit)
@@ -689,8 +696,6 @@ pub(crate) fn check_intrinsic_type(
689696

690697
sym::ub_checks | sym::overflow_checks => (0, 0, Vec::new(), tcx.types.bool),
691698

692-
sym::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))),
693-
694699
// contract_check_requires::<C>(C) -> bool, where C: impl Fn() -> bool
695700
sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit),
696701
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
@@ -3110,6 +3110,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
31103110
{
31113111
let deref_kind = if checked_ty.is_box() {
31123112
// detect Box::new(..)
3113+
// FIXME: use `box_new` diagnostic item instead?
31133114
if let ExprKind::Call(box_new, [_]) = expr.kind
31143115
&& let ExprKind::Path(qpath) = &box_new.kind
31153116
&& let Res::Def(DefKind::AssocFn, fn_id) =

0 commit comments

Comments
 (0)