Skip to content

Commit dffea43

Browse files
committed
Auto merge of #106180 - RalfJung:dereferenceable-generators, r=nbdd0121
make &mut !Unpin not dereferenceable, and Box<!Unpin> not noalias See rust-lang/unsafe-code-guidelines#381 and [this LLVM discussion](https://discourse.llvm.org/t/interaction-of-noalias-and-dereferenceable/66979). The exact semantics of how `noalias` and `dereferenceable` interact are unclear, and `@comex` found a case of LLVM actually exploiting that ambiguity for optimizations. I think for now we should treat LLVM `dereferenceable` as implying a "fake read" to happen immediately at the top of the function (standing in for the spurious reads that LLVM might introduce), and that fake read is subject to all the usual `noalias` restrictions. This means we cannot put `dereferenceable` on `&mut !Unpin` references as those references can alias with other references that are being read and written inside the function (e.g. for self-referential generators), meaning the fake read introduces aliasing conflicts with those other accesses. For `&` this is already not a problem due to #98017 which removed the `dereferenceable` attribute for other reasons. Regular `&mut Unpin` references are unaffected, so I hope the impact of this is going to be tiny. The first commit does some refactoring of the `PointerKind` enum since I found the old code very confusing each time I had to touch it. It doesn't change behavior. Fixes rust-lang/miri#2714 EDIT: Turns out our `Box<!Unpin>` treatment was incorrect, too, so the PR also fixes that now (in codegen and Miri): we do not put `noalias` on these boxes any more.
2 parents 35d6d70 + 1ef1687 commit dffea43

File tree

10 files changed

+245
-219
lines changed

10 files changed

+245
-219
lines changed

compiler/rustc_abi/src/lib.rs

+6-15
Original file line numberDiff line numberDiff line change
@@ -1439,21 +1439,12 @@ impl<V: Idx> fmt::Debug for LayoutS<V> {
14391439

14401440
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
14411441
pub enum PointerKind {
1442-
/// Most general case, we know no restrictions to tell LLVM.
1443-
SharedMutable,
1444-
1445-
/// `&T` where `T` contains no `UnsafeCell`, is `dereferenceable`, `noalias` and `readonly`.
1446-
Frozen,
1447-
1448-
/// `&mut T` which is `dereferenceable` and `noalias` but not `readonly`.
1449-
UniqueBorrowed,
1450-
1451-
/// `&mut !Unpin`, which is `dereferenceable` but neither `noalias` nor `readonly`.
1452-
UniqueBorrowedPinned,
1453-
1454-
/// `Box<T>`, which is `noalias` (even on return types, unlike the above) but neither `readonly`
1455-
/// nor `dereferenceable`.
1456-
UniqueOwned,
1442+
/// Shared reference. `frozen` indicates the absence of any `UnsafeCell`.
1443+
SharedRef { frozen: bool },
1444+
/// Mutable reference. `unpin` indicates the absence of any pinned data.
1445+
MutableRef { unpin: bool },
1446+
/// Box. `unpin` indicates the absence of any pinned data.
1447+
Box { unpin: bool },
14571448
}
14581449

14591450
/// Note that this information is advisory only, and backends are free to ignore it.

compiler/rustc_middle/src/ty/layout.rs

+95-106
Original file line numberDiff line numberDiff line change
@@ -818,125 +818,114 @@ where
818818
let tcx = cx.tcx();
819819
let param_env = cx.param_env();
820820

821-
let pointee_info =
822-
match *this.ty.kind() {
823-
ty::RawPtr(mt) if offset.bytes() == 0 => {
824-
tcx.layout_of(param_env.and(mt.ty)).ok().map(|layout| PointeeInfo {
825-
size: layout.size,
826-
align: layout.align.abi,
827-
safe: None,
828-
})
829-
}
830-
ty::FnPtr(fn_sig) if offset.bytes() == 0 => {
831-
tcx.layout_of(param_env.and(tcx.mk_fn_ptr(fn_sig))).ok().map(|layout| {
832-
PointeeInfo { size: layout.size, align: layout.align.abi, safe: None }
833-
})
834-
}
835-
ty::Ref(_, ty, mt) if offset.bytes() == 0 => {
836-
let kind = if tcx.sess.opts.optimize == OptLevel::No {
837-
// Use conservative pointer kind if not optimizing. This saves us the
838-
// Freeze/Unpin queries, and can save time in the codegen backend (noalias
839-
// attributes in LLVM have compile-time cost even in unoptimized builds).
840-
PointerKind::SharedMutable
841-
} else {
842-
match mt {
843-
hir::Mutability::Not => {
844-
if ty.is_freeze(tcx, cx.param_env()) {
845-
PointerKind::Frozen
846-
} else {
847-
PointerKind::SharedMutable
848-
}
849-
}
850-
hir::Mutability::Mut => {
851-
// References to self-referential structures should not be considered
852-
// noalias, as another pointer to the structure can be obtained, that
853-
// is not based-on the original reference. We consider all !Unpin
854-
// types to be potentially self-referential here.
855-
if ty.is_unpin(tcx, cx.param_env()) {
856-
PointerKind::UniqueBorrowed
857-
} else {
858-
PointerKind::UniqueBorrowedPinned
859-
}
860-
}
861-
}
862-
};
821+
let pointee_info = match *this.ty.kind() {
822+
ty::RawPtr(mt) if offset.bytes() == 0 => {
823+
tcx.layout_of(param_env.and(mt.ty)).ok().map(|layout| PointeeInfo {
824+
size: layout.size,
825+
align: layout.align.abi,
826+
safe: None,
827+
})
828+
}
829+
ty::FnPtr(fn_sig) if offset.bytes() == 0 => {
830+
tcx.layout_of(param_env.and(tcx.mk_fn_ptr(fn_sig))).ok().map(|layout| PointeeInfo {
831+
size: layout.size,
832+
align: layout.align.abi,
833+
safe: None,
834+
})
835+
}
836+
ty::Ref(_, ty, mt) if offset.bytes() == 0 => {
837+
// Use conservative pointer kind if not optimizing. This saves us the
838+
// Freeze/Unpin queries, and can save time in the codegen backend (noalias
839+
// attributes in LLVM have compile-time cost even in unoptimized builds).
840+
let optimize = tcx.sess.opts.optimize != OptLevel::No;
841+
let kind = match mt {
842+
hir::Mutability::Not => PointerKind::SharedRef {
843+
frozen: optimize && ty.is_freeze(tcx, cx.param_env()),
844+
},
845+
hir::Mutability::Mut => PointerKind::MutableRef {
846+
unpin: optimize && ty.is_unpin(tcx, cx.param_env()),
847+
},
848+
};
863849

864-
tcx.layout_of(param_env.and(ty)).ok().map(|layout| PointeeInfo {
865-
size: layout.size,
866-
align: layout.align.abi,
867-
safe: Some(kind),
868-
})
869-
}
850+
tcx.layout_of(param_env.and(ty)).ok().map(|layout| PointeeInfo {
851+
size: layout.size,
852+
align: layout.align.abi,
853+
safe: Some(kind),
854+
})
855+
}
870856

871-
_ => {
872-
let mut data_variant = match this.variants {
873-
// Within the discriminant field, only the niche itself is
874-
// always initialized, so we only check for a pointer at its
875-
// offset.
876-
//
877-
// If the niche is a pointer, it's either valid (according
878-
// to its type), or null (which the niche field's scalar
879-
// validity range encodes). This allows using
880-
// `dereferenceable_or_null` for e.g., `Option<&T>`, and
881-
// this will continue to work as long as we don't start
882-
// using more niches than just null (e.g., the first page of
883-
// the address space, or unaligned pointers).
884-
Variants::Multiple {
885-
tag_encoding: TagEncoding::Niche { untagged_variant, .. },
886-
tag_field,
887-
..
888-
} if this.fields.offset(tag_field) == offset => {
889-
Some(this.for_variant(cx, untagged_variant))
890-
}
891-
_ => Some(this),
892-
};
857+
_ => {
858+
let mut data_variant = match this.variants {
859+
// Within the discriminant field, only the niche itself is
860+
// always initialized, so we only check for a pointer at its
861+
// offset.
862+
//
863+
// If the niche is a pointer, it's either valid (according
864+
// to its type), or null (which the niche field's scalar
865+
// validity range encodes). This allows using
866+
// `dereferenceable_or_null` for e.g., `Option<&T>`, and
867+
// this will continue to work as long as we don't start
868+
// using more niches than just null (e.g., the first page of
869+
// the address space, or unaligned pointers).
870+
Variants::Multiple {
871+
tag_encoding: TagEncoding::Niche { untagged_variant, .. },
872+
tag_field,
873+
..
874+
} if this.fields.offset(tag_field) == offset => {
875+
Some(this.for_variant(cx, untagged_variant))
876+
}
877+
_ => Some(this),
878+
};
893879

894-
if let Some(variant) = data_variant {
895-
// We're not interested in any unions.
896-
if let FieldsShape::Union(_) = variant.fields {
897-
data_variant = None;
898-
}
880+
if let Some(variant) = data_variant {
881+
// We're not interested in any unions.
882+
if let FieldsShape::Union(_) = variant.fields {
883+
data_variant = None;
899884
}
885+
}
900886

901-
let mut result = None;
902-
903-
if let Some(variant) = data_variant {
904-
// FIXME(erikdesjardins): handle non-default addrspace ptr sizes
905-
// (requires passing in the expected address space from the caller)
906-
let ptr_end = offset + Pointer(AddressSpace::DATA).size(cx);
907-
for i in 0..variant.fields.count() {
908-
let field_start = variant.fields.offset(i);
909-
if field_start <= offset {
910-
let field = variant.field(cx, i);
911-
result = field.to_result().ok().and_then(|field| {
912-
if ptr_end <= field_start + field.size {
913-
// We found the right field, look inside it.
914-
let field_info =
915-
field.pointee_info_at(cx, offset - field_start);
916-
field_info
917-
} else {
918-
None
919-
}
920-
});
921-
if result.is_some() {
922-
break;
887+
let mut result = None;
888+
889+
if let Some(variant) = data_variant {
890+
// FIXME(erikdesjardins): handle non-default addrspace ptr sizes
891+
// (requires passing in the expected address space from the caller)
892+
let ptr_end = offset + Pointer(AddressSpace::DATA).size(cx);
893+
for i in 0..variant.fields.count() {
894+
let field_start = variant.fields.offset(i);
895+
if field_start <= offset {
896+
let field = variant.field(cx, i);
897+
result = field.to_result().ok().and_then(|field| {
898+
if ptr_end <= field_start + field.size {
899+
// We found the right field, look inside it.
900+
let field_info =
901+
field.pointee_info_at(cx, offset - field_start);
902+
field_info
903+
} else {
904+
None
923905
}
906+
});
907+
if result.is_some() {
908+
break;
924909
}
925910
}
926911
}
912+
}
927913

928-
// FIXME(eddyb) This should be for `ptr::Unique<T>`, not `Box<T>`.
929-
if let Some(ref mut pointee) = result {
930-
if let ty::Adt(def, _) = this.ty.kind() {
931-
if def.is_box() && offset.bytes() == 0 {
932-
pointee.safe = Some(PointerKind::UniqueOwned);
933-
}
914+
// FIXME(eddyb) This should be for `ptr::Unique<T>`, not `Box<T>`.
915+
if let Some(ref mut pointee) = result {
916+
if let ty::Adt(def, _) = this.ty.kind() {
917+
if def.is_box() && offset.bytes() == 0 {
918+
let optimize = tcx.sess.opts.optimize != OptLevel::No;
919+
pointee.safe = Some(PointerKind::Box {
920+
unpin: optimize && this.ty.boxed_ty().is_unpin(tcx, cx.param_env()),
921+
});
934922
}
935923
}
936-
937-
result
938924
}
939-
};
925+
926+
result
927+
}
928+
};
940929

941930
debug!(
942931
"pointee_info_at (offset={:?}, type kind: {:?}) => {:?}",

compiler/rustc_ty_utils/src/abi.rs

+20-19
Original file line numberDiff line numberDiff line change
@@ -254,15 +254,18 @@ fn adjust_for_rust_scalar<'tcx>(
254254
if let Some(kind) = pointee.safe {
255255
attrs.pointee_align = Some(pointee.align);
256256

257-
// `Box` (`UniqueBorrowed`) are not necessarily dereferenceable
258-
// for the entire duration of the function as they can be deallocated
259-
// at any time. Same for shared mutable references. If LLVM had a
260-
// way to say "dereferenceable on entry" we could use it here.
257+
// `Box` are not necessarily dereferenceable for the entire duration of the function as
258+
// they can be deallocated at any time. Same for non-frozen shared references (see
259+
// <https://github.com/rust-lang/rust/pull/98017>), and for mutable references to
260+
// potentially self-referential types (see
261+
// <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>). If LLVM had a way
262+
// to say "dereferenceable on entry" we could use it here.
261263
attrs.pointee_size = match kind {
262-
PointerKind::UniqueBorrowed
263-
| PointerKind::UniqueBorrowedPinned
264-
| PointerKind::Frozen => pointee.size,
265-
PointerKind::SharedMutable | PointerKind::UniqueOwned => Size::ZERO,
264+
PointerKind::Box { .. }
265+
| PointerKind::SharedRef { frozen: false }
266+
| PointerKind::MutableRef { unpin: false } => Size::ZERO,
267+
PointerKind::SharedRef { frozen: true }
268+
| PointerKind::MutableRef { unpin: true } => pointee.size,
266269
};
267270

268271
// The aliasing rules for `Box<T>` are still not decided, but currently we emit
@@ -275,26 +278,24 @@ fn adjust_for_rust_scalar<'tcx>(
275278
// versions at all anymore. We still support turning it off using -Zmutable-noalias.
276279
let noalias_mut_ref = cx.tcx.sess.opts.unstable_opts.mutable_noalias;
277280

278-
// `&mut` pointer parameters never alias other parameters,
279-
// or mutable global data
281+
// `&T` where `T` contains no `UnsafeCell<U>` is immutable, and can be marked as both
282+
// `readonly` and `noalias`, as LLVM's definition of `noalias` is based solely on memory
283+
// dependencies rather than pointer equality. However this only applies to arguments,
284+
// not return values.
280285
//
281-
// `&T` where `T` contains no `UnsafeCell<U>` is immutable,
282-
// and can be marked as both `readonly` and `noalias`, as
283-
// LLVM's definition of `noalias` is based solely on memory
284-
// dependencies rather than pointer equality
286+
// `&mut T` and `Box<T>` where `T: Unpin` are unique and hence `noalias`.
285287
let no_alias = match kind {
286-
PointerKind::SharedMutable | PointerKind::UniqueBorrowedPinned => false,
287-
PointerKind::UniqueBorrowed => noalias_mut_ref,
288-
PointerKind::UniqueOwned => noalias_for_box,
289-
PointerKind::Frozen => true,
288+
PointerKind::SharedRef { frozen } => frozen,
289+
PointerKind::MutableRef { unpin } => unpin && noalias_mut_ref,
290+
PointerKind::Box { unpin } => unpin && noalias_for_box,
290291
};
291292
// We can never add `noalias` in return position; that LLVM attribute has some very surprising semantics
292293
// (see <https://github.com/rust-lang/unsafe-code-guidelines/issues/385#issuecomment-1368055745>).
293294
if no_alias && !is_return {
294295
attrs.set(ArgAttribute::NoAlias);
295296
}
296297

297-
if kind == PointerKind::Frozen && !is_return {
298+
if matches!(kind, PointerKind::SharedRef { frozen: true }) && !is_return {
298299
attrs.set(ArgAttribute::ReadOnly);
299300
}
300301
}

src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs

+31-12
Original file line numberDiff line numberDiff line change
@@ -81,21 +81,18 @@ impl NewPermission {
8181
protector: None,
8282
}
8383
} else if pointee.is_unpin(*cx.tcx, cx.param_env()) {
84-
// A regular full mutable reference.
84+
// A regular full mutable reference. On `FnEntry` this is `noalias` and `dereferenceable`.
8585
NewPermission::Uniform {
8686
perm: Permission::Unique,
8787
access: Some(AccessKind::Write),
8888
protector,
8989
}
9090
} else {
91+
// `!Unpin` dereferences do not get `noalias` nor `dereferenceable`.
9192
NewPermission::Uniform {
9293
perm: Permission::SharedReadWrite,
93-
// FIXME: We emit `dereferenceable` for `!Unpin` mutable references, so we
94-
// should do fake accesses here. But then we run into
95-
// <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>, so for now
96-
// we don't do that.
9794
access: None,
98-
protector,
95+
protector: None,
9996
}
10097
}
10198
}
@@ -109,6 +106,7 @@ impl NewPermission {
109106
}
110107
}
111108
ty::Ref(_, _pointee, Mutability::Not) => {
109+
// Shared references. If frozen, these get `noalias` and `dereferenceable`; otherwise neither.
112110
NewPermission::FreezeSensitive {
113111
freeze_perm: Permission::SharedReadOnly,
114112
freeze_access: Some(AccessKind::Read),
@@ -137,6 +135,32 @@ impl NewPermission {
137135
}
138136
}
139137

138+
fn from_box_ty<'tcx>(
139+
ty: Ty<'tcx>,
140+
kind: RetagKind,
141+
cx: &crate::MiriInterpCx<'_, 'tcx>,
142+
) -> Self {
143+
// `ty` is not the `Box` but the field of the Box with this pointer (due to allocator handling).
144+
let pointee = ty.builtin_deref(true).unwrap().ty;
145+
if pointee.is_unpin(*cx.tcx, cx.param_env()) {
146+
// A regular box. On `FnEntry` this is `noalias`, but not `dereferenceable` (hence only
147+
// a weak protector).
148+
NewPermission::Uniform {
149+
perm: Permission::Unique,
150+
access: Some(AccessKind::Write),
151+
protector: (kind == RetagKind::FnEntry)
152+
.then_some(ProtectorKind::WeakProtector),
153+
}
154+
} else {
155+
// `!Unpin` boxes do not get `noalias` nor `dereferenceable`.
156+
NewPermission::Uniform {
157+
perm: Permission::SharedReadWrite,
158+
access: None,
159+
protector: None,
160+
}
161+
}
162+
}
163+
140164
fn protector(&self) -> Option<ProtectorKind> {
141165
match self {
142166
NewPermission::Uniform { protector, .. } => *protector,
@@ -916,12 +940,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
916940

917941
fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
918942
// Boxes get a weak protectors, since they may be deallocated.
919-
let new_perm = NewPermission::Uniform {
920-
perm: Permission::Unique,
921-
access: Some(AccessKind::Write),
922-
protector: (self.kind == RetagKind::FnEntry)
923-
.then_some(ProtectorKind::WeakProtector),
924-
};
943+
let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx);
925944
self.retag_ptr_inplace(place, new_perm, self.retag_cause)
926945
}
927946

0 commit comments

Comments
 (0)