Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add read helpers #5

Merged
merged 17 commits into from
Nov 6, 2023
Prev Previous commit
Next Next commit
add ffi readback helpers
fu5ha committed Nov 6, 2023
commit c7a29626cbef13aac5be07ae090bec667e83f69e
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<S: Slab>(
slab: &S,
start_offset: usize,
73 changes: 73 additions & 0 deletions src/read.rs
Original file line number Diff line number Diff line change
@@ -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::<T>();
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::<c_void>();

fill_slab(ptr);

let ptr = ptr.cast::<T>().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::<T>();
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::<c_void>();

let writable_size = slab.size() - offsets.end_padded;
let written_n_of_ts = fill_slab(ptr, writable_size);

let layout_claimed_written = Layout::array::<T>(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::<T>().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.