diff --git a/crates/clawhdf5/src/reader.rs b/crates/clawhdf5/src/reader.rs index 0b1ac71..191c614 100644 --- a/crates/clawhdf5/src/reader.rs +++ b/crates/clawhdf5/src/reader.rs @@ -64,6 +64,24 @@ impl Backing { } } +/// Evaluate `$body` with `$d` bound to the bytes the `clawhdf5_format` +/// parsers read: the in-memory slice when the file has one (a `Vec`, an +/// mmap), so local files run the parsers monomorphised for `[u8]` — the +/// slice code, as before the range-read migration — and otherwise the +/// [`FileData`] itself, whose reads go to the storage. +macro_rules! with_bytes { + ($data:expr, |$d:ident| $body:expr) => {{ + let data: &FileData = $data; + match data.contiguous() { + Some($d) => $body, + None => { + let $d = data; + $body + } + } + }}; +} + /// The file's bytes, viewed from the superblock on and up to the end of /// file the superblock records. A file may start with a user block (the /// superblock at 512, 1024, …); every HDF5 address is relative to the @@ -93,8 +111,28 @@ struct FileData { /// then); every object lookup here fails with this error, and no /// metadata is read from the file's own, possibly stale, bytes. image_error: Option, + /// [`Self::contiguous`], worked out once at open: every structure a + /// parser reads asks for it, and the patched/overlay checks and range + /// conversions behind it cost a local metadata walk a few percent. + contiguous: Option, } +/// A borrow of the HDF5 data held by a [`FileData`]'s own `backing` or +/// `patched` buffer (see [`FileData::contiguous`]). +#[derive(Clone, Copy)] +struct WholeView { + ptr: *const u8, + len: usize, +} + +// SAFETY: a `WholeView` is only a borrow of bytes owned (through `backing` +// or `patched`) by the `FileData` holding it, which is `Send + Sync`: the +// bytes are never written after open, so sharing the pointer across +// threads is sharing a `&[u8]`. +unsafe impl Send for WholeView {} +// SAFETY: as above. +unsafe impl Sync for WholeView {} + impl FileData { /// Locate the superblock and parse it. A truncated file is refused, and /// bytes past the recorded end of file are not read, as in libhdf5. @@ -127,17 +165,17 @@ impl FileData { ImageView::Patched(p) => (Some(p), None), ImageView::Unloadable(e) => (None, Some(e)), }; - Ok(( - Self { - backing, - base: base as u64, - end: end as u64, - patched, - overlay: Vec::new(), - image_error, - }, - superblock, - )) + let mut data = Self { + backing, + base: base as u64, + end: end as u64, + patched, + overlay: Vec::new(), + image_error, + contiguous: None, + }; + data.contiguous = data.find_contiguous(); + Ok((data, superblock)) } /// [`Self::new`] for a [`Storage`] backend: the same checks, through @@ -152,7 +190,11 @@ impl FileData { patched: None, overlay: Vec::new(), image_error: None, + contiguous: None, }; + // Worked out again below, once the end of file and any cache image + // are known. + data.contiguous = data.find_contiguous(); let superblock = Superblock::parse_in(&data, 0)?; data.end = base + superblock.data_end(base, file_len)?; match superblock_ext::cache_image_state_in(&data, &superblock)? { @@ -167,13 +209,37 @@ impl FileData { .collect(); } } + data.contiguous = data.find_contiguous(); Ok((data, superblock)) } /// The HDF5 data as one slice, when the file is in memory (a `Vec`, an /// mmap, or a storage that holds it all and has no cache image to lay /// over it). + #[inline] fn contiguous(&self) -> Option<&[u8]> { + // SAFETY: `find_contiguous` borrowed these bytes from `backing` or + // `patched`, which this `FileData` owns and never changes after + // open. They live on the heap or in a mapping (a `Vec`'s buffer, an + // mmap, a private copy, or a buffer inside the `Arc`'d storage), so + // they stay put when the `FileData` moves, and they live as long as + // `self`. + self.contiguous + .map(|v| unsafe { core::slice::from_raw_parts(v.ptr, v.len) }) + } + + /// [`Self::contiguous`], worked out from `backing` and `patched`. + fn find_contiguous(&self) -> Option { + let bytes = self.compute_contiguous()?; + Some(WholeView { + ptr: bytes.as_ptr(), + len: bytes.len(), + }) + } + + /// The HDF5 data as one slice, from `backing` and `patched` (see + /// [`Self::contiguous`]). + fn compute_contiguous(&self) -> Option<&[u8]> { if let Some(p) = &self.patched { return p.get(usize::try_from(self.base).ok()?..usize::try_from(self.end).ok()?); } @@ -224,6 +290,7 @@ impl FileData { } impl Storage for FileData { + #[inline] fn read_at(&self, offset: u64, len: usize) -> Result, FormatError> { if let Some(all) = self.contiguous() { return all.read_at(offset, len); @@ -261,6 +328,7 @@ impl Storage for FileData { .collect()) } + #[inline] fn as_contiguous(&self) -> Option<&[u8]> { self.contiguous() } @@ -400,8 +468,11 @@ impl File { /// /// The path uses `/` separators (e.g., `"group1/values"`). pub fn dataset(&self, path: &str) -> Result, Error> { - let data = self.data.meta()?; - let addr = group_v2::resolve_path_any_in(data, &self.superblock, path)?; + let addr = with_bytes!(self.data.meta()?, |d| group_v2::resolve_path_any_in( + d, + &self.superblock, + path + ))?; let hdr = self.parse_header(addr)?; if !has_message(&hdr, MessageType::DataLayout) { return Err(Error::NotADataset(path.to_string())); @@ -447,8 +518,11 @@ impl File { /// The path uses `/` separators (e.g., `"sensors"`). /// Use `"/"` or `""` for the root group. pub fn group(&self, path: &str) -> Result, Error> { - let data = self.data.meta()?; - let addr = group_v2::resolve_path_any_in(data, &self.superblock, path)?; + let addr = with_bytes!(self.data.meta()?, |d| group_v2::resolve_path_any_in( + d, + &self.superblock, + path + ))?; Ok(Group { file: self, address: addr, @@ -560,13 +634,13 @@ impl File { /// [`AttrValue::Raw`] attribute. Variable-length strings are resolved in /// this file's global heap; see [`Dataset::read_string`] for the values. pub fn decode_strings(&self, datatype: &Datatype, raw: &[u8]) -> Result, Error> { - crate::vlen::decode_strings( - &self.data, + with_bytes!(&self.data, |d| crate::vlen::decode_strings( + d, datatype, raw, self.offset_size(), self.length_size(), - ) + )) } /// Like [`decode_strings`](Self::decode_strings) for variable-length @@ -577,13 +651,13 @@ impl File { datatype: &Datatype, raw: &[u8], ) -> Result>, Error> { - crate::vlen::decode_string_bytes( - self.data.meta()?, + with_bytes!(self.data.meta()?, |d| crate::vlen::decode_string_bytes( + d, datatype, raw, self.offset_size(), self.length_size(), - ) + )) } /// Decode the variable-length sequences in `raw`, a buffer of elements @@ -595,22 +669,22 @@ impl File { datatype: &Datatype, raw: &[u8], ) -> Result>, Error> { - crate::vlen::decode_vlen( - self.data.meta()?, + with_bytes!(self.data.meta()?, |d| crate::vlen::decode_vlen( + d, datatype, raw, self.offset_size(), self.length_size(), - ) + )) } fn parse_header(&self, address: u64) -> Result { - ObjectHeader::parse_in( - self.data.meta()?, + with_bytes!(self.data.meta()?, |d| ObjectHeader::parse_in( + d, address, self.superblock.offset_size, self.superblock.length_size, - ) + )) } fn offset_size(&self) -> u8 { @@ -688,8 +762,12 @@ impl<'f> Group<'f> { &self, ) -> Result<(HashMap, Vec), Error> { let hdr = self.file.parse_header(self.address)?; - let data = &self.file.data; - read_attrs(data, &hdr, self.file.offset_size(), self.file.length_size()) + with_bytes!(&self.file.data, |d| read_attrs( + d, + &hdr, + self.file.offset_size(), + self.file.length_size() + )) } /// Get a dataset within this group by name. @@ -719,14 +797,13 @@ impl<'f> Group<'f> { /// stored densely. pub fn attr(&self, name: &str) -> Result, Error> { let hdr = self.file.parse_header(self.address)?; - let data = &self.file.data; - read_attr( - data, + with_bytes!(&self.file.data, |d| read_attr( + d, &hdr, name, self.file.offset_size(), self.file.length_size(), - ) + )) } /// The object header address of the child called `name`: the entry of @@ -734,9 +811,13 @@ impl<'f> Group<'f> { /// name index rather than by listing the group (see /// [`group_v2::resolve_child`]). fn child_address(&self, name: &str) -> Result { - let data = self.file.data.meta()?; - group_v2::resolve_child_in(data, &self.file.superblock, self.address, name) - .map_err(Error::Format) + with_bytes!(self.file.data.meta()?, |d| group_v2::resolve_child_in( + d, + &self.file.superblock, + self.address, + name + )) + .map_err(Error::Format) } /// This group's children that can be opened, as `(name, object header @@ -757,9 +838,10 @@ impl<'f> Group<'f> { /// [`group_v2::resolve_group_children`]); dangling, external and /// user-defined links are left out. fn children(&self) -> Result, Error> { - let data = self.file.data.meta()?; - group_v2::resolve_group_children_in(data, &self.file.superblock, self.address) - .map_err(Error::Format) + with_bytes!(self.file.data.meta()?, |d| { + group_v2::resolve_group_children_in(d, &self.file.superblock, self.address) + }) + .map_err(Error::Format) } } @@ -1332,13 +1414,12 @@ impl<'f> Dataset<'f> { pub fn attrs_with_errors( &self, ) -> Result<(HashMap, Vec), Error> { - let data = &self.file.data; - read_attrs( - data, + with_bytes!(&self.file.data, |d| read_attrs( + d, &self.header, self.file.offset_size(), self.file.length_size(), - ) + )) } /// The attribute called `name`, or `None` if it has none by that name @@ -1346,14 +1427,13 @@ impl<'f> Dataset<'f> { /// that name, found without reading the other attributes when they are /// stored densely. pub fn attr(&self, name: &str) -> Result, Error> { - let data = &self.file.data; - read_attr( - data, + with_bytes!(&self.file.data, |d| read_attr( + d, &self.header, name, self.file.offset_size(), self.file.length_size(), - ) + )) } /// Verify this dataset's content against its stored provenance hash @@ -1393,12 +1473,14 @@ impl<'f> Dataset<'f> { .iter() .find(|m| m.msg_type == msg_type) .map(|msg| { - clawhdf5_format::shared_message::message_data_in( - &self.file.data, - msg, - self.file.offset_size(), - self.file.length_size(), - ) + with_bytes!(&self.file.data, |d| { + clawhdf5_format::shared_message::message_data_in( + d, + msg, + self.file.offset_size(), + self.file.length_size(), + ) + }) .map_err(Error::Format) }) .transpose()