use crate::chunked_write::ChunkProvider;
use crate::datatype::Datatype;
use crate::error::FormatError;
use crate::mat::class::MatClass;
use crate::mat::error::MatError;
use crate::type_builders::{
CompoundTypeBuilder, make_f32_type, make_f64_type, make_i8_type, make_i16_type, make_i32_type,
make_i64_type, make_u8_type, make_u16_type, make_u32_type, make_u64_type,
};
use std::sync::{Arc, Mutex};
const TARGET_BLOCK_BYTES: u64 = 1 << 20;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Block {
pub index: usize,
pub first_element: u64,
pub elements: u64,
pub element_size: usize,
}
impl Block {
pub fn len(&self) -> usize {
usize::try_from(self.elements)
.ok()
.and_then(|n| n.checked_mul(self.element_size))
.unwrap_or(usize::MAX)
}
pub fn is_empty(&self) -> bool {
self.elements == 0
}
}
pub trait DataProducer: Send + Sync {
fn block_bytes(&self, block: Block, out: &mut Vec<u8>) -> Result<(), MatError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Blocking {
pub block_count: usize,
pub block_elements: u64,
pub last_block_elements: u64,
pub element_size: usize,
}
impl Blocking {
pub fn block(&self, index: usize) -> Block {
let elements = if index >= self.block_count {
0
} else if index == self.block_count - 1 {
self.last_block_elements
} else {
self.block_elements
};
Block {
index,
first_element: self.block_elements.saturating_mul(index as u64),
elements,
element_size: self.element_size,
}
}
pub fn block_len(&self, index: usize) -> usize {
self.block(index).len()
}
pub fn total_len(&self) -> u64 {
if self.block_count == 0 {
return 0;
}
self.block_elements
.saturating_mul(self.block_count as u64 - 1)
.saturating_add(self.last_block_elements)
.saturating_mul(self.element_size as u64)
}
pub fn plan<T: BlockElement>(matlab_dims: &[usize]) -> Result<Blocking, MatError> {
plan_blocking(total_elements(matlab_dims), T::ELEMENT_SIZE)
}
}
pub(crate) fn total_elements(matlab_dims: &[usize]) -> u64 {
matlab_dims
.iter()
.try_fold(1u64, |acc, &d| acc.checked_mul(d as u64))
.unwrap_or(u64::MAX)
}
pub(crate) fn plan_blocking(total: u64, element_size: usize) -> Result<Blocking, MatError> {
if total == 0 || element_size == 0 {
return Ok(Blocking {
block_count: 0,
block_elements: 0,
last_block_elements: 0,
element_size,
});
}
let total_bytes = total
.checked_mul(element_size as u64)
.filter(|&bytes| usize::try_from(bytes).is_ok())
.ok_or_else(|| too_large(total.saturating_mul(element_size as u64)))?;
let per_block = (TARGET_BLOCK_BYTES / element_size as u64).clamp(1, total);
let block_count = total.div_ceil(per_block);
let last = total - (block_count - 1) * per_block;
let block_bytes = per_block * element_size as u64;
if usize::try_from(block_bytes).is_err() {
return Err(too_large(block_bytes));
}
debug_assert!(total_bytes >= block_bytes);
Ok(Blocking {
block_count: usize::try_from(block_count).map_err(|_| too_large(block_count))?,
block_elements: per_block,
last_block_elements: last,
element_size,
})
}
fn too_large(value: u64) -> MatError {
MatError::Hdf5(crate::error::Error::Format(
FormatError::ValueTooLargeForPlatform {
value,
target: "usize",
},
))
}
pub(crate) struct ProducerChunks {
pub(crate) producer: Box<dyn DataProducer>,
pub(crate) blocking: Blocking,
pub(crate) error: Arc<Mutex<Option<MatError>>>,
}
impl ProducerChunks {
fn fail(&self, error: MatError) -> FormatError {
let mut slot = self
.error
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
slot.get_or_insert(error);
FormatError::SerializationError("a dataset's block producer failed".into())
}
}
impl ChunkProvider for ProducerChunks {
fn chunk_bytes(&self, index: usize, out: &mut Vec<u8>) -> Result<(), FormatError> {
let block = self.blocking.block(index);
let expected = block.len();
if let Err(e) = self.producer.block_bytes(block, out) {
return Err(self.fail(e));
}
if out.len() != expected {
return Err(self.fail(MatError::BlockSizeMismatch {
block: index,
expected,
actual: out.len(),
}));
}
Ok(())
}
}
mod sealed {
pub trait Sealed {}
}
pub trait BlockElement: sealed::Sealed {
const CLASS: MatClass;
const ELEMENT_SIZE: usize;
const INT_DECODE: Option<i32> = None;
fn datatype() -> Datatype;
}
macro_rules! block_elements {
($($ty:ty => $class:ident, $make:ident),* $(,)?) => {
$(
impl sealed::Sealed for $ty {}
impl BlockElement for $ty {
const CLASS: MatClass = MatClass::$class;
const ELEMENT_SIZE: usize = size_of::<$ty>();
fn datatype() -> Datatype {
$make()
}
}
impl sealed::Sealed for ($ty, $ty) {}
impl BlockElement for ($ty, $ty) {
const CLASS: MatClass = MatClass::$class;
const ELEMENT_SIZE: usize = 2 * size_of::<$ty>();
fn datatype() -> Datatype {
CompoundTypeBuilder::new()
.field("real", $make())
.field("imag", $make())
.build()
}
}
)*
};
}
block_elements! {
f64 => Double, make_f64_type,
f32 => Single, make_f32_type,
i8 => Int8, make_i8_type,
i16 => Int16, make_i16_type,
i32 => Int32, make_i32_type,
i64 => Int64, make_i64_type,
u8 => UInt8, make_u8_type,
u16 => UInt16, make_u16_type,
u32 => UInt32, make_u32_type,
u64 => UInt64, make_u64_type,
}
impl sealed::Sealed for bool {}
impl BlockElement for bool {
const CLASS: MatClass = MatClass::Logical;
const ELEMENT_SIZE: usize = 1;
const INT_DECODE: Option<i32> = Some(1);
fn datatype() -> Datatype {
make_u8_type()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_blocks_partition_the_dataset() {
let b = plan_blocking(3_000_000, 8).unwrap();
assert_eq!(b.block_elements, 131_072);
assert!(b.block_count > 1, "the fixture must span several blocks");
let total: usize = (0..b.block_count).map(|i| b.block_len(i)).sum();
assert_eq!(total as u64, 3_000_000 * 8);
assert_eq!(total as u64, b.total_len());
for i in 0..b.block_count - 1 {
assert_eq!(b.block_len(i), 131_072 * 8);
}
assert!(b.last_block_elements < b.block_elements);
assert_eq!(b.block_len(b.block_count), 0, "no block past the last");
}
#[test]
fn an_element_wider_than_the_target_becomes_the_block() {
let huge = (TARGET_BLOCK_BYTES + 8) as usize;
let b = plan_blocking(3, huge).unwrap();
assert_eq!(b.block_elements, 1);
assert_eq!(b.block_count, 3);
assert_eq!(b.block_len(0), huge);
}
#[test]
fn a_dataset_smaller_than_a_block_is_one_full_block() {
let b = plan_blocking(8, 8).unwrap();
assert_eq!(b.block_count, 1);
assert_eq!(b.block_elements, 8);
assert_eq!(b.last_block_elements, 8);
assert_eq!(b.block_len(0), 64);
assert_eq!(b.total_len(), 64);
}
#[test]
fn an_empty_shape_plans_no_blocks() {
let b = plan_blocking(total_elements(&[0, 0]), 8).unwrap();
assert_eq!(b.block_count, 0);
assert_eq!(b.block_len(0), 0);
assert_eq!(b.total_len(), 0);
}
#[test]
fn a_shape_whose_bytes_overflow_is_refused() {
assert!(plan_blocking(u64::MAX, 8).is_err());
let max_elements = (usize::MAX as u64) / 8;
let b = plan_blocking(max_elements, 8).unwrap();
assert_eq!(b.total_len(), max_elements * 8);
assert!(
usize::try_from(b.total_len()).is_ok(),
"the largest plannable region must be one this host can address"
);
assert!(plan_blocking(max_elements + 1, 8).is_err());
assert_eq!(
total_elements(&[usize::MAX, usize::MAX, usize::MAX]),
u64::MAX
);
assert!(Blocking::plan::<f64>(&[usize::MAX, usize::MAX, usize::MAX]).is_err());
}
#[test]
fn a_complex_pair_counts_both_components() {
assert_eq!(<(i16, i16) as BlockElement>::ELEMENT_SIZE, 4);
assert_eq!(<(i16, i16) as BlockElement>::CLASS, MatClass::Int16);
let b = Blocking::plan::<(i16, i16)>(&[2, 10]).unwrap();
assert_eq!(b.element_size, 4);
assert_eq!(b.total_len(), 2 * 10 * 4);
}
#[test]
fn mat_builder_keeps_its_auto_traits() {
fn assert_auto_traits<
T: Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe,
>() {
}
assert_auto_traits::<crate::mat::MatBuilder>();
}
#[test]
fn the_blocks_tile_the_dataset_without_gap_or_overlap() {
let b = plan_blocking(3_000_000, 8).unwrap();
let mut expected_start = 0u64;
for i in 0..b.block_count {
let block = b.block(i);
assert_eq!(block.index, i);
assert_eq!(
block.first_element,
expected_start,
"block {i} must start where block {} ended",
i.wrapping_sub(1)
);
assert!(!block.is_empty());
assert_eq!(block.len(), block.elements as usize * 8);
expected_start += block.elements;
}
assert_eq!(
expected_start, 3_000_000,
"the blocks together must be the whole dataset"
);
assert!(b.block(b.block_count).is_empty());
assert_eq!(b.block(b.block_count).len(), 0);
}
#[test]
fn a_forged_blocking_reports_rather_than_panics() {
let planned = plan_blocking(10, 8).unwrap();
assert_eq!(planned.block_len(usize::MAX), 0);
assert_eq!(planned.block_len(planned.block_count), 0);
assert!(planned.block(usize::MAX).is_empty());
let mut forged = planned;
forged.block_count = 3;
forged.block_elements = u64::MAX / 2;
forged.last_block_elements = u64::MAX;
assert_eq!(forged.total_len(), u64::MAX);
assert_eq!(forged.block_len(0), usize::MAX);
}
#[test]
fn logical_is_a_byte_with_a_decode_flag() {
assert_eq!(<bool as BlockElement>::ELEMENT_SIZE, 1);
assert_eq!(<bool as BlockElement>::INT_DECODE, Some(1));
assert_eq!(<f64 as BlockElement>::INT_DECODE, None);
}
}