Skip to content

Commit cb09a42

Browse files
committed
Emit nofree attribute
Treat the semantics of pointee.size as "dereferenceable-at-point" and always specify the size. Instead, use a separate NoFree attribute to determine whether dereferenceability extends to the whole function. Then in the LLVM backend, only actually emit dereferenceable if nofree is also set, as dereferenceable currently implies nofree. In addition, explicitly emit the nofree attribute, which will help when LLVM switches dereferenceable to be at-point.
1 parent 0e5924a commit cb09a42

23 files changed

Lines changed: 124 additions & 99 deletions

compiler/rustc_codegen_llvm/src/abi.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,16 @@ trait ArgAttributesExt {
3838
const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] =
3939
[(ArgAttribute::InReg, llvm::AttributeKind::InReg)];
4040

41-
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 5] = [
41+
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 6] = [
4242
(ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias),
4343
(ArgAttribute::NonNull, llvm::AttributeKind::NonNull),
4444
(ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly),
4545
(ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef),
4646
(ArgAttribute::Writable, llvm::AttributeKind::Writable),
47+
// Our internal NoFree attribute still allows deallocation of zero-size allocations. However,
48+
// these don't render any bytes non-dereferenceable, so it's still fine to apply LLVM NoFree
49+
// for them.
50+
(ArgAttribute::NoFree, llvm::AttributeKind::NoFree),
4751
];
4852

4953
const CAPTURES_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 3] = [
@@ -75,7 +79,9 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'
7579
// Only apply remaining attributes when optimizing
7680
if cx.sess().opts.optimize != config::OptLevel::No {
7781
let deref = this.pointee_size.bytes();
78-
if deref != 0 {
82+
// dereferenceable in LLVM currently implies nofree, so only emit dereferenceable if nofree
83+
// is also set.
84+
if deref != 0 && regular.contains(ArgAttribute::NoFree) {
7985
if regular.contains(ArgAttribute::NonNull) {
8086
attrs.push(llvm::CreateDereferenceableAttr(cx.llcx, deref));
8187
} else {

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ pub(crate) enum AttributeKind {
294294
SanitizeRealtimeNonblocking = 47,
295295
SanitizeRealtimeBlocking = 48,
296296
Convergent = 49,
297+
NoFree = 50,
297298
}
298299

299300
/// LLVMIntPredicate

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ enum class LLVMRustAttributeKind {
383383
SanitizeRealtimeNonblocking = 47,
384384
SanitizeRealtimeBlocking = 48,
385385
Convergent = 49,
386+
NoFree = 50,
386387
};
387388

388389
static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
@@ -481,6 +482,8 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
481482
return Attribute::SanitizeRealtimeBlocking;
482483
case LLVMRustAttributeKind::Convergent:
483484
return Attribute::Convergent;
485+
case LLVMRustAttributeKind::NoFree:
486+
return Attribute::NoFree;
484487
}
485488
report_fatal_error("bad LLVMRustAttributeKind");
486489
}

compiler/rustc_middle/src/ty/layout.rs

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -979,33 +979,19 @@ where
979979
}
980980
ty::Ref(_, ty, mt) if offset.bytes() == 0 => {
981981
tcx.layout_of(typing_env.as_query_input(ty)).ok().map(|layout| {
982-
let (size, kind);
983-
match mt {
982+
let kind = match mt {
984983
hir::Mutability::Not => {
985984
let frozen = optimize && ty.is_freeze(tcx, typing_env);
986-
987-
// Non-frozen shared references are not necessarily dereferenceable for the entire duration of the function
988-
// (see <https://github.com/rust-lang/rust/pull/98017>)
989-
// (if we had "dereferenceable on entry", we could support this)
990-
size = if frozen { layout.size } else { Size::ZERO };
991-
992-
kind = PointerKind::SharedRef { frozen };
985+
PointerKind::SharedRef { frozen }
993986
}
994987
hir::Mutability::Mut => {
995988
let unpin = optimize
996989
&& ty.is_unpin(tcx, typing_env)
997990
&& ty.is_unsafe_unpin(tcx, typing_env);
998-
999-
// Mutable references to potentially self-referential types are not
1000-
// necessarily dereferenceable for the entire duration of the function
1001-
// (see <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>)
1002-
// (if we had "dereferenceable on entry", we could support this)
1003-
size = if unpin { layout.size } else { Size::ZERO };
1004-
1005-
kind = PointerKind::MutableRef { unpin };
991+
PointerKind::MutableRef { unpin }
1006992
}
1007993
};
1008-
PointeeInfo { safe: Some(kind), size, align: layout.align.abi }
994+
PointeeInfo { safe: Some(kind), size: layout.size, align: layout.align.abi }
1009995
})
1010996
}
1011997

@@ -1021,12 +1007,7 @@ where
10211007
&& pointee.is_unsafe_unpin(tcx, typing_env),
10221008
global: this.ty.is_box_global(tcx),
10231009
}),
1024-
1025-
// `Box` are not necessarily dereferenceable for the entire duration of the function as
1026-
// they can be deallocated at any time.
1027-
// (if we had "dereferenceable on entry", we could support this)
1028-
size: Size::ZERO,
1029-
1010+
size: layout.size,
10301011
align: layout.align.abi,
10311012
})
10321013
}

compiler/rustc_target/src/callconv/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ mod attr_impl {
122122
const InReg = 1 << 6;
123123
const NoUndef = 1 << 7;
124124
const Writable = 1 << 8;
125+
/// It is UB for this pointer or any pointer derived from it to be used for
126+
/// deallocation (except for zero-sized deallocation) while the function is
127+
/// executing. Only valid on arguments (including return values that are passed
128+
/// indirectly as arguments).
129+
const NoFree = 1 << 9;
125130
}
126131
}
127132
rustc_data_structures::external_bitflags_debug! { ArgAttribute }
@@ -143,9 +148,8 @@ pub enum ArgExtension {
143148
pub struct ArgAttributes {
144149
pub regular: ArgAttribute,
145150
pub arg_ext: ArgExtension,
146-
/// The minimum size of the pointee, guaranteed to be valid for the duration of the whole call
147-
/// (corresponding to LLVM's dereferenceable_or_null attributes, i.e., it is okay for this to be
148-
/// set on a null pointer, but all non-null pointers must be dereferenceable).
151+
/// If the pointer is not null, the minimum dereferenceable size of the pointee, at the time of
152+
/// function entry (for arguments) or function return (for return values).
149153
pub pointee_size: Size,
150154
/// The minimum alignment of the pointee, if any.
151155
pub pointee_align: Option<Align>,
@@ -408,7 +412,8 @@ impl<'a, Ty> ArgAbi<'a, Ty> {
408412
.set(ArgAttribute::NoAlias)
409413
.set(ArgAttribute::CapturesAddress)
410414
.set(ArgAttribute::NonNull)
411-
.set(ArgAttribute::NoUndef);
415+
.set(ArgAttribute::NoUndef)
416+
.set(ArgAttribute::NoFree);
412417
attrs.pointee_size = layout.size;
413418
attrs.pointee_align = Some(layout.align.abi);
414419

compiler/rustc_ty_utils/src/abi.rs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -356,13 +356,7 @@ fn arg_attrs_for_rust_scalar<'tcx>(
356356
Some(pointee.align.min(cx.tcx().sess.target.max_reliable_alignment()));
357357
}
358358

359-
// LLVM dereferenceable attribute has unclear semantics on the return type,
360-
// they seem to be "dereferenceable until the end of the program", which is
361-
// generally, not valid for references. See
362-
// <https://rust-lang.zulipchat.com/#narrow/channel/136281-t-opsem/topic/LLVM.20dereferenceable.20on.20return.20type/with/563001493>
363-
if !is_return {
364-
attrs.pointee_size = pointee.size;
365-
};
359+
attrs.pointee_size = pointee.size;
366360

367361
if let Some(kind) = pointee.safe {
368362
// The aliasing rules for `Box<T>` are still not decided, but currently we emit
@@ -407,6 +401,26 @@ fn arg_attrs_for_rust_scalar<'tcx>(
407401
}
408402
}
409403

404+
// NoFree is not valid on return values. If it were, it would mean something like
405+
// "will not be freed until the end of the program", which is generally not valid for
406+
// references.
407+
let no_free = !is_return
408+
&& match kind {
409+
// Non-frozen shared references are not necessarily dereferenceable for the
410+
// entire duration of the function
411+
// (see <https://github.com/rust-lang/rust/pull/98017>).
412+
PointerKind::SharedRef { frozen } => frozen,
413+
// Mutable references to potentially self-referential types are not necessarily
414+
// dereferenceable for the entire duration of the function
415+
// (see <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>).
416+
PointerKind::MutableRef { unpin } => unpin,
417+
// Box may be deallocated during execution of the function.
418+
PointerKind::Box { .. } => false,
419+
};
420+
if no_free {
421+
attrs.set(ArgAttribute::NoFree);
422+
}
423+
410424
if matches!(kind, PointerKind::SharedRef { frozen: true }) && !is_return {
411425
attrs.set(ArgAttribute::ReadOnly);
412426
attrs.set(ArgAttribute::CapturesReadOnly);
@@ -423,11 +437,18 @@ fn fn_abi_sanity_check<'tcx>(
423437
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
424438
spec_abi: ExternAbi,
425439
) {
440+
fn fn_arg_attrs_sanity_check(attrs: &ArgAttributes, is_ret: bool) {
441+
if attrs.regular.contains(ArgAttribute::NoFree) {
442+
assert!(!is_ret, "NoFree not valid on return values");
443+
}
444+
}
445+
426446
fn fn_arg_sanity_check<'tcx>(
427447
cx: &LayoutCx<'tcx>,
428448
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
429449
spec_abi: ExternAbi,
430450
arg: &ArgAbi<'tcx, Ty<'tcx>>,
451+
is_ret: bool,
431452
) {
432453
let tcx = cx.tcx();
433454

@@ -452,7 +473,7 @@ fn fn_abi_sanity_check<'tcx>(
452473
PassMode::Ignore => {
453474
assert!(arg.layout.is_zst());
454475
}
455-
PassMode::Direct(_) => {
476+
PassMode::Direct(attrs) => {
456477
// Here the Rust type is used to determine the actual ABI, so we have to be very
457478
// careful. Scalar/Vector is fine, since backends will generally use
458479
// `layout.backend_repr` and ignore everything else. We should just reject
@@ -482,28 +503,33 @@ fn fn_abi_sanity_check<'tcx>(
482503
);
483504
}
484505
}
506+
fn_arg_attrs_sanity_check(attrs, is_ret);
485507
}
486-
PassMode::Pair(_, _) => {
508+
PassMode::Pair(attrs1, attrs2) => {
487509
// Similar to `Direct`, we need to make sure that backends use `layout.backend_repr`
488510
// and ignore the rest of the layout.
489511
assert!(
490512
matches!(arg.layout.backend_repr, BackendRepr::ScalarPair(..)),
491513
"PassMode::Pair for type {}",
492514
arg.layout.ty
493515
);
516+
fn_arg_attrs_sanity_check(attrs1, is_ret);
517+
fn_arg_attrs_sanity_check(attrs2, is_ret);
494518
}
495519
PassMode::Cast { .. } => {
496520
// `Cast` means "transmute to `CastType`"; that only makes sense for sized types.
497521
assert!(arg.layout.is_sized());
498522
}
499-
PassMode::Indirect { meta_attrs: None, .. } => {
523+
PassMode::Indirect { meta_attrs: None, attrs, .. } => {
500524
// No metadata, must be sized.
501525
// Conceptually, unsized arguments must be copied around, which requires dynamically
502526
// determining their size, which we cannot do without metadata. Consult
503527
// t-opsem before removing this check.
504528
assert!(arg.layout.is_sized());
529+
// Indirect returns are arguments from an ABI perspective.
530+
fn_arg_attrs_sanity_check(attrs, false);
505531
}
506-
PassMode::Indirect { meta_attrs: Some(_), on_stack, .. } => {
532+
PassMode::Indirect { meta_attrs: Some(meta_attrs), attrs, on_stack } => {
507533
// With metadata. Must be unsized and not on the stack.
508534
assert!(arg.layout.is_unsized() && !on_stack);
509535
// Also, must not be `extern` type.
@@ -515,14 +541,17 @@ fn fn_abi_sanity_check<'tcx>(
515541
// t-opsem before removing this check.
516542
panic!("unsized arguments must not be `extern` types");
517543
}
544+
// Indirect returns are arguments from an ABI perspective.
545+
fn_arg_attrs_sanity_check(attrs, false);
546+
fn_arg_attrs_sanity_check(meta_attrs, false);
518547
}
519548
}
520549
}
521550

522551
for arg in fn_abi.args.iter() {
523-
fn_arg_sanity_check(cx, fn_abi, spec_abi, arg);
552+
fn_arg_sanity_check(cx, fn_abi, spec_abi, arg, false);
524553
}
525-
fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret);
554+
fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret, true);
526555
}
527556

528557
#[tracing::instrument(

tests/codegen-llvm/addr-of-mutate.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Test for the absence of `readonly` on the argument when it is mutated via `&raw const`.
66
// See <https://github.com/rust-lang/rust/issues/111502>.
77

8-
// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias noundef align 1{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable(128) %x)
8+
// CHECK: i8 @foo(ptr{{( dead_on_return)?}} noalias nofree noundef align 1{{( captures\(address\))?}}{{( dead_on_return)?}} dereferenceable(128) %x)
99
#[no_mangle]
1010
pub fn foo(x: [u8; 128]) -> u8 {
1111
let ptr = core::ptr::addr_of!(x).cast_mut();
@@ -15,7 +15,7 @@ pub fn foo(x: [u8; 128]) -> u8 {
1515
x[0]
1616
}
1717

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

2626
// If going through a deref (and there are no other mutating accesses), then `readonly` is fine.
27-
// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias noundef readonly align {{[0-9]+}}{{( captures\(none\))?}}{{( dead_on_return)?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
27+
// CHECK: i1 @third(ptr{{( dead_on_return)?}} noalias nofree noundef readonly align {{[0-9]+}}{{( captures\(none\))?}}{{( dead_on_return)?}} dereferenceable({{[0-9]+}}) %a_ptr_and_b)
2828
#[no_mangle]
2929
pub unsafe fn third(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool {
3030
let b_bool_ptr = core::ptr::addr_of!((*a_ptr_and_b.0).1).cast_mut();

tests/codegen-llvm/drop-in-place-noalias.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
use std::marker::PhantomPinned;
99

10-
// CHECK: define internal void @{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}StructUnpin{{.*}}(ptr noalias noundef align 4 dereferenceable(12) %{{.+}})
10+
// CHECK: define internal void @{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}StructUnpin{{.*}}(ptr noalias nofree noundef align 4 dereferenceable(12) %{{.+}})
1111

1212
// CHECK: define internal void @{{.*}}core{{.*}}ptr{{.*}}drop_in_place{{.*}}StructNotUnpin{{.*}}(ptr noundef nonnull align 4 %{{.+}})
1313

0 commit comments

Comments
 (0)