Skip to content
29 changes: 28 additions & 1 deletion compiler/rustc_ty_utils/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ fn adjust_for_rust_scalar<'tcx>(
layout: TyAndLayout<'tcx>,
offset: Size,
is_return: bool,
is_drop_target: bool,
) {
// Booleans are always a noundef i1 that needs to be zero-extended.
if scalar.is_bool() {
Expand Down Expand Up @@ -275,6 +276,18 @@ fn adjust_for_rust_scalar<'tcx>(
attrs.set(ArgAttribute::NoAliasMutRef);
}
}

// If this is the argument to `drop_in_place`, the contents of which we fully control as the
// compiler, then we mark this argument as `noalias`, aligned, and dereferenceable. (The
// standard library documents the necessary requirements to uphold these attributes for code
// that calls this method directly.) This can enable better optimizations, such as argument
// promotion.
if is_drop_target {
attrs.set(ArgAttribute::NoAlias);
attrs.set(ArgAttribute::NonNull);
attrs.pointee_size = pointee.size;
attrs.pointee_align = Some(pointee.align);
}
}
}

Expand Down Expand Up @@ -331,10 +344,16 @@ fn fn_abi_new_uncached<'tcx>(
use SpecAbi::*;
let rust_abi = matches!(sig.abi, RustIntrinsic | PlatformIntrinsic | Rust | RustCall);

let is_drop_in_place = match (cx.tcx.lang_items().drop_in_place_fn(), fn_def_id) {
(Some(drop_in_place_fn), Some(fn_def_id)) => drop_in_place_fn == fn_def_id,
_ => false,
};
Comment on lines +347 to +350
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let is_drop_in_place = match (cx.tcx.lang_items().drop_in_place_fn(), fn_def_id) {
(Some(drop_in_place_fn), Some(fn_def_id)) => drop_in_place_fn == fn_def_id,
_ => false,
};
let is_drop_in_place = cx.tcx.lang_items().drop_in_place_fn() == Some(fn_def_id);

let arg_of = |ty: Ty<'tcx>, arg_idx: Option<usize>| -> Result<_, FnAbiError<'tcx>> {
let span = tracing::debug_span!("arg_of");
let _entered = span.enter();
let is_return = arg_idx.is_none();
let is_drop_target = is_drop_in_place && arg_idx == Some(0);

let layout = cx.layout_of(ty)?;
let layout = if force_thin_self_ptr && arg_idx == Some(0) {
Expand All @@ -348,7 +367,15 @@ fn fn_abi_new_uncached<'tcx>(

let mut arg = ArgAbi::new(cx, layout, |layout, scalar, offset| {
let mut attrs = ArgAttributes::new();
adjust_for_rust_scalar(*cx, &mut attrs, scalar, *layout, offset, is_return);
adjust_for_rust_scalar(
*cx,
&mut attrs,
scalar,
*layout,
offset,
is_return,
is_drop_target,
);
attrs
});

Expand Down
30 changes: 24 additions & 6 deletions library/core/src/ptr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,23 +436,41 @@ mod mut_ptr;
///
/// # Safety
///
/// Behavior is undefined if any of the following conditions are violated:
/// Immediately upon executing, `drop_in_place` takes out a mutable borrow on the
/// pointed-to-value. Effectively, this function is implemented like so:
///
/// ```
/// # struct Foo { x: i32 }
/// unsafe fn drop_in_place(to_drop: *mut Foo) {
/// let mut value = &mut *to_drop;
/// // ... drop the fields of `value` ...
/// }
/// ```
Comment on lines +439 to +448
Copy link
Member

@RalfJung RalfJung Nov 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we actually do is stronger than that: we have an &mut function argument. That makes a difference because for noalias, scope matters.

So if you want to write this in code, I'd suggest something like

/// ``` /// # struct Foo { x: i32 } /// unsafe fn drop_in_place(to_drop: *mut Foo) { /// drop_in_place_inner(&mut *to_drop); /// unsafe fn drop_in_place_inner(to_drop: &mut Foo) { /// // ... drop the fields of `value` ... /// } /// } /// ``` 
///
/// This implies that the behavior is undefined if any of the following
/// conditions are violated:
///
/// * `to_drop` must be [valid] for both reads and writes.
///
/// * `to_drop` must be properly aligned.
/// * `to_drop` must be properly aligned, even if T has size 0.
///
/// * `to_drop` must be nonnull, even if T has size 0.
Comment on lines +455 to +457
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// * `to_drop` must be properly aligned, even if T has size 0.
///
/// * `to_drop` must be nonnull, even if T has size 0.
/// * `to_drop` must be properly aligned, even if `T` has size 0.
///
/// * `to_drop` must be nonnull, even if `T` has size 0.
///
/// * The value `to_drop` points to must be valid for dropping, which may mean
/// it must uphold additional invariants. These invariants depend on the type
/// of the value being dropped. For instance, when dropping a Box, the box's
/// pointer to the heap must be valid.
///
/// * The value `to_drop` points to must be valid for dropping, which may mean it must uphold
/// additional invariants - this is type-dependent.
/// * While `drop_in_place` is executing, the only way to access parts of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RalfJung Would it be better to say

Suggested change
/// * While `drop_in_place` is executing, the only way to access parts of
/// * As soon as `drop_in_place` begins executing, the only way to access parts of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference you are getting at here?

One drop_in_place returns, old references can be used again, at least as far as the aliasing model goes.

/// `to_drop` is through the `&mut self` references supplied to the
/// `Drop::drop` methods that `drop_in_place` invokes.
///
/// Additionally, if `T` is not [`Copy`], using the pointed-to value after
/// calling `drop_in_place` can cause undefined behavior. Note that `*to_drop =
/// foo` counts as a use because it will cause the value to be dropped
/// again. [`write()`] can be used to overwrite data without causing it to be
/// dropped.
///
/// Note that even if `T` has size `0`, the pointer must be non-null and properly aligned.
///
/// [valid]: self#safety
///
/// # Examples
Expand Down
34 changes: 34 additions & 0 deletions src/test/codegen/drop-in-place-noalias.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Tests that the compiler can mark `drop_in_place` as `noalias` when safe to do so.

#![crate_type="lib"]

use std::hint::black_box;

// CHECK: define{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}Foo{{.*}}({{.*}}noalias {{.*}} align 4 dereferenceable(12){{.*}})

#[repr(C)]
pub struct Foo {
a: i32,
b: i32,
c: i32,
}

impl Drop for Foo {
#[inline(never)]
fn drop(&mut self) {
black_box(self.a);
}
}

extern {
fn bar();
fn baz(foo: Foo);
}

pub fn haha() {
let foo = Foo { a: 1, b: 2, c: 3 };
unsafe {
bar();
baz(foo);
}
}
2 changes: 1 addition & 1 deletion src/test/codegen/noalias-box-off.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
#![crate_type = "lib"]

// CHECK-LABEL: @box_should_not_have_noalias_if_disabled(
// CHECK-NOT: noalias
// CHECK-NOT: noalias{{.*}}%
#[no_mangle]
pub fn box_should_not_have_noalias_if_disabled(_b: Box<u8>) {}