use std::ops::Deref;
use std::sync::Arc;
use std::{fmt, mem, slice};
use memmap2::Mmap;
use super::advice::Madviseable;
use super::mmap_rw::Error;
type Result<T> = std::result::Result<T, Error>;
pub struct MmapTypeReadOnly<T>
where
T: ?Sized + 'static,
{
r#type: &'static T,
mmap: Arc<Mmap>,
}
impl<T: ?Sized> fmt::Debug for MmapTypeReadOnly<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { mmap, r#type: _ } = self;
f.debug_struct("MmapTypeReadOnly")
.field("mmap", mmap)
.finish_non_exhaustive()
}
}
impl<T> MmapTypeReadOnly<T>
where
T: Sized + 'static,
{
pub unsafe fn from(mmap_with_type: Mmap) -> Self {
unsafe { Self::try_from(mmap_with_type).unwrap() }
}
pub unsafe fn try_from(mmap_with_type: Mmap) -> Result<Self> {
let r#type = unsafe { mmap_prefix_to_type_unbounded(&mmap_with_type)? };
let mmap = Arc::new(mmap_with_type);
Ok(Self { r#type, mmap })
}
}
impl<T> MmapTypeReadOnly<[T]>
where
T: 'static,
{
pub unsafe fn try_slice_from(mmap_with_slice: Mmap) -> Result<Self> {
let r#type = unsafe { mmap_to_slice_unbounded(&mmap_with_slice, 0)? };
let mmap = Arc::new(mmap_with_slice);
Ok(Self { r#type, mmap })
}
pub fn populate(&self) -> std::io::Result<()> {
self.mmap.populate();
Ok(())
}
pub fn clear_cache(&self) -> std::io::Result<()> {
let Self { r#type: _, mmap } = self;
mmap.clear_cache();
Ok(())
}
}
unsafe fn mmap_prefix_to_type_unbounded<'unbnd, T>(mmap: &Mmap) -> Result<&'unbnd T>
where
T: Sized,
{
let size_t = mem::size_of::<T>();
if mmap.len() < size_t {
return Err(Error::SizeLess(size_t, mmap.len()));
}
let bytes: &'unbnd [u8] = unsafe {
let slice = mmap.deref();
slice::from_raw_parts(slice.as_ptr(), size_t)
};
assert_alignment::<_, T>(bytes);
#[cfg(debug_assertions)]
if mmap.len() != size_t {
log::warn!(
"Mmap length {} is not equal to size of type {}",
mmap.len(),
size_t,
);
}
#[cfg(debug_assertions)]
if bytes.len() != mem::size_of::<T>() {
return Err(Error::SizeExact(mem::size_of::<T>(), bytes.len()));
}
let ptr = bytes.as_ptr().cast::<T>();
Ok(unsafe { &*ptr })
}
unsafe fn mmap_to_slice_unbounded<'unbnd, T>(mmap: &Mmap, header_size: usize) -> Result<&'unbnd [T]>
where
T: Sized,
{
let size_t = mem::size_of::<T>();
if size_t == 0 {
debug_assert_eq!(
mmap.len().saturating_sub(header_size),
0,
"mmap data must be zero-sized, because size T is zero",
);
} else {
debug_assert_eq!(header_size % size_t, 0, "header not multiple of size T");
if !mmap.len().is_multiple_of(size_t) {
return Err(Error::SizeMultiple(size_t, mmap.len()));
}
}
let bytes: &'unbnd [u8] = unsafe {
let slice = mmap.deref();
&slice::from_raw_parts(slice.as_ptr(), slice.len())[header_size..]
};
assert_alignment::<_, T>(bytes);
debug_assert_eq!(bytes.len() + header_size, mmap.len());
unsafe {
Ok(slice::from_raw_parts(
bytes.as_ptr().cast::<T>(),
bytes.len().checked_div(size_t).unwrap_or(0),
))
}
}
impl<T> Deref for MmapTypeReadOnly<T>
where
T: ?Sized + 'static,
{
type Target = T;
#[allow(clippy::needless_lifetimes)]
fn deref<'bounded>(&'bounded self) -> &'bounded Self::Target {
self.r#type
}
}
pub struct MmapSliceReadOnly<T>
where
T: Sized + 'static,
{
mmap: MmapTypeReadOnly<[T]>,
}
impl<T> fmt::Debug for MmapSliceReadOnly<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { mmap } = self;
f.debug_struct("MmapSliceReadOnly")
.field("mmap", mmap)
.finish()
}
}
impl<T> MmapSliceReadOnly<T> {
pub unsafe fn from(mmap_with_slice: Mmap) -> Self {
unsafe { Self::try_from(mmap_with_slice).unwrap() }
}
pub unsafe fn try_from(mmap_with_slice: Mmap) -> Result<Self> {
let r#type = unsafe { MmapTypeReadOnly::try_slice_from(mmap_with_slice) };
r#type.map(|mmap| Self { mmap })
}
pub fn populate(&self) -> std::io::Result<()> {
self.mmap.populate()?;
Ok(())
}
pub fn clear_cache(&self) -> std::io::Result<()> {
let Self { mmap } = self;
mmap.clear_cache()?;
Ok(())
}
}
impl<T> Deref for MmapSliceReadOnly<T> {
type Target = MmapTypeReadOnly<[T]>;
fn deref(&self) -> &Self::Target {
&self.mmap
}
}
impl<T: 'static> AsRef<[T]> for MmapSliceReadOnly<T> {
fn as_ref(&self) -> &[T] {
&self.mmap
}
}
fn assert_alignment<S, T>(bytes: &[S]) {
assert_eq!(
bytes.as_ptr().align_offset(mem::align_of::<T>()),
0,
"type must be aligned",
);
}
#[cfg(test)]
mod tests {
use tempfile::{Builder, NamedTempFile};
use super::*;
use crate::common::mmap;
use crate::common::mmap::AdviceSetting;
fn create_temp_mmap_file(len: usize) -> NamedTempFile {
let tempfile = Builder::new()
.prefix("test.")
.suffix(".mmap")
.tempfile()
.unwrap();
#[allow(clippy::disallowed_methods, reason = "test code")]
tempfile.as_file().set_len(len as u64).unwrap();
tempfile
}
#[test]
fn test_open_from() {
const SIZE: usize = 1024;
let tempfile = create_temp_mmap_file(SIZE);
let mmap = mmap::open_read_mmap(tempfile.path(), AdviceSetting::Global, false).unwrap();
let result = unsafe { MmapSliceReadOnly::<u64>::try_from(mmap).unwrap() };
assert_eq!(result.len(), SIZE / size_of::<u64>());
assert_eq!(result[10], 0);
}
}