Skip to content

Commit 3ac83ce

Browse files
Rollup merge of #156281 - nikic:nofree, r=RalfJung
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. Relevant recent LLVM PR: llvm/llvm-project#195658
2 parents 9ae765d + cb09a42 commit 3ac83ce

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
@@ -318,6 +318,7 @@ pub(crate) enum AttributeKind {
318318
SanitizeRealtimeNonblocking = 47,
319319
SanitizeRealtimeBlocking = 48,
320320
Convergent = 49,
321+
NoFree = 50,
321322
}
322323

323324
/// LLVMIntPredicate

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ enum class LLVMRustAttributeKind {
388388
SanitizeRealtimeNonblocking = 47,
389389
SanitizeRealtimeBlocking = 48,
390390
Convergent = 49,
391+
NoFree = 50,
391392
};
392393

393394
static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
@@ -486,6 +487,8 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
486487
return Attribute::SanitizeRealtimeBlocking;
487488
case LLVMRustAttributeKind::Convergent:
488489
return Attribute::Convergent;
490+
case LLVMRustAttributeKind::NoFree:
491+
return Attribute::NoFree;
489492
}
490493
report_fatal_error("bad LLVMRustAttributeKind");
491494
}

compiler/rustc_middle/src/ty/layout.rs

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,33 +1038,19 @@ where
10381038
}
10391039
ty::Ref(_, ty, mt) if offset.bytes() == 0 => {
10401040
tcx.layout_of(typing_env.as_query_input(ty)).ok().map(|layout| {
1041-
let (size, kind);
1042-
match mt {
1041+
let kind = match mt {
10431042
hir::Mutability::Not => {
10441043
let frozen = optimize && ty.is_freeze(tcx, typing_env);
1045-
1046-
// Non-frozen shared references are not necessarily dereferenceable for the entire duration of the function
1047-
// (see <https://github.com/rust-lang/rust/pull/98017>)
1048-
// (if we had "dereferenceable on entry", we could support this)
1049-
size = if frozen { layout.size } else { Size::ZERO };
1050-
1051-
kind = PointerKind::SharedRef { frozen };
1044+
PointerKind::SharedRef { frozen }
10521045
}
10531046
hir::Mutability::Mut => {
10541047
let unpin = optimize
10551048
&& ty.is_unpin(tcx, typing_env)
10561049
&& ty.is_unsafe_unpin(tcx, typing_env);
1057-
1058-
// Mutable references to potentially self-referential types are not
1059-
// necessarily dereferenceable for the entire duration of the function
1060-
// (see <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>)
1061-
// (if we had "dereferenceable on entry", we could support this)
1062-
size = if unpin { layout.size } else { Size::ZERO };
1063-
1064-
kind = PointerKind::MutableRef { unpin };
1050+
PointerKind::MutableRef { unpin }
10651051
}
10661052
};
1067-
PointeeInfo { safe: Some(kind), size, align: layout.align.abi }
1053+
PointeeInfo { safe: Some(kind), size: layout.size, align: layout.align.abi }
10681054
})
10691055
}
10701056

@@ -1080,12 +1066,7 @@ where
10801066
&& pointee.is_unsafe_unpin(tcx, typing_env),
10811067
global: this.ty.is_box_global(tcx),
10821068
}),
1083-
1084-
// `Box` are not necessarily dereferenceable for the entire duration of the function as
1085-
// they can be deallocated at any time.
1086-
// (if we had "dereferenceable on entry", we could support this)
1087-
size: Size::ZERO,
1088-
1069+
size: layout.size,
10891070
align: layout.align.abi,
10901071
})
10911072
}

compiler/rustc_target/src/callconv/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ mod attr_impl {
123123
const InReg = 1 << 6;
124124
const NoUndef = 1 << 7;
125125
const Writable = 1 << 8;
126+
/// It is UB for this pointer or any pointer derived from it to be used for
127+
/// deallocation (except for zero-sized deallocation) while the function is
128+
/// executing. Only valid on arguments (including return values that are passed
129+
/// indirectly as arguments).
130+
const NoFree = 1 << 9;
126131
}
127132
}
128133
rustc_data_structures::external_bitflags_debug! { ArgAttribute }
@@ -144,9 +149,8 @@ pub enum ArgExtension {
144149
pub struct ArgAttributes {
145150
pub regular: ArgAttribute,
146151
pub arg_ext: ArgExtension,
147-
/// The minimum size of the pointee, guaranteed to be valid for the duration of the whole call
148-
/// (corresponding to LLVM's dereferenceable_or_null attributes, i.e., it is okay for this to be
149-
/// set on a null pointer, but all non-null pointers must be dereferenceable).
152+
/// If the pointer is not null, the minimum dereferenceable size of the pointee, at the time of
153+
/// function entry (for arguments) or function return (for return values).
150154
pub pointee_size: Size,
151155
/// The minimum alignment of the pointee, if any.
152156
pub pointee_align: Option<Align>,
@@ -414,7 +418,8 @@ impl<'a, Ty> ArgAbi<'a, Ty> {
414418
.set(ArgAttribute::NoAlias)
415419
.set(ArgAttribute::CapturesAddress)
416420
.set(ArgAttribute::NonNull)
417-
.set(ArgAttribute::NoUndef);
421+
.set(ArgAttribute::NoUndef)
422+
.set(ArgAttribute::NoFree);
418423
attrs.pointee_size = layout.size;
419424
attrs.pointee_align = Some(layout.align.abi);
420425

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)