Skip to content

Tell LLVM about read-only captures #145259

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
19 changes: 17 additions & 2 deletions compiler/rustc_codegen_llvm/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::attributes::{self, llfn_attrs_from_instance};
use crate::builder::Builder;
use crate::context::CodegenCx;
use crate::llvm::{self, Attribute, AttributePlace};
use crate::llvm_util;
use crate::type_::Type;
use crate::type_of::LayoutLlvmExt;
use crate::value::Value;
Expand All @@ -41,12 +42,13 @@ trait ArgAttributesExt {
const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] =
[(ArgAttribute::InReg, llvm::AttributeKind::InReg)];

const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 5] = [
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 6] = [
(ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias),
(ArgAttribute::NoCapture, llvm::AttributeKind::NoCapture),
(ArgAttribute::NonNull, llvm::AttributeKind::NonNull),
(ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly),
(ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef),
(ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly),
];

fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 8]> {
Expand Down Expand Up @@ -82,6 +84,10 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'
}
for (attr, llattr) in OPTIMIZATION_ATTRIBUTES {
if regular.contains(attr) {
// captures(address, read_provenance) is only available since LLVM 21.
if attr == ArgAttribute::CapturesReadOnly && llvm_util::get_version() < (21, 0, 0) {
continue;
}
attrs.push(llattr.create_attr(cx.llcx));
}
}
Expand Down Expand Up @@ -500,7 +506,16 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
}
}
PassMode::Indirect { attrs, meta_attrs: None, on_stack: false } => {
apply(attrs);
let i = apply(attrs);
if cx.sess().opts.optimize != config::OptLevel::No
&& llvm_util::get_version() >= (21, 0, 0)
{
attributes::apply_to_llfn(
llfn,
llvm::AttributePlace::Argument(i),
&[llvm::AttributeKind::DeadOnReturn.create_attr(cx.llcx)],
);
}
}
PassMode::Indirect { attrs, meta_attrs: Some(meta_attrs), on_stack } => {
assert!(!on_stack);
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_codegen_llvm/src/llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ pub(crate) enum AttributeKind {
FnRetThunkExtern = 41,
Writable = 42,
DeadOnUnwind = 43,
DeadOnReturn = 44,
CapturesReadOnly = 45,
}

/// LLVMIntPredicate
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ enum class LLVMRustAttributeKind {
FnRetThunkExtern = 41,
Writable = 42,
DeadOnUnwind = 43,
DeadOnReturn = 44,
CapturesReadOnly = 45,
};

static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
Expand Down Expand Up @@ -369,6 +371,14 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
return Attribute::Writable;
case LLVMRustAttributeKind::DeadOnUnwind:
return Attribute::DeadOnUnwind;
case LLVMRustAttributeKind::DeadOnReturn:
#if LLVM_VERSION_GE(21, 0)
return Attribute::DeadOnReturn;
#else
report_fatal_error("DeadOnReturn attribute requires LLVM 21 or later");
#endif
case LLVMRustAttributeKind::CapturesReadOnly:
report_fatal_error("Should be handled separately");
}
report_fatal_error("bad LLVMRustAttributeKind");
}
Expand Down Expand Up @@ -423,6 +433,11 @@ LLVMRustCreateAttrNoValue(LLVMContextRef C, LLVMRustAttributeKind RustAttr) {
if (RustAttr == LLVMRustAttributeKind::NoCapture) {
return wrap(Attribute::getWithCaptureInfo(*unwrap(C), CaptureInfo::none()));
}
if (RustAttr == LLVMRustAttributeKind::CapturesReadOnly) {
return wrap(Attribute::getWithCaptureInfo(*unwrap(C),
CaptureInfo(CaptureComponents::Address |
CaptureComponents::ReadProvenance)));
}
#endif
return wrap(Attribute::get(*unwrap(C), fromRust(RustAttr)));
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_target/src/callconv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ mod attr_impl {
const ReadOnly = 1 << 4;
const InReg = 1 << 5;
const NoUndef = 1 << 6;
const CapturesReadOnly = 1 << 7;
}
}
rustc_data_structures::external_bitflags_debug! { ArgAttribute }
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ty_utils/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ fn adjust_for_rust_scalar<'tcx>(

if matches!(kind, PointerKind::SharedRef { frozen: true }) && !is_return {
attrs.set(ArgAttribute::ReadOnly);
attrs.set(ArgAttribute::CapturesReadOnly);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions tests/codegen-llvm/addr-of-mutate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Test for the absence of `readonly` on the argument when it is mutated via `&raw const`.
// See <https://github.com/rust-lang/rust/issues/111502>.

// CHECK: i8 @foo(ptr noalias{{( nocapture)?}} noundef align 1{{( captures\(none\))?}} dereferenceable(128) %x)
// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef align 1{{( captures\(none\))?}} dereferenceable(128) %x)
#[no_mangle]
pub fn foo(x: [u8; 128]) -> u8 {
let ptr = core::ptr::addr_of!(x).cast_mut();
Expand All @@ -15,7 +15,7 @@ pub fn foo(x: [u8; 128]) -> u8 {
x[0]
}

// CHECK: i1 @second(ptr noalias{{( nocapture)?}} noundef align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
// CHECK: i1 @second(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
#[no_mangle]
pub unsafe fn second(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
let b_bool_ptr = core::ptr::addr_of!(a_ptr_and_b.1.1).cast_mut();
Expand All @@ -24,7 +24,7 @@ pub unsafe fn second(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
}

// If going through a deref (and there are no other mutating accesses), then `readonly` is fine.
// CHECK: i1 @third(ptr noalias{{( nocapture)?}} noundef readonly align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef readonly align {{[0-9]+}}{{( captures\(none\))?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
#[no_mangle]
pub unsafe fn third(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
let b_bool_ptr = core::ptr::addr_of!((*a_ptr_and_b.0).1).cast_mut();
Expand Down
31 changes: 31 additions & 0 deletions tests/codegen-llvm/dead_on_return.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//@ compile-flags: -C opt-level=3
//@ min-llvm-version: 21

#![crate_type = "lib"]
#![allow(unused_assignments, unused_variables)]

// Check that the old string is deallocated, but a new one is not initialized.
#[unsafe(no_mangle)]
pub fn test_str_new(mut s: String) {
// CHECK-LABEL: @test_str_new
// CHECK: __rust_dealloc
// CHECK-NOT: store
s = String::new();
}

#[unsafe(no_mangle)]
pub fn test_str_take(mut x: String) -> String {
// CHECK-LABEL: @test_str_take
// CHECK-NEXT: {{.*}}:
// CHECK-NEXT: call void @llvm.memcpy
// CHECK-NEXT: ret
core::mem::take(&mut x)
}

#[unsafe(no_mangle)]
pub fn test_array_store(mut x: [u32; 100]) {
// CHECK-LABEL: @test_array_store
// CHECK-NEXT: {{.*}}:
// CHECK-NEXT: ret
x[0] = 1;
}
22 changes: 11 additions & 11 deletions tests/codegen-llvm/function-arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub fn option_nonzero_int(x: Option<NonZero<u64>>) -> Option<NonZero<u64>> {
x
}

// CHECK: @readonly_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
// CHECK: @readonly_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(4) %_1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn readonly_borrow(_: &i32) {}
Expand All @@ -91,12 +91,12 @@ pub fn readonly_borrow_ret() -> &'static i32 {
loop {}
}

// CHECK: @static_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
// CHECK: @static_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(4) %_1)
// static borrow may be captured
#[no_mangle]
pub fn static_borrow(_: &'static i32) {}

// CHECK: @named_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
// CHECK: @named_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(4) %_1)
// borrow with named lifetime may be captured
#[no_mangle]
pub fn named_borrow<'r>(_: &'r i32) {}
Expand Down Expand Up @@ -129,21 +129,21 @@ pub fn mutable_borrow_ret() -> &'static mut i32 {
// <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>.
pub fn mutable_notunpin_borrow(_: &mut NotUnpin) {}

// CHECK: @notunpin_borrow(ptr noalias noundef readonly align 4 dereferenceable(4) %_1)
// CHECK: @notunpin_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(4) %_1)
// But `&NotUnpin` behaves perfectly normal.
#[no_mangle]
pub fn notunpin_borrow(_: &NotUnpin) {}

// CHECK: @indirect_struct(ptr noalias{{( nocapture)?}} noundef readonly align 4{{( captures\(none\))?}} dereferenceable(32) %_1)
// CHECK: @indirect_struct(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef readonly align 4{{( captures\(none\))?}} dereferenceable(32) %_1)
#[no_mangle]
pub fn indirect_struct(_: S) {}

// CHECK: @borrowed_struct(ptr noalias noundef readonly align 4 dereferenceable(32) %_1)
// CHECK: @borrowed_struct(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable(32) %_1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn borrowed_struct(_: &S) {}

// CHECK: @option_borrow(ptr noalias noundef readonly align 4 dereferenceable_or_null(4) %_x)
// CHECK: @option_borrow(ptr noalias noundef readonly align 4{{( captures\(address, read_provenance\))}} dereferenceable_or_null(4) %_x)
#[no_mangle]
pub fn option_borrow(_x: Option<&i32>) {}

Expand Down Expand Up @@ -185,7 +185,7 @@ pub fn _box(x: Box<i32>) -> Box<i32> {
// With a custom allocator, it should *not* have `noalias`. (See
// <https://github.com/rust-lang/miri/issues/3341> for why.) The second argument is the allocator,
// which is a reference here that still carries `noalias` as usual.
// CHECK: @_box_custom(ptr noundef nonnull align 4 %x.0, ptr noalias noundef nonnull readonly align 1 %x.1)
// CHECK: @_box_custom(ptr noundef nonnull align 4 %x.0, ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))}} %x.1)
#[no_mangle]
pub fn _box_custom(x: Box<i32, &std::alloc::Global>) {
drop(x)
Expand All @@ -208,7 +208,7 @@ pub fn struct_return() -> S {
#[no_mangle]
pub fn helper(_: usize) {}

// CHECK: @slice(ptr noalias noundef nonnull readonly align 1 %_1.0, [[USIZE]] noundef %_1.1)
// CHECK: @slice(ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))}} %_1.0, [[USIZE]] noundef %_1.1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn slice(_: &[u8]) {}
Expand All @@ -227,7 +227,7 @@ pub fn unsafe_slice(_: &[UnsafeInner]) {}
#[no_mangle]
pub fn raw_slice(_: *const [u8]) {}

// CHECK: @str(ptr noalias noundef nonnull readonly align 1 %_1.0, [[USIZE]] noundef %_1.1)
// CHECK: @str(ptr noalias noundef nonnull readonly align 1{{( captures\(address, read_provenance\))}} %_1.0, [[USIZE]] noundef %_1.1)
// FIXME #25759 This should also have `nocapture`
#[no_mangle]
pub fn str(_: &[u8]) {}
Expand Down Expand Up @@ -259,7 +259,7 @@ pub fn trait_option(x: Option<Box<dyn Drop + Unpin>>) -> Option<Box<dyn Drop + U
x
}

// CHECK: { ptr, [[USIZE]] } @return_slice(ptr noalias noundef nonnull readonly align 2 %x.0, [[USIZE]] noundef %x.1)
// CHECK: { ptr, [[USIZE]] } @return_slice(ptr noalias noundef nonnull readonly align 2{{( captures\(address, read_provenance\))}} %x.0, [[USIZE]] noundef %x.1)
#[no_mangle]
pub fn return_slice(x: &[u16]) -> &[u16] {
x
Expand Down
2 changes: 1 addition & 1 deletion tests/codegen-llvm/loongarch-abi/loongarch64-lp64d-abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ pub struct IntDoubleInt {
c: i32,
}

// CHECK: define void @f_int_double_int_s_arg(ptr noalias{{( nocapture)?}} noundef align 8{{( captures\(none\))?}} dereferenceable(24) %a)
// CHECK: define void @f_int_double_int_s_arg(ptr{{( dead_on_return)?}} noalias{{( nocapture)?}} noundef align 8{{( captures\(none\))?}} dereferenceable(24) %a)
#[no_mangle]
pub extern "C" fn f_int_double_int_s_arg(a: IntDoubleInt) {}

Expand Down
2 changes: 1 addition & 1 deletion tests/codegen-llvm/range-attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub fn enum2_value(x: Enum2) -> Enum2 {
x
}

// CHECK: noundef [[USIZE]] @takes_slice(ptr noalias noundef nonnull readonly align 4 %x.0, [[USIZE]] noundef %x.1)
// CHECK: noundef [[USIZE]] @takes_slice(ptr {{.*}} %x.0, [[USIZE]] noundef %x.1)
#[no_mangle]
pub fn takes_slice(x: &[i32]) -> usize {
x.len()
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/abi/debug.generic.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,7 @@ error: fn_abi_of(assoc_test) = FnAbi {
},
mode: Direct(
ArgAttributes {
regular: NoAlias | NonNull | ReadOnly | NoUndef,
regular: NoAlias | NonNull | ReadOnly | NoUndef | CapturesReadOnly,
arg_ext: None,
pointee_size: Size(2 bytes),
pointee_align: Some(
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/abi/debug.riscv64.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,7 @@ error: fn_abi_of(assoc_test) = FnAbi {
},
mode: Direct(
ArgAttributes {
regular: NoAlias | NonNull | ReadOnly | NoUndef,
regular: NoAlias | NonNull | ReadOnly | NoUndef | CapturesReadOnly,
arg_ext: None,
pointee_size: Size(2 bytes),
pointee_align: Some(
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/codegen/equal-pointers-unequal/as-cast/segfault.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//@ known-bug: #107975
//@ compile-flags: -Copt-level=2
//@ run-pass
//@ run-fail
//@ ignore-backends: gcc
//@ min-llvm-version: 21

// https://github.com/rust-lang/rust/issues/107975#issuecomment-1431758601

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//@ known-bug: #107975
//@ compile-flags: -Copt-level=2
//@ run-pass
//@ run-fail
//@ ignore-backends: gcc
//@ min-llvm-version: 21

// https://github.com/rust-lang/rust/issues/107975#issuecomment-1431758601

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//@ known-bug: #107975
//@ compile-flags: -Copt-level=2
//@ run-pass
//@ run-fail
//@ ignore-backends: gcc
//@ min-llvm-version: 21

// https://github.com/rust-lang/rust/issues/107975#issuecomment-1431758601

Expand Down
Loading