Skip to content

Commit

Permalink
[pointer] Store validity in the referent type
Browse files Browse the repository at this point in the history
Previously, validity was stored as part of the `I: Invariants` type
parameter on a `Ptr<T, I>`. In this commit, we migrate the validity to
being stored as the `V` in `Ptr<V, I>`; `I` now only stores aliasing and
alignment.

Bit validity is subtle, in that the pair of referent type and validity
invariant define a set of bit patterns which may appear in the `Ptr`'s
referent. By encoding the validity in the referent type instead of in
the `I` parameter, we ensure that the validity of the referent is
captured entirely by a single type parameter rather than by a pair of
two separate type parameters. This makes future changes (e.g. #1359)
easier to model.

This idea was originally proposed in #1866 (comment)

Makes progress on #1866

gherrit-pr-id: Ia73ca4e5dfb3b20f71ed72ffda24dd7450c427ba
  • Loading branch information
joshlf committed Feb 14, 2025
1 parent c43bbed commit 07239f8
Show file tree
Hide file tree
Showing 6 changed files with 370 additions and 326 deletions.
155 changes: 93 additions & 62 deletions src/pointer/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,42 @@

//! The parameterized invariants of a [`Ptr`][super::Ptr].
//!
//! Invariants are encoded as ([`Aliasing`], [`Alignment`], [`Validity`])
//! triples implementing the [`Invariants`] trait.
//! A `Ptr<V, I>` has the following invariants:
//! - [`V: Validity`][validity-trait] encodes the bit validity of `Ptr`'s
//! referent, which is of type [`V::Inner`][validity-inner]
//! - [`I: Invariants`][invariants-trait], where
//! [`I::Aliasing`][invariants-aliasing] and
//! [`I::Alignment`][invariants-alignment] encode the `Ptr`'s aliasing and
//! alignment invariants respectively
//!
//! [validity-trait]: Validity
//! [validity-inner]: Validity::Inner
//! [invariants-trait]: Invariants
//! [invariants-aliasing]: Invariants::Aliasing
//! [invariants-alignment]: Invariants::Alignment
use core::marker::PhantomData;

/// The invariants of a [`Ptr`][super::Ptr].
/// The aliasing and alignment invariants of a [`Ptr`][super::Ptr].
pub trait Invariants: Sealed {
type Aliasing: Aliasing;
type Alignment: Alignment;
type Validity: Validity;

/// Invariants identical to `Self` except with a different aliasing
/// invariant.
type WithAliasing<A: Aliasing>: Invariants<
Aliasing = A,
Alignment = Self::Alignment,
Validity = Self::Validity,
>;
type WithAliasing<A: Aliasing>: Invariants<Aliasing = A, Alignment = Self::Alignment>;

/// Invariants identical to `Self` except with a different alignment
/// invariant.
type WithAlignment<A: Alignment>: Invariants<
Aliasing = Self::Aliasing,
Alignment = A,
Validity = Self::Validity,
>;

/// Invariants identical to `Self` except with a different validity
/// invariant.
type WithValidity<V: Validity>: Invariants<
Aliasing = Self::Aliasing,
Alignment = Self::Alignment,
Validity = V,
>;
type WithAlignment<A: Alignment>: Invariants<Aliasing = Self::Aliasing, Alignment = A>;
}

impl<A: Aliasing, AA: Alignment, V: Validity> Invariants for (A, AA, V) {
impl<A: Aliasing, AA: Alignment> Invariants for (A, AA) {
type Aliasing = A;
type Alignment = AA;
type Validity = V;

type WithAliasing<AB: Aliasing> = (AB, AA, V);
type WithAlignment<AB: Alignment> = (A, AB, V);
type WithValidity<VB: Validity> = (A, AA, VB);
type WithAliasing<AB: Aliasing> = (AB, AA);
type WithAlignment<AB: Alignment> = (A, AB);
}

/// The aliasing invariant of a [`Ptr`][super::Ptr].
Expand Down Expand Up @@ -79,7 +73,24 @@ pub trait Alignment: Sealed {
}

/// The validity invariant of a [`Ptr`][super::Ptr].
///
/// A `V: Validity` defines both the referent type of a `Ptr<V>`
/// ([`V::Inner`](Validity::Inner)) and the bit validity of the referent value.
/// Bit validity specifies a set, `S`, of possible values which may exist at the
/// `Ptr`'s referent. Code operating on a `Ptr` may assume that bit validity
/// holds - namely, that it will only observe referent values in `S`. It must
/// also uphold bit validity - namely, it must only write values in `S` to the
/// referent.
///
/// The specific definition of `S` for a given validity type (i.e., `V:
/// Validity`) is documented on that type.
///
/// The available validities are [`Uninit`], [`AsInitialized`], [`Initialized`],
/// and [`Valid`].
pub trait Validity: Sealed {
type Inner: ?Sized;
type WithInner<T: ?Sized>: Validity<Inner = T>;

#[doc(hidden)]
type MappedTo<M: ValidityMapping>: Validity;
}
Expand All @@ -98,8 +109,19 @@ pub enum Unknown {}
impl Alignment for Unknown {
type MappedTo<M: AlignmentMapping> = M::FromUnknown;
}
impl Validity for Unknown {
type MappedTo<M: ValidityMapping> = M::FromUnknown;

/// A validity which permits arbitrary bytes - including uninitialized bytes -
/// at any byte offset.
///
/// The referent of a `Ptr<Uninit<T>>` may contain arbitrary bytes - including
/// uninitialized bytes - at any byte offset.
pub struct Uninit<T: ?Sized = ()>(PhantomData<T>);

impl<T: ?Sized> Validity for Uninit<T> {
type Inner = T;
type WithInner<U: ?Sized> = Uninit<U>;

type MappedTo<M: ValidityMapping> = M::FromUninit<T>;
}

/// The `Ptr<'a, T>` adheres to the aliasing rules of a `&'a T`.
Expand Down Expand Up @@ -138,11 +160,11 @@ impl Alignment for Aligned {

/// 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.
/// Formally: uninitialized bytes may only be present in
/// `Ptr<AsInitialized<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.
///
/// Formally, given `len = size_of_val_raw(ptr)`, at every byte offset, `b`, in
/// the range `[0, len)`:
Expand All @@ -162,22 +184,28 @@ impl Alignment for Aligned {
/// 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 {
type MappedTo<M: ValidityMapping> = M::FromAsInitialized;
pub struct AsInitialized<T: ?Sized = ()>(PhantomData<T>);
impl<T: ?Sized> Validity for AsInitialized<T> {
type Inner = T;
type WithInner<U: ?Sized> = AsInitialized<U>;
type MappedTo<M: ValidityMapping> = M::FromAsInitialized<T>;
}

/// 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 {
type MappedTo<M: ValidityMapping> = M::FromInitialized;
pub struct Initialized<T: ?Sized = ()>(PhantomData<T>);
impl<T: ?Sized> Validity for Initialized<T> {
type Inner = T;
type WithInner<U: ?Sized> = Initialized<U>;
type MappedTo<M: ValidityMapping> = M::FromInitialized<T>;
}

/// The referent is bit-valid for `T`.
pub enum Valid {}
impl Validity for Valid {
type MappedTo<M: ValidityMapping> = M::FromValid;
pub struct Valid<T: ?Sized = ()>(PhantomData<T>);
impl<T: ?Sized> Validity for Valid<T> {
type Inner = T;
type WithInner<U: ?Sized> = Valid<U>;
type MappedTo<M: ValidityMapping> = M::FromValid<T>;
}

/// [`Ptr`](crate::Ptr) referents that permit unsynchronized read operations.
Expand Down Expand Up @@ -224,16 +252,18 @@ mod sealed {

impl Sealed for Unknown {}

impl<T: ?Sized> Sealed for Uninit<T> {}

impl Sealed for Shared {}
impl Sealed for Exclusive {}

impl Sealed for Aligned {}

impl Sealed for AsInitialized {}
impl Sealed for Initialized {}
impl Sealed for Valid {}
impl<T: ?Sized> Sealed for AsInitialized<T> {}
impl<T: ?Sized> Sealed for Initialized<T> {}
impl<T: ?Sized> Sealed for Valid<T> {}

impl<A: Sealed, AA: Sealed, V: Sealed> Sealed for (A, AA, V) {}
impl<A: Sealed, AA: Sealed> Sealed for (A, AA) {}
}

pub use mapping::*;
Expand Down Expand Up @@ -268,10 +298,10 @@ mod mapping {
/// 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;
type FromUninit<T: ?Sized>: Validity;
type FromAsInitialized<T: ?Sized>: Validity;
type FromInitialized<T: ?Sized>: Validity;
type FromValid<T: ?Sized>: Validity;
}

/// The application of the [`AlignmentMapping`] `M` to the [`Alignment`] `A`.
Expand All @@ -280,7 +310,8 @@ mod mapping {

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

impl<FromUnknown: Alignment, FromAligned: Alignment> AlignmentMapping
for ((Unknown, FromUnknown), (Shared, FromAligned))
Expand All @@ -290,28 +321,28 @@ mod mapping {
}

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

impl<FromInitialized: Validity> ValidityMapping for (Initialized, FromInitialized) {
type FromUnknown = Unknown;
type FromAsInitialized = Unknown;
type FromInitialized = FromInitialized;
type FromValid = Unknown;
type FromUninit<T: ?Sized> = Uninit<T>;
type FromAsInitialized<T: ?Sized> = Uninit<T>;
type FromInitialized<T: ?Sized> = FromInitialized::WithInner<T>;
type FromValid<T: ?Sized> = Uninit<T>;
}
}
8 changes: 4 additions & 4 deletions src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ use crate::Unaligned;
///
/// [`TryFromBytes::is_bit_valid`]: crate::TryFromBytes::is_bit_valid
pub type Maybe<'a, T, Aliasing = invariant::Shared, Alignment = invariant::Unknown> =
Ptr<'a, T, (Aliasing, Alignment, invariant::Initialized)>;
Ptr<'a, invariant::Initialized<T>, (Aliasing, Alignment)>;

/// A semi-user-facing wrapper type representing a maybe-aligned reference, for
/// use in [`TryFromBytes::is_bit_valid`].
///
/// [`TryFromBytes::is_bit_valid`]: crate::TryFromBytes::is_bit_valid
pub type MaybeAligned<'a, T, Aliasing = invariant::Shared, Alignment = invariant::Unknown> =
Ptr<'a, T, (Aliasing, Alignment, invariant::Valid)>;
Ptr<'a, invariant::Valid<T>, (Aliasing, Alignment)>;

// These methods are defined on the type alias, `MaybeAligned`, so as to bring
// them to the forefront of the rendered rustdoc for that type alias.
Expand Down Expand Up @@ -77,10 +77,10 @@ where
}

/// Checks if the referent is zeroed.
pub(crate) fn is_zeroed<T, I>(ptr: Ptr<'_, T, I>) -> bool
pub(crate) fn is_zeroed<T, I>(ptr: Ptr<'_, invariant::Initialized<T>, I>) -> bool
where
T: crate::Immutable + crate::KnownLayout,
I: invariant::Invariants<Validity = invariant::Initialized>,
I: invariant::Invariants,
I::Aliasing: invariant::Reference,
{
ptr.as_bytes::<BecauseImmutable>().as_ref().iter().all(|&byte| byte == 0)
Expand Down
Loading

0 comments on commit 07239f8

Please sign in to comment.