Skip to content

Commit

Permalink
[pointer] Support generic invariant mapping (#1896)
Browse files Browse the repository at this point in the history
This commit adds a framework which supports encoding in the type system
any `I -> I` mapping where `I` is any `Invariant` type. This permits us
to make `cast_unsized`'s return value smarter, and as a result, allows
us to remove a lot of `unsafe` code.

Makes progress on #1122
  • Loading branch information
joshlf authored Oct 14, 2024
1 parent ae43fce commit 0665b90
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 94 deletions.
9 changes: 0 additions & 9 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,15 +290,6 @@ safety_comment! {
/// because `NonZeroXxx` and `xxx` have the same size. [1] Neither `r`
/// nor `t` refer to any `UnsafeCell`s because neither `NonZeroXxx` [2]
/// nor `xxx` do.
/// - Since the closure takes a `&xxx` argument, given a `Maybe<'a,
/// NonZeroXxx>` which satisfies the preconditions of
/// `TryFromBytes::<NonZeroXxx>::is_bit_valid`, it must be guaranteed
/// that the memory referenced by that `MabyeValid` always contains a
/// valid `xxx`. Since `NonZeroXxx`'s bytes are always initialized [1],
/// `is_bit_valid`'s precondition requires that the same is true of its
/// argument. Since `xxx`'s only bit validity invariant is that its
/// bytes must be initialized, this memory is guaranteed to contain a
/// valid `xxx`.
/// - The impl must only return `true` for its argument if the original
/// `Maybe<NonZeroXxx>` refers to a valid `NonZeroXxx`. The only
/// `xxx` which is not also a valid `NonZeroXxx` is 0. [1]
Expand Down
193 changes: 163 additions & 30 deletions src/pointer/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,22 @@ pub trait Aliasing: Sealed {
/// Aliasing>::Variance<'a, T>` to inherit this variance.
#[doc(hidden)]
type Variance<'a, T: 'a + ?Sized>;

#[doc(hidden)]
type MappedTo<M: AliasingMapping>: Aliasing;
}

/// The alignment invariant of a [`Ptr`][super::Ptr].
pub trait Alignment: Sealed {}
pub trait Alignment: Sealed {
#[doc(hidden)]
type MappedTo<M: AlignmentMapping>: Alignment;
}

/// The validity invariant of a [`Ptr`][super::Ptr].
pub trait Validity: Sealed {}
pub trait Validity: Sealed {
#[doc(hidden)]
type MappedTo<M: ValidityMapping>: Validity;
}

/// An [`Aliasing`] invariant which is either [`Shared`] or [`Exclusive`].
///
Expand All @@ -84,8 +93,12 @@ pub trait Reference: Aliasing + Sealed {}
/// It is unknown whether any invariant holds.
pub enum Unknown {}

impl Alignment for Unknown {}
impl Validity for Unknown {}
impl Alignment for Unknown {
type MappedTo<M: AlignmentMapping> = M::FromUnknown;
}
impl Validity for Unknown {
type MappedTo<M: ValidityMapping> = M::FromUnknown;
}

/// The `Ptr<'a, T>` does not permit any reads or writes from or to its referent.
pub enum Inaccessible {}
Expand All @@ -100,6 +113,7 @@ impl Aliasing for Inaccessible {
//
// [1] https://doc.rust-lang.org/1.81.0/reference/subtyping.html#variance
type Variance<'a, T: 'a + ?Sized> = &'a T;
type MappedTo<M: AliasingMapping> = M::FromInaccessible;
}

/// The `Ptr<'a, T>` adheres to the aliasing rules of a `&'a T`.
Expand All @@ -114,6 +128,7 @@ pub enum Shared {}
impl Aliasing for Shared {
const IS_EXCLUSIVE: bool = false;
type Variance<'a, T: 'a + ?Sized> = &'a T;
type MappedTo<M: AliasingMapping> = M::FromShared;
}
impl Reference for Shared {}

Expand All @@ -126,51 +141,60 @@ pub enum Exclusive {}
impl Aliasing for Exclusive {
const IS_EXCLUSIVE: bool = true;
type Variance<'a, T: 'a + ?Sized> = &'a mut T;
type MappedTo<M: AliasingMapping> = M::FromExclusive;
}
impl Reference for Exclusive {}

/// The referent is aligned: for `Ptr<T>`, the referent's address is a multiple
/// of the `T`'s alignment.
/// The referent is aligned: for `Ptr<T>`, the referent's address is a
/// multiple of the `T`'s alignment.
pub enum Aligned {}
impl Alignment for Aligned {}
impl Alignment for Aligned {
type MappedTo<M: AlignmentMapping> = M::FromAligned;
}

/// The byte ranges initialized in `T` are also initialized in the referent.
///
/// Formally: uninitialized bytes may only be present in `Ptr<T>`'s referent
/// where they are guaranteed to be present in `T`. This is a dynamic
/// property: if, at a particular byte offset, a valid enum discriminant is
/// set, the subsequent bytes may only have uninitialized bytes as
/// specificed by the corresponding enum.
/// where they are guaranteed to be present in `T`. This is a dynamic property:
/// if, at a particular byte offset, a valid enum discriminant is set, the
/// subsequent bytes may only have uninitialized bytes as specificed by the
/// corresponding enum.
///
/// Formally, given `len = size_of_val_raw(ptr)`, at every byte offset, `b`,
/// in the range `[0, len)`:
/// - If, in any instance `t: T` of length `len`, the byte at offset `b` in
/// `t` is initialized, then the byte at offset `b` within `*ptr` must be
/// Formally, given `len = size_of_val_raw(ptr)`, at every byte offset, `b`, in
/// the range `[0, len)`:
/// - If, in any instance `t: T` of length `len`, the byte at offset `b` in `t`
/// is initialized, then the byte at offset `b` within `*ptr` must be
/// initialized.
/// - Let `c` be the contents of the byte range `[0, b)` in `*ptr`. Let `S`
/// be the subset of valid instances of `T` of length `len` which contain
/// `c` in the offset range `[0, b)`. If, in any instance of `t: T` in
/// `S`, the byte at offset `b` in `t` is initialized, then the byte at
/// offset `b` in `*ptr` must be initialized.
/// - Let `c` be the contents of the byte range `[0, b)` in `*ptr`. Let `S` be
/// the subset of valid instances of `T` of length `len` which contain `c` in
/// the offset range `[0, b)`. If, in any instance of `t: T` in `S`, the byte
/// at offset `b` in `t` is initialized, then the byte at offset `b` in `*ptr`
/// must be initialized.
///
/// Pragmatically, this means that if `*ptr` is guaranteed to contain an
/// enum type at a particular offset, and the enum discriminant stored in
/// `*ptr` corresponds to a valid variant of that enum type, then it is
/// guaranteed that the appropriate bytes of `*ptr` are initialized as
/// defined by that variant's bit validity (although note that the variant
/// may contain another enum type, in which case the same rules apply
/// depending on the state of its discriminant, and so on recursively).
/// Pragmatically, this means that if `*ptr` is guaranteed to contain an enum
/// type at a particular offset, and the enum discriminant stored in `*ptr`
/// corresponds to a valid variant of that enum type, then it is guaranteed
/// that the appropriate bytes of `*ptr` are initialized as defined by that
/// variant's bit validity (although note that the variant may contain another
/// enum type, in which case the same rules apply depending on the state of
/// its discriminant, and so on recursively).
pub enum AsInitialized {}
impl Validity for AsInitialized {}
impl Validity for AsInitialized {
type MappedTo<M: ValidityMapping> = M::FromAsInitialized;
}

/// The byte ranges in the referent are fully initialized. In other words, if
/// the referent is `N` bytes long, then it contains a bit-valid `[u8; N]`.
pub enum Initialized {}
impl Validity for Initialized {}
impl Validity for Initialized {
type MappedTo<M: ValidityMapping> = M::FromInitialized;
}

/// The referent is bit-valid for `T`.
pub enum Valid {}
impl Validity for Valid {}
impl Validity for Valid {
type MappedTo<M: ValidityMapping> = M::FromValid;
}

/// [`Ptr`](crate::Ptr) referents that permit unsynchronized read operations.
///
Expand Down Expand Up @@ -231,3 +255,112 @@ mod sealed {
impl Sealed for BecauseImmutable {}
impl Sealed for BecauseExclusive {}
}

pub use mapping::*;
mod mapping {
use super::*;

/// A mapping from one [`Aliasing`] type to another.
///
/// An `AliasingMapping` is a type-level map which maps one `Aliasing` type
/// to another. It is always "total" in the sense of having a mapping for
/// any `A: Aliasing`.
///
/// Given `A: Aliasing` and `M: AliasingMapping`, `M` can be applied to `A`
/// as [`MappedAliasing<A, M>`](MappedAliasing).
///
/// Mappings are used by [`Ptr`](crate::Ptr) conversion methods to preserve
/// or modify invariants as required by each method's semantics.
pub trait AliasingMapping {
type FromInaccessible: Aliasing;
type FromShared: Aliasing;
type FromExclusive: Aliasing;
}

/// A mapping from one [`Alignment`] type to another.
///
/// An `AlignmentMapping` is a type-level map which maps one `Alignment`
/// type to another. It is always "total" in the sense of having a mapping
/// for any `A: Alignment`.
///
/// Given `A: Alignment` and `M: AlignmentMapping`, `M` can be applied to
/// `A` as [`MappedAlignment<A, M>`](MappedAlignment).
///
/// Mappings are used by [`Ptr`](crate::Ptr) conversion methods to preserve
/// or modify invariants as required by each method's semantics.
pub trait AlignmentMapping {
type FromUnknown: Alignment;
type FromAligned: Alignment;
}

/// A mapping from one [`Validity`] type to another.
///
/// An `ValidityMapping` is a type-level map which maps one `Validity` type
/// to another. It is always "total" in the sense of having a mapping for
/// any `A: Validity`.
///
/// Given `V: Validity` and `M: ValidityMapping`, `M` can be applied to `V`
/// as [`MappedValidity<A, M>`](MappedValidity).
///
/// Mappings are used by [`Ptr`](crate::Ptr) conversion methods to preserve
/// or modify invariants as required by each method's semantics.
pub trait ValidityMapping {
type FromUnknown: Validity;
type FromAsInitialized: Validity;
type FromInitialized: Validity;
type FromValid: Validity;
}

/// The application of the [`AliasingMapping`] `M` to the [`Aliasing`] `A`.
#[allow(type_alias_bounds)]
pub type MappedAliasing<A: Aliasing, M: AliasingMapping> = A::MappedTo<M>;

/// The application of the [`AlignmentMapping`] `M` to the [`Alignment`] `A`.
#[allow(type_alias_bounds)]
pub type MappedAlignment<A: Alignment, M: AlignmentMapping> = A::MappedTo<M>;

/// The application of the [`ValidityMapping`] `M` to the [`Validity`] `A`.
#[allow(type_alias_bounds)]
pub type MappedValidity<V: Validity, M: ValidityMapping> = V::MappedTo<M>;

impl<FromInaccessible: Aliasing, FromShared: Aliasing, FromExclusive: Aliasing> AliasingMapping
for ((Inaccessible, FromInaccessible), (Shared, FromShared), (Exclusive, FromExclusive))
{
type FromInaccessible = FromInaccessible;
type FromShared = FromShared;
type FromExclusive = FromExclusive;
}

impl<FromUnknown: Alignment, FromAligned: Alignment> AlignmentMapping
for ((Unknown, FromUnknown), (Shared, FromAligned))
{
type FromUnknown = FromUnknown;
type FromAligned = FromAligned;
}

impl<
FromUnknown: Validity,
FromAsInitialized: Validity,
FromInitialized: Validity,
FromValid: Validity,
> ValidityMapping
for (
(Unknown, FromUnknown),
(AsInitialized, FromAsInitialized),
(Initialized, FromInitialized),
(Valid, FromValid),
)
{
type FromUnknown = FromUnknown;
type FromAsInitialized = FromAsInitialized;
type FromInitialized = FromInitialized;
type FromValid = FromValid;
}

impl<FromInitialized: Validity> ValidityMapping for (Initialized, FromInitialized) {
type FromUnknown = Unknown;
type FromAsInitialized = Unknown;
type FromInitialized = FromInitialized;
type FromValid = Unknown;
}
}
37 changes: 19 additions & 18 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,11 @@ mod _casts {
pub unsafe fn cast_unsized_unchecked<U: 'a + ?Sized, F: FnOnce(*mut T) -> *mut U>(
self,
cast: F,
) -> Ptr<'a, U, (I::Aliasing, Unknown, Unknown)> {
) -> Ptr<
'a,
U,
(I::Aliasing, Unknown, MappedValidity<I::Validity, (Initialized, Initialized)>),
> {
let ptr = cast(self.as_inner().as_non_null().as_ptr());

// SAFETY: Caller promises that `cast` returns a pointer whose
Expand Down Expand Up @@ -770,8 +774,13 @@ mod _casts {
// - `Inaccessible`: There are no restrictions we need to uphold.
// 7. `ptr`, trivially, conforms to the alignment invariant of
// `Unknown`.
// 8. `ptr`, trivially, conforms to the validity invariant of
// `Unknown`.
// 8. If `I::Validity = Unknown`, `AsInitialized`, or `Valid`, the
// output validity invariant is `Unknown`. `ptr` trivially
// conforms to this invariant. If `I::Validity = Initialized`,
// the output validity invariant is `Initialized`. Regardless of
// what subset of `self`'s referent is referred to by `ptr`, if
// all of `self`'s referent is initialized, then the same holds
// of `ptr`'s referent.
unsafe { Ptr::new(ptr) }
}

Expand All @@ -788,7 +797,11 @@ mod _casts {
pub unsafe fn cast_unsized<U, F, R, S>(
self,
cast: F,
) -> Ptr<'a, U, (I::Aliasing, Unknown, Unknown)>
) -> Ptr<
'a,
U,
(I::Aliasing, Unknown, MappedValidity<I::Validity, (Initialized, Initialized)>),
>
where
T: Read<I::Aliasing, R>,
U: 'a + ?Sized + Read<I::Aliasing, S>,
Expand Down Expand Up @@ -842,14 +855,7 @@ mod _casts {
})
};

let ptr = ptr.bikeshed_recall_aligned();

// SAFETY: `ptr`'s referent begins as `Initialized`, denoting that
// all bytes of the referent are initialized bytes. The referent
// type is then casted to `[u8]`, whose only validity invariant is
// that its bytes are initialized. This validity invariant is
// satisfied by the `Initialized` invariant on the starting `ptr`.
unsafe { ptr.assume_validity::<Valid>() }
ptr.bikeshed_recall_aligned().bikeshed_recall_valid()
}
}

Expand Down Expand Up @@ -1082,12 +1088,7 @@ mod _project {

// SAFETY: This method has the same safety preconditions as
// `cast_unsized_unchecked`.
let ptr = unsafe { self.cast_unsized_unchecked(projector) };

// SAFETY: If all of the bytes of `self` are initialized (as
// promised by `I: Invariants<Validity = Initialized>`), then any
// subset of those bytes are also all initialized.
unsafe { ptr.assume_validity::<Initialized>() }
unsafe { self.cast_unsized_unchecked(projector) }
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/util/macro_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ macro_rules! assert_align_gt_eq {
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
#[macro_export]
macro_rules! assert_size_eq {
($t:ident, $u: ident) => {{
($t:ident, $u:ident) => {{
// The comments here should be read in the context of this macro's
// invocations in `transmute_ref!` and `transmute_mut!`.
if false {
Expand Down
13 changes: 1 addition & 12 deletions src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ macro_rules! safety_comment {
/// referred to by `t`.
/// - `r` refers to an object with `UnsafeCell`s at the same byte ranges as
/// the object referred to by `t`.
/// - If the provided closure takes a `&$repr` argument, then given a `Ptr<'a,
/// $ty>` which satisfies the preconditions of
/// `TryFromBytes::<$ty>::is_bit_valid`, it must be guaranteed that the
/// memory referenced by that `Ptr` always contains a valid `$repr`.
/// - The impl of `is_bit_valid` must only return `true` for its argument
/// `Ptr<$repr>` if the original `Ptr<$ty>` refers to a valid `$ty`.
macro_rules! unsafe_impl {
Expand Down Expand Up @@ -153,9 +149,7 @@ macro_rules! unsafe_impl {
#[allow(clippy::as_conversions)]
let candidate = unsafe { candidate.cast_unsized_unchecked::<$repr, _>(|p| p as *mut _) };

// SAFETY: The caller has promised that the referenced memory region
// will contain a valid `$repr`.
let $candidate = unsafe { candidate.assume_validity::<crate::pointer::invariant::Valid>() };
let $candidate = candidate.bikeshed_recall_valid();
$is_bit_valid
}
};
Expand All @@ -177,11 +171,6 @@ macro_rules! unsafe_impl {
#[allow(clippy::as_conversions)]
let $candidate = unsafe { candidate.cast_unsized_unchecked::<$repr, _>(|p| p as *mut _) };

// Restore the invariant that the referent bytes are initialized.
// SAFETY: The above cast does not uninitialize any referent bytes;
// they remain initialized.
let $candidate = unsafe { $candidate.assume_validity::<crate::pointer::invariant::Initialized>() };

$is_bit_valid
}
};
Expand Down
Loading

0 comments on commit 0665b90

Please sign in to comment.