Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rustc_ty_utils] Add the LLVM noalias parameter attribute to drop_in_place in certain cases. #103614

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 }
/// fn drop_in_place(to_drop: *mut Foo) {
/// let mut value = &mut *to_drop;
/// // ... 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>) {}