use crate::{DType, Error, Layout, Result, Shape};
#[derive(Debug, Clone)]
pub struct OutputView {
pub byte_offset: usize,
pub len_elements: usize,
pub dtype: DType,
pub shape: Shape,
pub layout: Layout,
pub name: Option<&'static str>,
}
impl OutputView {
pub fn len_bytes(&self) -> usize {
self.len_elements.saturating_mul(self.dtype.size_in_bytes())
}
}
#[derive(Debug, Clone)]
pub struct OutputViewSpec {
pub dtype: DType,
pub shape: Shape,
pub layout: Layout,
pub name: Option<&'static str>,
}
impl OutputViewSpec {
pub fn contiguous(dtype: DType, shape: Shape) -> Self {
let layout = Layout::contiguous(shape.clone());
Self {
dtype,
shape,
layout,
name: None,
}
}
pub fn elem_count(&self) -> usize {
self.shape.elem_count()
}
pub fn len_bytes(&self) -> usize {
self.elem_count().saturating_mul(self.dtype.size_in_bytes())
}
}
pub fn compose_bundle(specs: &[OutputViewSpec]) -> Result<(usize, Vec<OutputView>)> {
if specs.is_empty() {
return Err(Error::Msg("compose_bundle: spec list must be non-empty".into()).bt());
}
let mut views = Vec::with_capacity(specs.len());
let mut cursor: usize = 0;
for (i, spec) in specs.iter().enumerate() {
if spec.layout.shape() != &spec.shape {
return Err(Error::Msg(format!(
"compose_bundle: slot {i} layout.shape() = {:?} \
disagrees with spec shape {:?}",
spec.layout.shape(),
spec.shape,
))
.bt());
}
let align = spec.dtype.size_in_bytes().max(1);
let rem = cursor % align;
if rem != 0 {
cursor += align - rem;
}
let len_elements = spec.elem_count();
views.push(OutputView {
byte_offset: cursor,
len_elements,
dtype: spec.dtype,
shape: spec.shape.clone(),
layout: spec.layout.clone(),
name: spec.name,
});
cursor = cursor.saturating_add(len_elements.saturating_mul(spec.dtype.size_in_bytes()));
}
Ok((cursor, views))
}
#[cfg(test)]
mod multi_output_specs {
use super::*;
fn f32_spec(dims: &[usize]) -> OutputViewSpec {
OutputViewSpec::contiguous(DType::F32, Shape::from_dims(dims))
}
fn f64_spec(dims: &[usize]) -> OutputViewSpec {
OutputViewSpec::contiguous(DType::F64, Shape::from_dims(dims))
}
#[test]
fn compose_bundle_single_slot() {
let specs = vec![f32_spec(&[2, 3])];
let (total, views) = compose_bundle(&specs).expect("single slot composes");
assert_eq!(views.len(), 1);
assert_eq!(views[0].byte_offset, 0);
assert_eq!(views[0].len_elements, 6);
assert_eq!(views[0].dtype, DType::F32);
assert_eq!(total, 24); }
#[test]
fn compose_bundle_two_slot_aligned() {
let specs = vec![f32_spec(&[2, 3]), f64_spec(&[3])];
let (total, views) = compose_bundle(&specs).expect("aligned compose");
assert_eq!(views.len(), 2);
assert_eq!(views[0].byte_offset, 0);
assert_eq!(views[0].len_elements, 6);
assert_eq!(views[1].byte_offset, 24);
assert_eq!(views[1].len_elements, 3);
assert_eq!(views[1].dtype, DType::F64);
assert_eq!(total, 48); }
#[test]
fn compose_bundle_pads_for_alignment() {
let specs = vec![f32_spec(&[1]), f64_spec(&[1])];
let (total, views) = compose_bundle(&specs).expect("padded compose");
assert_eq!(views[0].byte_offset, 0);
assert_eq!(views[1].byte_offset, 8); assert_eq!(total, 16); }
#[test]
fn compose_bundle_rejects_empty() {
let err = compose_bundle(&[]).expect_err("empty spec list must error");
assert!(format!("{err}").contains("non-empty"));
}
#[test]
fn compose_bundle_rejects_shape_layout_mismatch() {
let s = Shape::from_dims(&[2, 3]);
let bogus_layout = Layout::contiguous(Shape::from_dims(&[3, 2]));
let bad = OutputViewSpec {
dtype: DType::F32,
shape: s,
layout: bogus_layout,
name: None,
};
let err = compose_bundle(&[bad]).expect_err("shape/layout mismatch must error");
assert!(format!("{err}").contains("disagrees"));
}
#[test]
fn output_view_spec_contiguous_helper() {
let s = f32_spec(&[4, 5]);
assert_eq!(s.dtype, DType::F32);
assert_eq!(s.shape, Shape::from_dims(&[4, 5]));
assert_eq!(s.layout.shape(), &Shape::from_dims(&[4, 5]));
assert_eq!(s.elem_count(), 20);
assert_eq!(s.len_bytes(), 80);
assert!(s.name.is_none());
}
}