From 2f1690524a1e6eec62e201a25d752769b316c3af Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Tue, 18 Oct 2022 19:57:25 +0200 Subject: [PATCH 01/17] genericize Error and compute_offsets, move copy to its own mod --- src/copy.rs | 428 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 557 ++++++++-------------------------------------------- src/read.rs | 1 + 3 files changed, 512 insertions(+), 474 deletions(-) create mode 100644 src/copy.rs create mode 100644 src/read.rs diff --git a/src/copy.rs b/src/copy.rs new file mode 100644 index 0000000..736c8dc --- /dev/null +++ b/src/copy.rs @@ -0,0 +1,428 @@ +use super::*; + +/// Record of the results of a copy operation +#[derive(Debug, Copy, Clone)] +pub struct CopyRecord { + /// The offset from the start of the allocation, in bytes, at which the + /// copy operation began to write data. + /// + /// Not necessarily equal to the `start_offset` provided to the copy function, since this offset + /// includes necessary padding to assure alignment. + pub copy_start_offset: usize, + + /// The offset from the start of the allocation, in bytes, at which the + /// copy operation no longer wrote data. + /// + /// This does not include any padding at the end necessary to maintain + /// alignment requirements. + /// + /// Unless you really know what you're doing, you *likely* want to use `end_offset_padded` instead. + pub copy_end_offset: usize, + + /// The offset from the start of the allocation, in bytes, at which the + /// copy operation no longer wrote data, plus any padding necessary to + /// maintain derived alignment requirements. + pub copy_end_offset_padded: usize, +} + +impl From for CopyRecord { + fn from( + ComputedOffsets { + start, + end, + end_padded, + }: ComputedOffsets, + ) -> Self { + Self { + copy_start_offset: start, + copy_end_offset: end, + copy_end_offset_padded: end_padded, + } + } +} + +/// Copies `src` into the memory represented by `dst` starting at *exactly* +/// `start_offset` bytes past the start of `dst` +/// +/// - `start_offset` is the offset into the allocation represented by `dst`, in bytes, +/// where the first byte of the copied data will be placed. If the requested +/// start offset does not satisfy computed alignment requirements, an error will +/// be returned and no data will be copied. +/// +/// # Safety +/// +/// This function is safe on its own, however it is very possible to do unsafe +/// things if you read the copied data in the wrong way. See the +/// [crate-level Safety documentation][`crate#safety`] for more. +#[inline] +pub fn copy_to_offset_exact( + src: &T, + dst: &mut S, + start_offset: usize, +) -> Result { + copy_to_offset_with_align_exact(src, dst, start_offset, 1) +} + +/// Copies `src` into the memory represented by `dst` starting at *exactly* +/// `start_offset` bytes past the start of `dst` and with minimum alignment +/// `min_alignment`. +/// +/// - `start_offset` is the offset into the allocation represented by `dst`, in bytes, +/// where the first byte of the copied data will be placed. If the requested +/// start offset does not satisfy computed alignment requirements, an error will +/// be returned and no data will be copied. +/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The +/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements +/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). +/// +/// # Safety +/// +/// This function is safe on its own, however it is very possible to do unsafe +/// things if you read the copied data in the wrong way. See the +/// [crate-level Safety documentation][`crate#safety`] for more. +#[inline] +pub fn copy_to_offset_with_align_exact( + src: &T, + dst: &mut S, + start_offset: usize, + min_alignment: usize, +) -> Result { + let t_layout = Layout::new::(); + let offsets = compute_offsets(&*dst, start_offset, t_layout, min_alignment, true)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); + + // SAFETY: + // - src is valid as we have a reference to it + // - dst is valid so long as requirements for `slab` were met, i.e. + // we have unique access to the region described and that it is valid for the duration + // of 'a. + // - areas not overlapping as long as safety requirements of creation of `self` were met, + // i.e. that we have exclusive access to the region of memory described. + // - dst aligned at least to align_of::() + // - checked that copy stays within bounds of our allocation + unsafe { + core::ptr::copy_nonoverlapping(src as *const T, dst_ptr, 1); + } + + Ok(offsets.into()) +} + +/// Copies `src` into the memory represented by `dst` starting at a minimum location +/// of `start_offset` bytes past the start of `dst`. +/// +/// - `start_offset` is the offset into the allocation represented by `dst`, +/// in bytes, before which any copied data will *certainly not* be placed. However, +/// the actual beginning of the copied data may not be exactly at `start_offset` if +/// padding bytes are needed to satisfy alignment requirements. The actual beginning +/// of the copied bytes is contained in the returned [`CopyRecord`]. +/// +/// # Safety +/// +/// This function is safe on its own, however it is very possible to do unsafe +/// things if you read the copied data in the wrong way. See the +/// [crate-level Safety documentation][`crate#safety`] for more. +#[inline] +pub fn copy_to_offset( + src: &T, + dst: &mut S, + start_offset: usize, +) -> Result { + copy_to_offset_with_align(src, dst, start_offset, 1) +} + +/// Copies `src` into the memory represented by `dst` starting at a minimum location +/// of `start_offset` bytes past the start of `dst` and with minimum alignment +/// `min_alignment`. +/// +/// - `start_offset` is the offset into the allocation represented by `dst`, +/// in bytes, before which any copied data will *certainly not* be placed. However, +/// the actual beginning of the copied data may not be exactly at `start_offset` if +/// padding bytes are needed to satisfy alignment requirements. The actual beginning +/// of the copied bytes is contained in the returned [`CopyRecord`]. +/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The +/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements +/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). +/// +/// # Safety +/// +/// This function is safe on its own, however it is very possible to do unsafe +/// things if you read the copied data in the wrong way. See the +/// [crate-level Safety documentation][`crate#safety`] for more. +#[inline] +pub fn copy_to_offset_with_align( + src: &T, + dst: &mut S, + start_offset: usize, + min_alignment: usize, +) -> Result { + let t_layout = Layout::new::(); + let offsets = compute_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); + + // SAFETY: + // - src is valid as we have a reference to it + // - dst is valid so long as requirements for `slab` were met, i.e. + // we have unique access to the region described and that it is valid for the duration + // of 'a. + // - areas not overlapping as long as safety requirements of creation of `self` were met, + // i.e. that we have exclusive access to the region of memory described. + // - dst aligned at least to align_of::() + // - checked that copy stays within bounds of our allocation + unsafe { + core::ptr::copy_nonoverlapping(src as *const T, dst_ptr, 1); + } + + Ok(offsets.into()) +} + +/// Copies from `slice` into the memory represented by `dst` starting at *exactly* +/// `start_offset` bytes past the start of `self`. +/// +/// - `start_offset` is the offset into the allocation represented by `dst`, in bytes, +/// where the first byte of the copied data will be placed. If the requested +/// start offset does not satisfy computed alignment requirements, an error will +/// be returned and no data will be copied. +/// +/// # Safety +/// +/// This function is safe on its own, however it is very possible to do unsafe +/// things if you read the copied data in the wrong way. See the +/// [crate-level Safety documentation][`crate#safety`] for more. +#[inline] +pub fn copy_from_slice_to_offset_exact( + src: &[T], + dst: &mut S, + start_offset: usize, +) -> Result { + copy_from_slice_to_offset_with_align(src, dst, start_offset, 1) +} + +/// Copies from `slice` into the memory represented by `dst` starting at *exactly* +/// `start_offset` bytes past the start of `dst` and with minimum alignment `min_alignment`. +/// +/// - `start_offset` is the offset into the allocation represented by `dst`, in bytes, +/// where the first byte of the copied data will be placed. If the requested +/// start offset does not satisfy computed alignment requirements, an error will +/// be returned and no data will be copied. +/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The +/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements +/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). +/// - The whole data of the slice will be copied directly, so, alignment between elements +/// ignores `min_alignment`. +/// +/// # Safety +/// +/// This function is safe on its own, however it is very possible to do unsafe +/// things if you read the copied data in the wrong way. See the +/// [crate-level Safety documentation][`crate#safety`] for more. +#[inline] +pub fn copy_from_slice_to_offset_with_align_exact( + src: &[T], + dst: &mut S, + start_offset: usize, + min_alignment: usize, +) -> Result { + let t_layout = Layout::for_value(src); + let offsets = compute_offsets(&*dst, start_offset, t_layout, min_alignment, true)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); + + // SAFETY: + // - src is valid as we have a reference to it + // - dst is valid so long as requirements for `slab` were met, i.e. + // we have unique access to the region described and that it is valid for the duration + // of 'a. + // - areas not overlapping as long as safety requirements of creation of `self` were met, + // i.e. that we have exclusive access to the region of memory described. + // - dst aligned at least to align_of::() + // - checked that copy stays within bounds of our allocation + unsafe { + core::ptr::copy_nonoverlapping(src.as_ptr(), dst_ptr, src.len()); + } + + Ok(offsets.into()) +} + +/// Copies from `slice` into the memory represented by `dst` starting at a minimum location +/// of `start_offset` bytes past the start of `self`. +/// +/// - `start_offset` is the offset into the allocation represented by `dst`, +/// in bytes, before which any copied data will *certainly not* be placed. However, +/// the actual beginning of the copied data may not be exactly at `start_offset` if +/// padding bytes are needed to satisfy alignment requirements. The actual beginning +/// of the copied bytes is contained in the returned [`CopyRecord`]. +/// +/// # Safety +/// +/// This function is safe on its own, however it is very possible to do unsafe +/// things if you read the copied data in the wrong way. See the +/// [crate-level Safety documentation][`crate#safety`] for more. +#[inline] +pub fn copy_from_slice_to_offset( + src: &[T], + dst: &mut S, + start_offset: usize, +) -> Result { + copy_from_slice_to_offset_with_align(src, dst, start_offset, 1) +} + +/// Copies from `slice` into the memory represented by `dst` starting at a minimum location +/// of `start_offset` bytes past the start of `dst`. +/// +/// - `start_offset` is the offset into the allocation represented by `dst`, +/// in bytes, before which any copied data will *certainly not* be placed. However, +/// the actual beginning of the copied data may not be exactly at `start_offset` if +/// padding bytes are needed to satisfy alignment requirements. The actual beginning +/// of the copied bytes is contained in the returned [`CopyRecord`]. +/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The +/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements +/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). +/// - The whole data of the slice will be copied directly, so, alignment between elements +/// ignores `min_alignment`. +/// +/// # Safety +/// +/// This function is safe on its own, however it is very possible to do unsafe +/// things if you read the copied data in the wrong way. See the +/// [crate-level Safety documentation][`crate#safety`] for more. +#[inline] +pub fn copy_from_slice_to_offset_with_align( + src: &[T], + dst: &mut S, + start_offset: usize, + min_alignment: usize, +) -> Result { + let t_layout = Layout::for_value(src); + let offsets = compute_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); + + // SAFETY: + // - src is valid as we have a reference to it + // - dst is valid so long as requirements for `slab` were met, i.e. + // we have unique access to the region described and that it is valid for the duration + // of 'a. + // - areas not overlapping as long as safety requirements of creation of `self` were met, + // i.e. that we have exclusive access to the region of memory described. + // - dst aligned at least to align_of::() + // - checked that copy stays within bounds of our allocation + unsafe { + core::ptr::copy_nonoverlapping(src.as_ptr(), dst_ptr, src.len()); + } + + Ok(offsets.into()) +} + +/// Copies from `src` iterator into the memory represented by `dst` starting at a minimum location +/// of `start_offset` bytes past the start of `dst`. +/// +/// Returns a vector of [`CopyRecord`]s, one for each item in the `src` iterator. +/// +/// - `start_offset` is the offset into the allocation represented by `dst`, +/// in bytes, before which any copied data will *certainly not* be placed. However, +/// the actual beginning of the copied data may not be exactly at `start_offset` if +/// padding bytes are needed to satisfy alignment requirements. The actual beginning +/// of the copied bytes is contained in the returned [`CopyRecord`]s. +/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The +/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements +/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). +/// - For this variation, `min_alignment` will also be respected *between* elements yielded by +/// the iterator. To copy inner elements aligned only to `align_of::()` (i.e. with the layout of +/// an `[T]`), see [`copy_from_iter_to_offset_with_align_packed`]. +/// +/// # Safety +/// +/// This function is safe on its own, however it is very possible to do unsafe +/// things if you read the copied data in the wrong way. See the +/// [crate-level Safety documentation][`crate#safety`] for more. +#[cfg(feature = "std")] +#[inline] +pub fn copy_from_iter_to_offset_with_align, S: Slab>( + src: Iter, + dst: &mut S, + start_offset: usize, + min_alignment: usize, +) -> Result, Error> { + let mut offset = start_offset; + + src.map(|item| { + let copy_record = copy_to_offset_with_align(&item, dst, offset, min_alignment)?; + offset = copy_record.copy_end_offset; + Ok(copy_record) + }) + .collect::, _>>() +} + +/// Like [`copy_from_iter_to_offset_with_align`] except that +/// alignment between elements yielded by the iterator will ignore `min_alignment` +/// and rather only be aligned to the alignment of `T`. +/// +/// Because of this, only one [`CopyRecord`] is returned specifying the record of the +/// entire block of copied data. If the `src` iterator is empty, returns `None`. +#[inline] +pub fn copy_from_iter_to_offset_with_align_packed, S: Slab>( + mut src: Iter, + dst: &mut S, + start_offset: usize, + min_alignment: usize, +) -> Result, Error> { + let first_record = if let Some(first_item) = src.next() { + copy_to_offset_with_align(&first_item, dst, start_offset, min_alignment)? + } else { + return Ok(None); + }; + + let mut prev_record = first_record; + + for item in src { + let copy_record = copy_to_offset_with_align(&item, dst, prev_record.copy_end_offset, 1)?; + prev_record = copy_record; + } + + Ok(Some(CopyRecord { + copy_start_offset: first_record.copy_start_offset, + copy_end_offset: prev_record.copy_end_offset, + copy_end_offset_padded: prev_record.copy_end_offset_padded, + })) +} + +/// Like [`copy_from_iter_to_offset_with_align_packed`] except that it will return an error +/// and no data will be copied if the supplied `start_offset` doesn't meet the computed alignment +/// requirements. +#[inline] +pub fn copy_from_iter_to_offset_with_align_exact_packed< + T: Copy, + Iter: Iterator, + S: Slab, +>( + mut src: Iter, + dst: &mut S, + start_offset: usize, + min_alignment: usize, +) -> Result, Error> { + let first_record = if let Some(first_item) = src.next() { + copy_to_offset_with_align_exact(&first_item, dst, start_offset, min_alignment)? + } else { + return Ok(None); + }; + + let mut prev_record = first_record; + + for item in src { + let copy_record = + copy_to_offset_with_align_exact(&item, dst, prev_record.copy_end_offset, 1)?; + prev_record = copy_record; + } + + Ok(Some(CopyRecord { + copy_start_offset: first_record.copy_start_offset, + copy_end_offset: prev_record.copy_end_offset, + copy_end_offset_padded: prev_record.copy_end_offset_padded, + })) +} diff --git a/src/lib.rs b/src/lib.rs index 4593726..56eed39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,8 +117,14 @@ use core::marker::PhantomData; use core::mem::MaybeUninit; use core::ptr::NonNull; +mod copy; +mod read; + +pub use copy::*; +pub use read::*; + /// Represents a contiguous piece of a single allocation with some layout that is used as a -/// data copying destination. May be wholly or partially uninitialized. +/// data copying destination or reading source. May be wholly or partially uninitialized. /// /// This trait is *basically* equivalent to implementing `Deref`/`DerefMut` with /// `Target = [MaybeUninit]` in terms of safety requirements. It is a separate @@ -289,6 +295,43 @@ unsafe impl Slab for [MaybeUninit] { } } +/// An error that may occur during a copy or read operation. +#[derive(Debug)] +pub enum Error { + /// Copy or read would exceed the end of the allocation + OutOfMemory, + /// Requested to copy to or read from an offset outside the bounds of the allocation + OffsetOutOfBounds, + /// Computed invalid layout for copy operation, probably caused by incredibly large size, offset, or min-alignment parameters + InvalidLayout, + /// The requested offset was unalignd. In a read operation, this means the provided offset into the buffer was not properly aligned + /// for the requested type. + /// + /// In an `exact` variant copy function, the computed copy start offset did not match the requested start offset, + /// meaning the requested start offset was not properly aligned. + RequestedOffsetUnaligned, +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", match self { + Self::OutOfMemory => "End of copy or read operation would exceed the end of the allocation", + Self::OffsetOutOfBounds => "Requested read from or copy to a location starting outside the allocation", + Self::InvalidLayout => "Computed invalid layout requirements, probably caused by incredibly large size, offset, or alignment parameters", + Self::RequestedOffsetUnaligned => "Requested offset into Slab did not satisfy computed alignment requirements", + }) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +impl From for Error { + fn from(_err: LayoutError) -> Self { + Self::InvalidLayout + } +} + /// Takes a `Vec` and unsafely resizes it to the given length, returning a mutable slice to `MaybeUninit` for each /// item in the newly-resized `Vec`. /// @@ -475,496 +518,62 @@ unsafe impl<'a> Slab for BorrowedRawAllocation<'a> { } } -/// Given pointer and offset, returns a new offset aligned to `align`. -/// -/// `align` *must* be a power of two and >= 1 or else the result is meaningless. -fn align_offset_up_to(ptr: usize, offset: usize, align: usize) -> Option { - let offsetted_ptr = ptr.checked_add(offset)?; - let aligned_ptr = offsetted_ptr.checked_add(align - 1)? & !(align - 1); - // don't need to check since we know aligned_ptr is >= ptr at this point - Some(aligned_ptr - ptr) +/// Computed offsets necessary for a copy or read operation with some layout. Should only be +/// created by [`compute_offsets`] +#[derive(Debug, Copy, Clone)] +pub(crate) struct ComputedOffsets { + start: usize, + end: usize, + end_padded: usize, } -/// Compute and validate offsets for a copy operation with the given parameters. -fn compute_offsets( - dst: &S, +/// Compute and validate offsets for a copy or read operation with the given parameters. +pub(crate) fn compute_offsets( + slab: &S, start_offset: usize, t_layout: Layout, min_alignment: usize, require_exact_start_offset: bool, -) -> Result { - let copy_layout = t_layout.align_to(min_alignment.next_power_of_two())?; - - let copy_start_offset = - align_offset_up_to(dst.base_ptr() as usize, start_offset, copy_layout.align()) - .ok_or(CopyError::InvalidLayout)?; - if require_exact_start_offset && start_offset != copy_start_offset { - return Err(CopyError::RequestedOffsetUnaligned); +) -> Result { + let layout = t_layout.align_to(min_alignment.next_power_of_two())?; + + let computed_start_offset = + align_offset_up_to(slab.base_ptr() as usize, start_offset, layout.align()) + .ok_or(Error::InvalidLayout)?; + if require_exact_start_offset && start_offset != computed_start_offset { + return Err(Error::RequestedOffsetUnaligned); } - let copy_end_offset = copy_start_offset - .checked_add(copy_layout.size()) - .ok_or(CopyError::InvalidLayout)?; - let copy_end_offset_padded = copy_start_offset - .checked_add(copy_layout.pad_to_align().size()) - .ok_or(CopyError::InvalidLayout)?; + let computed_end_offset = computed_start_offset + .checked_add(layout.size()) + .ok_or(Error::InvalidLayout)?; + let computed_end_offset_padded = computed_start_offset + .checked_add(layout.pad_to_align().size()) + .ok_or(Error::InvalidLayout)?; // check start is inside slab // if within slab, we also know that copy_start_offset is <= isize::MAX since slab.size() must be <= isize::MAX - if copy_start_offset > dst.size() { - return Err(CopyError::OffsetOutOfBounds); + if computed_start_offset > slab.size() { + return Err(Error::OffsetOutOfBounds); } // check end is inside slab - if copy_end_offset_padded > dst.size() { - return Err(CopyError::OutOfMemory); + if computed_end_offset_padded > slab.size() { + return Err(Error::OutOfMemory); } - Ok(CopyRecord { - copy_start_offset, - copy_end_offset, - copy_end_offset_padded, + Ok(ComputedOffsets { + start: computed_start_offset, + end: computed_end_offset, + end_padded: computed_end_offset_padded, }) } -/// An error that may occur during a copy operation. -#[derive(Debug)] -pub enum CopyError { - /// Copy would exceed the end of the allocation - OutOfMemory, - /// Requested to copy to an offset outside the bounds of the allocation - OffsetOutOfBounds, - /// Computed invalid layout for copy operation, probably caused by incredibly large size, offset, or min-alignment parameters - InvalidLayout, - /// In an `exact` variant copy function, the computed copy start offset did not match the requested start offset, - /// meaning the requested start offset was not properly aligned - RequestedOffsetUnaligned, -} - -impl core::fmt::Display for CopyError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", match self { - Self::OutOfMemory => "Copy would exceed the end of the allocation", - Self::OffsetOutOfBounds => "Requested copy to a location starting outside the allocation", - Self::InvalidLayout => "Invalid layout, probably caused by incredibly large size, offset, or alignment parameters", - Self::RequestedOffsetUnaligned => "Requested start_offset did not satisfy computed alignment requirements", - }) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for CopyError {} - -impl From for CopyError { - fn from(_err: LayoutError) -> Self { - Self::InvalidLayout - } -} - -/// Record of the results of a copy operation -#[derive(Debug, Copy, Clone)] -pub struct CopyRecord { - /// The offset from the start of the allocation, in bytes, at which the - /// copy operation began to write data. - /// - /// Not necessarily equal to the `start_offset`, since this offset - /// includes necessary padding to assure alignment. - pub copy_start_offset: usize, - - /// The offset from the start of the allocation, in bytes, at which the - /// copy operation no longer wrote data. - /// - /// This does not include any padding at the end necessary to maintain - /// alignment requirements. - pub copy_end_offset: usize, - - /// The offset from the start of the allocation, in bytes, at which the - /// copy operation no longer wrote data, plus any padding necessary to - /// maintain derived alignment requirements. - pub copy_end_offset_padded: usize, -} - -/// Copies `src` into the memory represented by `dst` starting at *exactly* -/// `start_offset` bytes past the start of `dst` -/// -/// - `start_offset` is the offset into the allocation represented by `dst`, in bytes, -/// where the first byte of the copied data will be placed. If the requested -/// start offset does not satisfy computed alignment requirements, an error will -/// be returned and no data will be copied. -/// -/// # Safety -/// -/// This function is safe on its own, however it is very possible to do unsafe -/// things if you read the copied data in the wrong way. See the -/// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] -pub fn copy_to_offset_exact( - src: &T, - dst: &mut S, - start_offset: usize, -) -> Result { - copy_to_offset_with_align_exact(src, dst, start_offset, 1) -} - -/// Copies `src` into the memory represented by `dst` starting at *exactly* -/// `start_offset` bytes past the start of `dst` and with minimum alignment -/// `min_alignment`. -/// -/// - `start_offset` is the offset into the allocation represented by `dst`, in bytes, -/// where the first byte of the copied data will be placed. If the requested -/// start offset does not satisfy computed alignment requirements, an error will -/// be returned and no data will be copied. -/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The -/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements -/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). -/// -/// # Safety -/// -/// This function is safe on its own, however it is very possible to do unsafe -/// things if you read the copied data in the wrong way. See the -/// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] -pub fn copy_to_offset_with_align_exact( - src: &T, - dst: &mut S, - start_offset: usize, - min_alignment: usize, -) -> Result { - let t_layout = Layout::new::(); - let record = compute_offsets(&*dst, start_offset, t_layout, min_alignment, true)?; - - // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. - let dst_ptr = unsafe { dst.base_ptr_mut().add(record.copy_start_offset) }.cast::(); - - // SAFETY: - // - src is valid as we have a reference to it - // - dst is valid so long as requirements for `slab` were met, i.e. - // we have unique access to the region described and that it is valid for the duration - // of 'a. - // - areas not overlapping as long as safety requirements of creation of `self` were met, - // i.e. that we have exclusive access to the region of memory described. - // - dst aligned at least to align_of::() - // - checked that copy stays within bounds of our allocation - unsafe { - core::ptr::copy_nonoverlapping(src as *const T, dst_ptr, 1); - } - - Ok(record) -} - -/// Copies `src` into the memory represented by `dst` starting at a minimum location -/// of `start_offset` bytes past the start of `dst`. -/// -/// - `start_offset` is the offset into the allocation represented by `dst`, -/// in bytes, before which any copied data will *certainly not* be placed. However, -/// the actual beginning of the copied data may not be exactly at `start_offset` if -/// padding bytes are needed to satisfy alignment requirements. The actual beginning -/// of the copied bytes is contained in the returned [`CopyRecord`]. -/// -/// # Safety -/// -/// This function is safe on its own, however it is very possible to do unsafe -/// things if you read the copied data in the wrong way. See the -/// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] -pub fn copy_to_offset( - src: &T, - dst: &mut S, - start_offset: usize, -) -> Result { - copy_to_offset_with_align(src, dst, start_offset, 1) -} - -/// Copies `src` into the memory represented by `dst` starting at a minimum location -/// of `start_offset` bytes past the start of `dst` and with minimum alignment -/// `min_alignment`. -/// -/// - `start_offset` is the offset into the allocation represented by `dst`, -/// in bytes, before which any copied data will *certainly not* be placed. However, -/// the actual beginning of the copied data may not be exactly at `start_offset` if -/// padding bytes are needed to satisfy alignment requirements. The actual beginning -/// of the copied bytes is contained in the returned [`CopyRecord`]. -/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The -/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements -/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). -/// -/// # Safety -/// -/// This function is safe on its own, however it is very possible to do unsafe -/// things if you read the copied data in the wrong way. See the -/// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] -pub fn copy_to_offset_with_align( - src: &T, - dst: &mut S, - start_offset: usize, - min_alignment: usize, -) -> Result { - let t_layout = Layout::new::(); - let record = compute_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; - - // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. - let dst_ptr = unsafe { dst.base_ptr_mut().add(record.copy_start_offset) }.cast::(); - - // SAFETY: - // - src is valid as we have a reference to it - // - dst is valid so long as requirements for `slab` were met, i.e. - // we have unique access to the region described and that it is valid for the duration - // of 'a. - // - areas not overlapping as long as safety requirements of creation of `self` were met, - // i.e. that we have exclusive access to the region of memory described. - // - dst aligned at least to align_of::() - // - checked that copy stays within bounds of our allocation - unsafe { - core::ptr::copy_nonoverlapping(src as *const T, dst_ptr, 1); - } - - Ok(record) -} - -/// Copies from `slice` into the memory represented by `dst` starting at *exactly* -/// `start_offset` bytes past the start of `self`. -/// -/// - `start_offset` is the offset into the allocation represented by `dst`, in bytes, -/// where the first byte of the copied data will be placed. If the requested -/// start offset does not satisfy computed alignment requirements, an error will -/// be returned and no data will be copied. -/// -/// # Safety -/// -/// This function is safe on its own, however it is very possible to do unsafe -/// things if you read the copied data in the wrong way. See the -/// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] -pub fn copy_from_slice_to_offset_exact( - src: &[T], - dst: &mut S, - start_offset: usize, -) -> Result { - copy_from_slice_to_offset_with_align(src, dst, start_offset, 1) -} - -/// Copies from `slice` into the memory represented by `dst` starting at *exactly* -/// `start_offset` bytes past the start of `dst` and with minimum alignment `min_alignment`. -/// -/// - `start_offset` is the offset into the allocation represented by `dst`, in bytes, -/// where the first byte of the copied data will be placed. If the requested -/// start offset does not satisfy computed alignment requirements, an error will -/// be returned and no data will be copied. -/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The -/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements -/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). -/// - The whole data of the slice will be copied directly, so, alignment between elements -/// ignores `min_alignment`. -/// -/// # Safety -/// -/// This function is safe on its own, however it is very possible to do unsafe -/// things if you read the copied data in the wrong way. See the -/// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] -pub fn copy_from_slice_to_offset_with_align_exact( - src: &[T], - dst: &mut S, - start_offset: usize, - min_alignment: usize, -) -> Result { - let t_layout = Layout::for_value(src); - let record = compute_offsets(&*dst, start_offset, t_layout, min_alignment, true)?; - - // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. - let dst_ptr = unsafe { dst.base_ptr_mut().add(record.copy_start_offset) }.cast::(); - - // SAFETY: - // - src is valid as we have a reference to it - // - dst is valid so long as requirements for `slab` were met, i.e. - // we have unique access to the region described and that it is valid for the duration - // of 'a. - // - areas not overlapping as long as safety requirements of creation of `self` were met, - // i.e. that we have exclusive access to the region of memory described. - // - dst aligned at least to align_of::() - // - checked that copy stays within bounds of our allocation - unsafe { - core::ptr::copy_nonoverlapping(src.as_ptr(), dst_ptr, src.len()); - } - - Ok(record) -} - -/// Copies from `slice` into the memory represented by `dst` starting at a minimum location -/// of `start_offset` bytes past the start of `self`. -/// -/// - `start_offset` is the offset into the allocation represented by `dst`, -/// in bytes, before which any copied data will *certainly not* be placed. However, -/// the actual beginning of the copied data may not be exactly at `start_offset` if -/// padding bytes are needed to satisfy alignment requirements. The actual beginning -/// of the copied bytes is contained in the returned [`CopyRecord`]. -/// -/// # Safety -/// -/// This function is safe on its own, however it is very possible to do unsafe -/// things if you read the copied data in the wrong way. See the -/// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] -pub fn copy_from_slice_to_offset( - src: &[T], - dst: &mut S, - start_offset: usize, -) -> Result { - copy_from_slice_to_offset_with_align(src, dst, start_offset, 1) -} - -/// Copies from `slice` into the memory represented by `dst` starting at a minimum location -/// of `start_offset` bytes past the start of `dst`. -/// -/// - `start_offset` is the offset into the allocation represented by `dst`, -/// in bytes, before which any copied data will *certainly not* be placed. However, -/// the actual beginning of the copied data may not be exactly at `start_offset` if -/// padding bytes are needed to satisfy alignment requirements. The actual beginning -/// of the copied bytes is contained in the returned [`CopyRecord`]. -/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The -/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements -/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). -/// - The whole data of the slice will be copied directly, so, alignment between elements -/// ignores `min_alignment`. -/// -/// # Safety -/// -/// This function is safe on its own, however it is very possible to do unsafe -/// things if you read the copied data in the wrong way. See the -/// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] -pub fn copy_from_slice_to_offset_with_align( - src: &[T], - dst: &mut S, - start_offset: usize, - min_alignment: usize, -) -> Result { - let t_layout = Layout::for_value(src); - let record = compute_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; - - // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. - let dst_ptr = unsafe { dst.base_ptr_mut().add(record.copy_start_offset) }.cast::(); - - // SAFETY: - // - src is valid as we have a reference to it - // - dst is valid so long as requirements for `slab` were met, i.e. - // we have unique access to the region described and that it is valid for the duration - // of 'a. - // - areas not overlapping as long as safety requirements of creation of `self` were met, - // i.e. that we have exclusive access to the region of memory described. - // - dst aligned at least to align_of::() - // - checked that copy stays within bounds of our allocation - unsafe { - core::ptr::copy_nonoverlapping(src.as_ptr(), dst_ptr, src.len()); - } - - Ok(record) -} - -/// Copies from `src` iterator into the memory represented by `dst` starting at a minimum location -/// of `start_offset` bytes past the start of `dst`. -/// -/// Returns a vector of [`CopyRecord`]s, one for each item in the `src` iterator. -/// -/// - `start_offset` is the offset into the allocation represented by `dst`, -/// in bytes, before which any copied data will *certainly not* be placed. However, -/// the actual beginning of the copied data may not be exactly at `start_offset` if -/// padding bytes are needed to satisfy alignment requirements. The actual beginning -/// of the copied bytes is contained in the returned [`CopyRecord`]s. -/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The -/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements -/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). -/// - For this variation, `min_alignment` will also be respected *between* elements yielded by -/// the iterator. To copy inner elements aligned only to `align_of::()` (i.e. with the layout of -/// an `[T]`), see [`copy_from_iter_to_offset_with_align_packed`]. -/// -/// # Safety -/// -/// This function is safe on its own, however it is very possible to do unsafe -/// things if you read the copied data in the wrong way. See the -/// [crate-level Safety documentation][`crate#safety`] for more. -#[cfg(feature = "std")] -#[inline] -pub fn copy_from_iter_to_offset_with_align, S: Slab>( - src: Iter, - dst: &mut S, - start_offset: usize, - min_alignment: usize, -) -> Result, CopyError> { - let mut offset = start_offset; - - src.map(|item| { - let copy_record = copy_to_offset_with_align(&item, dst, offset, min_alignment)?; - offset = copy_record.copy_end_offset; - Ok(copy_record) - }) - .collect::, _>>() -} - -/// Like [`copy_from_iter_to_offset_with_align`] except that -/// alignment between elements yielded by the iterator will ignore `min_alignment` -/// and rather only be aligned to the alignment of `T`. +/// Given pointer and offset, returns a new offset aligned to `align`. /// -/// Because of this, only one [`CopyRecord`] is returned specifying the record of the -/// entire block of copied data. If the `src` iterator is empty, returns `None`. -#[inline] -pub fn copy_from_iter_to_offset_with_align_packed, S: Slab>( - mut src: Iter, - dst: &mut S, - start_offset: usize, - min_alignment: usize, -) -> Result, CopyError> { - let first_record = if let Some(first_item) = src.next() { - copy_to_offset_with_align(&first_item, dst, start_offset, min_alignment)? - } else { - return Ok(None); - }; - - let mut prev_record = first_record; - - for item in src { - let copy_record = copy_to_offset_with_align(&item, dst, prev_record.copy_end_offset, 1)?; - prev_record = copy_record; - } - - Ok(Some(CopyRecord { - copy_start_offset: first_record.copy_start_offset, - copy_end_offset: prev_record.copy_end_offset, - copy_end_offset_padded: prev_record.copy_end_offset_padded, - })) -} - -/// Like [`copy_from_iter_to_offset_with_align_packed`] except that it will return an error -/// and no data will be copied if the supplied `start_offset` doesn't meet the computed alignment -/// requirements. -#[inline] -pub fn copy_from_iter_to_offset_with_align_exact_packed< - T: Copy, - Iter: Iterator, - S: Slab, ->( - mut src: Iter, - dst: &mut S, - start_offset: usize, - min_alignment: usize, -) -> Result, CopyError> { - let first_record = if let Some(first_item) = src.next() { - copy_to_offset_with_align_exact(&first_item, dst, start_offset, min_alignment)? - } else { - return Ok(None); - }; - - let mut prev_record = first_record; - - for item in src { - let copy_record = - copy_to_offset_with_align_exact(&item, dst, prev_record.copy_end_offset, 1)?; - prev_record = copy_record; - } - - Ok(Some(CopyRecord { - copy_start_offset: first_record.copy_start_offset, - copy_end_offset: prev_record.copy_end_offset, - copy_end_offset_padded: prev_record.copy_end_offset_padded, - })) +/// `align` *must* be a power of two and >= 1 or else the result is meaningless. +fn align_offset_up_to(ptr: usize, offset: usize, align: usize) -> Option { + let offsetted_ptr = ptr.checked_add(offset)?; + let aligned_ptr = offsetted_ptr.checked_add(align - 1)? & !(align - 1); + // don't need to check since we know aligned_ptr is >= ptr at this point + Some(aligned_ptr - ptr) } diff --git a/src/read.rs b/src/read.rs new file mode 100644 index 0000000..4563e55 --- /dev/null +++ b/src/read.rs @@ -0,0 +1 @@ +use super::*; From 890350b6d83ef608fc39797b1109326f8f5321c5 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Tue, 18 Oct 2022 21:00:43 +0200 Subject: [PATCH 02/17] add read function set --- src/read.rs | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) diff --git a/src/read.rs b/src/read.rs index 4563e55..c6c5f49 100644 --- a/src/read.rs +++ b/src/read.rs @@ -1 +1,203 @@ use super::*; + +/// Gets a shared reference to a `T` within `slab` at `offset`. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. +/// +/// The function will return an error if: +/// - `offset` within `slab` is not properly aligned for `T` +/// - `offset` is out of bounds of the `slab` +/// - `offset + size_of::` is out of bounds of the `slab` +/// +/// # Safety +/// +/// You must have previously **fully-initialized** a **valid** `T` at the given offset into `slab`. +/// Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub unsafe fn read_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize) -> Result<&'a T, Error> { + let t_layout = Layout::new::(); + let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let ptr = unsafe { slab.base_ptr().add(offsets.start) }.cast::(); + + // SAFETY: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for `T` at `ptr`, checked by + // - if the function-level safety guarantees are met, then: + // - `ptr` contains a previously-placed `T` + // - we have shared access to all of `slab`, which includes `ptr`. + Ok(unsafe { &*ptr }) +} + +/// Gets a mutable reference to a `T` within `slab` at `offset`. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. +/// +/// The function will return an error if: +/// - `offset` within `slab` is not properly aligned for `T` +/// - `offset` is out of bounds of the `slab` +/// - `offset + size_of::` is out of bounds of the `slab` +/// +/// # Safety +/// +/// You must have previously **fully-initialized** a **valid**\* `T` at the given offset into `slab`. If you want to fill an uninitialized +/// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. +/// +/// \* Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub unsafe fn read_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) -> Result<&'a mut T, Error> { + let t_layout = Layout::new::(); + let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::(); + + // SAFETY: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for `T` at `ptr`, checked by + // - if the function-level safety guarantees are met, then: + // - `ptr` contains a previously-placed `T` + // - we have unique access to all of `slab`, which includes `ptr`. + Ok(unsafe { &mut *ptr }) +} + +/// Gets a mutable reference to a `MaybeUninit` within `slab` at `offset`. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` may be placed. +/// +/// The function will return an error if: +/// - `offset` within `slab` is not properly aligned for `T` +/// - `offset` is out of bounds of the `slab` +/// - `offset + size_of::` is out of bounds of the `slab` +/// +/// # Safety +/// +/// This function is safe since in order to read any data you need to call the unsafe [`MaybeUninit::assume_init`] on the returned value. +/// However, you should know that if you do that, you must have ensured that there is indeed a **valid** `T` in its place. +/// Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub fn read_maybe_uninit_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) -> Result<&'a mut MaybeUninit, Error> { + let t_layout = Layout::new::(); + let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::>(); + + // SAFETY: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for `T` at `ptr`, checked by us + // - if the function-level safety guarantees are met, then: + // - we have unique access to all of `slab`, which includes `ptr`. + Ok(unsafe { &mut *ptr }) +} + +/// Reads a `&[T]` within `slab` at `offset`. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` is placed. +/// - `len` is the length of the returned slice, counted in elements of `T`. +/// +/// The function will return an error if: +/// - `offset` within `slab` is not properly aligned for `T` +/// - `offset` is out of bounds of the `slab` +/// - `offset + size_of:: * len` is out of bounds of the `slab` +/// +/// # Safety +/// +/// You must have previously **fully-initialized** a **valid** a `[T; len]` at the given offset into `slab`. +/// Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub unsafe fn read_slice_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize, len: usize) -> Result<&'a [T], Error> { + let t_layout = match Layout::array::(len) { + Ok(layout) => layout, + Err(_) => return Err(Error::InvalidLayout), + }; + let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let ptr = unsafe { slab.base_ptr().add(offsets.start) }.cast::(); + + // SAFETY: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for the slice's layout, checked by us + // - if the function-level safety guarantees are met, then: + // - `ptr` contains a previously-placed `[T; len]` + // - we have shared access to all of `slab`, which includes `ptr`. + Ok(unsafe { core::slice::from_raw_parts(ptr, len) }) +} + +/// Reads a `&mut [T]` within `slab` at `offset`. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` is placed. +/// - `len` is the length of the returned slice, counted in elements of `T`. +/// +/// The function will return an error if: +/// - `offset` within `slab` is not properly aligned for `T` +/// - `offset` is out of bounds of the `slab` +/// - `offset + size_of:: * len` is out of bounds of the `slab` +/// +/// # Safety +/// +/// You must have previously **fully-initialized** a **valid**\* `[T; len]` at the given offset into `slab`. If you want to fill an uninitialized +/// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. +/// +/// \* Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub unsafe fn read_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize, len: usize) -> Result<&'a mut [T], Error> { + let t_layout = match Layout::array::(len) { + Ok(layout) => layout, + Err(_) => return Err(Error::InvalidLayout), + }; + let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::(); + + // SAFETY: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for the slice's layout, checked by us + // - if the function-level safety guarantees are met, then: + // - `ptr` contains a previously-placed `[T; len]` + // - we have mutable access to all of `slab`, which includes `ptr`. + Ok(unsafe { core::slice::from_raw_parts_mut(ptr, len) }) +} + +/// Gets a `&mut [MaybeUninit]` within `slab` at `offset`. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` may be placed. +/// - `len` is the length of the returned slice, counted in elements of `T`. +/// +/// The function will return an error if: +/// - `offset` within `slab` is not properly aligned for `T` +/// - `offset` is out of bounds of the `slab` +/// - `offset + size_of:: * len` is out of bounds of the `slab` +/// +/// # Safety +/// +/// This function is safe since in order to read any data you need to call the unsafe [`MaybeUninit::assume_init`] on the returned value. +/// However, you should know that if you do that, you must have ensured that there is indeed a **valid** `T` in its place. +/// Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub fn read_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize, len: usize) -> Result<&'a mut [MaybeUninit], Error> { + let t_layout = match Layout::array::(len) { + Ok(layout) => layout, + Err(_) => return Err(Error::InvalidLayout), + }; + let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::>(); + + // SAFETY: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for the slice's layout, checked by us + // - if the function-level safety guarantees are met, then: + // - we have mutable access to all of `slab`, which includes `ptr`. + Ok(unsafe { core::slice::from_raw_parts_mut(ptr, len) }) +} From fc9f14a2cca51f62ab6046c6bc4c9b2bd9e2b931 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Tue, 18 Oct 2022 21:09:11 +0200 Subject: [PATCH 03/17] update top level docs --- src/lib.rs | 16 +++++++++++++--- src/read.rs | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 56eed39..f8988a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! //! ## Motivation //! -//! `presser` can help you when copying data into raw buffers. One primary use-case is copying data into +//! `presser` can help you when copying data into and reading data out of raw buffers. One primary use-case is copying data into //! graphics-api-allocated buffers which will then be accessed by the GPU. Common methods for doing this //! right now in Rust can often invoke UB in subtle and hard-to-see ways. For example, viewing an allocated //! but uninitialized buffer as an `&mut [u8]` **is instantly undefined behavior**\*, and `transmute`ing even a @@ -44,7 +44,7 @@ //! a [`BorrowedRawAllocation`] (which implements [`Slab`]) by calling the unsafe function //! [`RawAllocation::borrow_as_slab`] //! -//! Once you have a slab, you can use the copy helper functions provided at the crate root, for example, +//! Once you have a slab, you can use the helper functions provided at the crate root, for example, //! [`copy_to_offset`] and [`copy_to_offset_with_align`]. //! //! ### Example @@ -78,6 +78,13 @@ //! // `my_data` may be placed at a different offset than requested. so, we check the returned //! // `CopyRecord` to check the actual start offset of the copied data. //! let actual_start_offset = copy_record.copy_start_offset; +//! +//! // we may later (*unsafely*) read back our data. note that the read helpers provided by presser +//! // are mostly unsafe. They do help protect you from some common footguns, but you still ultimately need +//! // to guarantee you put the proper data where you're telling it you put the proper data. +//! let my_copied_data_in_my_buffer: &MyDataStruct = unsafe { +//! presser::read_at_offset(&slab, actual_start_offset)? +//! }; //! ``` //! //! ### `#[no_std]` @@ -94,13 +101,16 @@ //! using same backing memory that the [`Slab`] you copied into used are still safe. //! //! For example, say you have a fully-initialized -//! chunk of bytes (like a `Vec`), which you view as a [`Slab`], and then (safely) perform a copy +//! chunk of bytes (like a `Vec`), which you (unsafely\*) view as a [`Slab`], and then (safely) perform a copy //! operation into using [`copy_to_offset`]. If the `T` you copied into it has any padding bytes in //! its memory layout, then the memory locations where those padding bytes now exist in the underlying `Vec`'s //! memory must now be treated as uninitialized. As such, taking any view into that byte vector which //! relies on those newly-uninit bytes being initialized to be valid (for example, taking a `&[u8]` slice of the `Vec` //! which includes those bytes, ***even if your code never actually reads from that slice***) //! is now instant **undefined behavior**. +//! +//! \* *Note: this is unsafe because, as exemplified, you may copy uninit data into the buffer. Hence, care should +//! be taken when implementing [`Slab`] and then providing a safe interface on top of a low level buffer type.* #![cfg_attr(not(feature = "std"), no_std)] #![deny(unsafe_op_in_unsafe_fn)] #![deny(missing_docs)] diff --git a/src/read.rs b/src/read.rs index c6c5f49..718592b 100644 --- a/src/read.rs +++ b/src/read.rs @@ -80,7 +80,7 @@ pub unsafe fn read_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) /// Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] -pub fn read_maybe_uninit_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) -> Result<&'a mut MaybeUninit, Error> { +pub fn get_maybe_uninit_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) -> Result<&'a mut MaybeUninit, Error> { let t_layout = Layout::new::(); let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; @@ -184,7 +184,7 @@ pub unsafe fn read_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: /// Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] -pub fn read_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize, len: usize) -> Result<&'a mut [MaybeUninit], Error> { +pub fn get_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize, len: usize) -> Result<&'a mut [MaybeUninit], Error> { let t_layout = match Layout::array::(len) { Ok(layout) => layout, Err(_) => return Err(Error::InvalidLayout), From b641579f5e81ca69c55ebf446eedae79ede621a7 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Tue, 18 Oct 2022 21:09:33 +0200 Subject: [PATCH 04/17] rename compute_offsets to compute_and_validate_offsets --- src/copy.rs | 8 ++++---- src/lib.rs | 2 +- src/read.rs | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/copy.rs b/src/copy.rs index 736c8dc..086f48f 100644 --- a/src/copy.rs +++ b/src/copy.rs @@ -88,7 +88,7 @@ pub fn copy_to_offset_with_align_exact( min_alignment: usize, ) -> Result { let t_layout = Layout::new::(); - let offsets = compute_offsets(&*dst, start_offset, t_layout, min_alignment, true)?; + let offsets = compute_and_validate_offsets(&*dst, start_offset, t_layout, min_alignment, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); @@ -158,7 +158,7 @@ pub fn copy_to_offset_with_align( min_alignment: usize, ) -> Result { let t_layout = Layout::new::(); - let offsets = compute_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; + let offsets = compute_and_validate_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); @@ -227,7 +227,7 @@ pub fn copy_from_slice_to_offset_with_align_exact( min_alignment: usize, ) -> Result { let t_layout = Layout::for_value(src); - let offsets = compute_offsets(&*dst, start_offset, t_layout, min_alignment, true)?; + let offsets = compute_and_validate_offsets(&*dst, start_offset, t_layout, min_alignment, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); @@ -298,7 +298,7 @@ pub fn copy_from_slice_to_offset_with_align( min_alignment: usize, ) -> Result { let t_layout = Layout::for_value(src); - let offsets = compute_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; + let offsets = compute_and_validate_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); diff --git a/src/lib.rs b/src/lib.rs index f8988a6..c03fbd3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -538,7 +538,7 @@ pub(crate) struct ComputedOffsets { } /// Compute and validate offsets for a copy or read operation with the given parameters. -pub(crate) fn compute_offsets( +pub(crate) fn compute_and_validate_offsets( slab: &S, start_offset: usize, t_layout: Layout, diff --git a/src/read.rs b/src/read.rs index 718592b..36012de 100644 --- a/src/read.rs +++ b/src/read.rs @@ -17,7 +17,7 @@ use super::*; #[inline] pub unsafe fn read_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize) -> Result<&'a T, Error> { let t_layout = Layout::new::(); - let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + let offsets = compute_and_validate_offsets(slab, offset, t_layout, 1, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr().add(offsets.start) }.cast::(); @@ -50,7 +50,7 @@ pub unsafe fn read_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize) -> Resu #[inline] pub unsafe fn read_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) -> Result<&'a mut T, Error> { let t_layout = Layout::new::(); - let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + let offsets = compute_and_validate_offsets(slab, offset, t_layout, 1, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::(); @@ -82,7 +82,7 @@ pub unsafe fn read_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) #[inline] pub fn get_maybe_uninit_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) -> Result<&'a mut MaybeUninit, Error> { let t_layout = Layout::new::(); - let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + let offsets = compute_and_validate_offsets(slab, offset, t_layout, 1, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::>(); @@ -116,7 +116,7 @@ pub unsafe fn read_slice_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize, l Ok(layout) => layout, Err(_) => return Err(Error::InvalidLayout), }; - let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + let offsets = compute_and_validate_offsets(slab, offset, t_layout, 1, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr().add(offsets.start) }.cast::(); @@ -153,7 +153,7 @@ pub unsafe fn read_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: Ok(layout) => layout, Err(_) => return Err(Error::InvalidLayout), }; - let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + let offsets = compute_and_validate_offsets(slab, offset, t_layout, 1, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::(); @@ -189,7 +189,7 @@ pub fn get_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, off Ok(layout) => layout, Err(_) => return Err(Error::InvalidLayout), }; - let offsets = compute_offsets(slab, offset, t_layout, 1, true)?; + let offsets = compute_and_validate_offsets(slab, offset, t_layout, 1, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::>(); From a82894144d8cc6ffcecaf20e5fea53e579f4bc96 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Tue, 18 Oct 2022 21:09:45 +0200 Subject: [PATCH 05/17] fmt --- src/copy.rs | 6 ++-- src/lib.rs | 6 ++-- src/read.rs | 92 ++++++++++++++++++++++++++++++++--------------------- 3 files changed, 62 insertions(+), 42 deletions(-) diff --git a/src/copy.rs b/src/copy.rs index 086f48f..b08879a 100644 --- a/src/copy.rs +++ b/src/copy.rs @@ -158,7 +158,8 @@ pub fn copy_to_offset_with_align( min_alignment: usize, ) -> Result { let t_layout = Layout::new::(); - let offsets = compute_and_validate_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; + let offsets = + compute_and_validate_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); @@ -298,7 +299,8 @@ pub fn copy_from_slice_to_offset_with_align( min_alignment: usize, ) -> Result { let t_layout = Layout::for_value(src); - let offsets = compute_and_validate_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; + let offsets = + compute_and_validate_offsets(&*dst, start_offset, t_layout, min_alignment, false)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let dst_ptr = unsafe { dst.base_ptr_mut().add(offsets.start) }.cast::(); diff --git a/src/lib.rs b/src/lib.rs index c03fbd3..9915406 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,11 +78,11 @@ //! // `my_data` may be placed at a different offset than requested. so, we check the returned //! // `CopyRecord` to check the actual start offset of the copied data. //! let actual_start_offset = copy_record.copy_start_offset; -//! +//! //! // we may later (*unsafely*) read back our data. note that the read helpers provided by presser //! // are mostly unsafe. They do help protect you from some common footguns, but you still ultimately need //! // to guarantee you put the proper data where you're telling it you put the proper data. -//! let my_copied_data_in_my_buffer: &MyDataStruct = unsafe { +//! let my_copied_data_in_my_buffer: &MyDataStruct = unsafe { //! presser::read_at_offset(&slab, actual_start_offset)? //! }; //! ``` @@ -108,7 +108,7 @@ //! relies on those newly-uninit bytes being initialized to be valid (for example, taking a `&[u8]` slice of the `Vec` //! which includes those bytes, ***even if your code never actually reads from that slice***) //! is now instant **undefined behavior**. -//! +//! //! \* *Note: this is unsafe because, as exemplified, you may copy uninit data into the buffer. Hence, care should //! be taken when implementing [`Slab`] and then providing a safe interface on top of a low level buffer type.* #![cfg_attr(not(feature = "std"), no_std)] diff --git a/src/read.rs b/src/read.rs index 36012de..2373da7 100644 --- a/src/read.rs +++ b/src/read.rs @@ -1,16 +1,16 @@ use super::*; /// Gets a shared reference to a `T` within `slab` at `offset`. -/// +/// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. -/// +/// /// The function will return an error if: /// - `offset` within `slab` is not properly aligned for `T` /// - `offset` is out of bounds of the `slab` /// - `offset + size_of::` is out of bounds of the `slab` -/// +/// /// # Safety -/// +/// /// You must have previously **fully-initialized** a **valid** `T` at the given offset into `slab`. /// Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. @@ -22,7 +22,7 @@ pub unsafe fn read_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize) -> Resu // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr().add(offsets.start) }.cast::(); - // SAFETY: + // SAFETY: // - `ptr` is properly aligned, checked by us // - `slab` contains enough space for `T` at `ptr`, checked by // - if the function-level safety guarantees are met, then: @@ -32,30 +32,33 @@ pub unsafe fn read_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize) -> Resu } /// Gets a mutable reference to a `T` within `slab` at `offset`. -/// +/// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. -/// +/// /// The function will return an error if: /// - `offset` within `slab` is not properly aligned for `T` /// - `offset` is out of bounds of the `slab` /// - `offset + size_of::` is out of bounds of the `slab` -/// +/// /// # Safety -/// +/// /// You must have previously **fully-initialized** a **valid**\* `T` at the given offset into `slab`. If you want to fill an uninitialized /// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. -/// +/// /// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] -pub unsafe fn read_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) -> Result<&'a mut T, Error> { +pub unsafe fn read_at_offset_mut<'a, T, S: Slab>( + slab: &'a mut S, + offset: usize, +) -> Result<&'a mut T, Error> { let t_layout = Layout::new::(); let offsets = compute_and_validate_offsets(slab, offset, t_layout, 1, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::(); - // SAFETY: + // SAFETY: // - `ptr` is properly aligned, checked by us // - `slab` contains enough space for `T` at `ptr`, checked by // - if the function-level safety guarantees are met, then: @@ -65,29 +68,32 @@ pub unsafe fn read_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) } /// Gets a mutable reference to a `MaybeUninit` within `slab` at `offset`. -/// +/// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` may be placed. -/// +/// /// The function will return an error if: /// - `offset` within `slab` is not properly aligned for `T` /// - `offset` is out of bounds of the `slab` /// - `offset + size_of::` is out of bounds of the `slab` -/// +/// /// # Safety -/// +/// /// This function is safe since in order to read any data you need to call the unsafe [`MaybeUninit::assume_init`] on the returned value. /// However, you should know that if you do that, you must have ensured that there is indeed a **valid** `T` in its place. /// Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] -pub fn get_maybe_uninit_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize) -> Result<&'a mut MaybeUninit, Error> { +pub fn get_maybe_uninit_at_offset_mut<'a, T, S: Slab>( + slab: &'a mut S, + offset: usize, +) -> Result<&'a mut MaybeUninit, Error> { let t_layout = Layout::new::(); let offsets = compute_and_validate_offsets(slab, offset, t_layout, 1, true)?; // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::>(); - // SAFETY: + // SAFETY: // - `ptr` is properly aligned, checked by us // - `slab` contains enough space for `T` at `ptr`, checked by us // - if the function-level safety guarantees are met, then: @@ -96,22 +102,26 @@ pub fn get_maybe_uninit_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: u } /// Reads a `&[T]` within `slab` at `offset`. -/// +/// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` is placed. /// - `len` is the length of the returned slice, counted in elements of `T`. -/// +/// /// The function will return an error if: /// - `offset` within `slab` is not properly aligned for `T` /// - `offset` is out of bounds of the `slab` /// - `offset + size_of:: * len` is out of bounds of the `slab` -/// +/// /// # Safety -/// +/// /// You must have previously **fully-initialized** a **valid** a `[T; len]` at the given offset into `slab`. /// Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] -pub unsafe fn read_slice_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize, len: usize) -> Result<&'a [T], Error> { +pub unsafe fn read_slice_at_offset<'a, T, S: Slab>( + slab: &'a S, + offset: usize, + len: usize, +) -> Result<&'a [T], Error> { let t_layout = match Layout::array::(len) { Ok(layout) => layout, Err(_) => return Err(Error::InvalidLayout), @@ -121,7 +131,7 @@ pub unsafe fn read_slice_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize, l // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr().add(offsets.start) }.cast::(); - // SAFETY: + // SAFETY: // - `ptr` is properly aligned, checked by us // - `slab` contains enough space for the slice's layout, checked by us // - if the function-level safety guarantees are met, then: @@ -131,24 +141,28 @@ pub unsafe fn read_slice_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize, l } /// Reads a `&mut [T]` within `slab` at `offset`. -/// +/// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` is placed. /// - `len` is the length of the returned slice, counted in elements of `T`. -/// +/// /// The function will return an error if: /// - `offset` within `slab` is not properly aligned for `T` /// - `offset` is out of bounds of the `slab` /// - `offset + size_of:: * len` is out of bounds of the `slab` -/// +/// /// # Safety -/// +/// /// You must have previously **fully-initialized** a **valid**\* `[T; len]` at the given offset into `slab`. If you want to fill an uninitialized /// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. -/// +/// /// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] -pub unsafe fn read_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize, len: usize) -> Result<&'a mut [T], Error> { +pub unsafe fn read_slice_at_offset_mut<'a, T, S: Slab>( + slab: &'a mut S, + offset: usize, + len: usize, +) -> Result<&'a mut [T], Error> { let t_layout = match Layout::array::(len) { Ok(layout) => layout, Err(_) => return Err(Error::InvalidLayout), @@ -158,7 +172,7 @@ pub unsafe fn read_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::(); - // SAFETY: + // SAFETY: // - `ptr` is properly aligned, checked by us // - `slab` contains enough space for the slice's layout, checked by us // - if the function-level safety guarantees are met, then: @@ -168,23 +182,27 @@ pub unsafe fn read_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: } /// Gets a `&mut [MaybeUninit]` within `slab` at `offset`. -/// +/// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` may be placed. /// - `len` is the length of the returned slice, counted in elements of `T`. -/// +/// /// The function will return an error if: /// - `offset` within `slab` is not properly aligned for `T` /// - `offset` is out of bounds of the `slab` /// - `offset + size_of:: * len` is out of bounds of the `slab` -/// +/// /// # Safety -/// +/// /// This function is safe since in order to read any data you need to call the unsafe [`MaybeUninit::assume_init`] on the returned value. /// However, you should know that if you do that, you must have ensured that there is indeed a **valid** `T` in its place. /// Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] -pub fn get_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, offset: usize, len: usize) -> Result<&'a mut [MaybeUninit], Error> { +pub fn get_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>( + slab: &'a mut S, + offset: usize, + len: usize, +) -> Result<&'a mut [MaybeUninit], Error> { let t_layout = match Layout::array::(len) { Ok(layout) => layout, Err(_) => return Err(Error::InvalidLayout), @@ -194,7 +212,7 @@ pub fn get_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>(slab: &'a mut S, off // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::>(); - // SAFETY: + // SAFETY: // - `ptr` is properly aligned, checked by us // - `slab` contains enough space for the slice's layout, checked by us // - if the function-level safety guarantees are met, then: From 3a4840901d5ef8afd16e2934dd2a263ef0a4d5fa Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Tue, 18 Oct 2022 21:20:30 +0200 Subject: [PATCH 06/17] update readme --- README.md | 18 ++++++++++++++---- src/lib.rs | 6 +++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2a462ee..9dc86e1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ # `🗜 presser` -**Utilities to help make copying data around into raw, possibly-uninitialized buffers easier and safer.** +**Utilities to help make working with raw, possibly-uninitialized buffers easier and safer.** [![Embark](https://img.shields.io/badge/embark-open%20source-blueviolet.svg)](https://embark.dev) [![Embark](https://img.shields.io/badge/discord-ark-%237289da.svg?logo=discord)](https://discord.gg/dAuKfZS) @@ -18,7 +18,7 @@ [![dependency status](https://deps.rs/repo/github/EmbarkStudios/presser/status.svg)](https://deps.rs/repo/github/EmbarkStudios/presser) -`presser` can help you when copying data into raw buffers. One primary use-case is copying data into +`presser` can help you when copying data into and reading data from raw buffers. One primary use-case is copying data into graphics-api-allocated buffers which will then be accessed by the GPU. Common methods for doing this right now in Rust can often invoke UB in subtle and hard-to-see ways. For example, viewing an allocated but uninitialized buffer as an `&mut [u8]` **is instantly undefined behavior**\*, and `transmute`ing even a @@ -99,10 +99,20 @@ let copy_record = presser::copy_to_offset(&my_data, &mut slab, 0)?; // `my_data` may be placed at a different offset than requested. so, we check the returned // `CopyRecord` to check the actual start offset of the copied data. let actual_start_offset = copy_record.copy_start_offset; + +// we may later (*unsafely*) read back our data. note that the read helpers provided by presser +// are mostly unsafe. They do help protect you from some common footguns, but you still ultimately need +// to guarantee you put the proper data where you're telling it you put the proper data. +let my_copied_data_in_my_buffer: &MyDataStruct = unsafe { + presser::read_at_offset(&slab, actual_start_offset)? +}; ``` -Note that actually accessing the copied data is a completely separate issue which `presser` does not -(as of now) concern itself with. BE CAREFUL! +Note that, as seen at the end, actually accessing the copied data is still unsafe. This means that you still need +to take care that you're laying out your data exactly as whatever later reads it expects, whether that be a graphics +API or your own data structure built on top of `presser`. The read functions that `presser` provides help check some +common footguns (ensuring the given offset within the slab is properly aligned and the slab has enough memory to contain +the wanted type), but they're still ultimately unsafe and require you to assert you put the proper data in the proper place. See more in [the git `main` docs](https://embarkstudios.github.io/presser/presser/index.html) or [the released version docs](https://docs.rs/presser). diff --git a/src/lib.rs b/src/lib.rs index 9915406..7acb286 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ //! `T: Copy` type which has *any padding bytes in its layout* as a `&[u8]` to be the source of a copy is //! **also instantly undefined behavior**, in both cases because it is *invalid* to create a reference to an invalid //! value (and uninitialized memory is an invalid `u8`), *even if* your code never actually accesses that memory. -//! This immediately makes what seems like the most straightforward way to copy data into buffers unsound 😬. +//! This immediately makes what seems like the most straightforward way to copy data into buffers unsound 😬 //! //! `presser` helps with this by allowing you to view raw allocated memory of some size as a "[`Slab`]" of memory and then //! provides *safe, valid* ways to copy data into that memory. For example, you could implement [`Slab`] for your @@ -24,10 +24,10 @@ //! \* *If you're currently thinking to yourself "bah! what's the issue? surely an uninit u8 is just any random bit pattern //! and that's fine we don't care," [check out this blog post](https://www.ralfj.de/blog/2019/07/14/uninit.html) by //! @RalfJung, one of the people leading the effort to better define Rust's memory and execution model. As is explored -//! in that blog post, an *uninit* piece of memory is not simply *an arbitrary bit pattern*, it is a wholly separate +//! in that blog post, an* uninit *piece of memory is not simply* an arbitrary bit pattern, *it is a wholly separate //! state about a piece of memory, outside of its value, which lets the compiler perform optimizations that reorder, //! delete, and otherwise change the actual execution flow of your program in ways that cannot be described simply -//! by "the value could have *some* possible bit pattern". LLVM and Clang are changing themselves to require special +//! by "the value could have* some *possible bit pattern". LLVM and Clang are changing themselves to require special //! `noundef` attribute to perform many important optimizations that are otherwise unsound. For a concrete example //! of the sorts of problems this can cause, //! [see this issue @scottmcm hit](https://github.com/rust-lang/rust/pull/98919#issuecomment-1186106387).* From 065acf6ffc0e63f28094effb3b3acc5478413733 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Tue, 18 Oct 2022 21:21:23 +0200 Subject: [PATCH 07/17] changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a698e5..b3e850f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `[]_exact` versions of copy functions which ensure that the copy will actually start at the provided start_offset, otherwise returning an error. - Add `CopyError::RequestedOffsetUnaligned` to support the above error case. +- Add `read_[]` and `get_maybe_uninit_[]_mut` helper functions for accessing copied data. ## [0.3.1] - 2022-10-16 From b09c828656b49fca1b099f59ece1aae607b4a2f1 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Tue, 18 Oct 2022 21:40:24 +0200 Subject: [PATCH 08/17] add unchecked variants of read functions --- src/read.rs | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 204 insertions(+), 2 deletions(-) diff --git a/src/read.rs b/src/read.rs index 2373da7..9240ff0 100644 --- a/src/read.rs +++ b/src/read.rs @@ -31,6 +31,35 @@ pub unsafe fn read_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize) -> Resu Ok(unsafe { &*ptr }) } +/// Gets a shared reference to a `T` within `slab` at `offset`, not checking any requirements. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. +/// +/// # Safety +/// +/// You must ensure: +/// +/// - `offset` within `slab` is properly aligned for `T` +/// - `offset` is within bounds of the `slab` +/// - `offset + size_of::` is within bounds of the `slab` +/// - You must have previously **fully-initialized** a **valid** `T` at the given offset into `slab`. +/// Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub unsafe fn read_at_offset_unchecked<'a, T, S: Slab>(slab: &'a S, offset: usize) -> &'a T { + // SAFETY: if offset is within the slab as guaranteed by function-level safety, this is + // safe since a slab's size must be < isize::MAX + let ptr = unsafe { slab.base_ptr().add(offset) }.cast::(); + + // SAFETY: + // - we have shared access to all of `slab`, which includes `ptr`. + // - if the function-level safety guarantees are met, then: + // - `ptr` is properly aligned + // - `slab` contains enough space for `T` at `ptr` + // - `ptr` contains a previously-placed `T` + unsafe { &*ptr } +} + /// Gets a mutable reference to a `T` within `slab` at `offset`. /// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. @@ -60,13 +89,47 @@ pub unsafe fn read_at_offset_mut<'a, T, S: Slab>( // SAFETY: // - `ptr` is properly aligned, checked by us - // - `slab` contains enough space for `T` at `ptr`, checked by + // - `slab` contains enough space for `T` at `ptr`, checked by us + // - we have unique access to all of `slab`, which includes `ptr`. // - if the function-level safety guarantees are met, then: // - `ptr` contains a previously-placed `T` - // - we have unique access to all of `slab`, which includes `ptr`. Ok(unsafe { &mut *ptr }) } +/// Gets a mutable reference to a `T` within `slab` at `offset`, not checking any requirements. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. +/// +/// # Safety +/// +/// You must ensure: +/// +/// - `offset` within `slab` is properly aligned for `T` +/// - `offset` is within bounds of the `slab` +/// - `offset + size_of::` is within bounds of the `slab` +/// - You must have previously **fully-initialized** a **valid** `T` at the given offset into `slab`. If you want to fill an uninitialized +/// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. +/// +/// \* Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub unsafe fn read_at_offset_mut_unchecked<'a, T, S: Slab>( + slab: &'a mut S, + offset: usize, +) -> &'a mut T { + // SAFETY: if offset is within the slab as guaranteed by function-level safety, this is + // safe since a slab's size must be < isize::MAX + let ptr = unsafe { slab.base_ptr_mut().add(offset) }.cast::(); + + // SAFETY: + // - we have mutable access to all of `slab`, which includes `ptr`. + // - if the function-level safety guarantees are met, then: + // - `ptr` is properly aligned + // - `slab` contains enough space for `T` at `ptr` + // - `ptr` contains a previously-placed `T` + unsafe { &mut *ptr } +} + /// Gets a mutable reference to a `MaybeUninit` within `slab` at `offset`. /// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` may be placed. @@ -101,6 +164,38 @@ pub fn get_maybe_uninit_at_offset_mut<'a, T, S: Slab>( Ok(unsafe { &mut *ptr }) } +/// Gets a mutable reference to a `MaybeUninit` within `slab` at `offset`, not checking any requirements. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. +/// +/// # Safety +/// +/// You must ensure: +/// +/// - `offset` within `slab` is properly aligned for `T` +/// - `offset` is within bounds of the `slab` +/// - `offset + size_of::` is within bounds of the `slab` +/// +/// You must have ensured there is a **fully-initialized** and **valid** `T` at the given offset into `slab` before calling [`MaybeUninit::assume_init`]. +/// Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub unsafe fn get_maybe_uninit_at_offset_mut_unchecked<'a, T, S: Slab>( + slab: &'a mut S, + offset: usize, +) -> &'a mut MaybeUninit { + // SAFETY: if offset is within the slab as guaranteed by function-level safety, this is + // safe since a slab's size must be < isize::MAX + let ptr = unsafe { slab.base_ptr_mut().add(offset) }.cast::>(); + + // SAFETY: + // - we have mutable access to all of `slab`, which includes `ptr`. + // - if the function-level safety guarantees are met, then: + // - `ptr` is properly aligned + // - `slab` contains enough space for `T` at `ptr` + unsafe { &mut *ptr } +} + /// Reads a `&[T]` within `slab` at `offset`. /// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` is placed. @@ -140,6 +235,41 @@ pub unsafe fn read_slice_at_offset<'a, T, S: Slab>( Ok(unsafe { core::slice::from_raw_parts(ptr, len) }) } +/// Reads a `&[T]` within `slab` at `offset`, not checking any requirements. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` is placed. +/// - `len` is the length of the returned slice, counted in elements of `T`. +/// +/// # Safety +/// +/// You must ensure: +/// +/// - `offset` within `slab` is properly aligned for `T` +/// - `offset` is within bounds of the `slab` +/// - `offset + size_of:: * len` is within bounds of the `slab` +/// - You must have previously **fully-initialized** a **valid** a `[T; len]` at the given offset into `slab`. +/// Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +/// - See also safety docs of [`core::slice::from_raw_parts`]. +#[inline] +pub unsafe fn read_slice_at_offset_unchecked<'a, T, S: Slab>( + slab: &'a S, + offset: usize, + len: usize, +) -> &'a [T] { + // SAFETY: if offset is within the slab as guaranteed by function-level safety, this is + // safe since a slab's size must be < isize::MAX + let ptr = unsafe { slab.base_ptr().add(offset) }.cast::(); + + // SAFETY: + // - we have shared access to all of `slab`, which includes `ptr`. + // - if the function-level safety guarantees are met, then: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for the slice's layout, checked by us + // - `ptr` contains a previously-placed `[T; len]` + unsafe { core::slice::from_raw_parts(ptr, len) } +} + /// Reads a `&mut [T]` within `slab` at `offset`. /// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` is placed. @@ -181,6 +311,43 @@ pub unsafe fn read_slice_at_offset_mut<'a, T, S: Slab>( Ok(unsafe { core::slice::from_raw_parts_mut(ptr, len) }) } +/// Reads a `&mut [T]` within `slab` at `offset`, not checking any requirements. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` is placed. +/// - `len` is the length of the returned slice, counted in elements of `T`. +/// +/// # Safety +/// +/// You must ensure: +/// +/// - `offset` within `slab` is properly aligned for `T` +/// - `offset` is within bounds of the `slab` +/// - `offset + size_of:: * len` is within bounds of the `slab` +/// - You must have previously **fully-initialized** a **valid** a `[T; len]` at the given offset into `slab`. If you want to fill an uninitialized +/// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. +/// - See also safety docs of [`core::slice::from_raw_parts_mut`]. +/// +/// \* Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub unsafe fn read_slice_at_offset_mut_unchecked<'a, T, S: Slab>( + slab: &'a mut S, + offset: usize, + len: usize, +) -> &'a mut [T] { + // SAFETY: if offset is within the slab as guaranteed by function-level safety, this is + // safe since a slab's size must be < isize::MAX + let ptr = unsafe { slab.base_ptr_mut().add(offset) }.cast::(); + + // SAFETY: + // - we have shared access to all of `slab`, which includes `ptr`. + // - if the function-level safety guarantees are met, then: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for the slice's layout, checked by us + // - `ptr` contains a previously-placed `[T; len]` + unsafe { core::slice::from_raw_parts_mut(ptr, len) } +} + /// Gets a `&mut [MaybeUninit]` within `slab` at `offset`. /// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` may be placed. @@ -219,3 +386,38 @@ pub fn get_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>( // - we have mutable access to all of `slab`, which includes `ptr`. Ok(unsafe { core::slice::from_raw_parts_mut(ptr, len) }) } + +/// Gets a `&mut [MaybeUninit]` within `slab` at `offset`, not checking any requirements. +/// +/// - `offset` is the offset, in bytes, after the start of `slab` at which a `[T; len]` is placed. +/// - `len` is the length of the returned slice, counted in elements of `T`. +/// +/// # Safety +/// +/// You must ensure: +/// +/// - `offset` within `slab` is properly aligned for `T` +/// - `offset` is within bounds of the `slab` +/// - `offset + size_of:: * len` is within bounds of the `slab` +/// - See also safety docs of [`core::slice::from_raw_parts_mut`]. +/// +/// You must have ensured there is a **fully-initialized** and **valid** `T` in each returned `MaybeUninit` before calling [`MaybeUninit::assume_init`]. +/// Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. +#[inline] +pub unsafe fn get_maybe_uninit_slice_at_offset_mut_unchecked<'a, T, S: Slab>( + slab: &'a mut S, + offset: usize, + len: usize, +) -> &'a mut [MaybeUninit] { + // SAFETY: if offset is within the slab as guaranteed by function-level safety, this is + // safe since a slab's size must be < isize::MAX + let ptr = unsafe { slab.base_ptr_mut().add(offset) }.cast::>(); + + // SAFETY: + // - we have shared access to all of `slab`, which includes `ptr`. + // - if the function-level safety guarantees are met, then: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for the slice's layout, checked by us + unsafe { core::slice::from_raw_parts_mut(ptr, len) } +} From 37f469deb38bc339b28a5537a72da06bed6534f7 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Tue, 18 Oct 2022 21:42:23 +0200 Subject: [PATCH 09/17] allow needless lifetimes in read module --- src/read.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/read.rs b/src/read.rs index 9240ff0..f9783bd 100644 --- a/src/read.rs +++ b/src/read.rs @@ -1,3 +1,5 @@ +#![allow(clippy::needless_lifetimes)] // important to be explicit here imo + use super::*; /// Gets a shared reference to a `T` within `slab` at `offset`. From 970a54aa69f80065934917fe9151dd060eac9d05 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Mon, 6 Nov 2023 16:29:35 +0100 Subject: [PATCH 10/17] Improve docs, add HeapSlab and `make_stack_slab` --- src/copy.rs | 74 ++++++++++++++++-------------- src/lib.rs | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/read.rs | 91 ++++++++++++++++++++++++++++++------ 3 files changed, 244 insertions(+), 50 deletions(-) diff --git a/src/copy.rs b/src/copy.rs index b08879a..c6fedf7 100644 --- a/src/copy.rs +++ b/src/copy.rs @@ -8,7 +8,7 @@ pub struct CopyRecord { /// /// Not necessarily equal to the `start_offset` provided to the copy function, since this offset /// includes necessary padding to assure alignment. - pub copy_start_offset: usize, + pub start_offset: usize, /// The offset from the start of the allocation, in bytes, at which the /// copy operation no longer wrote data. @@ -16,13 +16,14 @@ pub struct CopyRecord { /// This does not include any padding at the end necessary to maintain /// alignment requirements. /// - /// Unless you really know what you're doing, you *likely* want to use `end_offset_padded` instead. - pub copy_end_offset: usize, + /// Unless you have a good reason otherwise, you *likely* want to use + /// [`end_offset_padded`][CopyRecord::end_offset_padded] instead. + pub end_offset: usize, /// The offset from the start of the allocation, in bytes, at which the /// copy operation no longer wrote data, plus any padding necessary to /// maintain derived alignment requirements. - pub copy_end_offset_padded: usize, + pub end_offset_padded: usize, } impl From for CopyRecord { @@ -34,9 +35,9 @@ impl From for CopyRecord { }: ComputedOffsets, ) -> Self { Self { - copy_start_offset: start, - copy_end_offset: end, - copy_end_offset_padded: end_padded, + start_offset: start, + end_offset: end, + end_offset_padded: end_padded, } } } @@ -54,7 +55,7 @@ impl From for CopyRecord { /// This function is safe on its own, however it is very possible to do unsafe /// things if you read the copied data in the wrong way. See the /// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] +#[inline(always)] pub fn copy_to_offset_exact( src: &T, dst: &mut S, @@ -65,22 +66,24 @@ pub fn copy_to_offset_exact( /// Copies `src` into the memory represented by `dst` starting at *exactly* /// `start_offset` bytes past the start of `dst` and with minimum alignment -/// `min_alignment`. +/// `min_alignment`. If the requested parameters would be violated by computed alignment requirements, +/// an error will be returned. /// /// - `start_offset` is the offset into the allocation represented by `dst`, in bytes, /// where the first byte of the copied data will be placed. If the requested /// start offset does not satisfy computed alignment requirements, an error will /// be returned and no data will be copied. -/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The -/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements -/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). +/// - `min_alignment` is the minimum alignment that you are requesting the copy be aligned to. The +/// copy may be aligned greater than `min_alignment` depending on the alignment requirements +/// of `T` (the actual alignment will be the greater of the two between `align_of::()` and +/// `min_align.next_power_of_two()`). /// /// # Safety /// /// This function is safe on its own, however it is very possible to do unsafe /// things if you read the copied data in the wrong way. See the /// [crate-level Safety documentation][`crate#safety`] for more. -#[inline] +#[inline(always)] pub fn copy_to_offset_with_align_exact( src: &T, dst: &mut S, @@ -209,11 +212,12 @@ pub fn copy_from_slice_to_offset_exact( /// where the first byte of the copied data will be placed. If the requested /// start offset does not satisfy computed alignment requirements, an error will /// be returned and no data will be copied. -/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The -/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements -/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). +/// - `min_alignment` is the minimum alignment that you are requesting the copy be aligned to. The +/// copy may be aligned greater than `min_alignment` depending on the alignment requirements +/// of `T` (the actual alignment will be the greater of the two between `align_of::()` and +/// `min_align.next_power_of_two()`). /// - The whole data of the slice will be copied directly, so, alignment between elements -/// ignores `min_alignment`. +/// ignores `min_alignment`. /// /// # Safety /// @@ -280,11 +284,12 @@ pub fn copy_from_slice_to_offset( /// the actual beginning of the copied data may not be exactly at `start_offset` if /// padding bytes are needed to satisfy alignment requirements. The actual beginning /// of the copied bytes is contained in the returned [`CopyRecord`]. -/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The -/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements -/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). -/// - The whole data of the slice will be copied directly, so, alignment between elements -/// ignores `min_alignment`. +/// - `min_alignment` is the minimum alignment that you are requesting the copy be aligned to. The +/// copy may be aligned greater than `min_alignment` depending on the alignment requirements +/// of `T` (the actual alignment will be the greater of the two between `align_of::()` and +/// `min_align.next_power_of_two()`). +/// - The whole data of the slice will be copied directly, so alignment between elements +/// ignores `min_alignment`. /// /// # Safety /// @@ -331,9 +336,10 @@ pub fn copy_from_slice_to_offset_with_align( /// the actual beginning of the copied data may not be exactly at `start_offset` if /// padding bytes are needed to satisfy alignment requirements. The actual beginning /// of the copied bytes is contained in the returned [`CopyRecord`]s. -/// - `min_alignment` is the minimum alignment to which the copy will be aligned. The -/// copy may not actually be aligned to `min_alignment` depending on the alignment requirements -/// of `T` (the actual alignment will be the greater between `align_of::` and `min_align.next_power_of_two()`). +/// - `min_alignment` is the minimum alignment that you are requesting the copy be aligned to. The +/// copy may be aligned greater than `min_alignment` depending on the alignment requirements +/// of `T` (the actual alignment will be the greater of the two between `align_of::()` and +/// `min_align.next_power_of_two()`). /// - For this variation, `min_alignment` will also be respected *between* elements yielded by /// the iterator. To copy inner elements aligned only to `align_of::()` (i.e. with the layout of /// an `[T]`), see [`copy_from_iter_to_offset_with_align_packed`]. @@ -355,7 +361,7 @@ pub fn copy_from_iter_to_offset_with_align, S: src.map(|item| { let copy_record = copy_to_offset_with_align(&item, dst, offset, min_alignment)?; - offset = copy_record.copy_end_offset; + offset = copy_record.end_offset; Ok(copy_record) }) .collect::, _>>() @@ -383,14 +389,14 @@ pub fn copy_from_iter_to_offset_with_align_packed]`, +/// but the idea is that you can also implement this for your own data structure which can +/// serve as a slab and then use that structure directly with `presser`'s helpers. +/// +/// For built-in slabs, see [`RawAllocation`], [`HeapSlab`], and [`make_stack_slab`]. +/// /// # Safety /// /// Implementors of this trait must ensure these guarantees: @@ -176,6 +186,7 @@ pub unsafe trait Slab { /// Interpret a portion of `self` as a slice of [`MaybeUninit`]. This is likely not /// incredibly useful, you probably want to use [`Slab::as_maybe_uninit_bytes_mut`] + #[inline(always)] fn as_maybe_uninit_bytes(&self) -> &[MaybeUninit] { // SAFETY: Safe so long as top level safety guarantees are held, since // `MaybeUninit` has same layout as bare type. @@ -183,6 +194,7 @@ pub unsafe trait Slab { } /// Interpret a portion of `self` as a mutable slice of [`MaybeUninit`]. + #[inline(always)] fn as_maybe_uninit_bytes_mut(&mut self) -> &mut [MaybeUninit] { // SAFETY: Safe so long as top level safety guarantees are held, since // `MaybeUninit` has same layout as bare type. @@ -201,6 +213,7 @@ pub unsafe trait Slab { /// behavior***, even if you *do noting* with the result. /// /// Also see the [crate-level Safety documentation][`crate#safety`] for more. + #[inline(always)] unsafe fn assume_initialized_as_bytes(&self) -> &[u8] { // SAFETY: same requirements as function-level safety assuming the requirements // for creating `self` are met @@ -219,6 +232,7 @@ pub unsafe trait Slab { /// behavior***, even if you *do noting* with the result. /// /// Also see the [crate-level Safety documentation][`crate#safety`] for more. + #[inline(always)] unsafe fn assume_initialized_as_bytes_mut(&mut self) -> &mut [u8] { // SAFETY: same requirements as function-level safety assuming the requirements // for creating `self` are met @@ -241,6 +255,7 @@ pub unsafe trait Slab { /// behavior***, even if you *do noting* with the result. /// /// Also see the [crate-level Safety documentation][`crate#safety`] for more. + #[inline(always)] unsafe fn assume_range_initialized_as_bytes(&self, range: R) -> &[u8] where R: core::slice::SliceIndex<[MaybeUninit], Output = [MaybeUninit]>, @@ -272,6 +287,7 @@ pub unsafe trait Slab { /// behavior***, even if you *do noting* with the result. /// /// Also see the [crate-level Safety documentation][`crate#safety`] for more. + #[inline(always)] unsafe fn assume_range_initialized_as_bytes_mut(&mut self, range: R) -> &mut [u8] where R: core::slice::SliceIndex<[MaybeUninit], Output = [MaybeUninit]>, @@ -286,6 +302,51 @@ pub unsafe trait Slab { ) } } + + /// View a portion of `self` as a [`c_void`] pointer and size, appropriate for sending to an FFI function + /// to have it read the contents of `self`. If you want the buffer to be filled with data + /// from the other side of the ffi and then read it back, use + /// [`as_ffi_readback_buffer`][Slab::as_ffi_readback_buffer] instead. + /// + /// # Panics + /// + /// Panics if the range is out of bounds of `self` + /// + /// # Safety + /// + /// This function is safe in and of itself, but you must be careful not to use `self` for + /// anything else while the returned pointer is in use by whatever you're sending it to, and + /// be sure that you're upholding any alignment requirements needed. + #[inline(always)] + fn as_ffi_buffer(&self, range: R) -> (*const c_void, usize) + where + R: core::slice::SliceIndex<[MaybeUninit], Output = [MaybeUninit]>, + { + let maybe_uninit_slice = &self.as_maybe_uninit_bytes()[range]; + + (maybe_uninit_slice.base_ptr().cast(), maybe_uninit_slice.len()) + } + + /// View a portion of `self` as a [`c_void`] pointer and size, appropriate for sending to an FFI function + /// to be filled and then read using one or more of the `read_` helper functions. + /// + /// # Panics + /// + /// Panics if the range is out of bounds of `self` + /// + /// # Safety + /// + /// This function is safe in and of itself, but you must be careful not to use `self` for + /// anything else while the returned pointer is in use by whatever you're sending it to, + /// and be sure that you're upholding any alignment requirements needed. + #[inline(always)] + fn as_ffi_readback_buffer(&mut self, range: R) -> (*mut c_void, usize) + where + R: core::slice::SliceIndex<[MaybeUninit], Output = [MaybeUninit]>, + { + let maybe_uninit_slice = &mut self.as_maybe_uninit_bytes_mut()[range]; + (maybe_uninit_slice.base_ptr_mut().cast(), maybe_uninit_slice.len()) + } } // SAFETY: The captured `[MaybeUninit]` will all be part of the same allocation object, and borrowck @@ -529,7 +590,7 @@ unsafe impl<'a> Slab for BorrowedRawAllocation<'a> { } /// Computed offsets necessary for a copy or read operation with some layout. Should only be -/// created by [`compute_offsets`] +/// created by [`compute_and_validate_offsets`] #[derive(Debug, Copy, Clone)] pub(crate) struct ComputedOffsets { start: usize, @@ -538,6 +599,7 @@ pub(crate) struct ComputedOffsets { } /// Compute and validate offsets for a copy or read operation with the given parameters. +#[inline] pub(crate) fn compute_and_validate_offsets( slab: &S, start_offset: usize, @@ -581,9 +643,72 @@ pub(crate) fn compute_and_validate_offsets( /// Given pointer and offset, returns a new offset aligned to `align`. /// /// `align` *must* be a power of two and >= 1 or else the result is meaningless. +#[inline(always)] fn align_offset_up_to(ptr: usize, offset: usize, align: usize) -> Option { let offsetted_ptr = ptr.checked_add(offset)?; let aligned_ptr = offsetted_ptr.checked_add(align - 1)? & !(align - 1); // don't need to check since we know aligned_ptr is >= ptr at this point Some(aligned_ptr - ptr) } + +/// Make a `[MaybeUninit; N]` on the stack, which implements [`Slab`] and can therefore be used +/// with many of the helpers provided by this crate. +pub fn make_stack_slab() -> [MaybeUninit; N] { + [MaybeUninit::uninit(); N] +} + +/// A raw allocation on the heap which implements [`Slab`] and gets deallocated on [`Drop`]. +#[cfg(feature = "std")] +pub struct HeapSlab { + base_ptr: NonNull, + layout: Layout, +} + +#[cfg(feature = "std")] +impl HeapSlab { + /// Make a new slab space on the heap. Begins as uninitialized. The memory will be be deallocated on drop. + /// + /// # Panics + /// + /// Panics if the size of the given layout is 0. + pub fn new(layout: Layout) -> Self { + if layout.size() == 0 { + panic!("cannot make a heap slab of size 0") + } + // SAFETY: we just checked size is not 0, and we got the ptr back from alloc so we no it's + // not null. + let base_ptr = unsafe { + NonNull::new_unchecked(std::alloc::alloc(layout)) + }; + Self { base_ptr, layout } + } +} + +#[cfg(feature = "std")] +impl Drop for HeapSlab { + fn drop(&mut self) { + // SAFETY: we know that size isn't 0 since we checked that in new, and unless the user + // did something unsafely wrong, this memory won't be used after drop. + unsafe { std::alloc::dealloc(self.base_ptr.as_ptr(), self.layout) } + } +} + +// SAFETY: We point to a single valid allocation, and the size is valid since it's a valid `Layout`. +// Our allocation is valid until we are dropped, so our `base_ptr` access is as required +#[cfg(feature = "std")] +unsafe impl Slab for HeapSlab { + #[inline(always)] + fn base_ptr(&self) -> *const u8 { + self.base_ptr.as_ptr().cast_const() + } + + #[inline(always)] + fn base_ptr_mut(&mut self) -> *mut u8 { + self.base_ptr.as_ptr() + } + + #[inline(always)] + fn size(&self) -> usize { + self.layout.size() + } +} diff --git a/src/read.rs b/src/read.rs index f9783bd..a8dbea8 100644 --- a/src/read.rs +++ b/src/read.rs @@ -44,8 +44,9 @@ pub unsafe fn read_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize) -> Resu /// - `offset` within `slab` is properly aligned for `T` /// - `offset` is within bounds of the `slab` /// - `offset + size_of::` is within bounds of the `slab` -/// - You must have previously **fully-initialized** a **valid** `T` at the given offset into `slab`. -/// Validity is a complex topic not to be taken lightly. +/// - You must have previously **fully-initialized** a **valid**\* `T` at the given offset into `slab`. +/// +/// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] pub unsafe fn read_at_offset_unchecked<'a, T, S: Slab>(slab: &'a S, offset: usize) -> &'a T { @@ -76,6 +77,13 @@ pub unsafe fn read_at_offset_unchecked<'a, T, S: Slab>(slab: &'a S, offset: usiz /// You must have previously **fully-initialized** a **valid**\* `T` at the given offset into `slab`. If you want to fill an uninitialized /// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. /// +/// **Note that *if you write through the returned reference***, any *padding bytes* within the layout of `T` +/// (which for a `repr(Rust)` type is arbitrary and unknown) must thereafter be considered *uninitialized* +/// until you explicitly initialize them again. This means that if you write a `T` which contains +/// padding into `slab`, you **must not**, for example, try to read those bytes as `&[u8]` afterwards +/// (or as some other type which expects those bytes to be initialized), as you would then be +/// reading uninitialized memory, which is *undefined behavior*. +/// /// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] @@ -112,6 +120,13 @@ pub unsafe fn read_at_offset_mut<'a, T, S: Slab>( /// - You must have previously **fully-initialized** a **valid** `T` at the given offset into `slab`. If you want to fill an uninitialized /// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. /// +/// **Note that *if you write through the returned reference***, any *padding bytes* within the layout of `T` +/// (which for a `repr(Rust)` type is arbitrary and unknown) must thereafter be considered *uninitialized* +/// until you explicitly initialize them again. This means that if you write a `T` which contains +/// padding into `slab`, you **must not**, for example, try to read those bytes as `&[u8]` afterwards +/// (or as some other type which expects those bytes to be initialized), as you would then be +/// reading uninitialized memory, which is *undefined behavior*. +/// /// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] @@ -144,8 +159,16 @@ pub unsafe fn read_at_offset_mut_unchecked<'a, T, S: Slab>( /// # Safety /// /// This function is safe since in order to read any data you need to call the unsafe [`MaybeUninit::assume_init`] on the returned value. -/// However, you should know that if you do that, you must have ensured that there is indeed a **valid** `T` in its place. -/// Validity is a complex topic not to be taken lightly. +/// However, you should know that if you do that, you must have ensured that there is indeed a **valid**\* `T` in its place. +/// +/// **Note that *if you write through the returned reference***, any *padding bytes* within the layout of `T` +/// (which for a `repr(Rust)` type is arbitrary and unknown) must thereafter be considered *uninitialized* +/// until you explicitly initialize them again. This means that if you write a `T` which contains +/// padding into `slab`, you **must not**, for example, try to read those bytes as `&[u8]` afterwards +/// (or as some other type which expects those bytes to be initialized), as you would then be +/// reading uninitialized memory, which is *undefined behavior*. +/// +/// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] pub fn get_maybe_uninit_at_offset_mut<'a, T, S: Slab>( @@ -178,8 +201,16 @@ pub fn get_maybe_uninit_at_offset_mut<'a, T, S: Slab>( /// - `offset` is within bounds of the `slab` /// - `offset + size_of::` is within bounds of the `slab` /// -/// You must have ensured there is a **fully-initialized** and **valid** `T` at the given offset into `slab` before calling [`MaybeUninit::assume_init`]. -/// Validity is a complex topic not to be taken lightly. +/// You must have ensured there is a **fully-initialized** and **valid**\* `T` at the given offset into `slab` before calling [`MaybeUninit::assume_init`]. +/// +/// **Note that *if you write through the returned reference***, any *padding bytes* within the layout of `T` +/// (which for a `repr(Rust)` type is arbitrary and unknown) must thereafter be considered *uninitialized* +/// until you explicitly initialize them again. This means that if you write a `T` which contains +/// padding into `slab`, you **must not**, for example, try to read those bytes as `&[u8]` afterwards +/// (or as some other type which expects those bytes to be initialized), as you would then be +/// reading uninitialized memory, which is *undefined behavior*. +/// +/// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] pub unsafe fn get_maybe_uninit_at_offset_mut_unchecked<'a, T, S: Slab>( @@ -210,8 +241,9 @@ pub unsafe fn get_maybe_uninit_at_offset_mut_unchecked<'a, T, S: Slab>( /// /// # Safety /// -/// You must have previously **fully-initialized** a **valid** a `[T; len]` at the given offset into `slab`. -/// Validity is a complex topic not to be taken lightly. +/// You must have previously **fully-initialized** a **valid**\* a `[T; len]` at the given offset into `slab`. +/// +/// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] pub unsafe fn read_slice_at_offset<'a, T, S: Slab>( @@ -249,8 +281,9 @@ pub unsafe fn read_slice_at_offset<'a, T, S: Slab>( /// - `offset` within `slab` is properly aligned for `T` /// - `offset` is within bounds of the `slab` /// - `offset + size_of:: * len` is within bounds of the `slab` -/// - You must have previously **fully-initialized** a **valid** a `[T; len]` at the given offset into `slab`. -/// Validity is a complex topic not to be taken lightly. +/// - You must have previously **fully-initialized** a **valid**\* a `[T; len]` at the given offset into `slab`. +/// +/// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. /// - See also safety docs of [`core::slice::from_raw_parts`]. #[inline] @@ -287,6 +320,13 @@ pub unsafe fn read_slice_at_offset_unchecked<'a, T, S: Slab>( /// You must have previously **fully-initialized** a **valid**\* `[T; len]` at the given offset into `slab`. If you want to fill an uninitialized /// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. /// +/// **Note that *if you write through the returned reference***, any *padding bytes* within the layout of `T` +/// (which for a `repr(Rust)` type is arbitrary and unknown) must thereafter be considered *uninitialized* +/// until you explicitly initialize them again. This means that if you write a `T` which contains +/// padding into `slab`, you **must not**, for example, try to read those bytes as `&[u8]` afterwards +/// (or as some other type which expects those bytes to be initialized), as you would then be +/// reading uninitialized memory, which is *undefined behavior*. +/// /// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] @@ -329,6 +369,13 @@ pub unsafe fn read_slice_at_offset_mut<'a, T, S: Slab>( /// buffer with data, you should instead use any of the copy helper functions or one of the `maybe_uninit_mut` read functions. /// - See also safety docs of [`core::slice::from_raw_parts_mut`]. /// +/// **Note that *if you write through the returned reference***, any *padding bytes* within the layout of `T` +/// (which for a `repr(Rust)` type is arbitrary and unknown) must thereafter be considered *uninitialized* +/// until you explicitly initialize them again. This means that if you write a `T` which contains +/// padding into `slab`, you **must not**, for example, try to read those bytes as `&[u8]` afterwards +/// (or as some other type which expects those bytes to be initialized), as you would then be +/// reading uninitialized memory, which is *undefined behavior*. +/// /// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] @@ -363,8 +410,16 @@ pub unsafe fn read_slice_at_offset_mut_unchecked<'a, T, S: Slab>( /// # Safety /// /// This function is safe since in order to read any data you need to call the unsafe [`MaybeUninit::assume_init`] on the returned value. -/// However, you should know that if you do that, you must have ensured that there is indeed a **valid** `T` in its place. -/// Validity is a complex topic not to be taken lightly. +/// However, you should know that if you do that, you must have ensured that there is indeed a **valid**\* `T` in its place. +/// +/// **Note that *if you write through the returned reference***, any *padding bytes* within the layout of `T` +/// (which for a `repr(Rust)` type is arbitrary and unknown) must thereafter be considered *uninitialized* +/// until you explicitly initialize them again. This means that if you write a `T` which contains +/// padding into `slab`, you **must not**, for example, try to read those bytes as `&[u8]` afterwards +/// (or as some other type which expects those bytes to be initialized), as you would then be +/// reading uninitialized memory, which is *undefined behavior*. +/// +/// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] pub fn get_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>( @@ -403,8 +458,16 @@ pub fn get_maybe_uninit_slice_at_offset_mut<'a, T, S: Slab>( /// - `offset + size_of:: * len` is within bounds of the `slab` /// - See also safety docs of [`core::slice::from_raw_parts_mut`]. /// -/// You must have ensured there is a **fully-initialized** and **valid** `T` in each returned `MaybeUninit` before calling [`MaybeUninit::assume_init`]. -/// Validity is a complex topic not to be taken lightly. +/// You must have ensured there is a **fully-initialized** and **valid**\* `T` in each returned `MaybeUninit` before calling [`MaybeUninit::assume_init`]. +/// +/// **Note that *if you write through the returned reference***, any *padding bytes* within the layout of `T` +/// (which for a `repr(Rust)` type is arbitrary and unknown) must thereafter be considered *uninitialized* +/// until you explicitly initialize them again. This means that if you write a `T` which contains +/// padding into `slab`, you **must not**, for example, try to read those bytes as `&[u8]` afterwards +/// (or as some other type which expects those bytes to be initialized), as you would then be +/// reading uninitialized memory, which is *undefined behavior*. +/// +/// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] pub unsafe fn get_maybe_uninit_slice_at_offset_mut_unchecked<'a, T, S: Slab>( From 3207cfd29f725f390686b5972fd755ef4f5abcd6 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Mon, 6 Nov 2023 16:51:49 +0100 Subject: [PATCH 11/17] fmt --- src/copy.rs | 3 +-- src/lib.rs | 14 +++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/copy.rs b/src/copy.rs index c6fedf7..fb74a31 100644 --- a/src/copy.rs +++ b/src/copy.rs @@ -423,8 +423,7 @@ pub fn copy_from_iter_to_offset_with_align_exact_packed< let mut prev_record = first_record; for item in src { - let copy_record = - copy_to_offset_with_align_exact(&item, dst, prev_record.end_offset, 1)?; + let copy_record = copy_to_offset_with_align_exact(&item, dst, prev_record.end_offset, 1)?; prev_record = copy_record; } diff --git a/src/lib.rs b/src/lib.rs index ac49db6..340970e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -324,7 +324,10 @@ pub unsafe trait Slab { { let maybe_uninit_slice = &self.as_maybe_uninit_bytes()[range]; - (maybe_uninit_slice.base_ptr().cast(), maybe_uninit_slice.len()) + ( + maybe_uninit_slice.base_ptr().cast(), + maybe_uninit_slice.len(), + ) } /// View a portion of `self` as a [`c_void`] pointer and size, appropriate for sending to an FFI function @@ -345,7 +348,10 @@ pub unsafe trait Slab { R: core::slice::SliceIndex<[MaybeUninit], Output = [MaybeUninit]>, { let maybe_uninit_slice = &mut self.as_maybe_uninit_bytes_mut()[range]; - (maybe_uninit_slice.base_ptr_mut().cast(), maybe_uninit_slice.len()) + ( + maybe_uninit_slice.base_ptr_mut().cast(), + maybe_uninit_slice.len(), + ) } } @@ -677,9 +683,7 @@ impl HeapSlab { } // SAFETY: we just checked size is not 0, and we got the ptr back from alloc so we no it's // not null. - let base_ptr = unsafe { - NonNull::new_unchecked(std::alloc::alloc(layout)) - }; + let base_ptr = unsafe { NonNull::new_unchecked(std::alloc::alloc(layout)) }; Self { base_ptr, layout } } } From d816aafd2de2fea47e0eb3946dab6b26bcb79ec1 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Mon, 6 Nov 2023 16:53:57 +0100 Subject: [PATCH 12/17] assure -> ensure --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 340970e..6e6376a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -356,7 +356,7 @@ pub unsafe trait Slab { } // SAFETY: The captured `[MaybeUninit]` will all be part of the same allocation object, and borrowck -// will assure that the borrows that occur on `self` on the relevant methods live long enough since they are +// will ensure that the borrows that occur on `self` on the relevant methods live long enough since they are // native borrows anyway. unsafe impl Slab for [MaybeUninit] { fn base_ptr(&self) -> *const u8 { From c4288b539ede0d1321f9a320b2f7b80b0bc8ce0e Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Mon, 6 Nov 2023 16:58:41 +0100 Subject: [PATCH 13/17] MaybeUnint -> MaybeUninit --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6e6376a..c0946a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,7 @@ pub use read::*; /// trait for the extra flexibility having a trait we own provides: namely, the ability /// to implement it on foreign types. /// -/// It is implemented for a couple of built-in slab providers, as well as for `[MaybeUnint]`, +/// It is implemented for a couple of built-in slab providers, as well as for `[MaybeUninit]`, /// but the idea is that you can also implement this for your own data structure which can /// serve as a slab and then use that structure directly with `presser`'s helpers. /// @@ -262,7 +262,7 @@ pub unsafe trait Slab { { let maybe_uninit_slice = &self.as_maybe_uninit_bytes()[range]; // SAFETY: same requirements as function-level safety assuming the requirements - // for creating `self` are met since `MaybeUnint` has same layout as `T` + // for creating `self` are met since `MaybeUninit` has same layout as `T` unsafe { core::slice::from_raw_parts( maybe_uninit_slice.as_ptr().cast(), @@ -294,7 +294,7 @@ pub unsafe trait Slab { { let maybe_uninit_slice = &mut self.as_maybe_uninit_bytes_mut()[range]; // SAFETY: same requirements as function-level safety assuming the requirements - // for creating `self` are met since `MaybeUnint` has same layout as `T` + // for creating `self` are met since `MaybeUninit` has same layout as `T` unsafe { core::slice::from_raw_parts_mut( maybe_uninit_slice.as_mut_ptr().cast(), From d1137aa2fd6a8c9c04a7bf54ff00a6aeea1e57ad Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Mon, 6 Nov 2023 17:01:59 +0100 Subject: [PATCH 14/17] typos --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c0946a4..0cb8072 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -381,7 +381,7 @@ pub enum Error { OffsetOutOfBounds, /// Computed invalid layout for copy operation, probably caused by incredibly large size, offset, or min-alignment parameters InvalidLayout, - /// The requested offset was unalignd. In a read operation, this means the provided offset into the buffer was not properly aligned + /// The requested offset was unaligned. In a read operation, this means the provided offset into the buffer was not properly aligned /// for the requested type. /// /// In an `exact` variant copy function, the computed copy start offset did not match the requested start offset, From c7a29626cbef13aac5be07ae090bec667e83f69e Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Mon, 6 Nov 2023 23:02:01 +0100 Subject: [PATCH 15/17] add ffi readback helpers --- src/lib.rs | 5 +++- src/read.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0cb8072..c3ab638 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -333,6 +333,9 @@ pub unsafe trait Slab { /// View a portion of `self` as a [`c_void`] pointer and size, appropriate for sending to an FFI function /// to be filled and then read using one or more of the `read_` helper functions. /// + /// You may want to use [`readback_from_ffi`] or [`readback_sice_from_ffi`] instead, which are + /// even less prone to misuse. + /// /// # Panics /// /// Panics if the range is out of bounds of `self` @@ -605,7 +608,7 @@ pub(crate) struct ComputedOffsets { } /// Compute and validate offsets for a copy or read operation with the given parameters. -#[inline] +#[inline(always)] pub(crate) fn compute_and_validate_offsets( slab: &S, start_offset: usize, diff --git a/src/read.rs b/src/read.rs index a8dbea8..4c3ff0a 100644 --- a/src/read.rs +++ b/src/read.rs @@ -2,6 +2,79 @@ use super::*; +/// Helper to read back data from an ffi function which expects a pointer into which it will write +/// a `T`. +/// +/// `fill_slab` is a function in which you must guarantee to write a valid `T` at the given +/// [`*mut c_void`](c_void) pointer. +/// +/// `slab` will be used as the backing data to write the `T` into. The `*mut c_void` pointer given +/// to the function will be as close to the beginning of `slab` as possible while upholding the +/// alignment requirements of `T`. If a `T` cannot fit into `slab` while upholding those alignment +/// requirements and the size of `T`, an error will be returned and `fill_slab` will not be called. +pub unsafe fn readback_from_ffi<'a, T, S, F>(slab: &'a mut S, fill_slab: F) -> Result<&'a T, Error> +where + S: Slab, + F: FnOnce(*mut c_void), +{ + let t_layout = Layout::new::(); + let offsets = compute_and_validate_offsets(slab, 0, t_layout, 1, false)?; + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::(); + + fill_slab(ptr); + + let ptr = ptr.cast::().cast_const(); + + // SAFETY: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for `T` at `ptr`, checked by us + // - if the function-level safety guarantees are met, then: + // - `ptr` contains a previously-placed `T` + // - we have mutable access to all of `slab`, which includes `ptr`. + Ok(unsafe { &*ptr }) +} + +/// Helper to read back data from an ffi function which expects a pointer into which it will write +/// a slice (in C language, an array) of `T`s. +/// +/// `fill_slab` is a function which takes as parameters first an aligned (for T) +/// [`*mut c_void`](c_void) and second the number of bytes left in `slab` available for writing. +/// It must then write a slice of `T`s into the given pointer and return the length, in units of +/// `T`, of the slice it wrote. +/// +/// `slab` will be used as the backing data to write the slice of `T`s into. The `*mut c_void` +/// pointer given to the function will be as close to the beginning of `slab` as possible while +/// upholding the alignment requirements of `T`. +pub unsafe fn readback_slice_from_ffi<'a, T, S, F>(slab: &'a mut S, fill_slab: F) -> Result<&'a [T], Error> +where + S: Slab, + F: FnOnce(*mut c_void, usize) -> usize, +{ + let t_layout = Layout::new::(); + let offsets = compute_and_validate_offsets(slab, 0, t_layout, 1, false)?; + // SAFETY: if compute_offsets succeeded, this has already been checked to be safe. + let ptr = unsafe { slab.base_ptr_mut().add(offsets.start) }.cast::(); + + let writable_size = slab.size() - offsets.end_padded; + let written_n_of_ts = fill_slab(ptr, writable_size); + + let layout_claimed_written = Layout::array::(written_n_of_ts)?; + let end_offset = offsets.start + layout_claimed_written.size(); + if end_offset > slab.size() { + return Err(Error::OutOfMemory); + } + + let ptr = ptr.cast::().cast_const(); + + // SAFETY: + // - `ptr` is properly aligned, checked by us + // - `slab` contains enough space for `[T; written_n_of_ts]` at `ptr`, checked by us + // - if the function-level safety guarantees are met, then: + // - `ptr` contains a previously-placed `T` + // - we have mutable access to all of `slab`, which includes `ptr`. + Ok(unsafe { core::slice::from_raw_parts(ptr, written_n_of_ts) }) +} /// Gets a shared reference to a `T` within `slab` at `offset`. /// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. From 1eb5dfcc7b0f8bb95ee39e52f20c458b1cbac36e Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Mon, 6 Nov 2023 23:02:24 +0100 Subject: [PATCH 16/17] fmt --- src/read.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/read.rs b/src/read.rs index 4c3ff0a..2476405 100644 --- a/src/read.rs +++ b/src/read.rs @@ -46,7 +46,10 @@ where /// `slab` will be used as the backing data to write the slice of `T`s into. The `*mut c_void` /// pointer given to the function will be as close to the beginning of `slab` as possible while /// upholding the alignment requirements of `T`. -pub unsafe fn readback_slice_from_ffi<'a, T, S, F>(slab: &'a mut S, fill_slab: F) -> Result<&'a [T], Error> +pub unsafe fn readback_slice_from_ffi<'a, T, S, F>( + slab: &'a mut S, + fill_slab: F, +) -> Result<&'a [T], Error> where S: Slab, F: FnOnce(*mut c_void, usize) -> usize, From 27345b3a95b88f9be15b0e17ec6fece29c916186 Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Mon, 6 Nov 2023 23:06:44 +0100 Subject: [PATCH 17/17] add safety doc comments to readback helpers --- src/read.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/read.rs b/src/read.rs index 2476405..b0b86c0 100644 --- a/src/read.rs +++ b/src/read.rs @@ -12,6 +12,14 @@ use super::*; /// to the function will be as close to the beginning of `slab` as possible while upholding the /// alignment requirements of `T`. If a `T` cannot fit into `slab` while upholding those alignment /// requirements and the size of `T`, an error will be returned and `fill_slab` will not be called. +/// +/// # Safety +/// +/// You must during the execution of `fill_slab` **fully-initialize** a **valid**\* `T` +/// at the given pointer. +/// +/// \* Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. pub unsafe fn readback_from_ffi<'a, T, S, F>(slab: &'a mut S, fill_slab: F) -> Result<&'a T, Error> where S: Slab, @@ -46,6 +54,15 @@ where /// `slab` will be used as the backing data to write the slice of `T`s into. The `*mut c_void` /// pointer given to the function will be as close to the beginning of `slab` as possible while /// upholding the alignment requirements of `T`. +/// +/// # Safety +/// +/// You must during the execution of `fill_slab` **fully-initialize** a **valid**\* slice of `T` +/// beginning at the given pointer and with length greater than or equal to the length you return +/// from that function. +/// +/// \* Validity is a complex topic not to be taken lightly. +/// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. pub unsafe fn readback_slice_from_ffi<'a, T, S, F>( slab: &'a mut S, fill_slab: F, @@ -78,6 +95,7 @@ where // - we have mutable access to all of `slab`, which includes `ptr`. Ok(unsafe { core::slice::from_raw_parts(ptr, written_n_of_ts) }) } + /// Gets a shared reference to a `T` within `slab` at `offset`. /// /// - `offset` is the offset, in bytes, after the start of `slab` at which a `T` is placed. @@ -89,8 +107,9 @@ where /// /// # Safety /// -/// You must have previously **fully-initialized** a **valid** `T` at the given offset into `slab`. -/// Validity is a complex topic not to be taken lightly. +/// You must have previously **fully-initialized** a **valid**\* `T` at the given offset into `slab`. +/// +/// \* Validity is a complex topic not to be taken lightly. /// See [this rust reference page](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) for more details. #[inline] pub unsafe fn read_at_offset<'a, T, S: Slab>(slab: &'a S, offset: usize) -> Result<&'a T, Error> {