use fuel_ir::{DType, Layout};
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct StructureKeyToken(pub String);
impl StructureKeyToken {
pub fn new(token: impl Into<String>) -> Self {
Self(token.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Contiguity {
Contiguous,
Strided,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct FdxOperandDesc {
pub dtype: DType,
pub contiguity: Contiguity,
pub broadcast: bool,
pub flipped: bool,
pub shape: Vec<i64>,
pub strides: Vec<i64>,
pub align_bytes: u32,
}
impl FdxOperandDesc {
pub fn from_layout(layout: &Layout, dtype: DType) -> Self {
let raw_strides = layout.stride();
let dims = layout.dims();
let flipped = raw_strides.iter().any(|&s| s < 0);
let broadcast = raw_strides
.iter()
.zip(dims.iter())
.any(|(&s, &d)| s == 0 && d > 1);
let contiguity = if layout.is_contiguous() {
Contiguity::Contiguous
} else {
Contiguity::Strided
};
Self {
dtype,
contiguity,
broadcast,
flipped,
shape: dims.iter().map(|&d| d as i64).collect(),
strides: raw_strides.iter().map(|&s| s as i64).collect(),
align_bytes: estimate_align_bytes(layout.start_offset(), dtype),
}
}
}
const BASE_ALLOC_ALIGN_BYTES: u32 = 256;
pub fn estimate_align_bytes(start_offset_elems: usize, dtype: DType) -> u32 {
let elem = dtype.size_in_bytes();
if elem == 0 {
return 1; }
let offset_bytes = start_offset_elems.saturating_mul(elem);
if offset_bytes == 0 {
return BASE_ALLOC_ALIGN_BYTES;
}
let tz = offset_bytes.trailing_zeros().min(8);
(1u32 << tz).min(BASE_ALLOC_ALIGN_BYTES)
}
pub trait StructureKeyProvider: Send + Sync {
fn structure_key(
&self,
op_class: &str,
operands: &[FdxOperandDesc],
arch: &str,
) -> Option<StructureKeyToken>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NullStructureKeyProvider;
impl StructureKeyProvider for NullStructureKeyProvider {
fn structure_key(
&self,
_op_class: &str,
_operands: &[FdxOperandDesc],
_arch: &str,
) -> Option<StructureKeyToken> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuel_ir::{Layout, Shape, StrideVec};
#[test]
fn structure_key_token_constructs_and_reads_through_the_reserved_surface() {
let t = StructureKeyToken::new("matmul:innerdiv16:vec8:f16");
assert_eq!(t.as_str(), "matmul:innerdiv16:vec8:f16");
assert_eq!(
t,
StructureKeyToken::new(String::from("matmul:innerdiv16:vec8:f16"))
);
}
#[test]
fn flipped_operand_sets_flipped_axis_in_fdx_desc() {
let shape = Shape::from(vec![4usize, 3]);
let stride: StrideVec = [-3isize, 1].into_iter().collect();
let layout = Layout::new(shape, stride, 9);
let desc = FdxOperandDesc::from_layout(&layout, DType::F32);
assert!(desc.flipped, "negative stride must set flipped");
assert_eq!(
desc.contiguity,
Contiguity::Strided,
"a flip is not contiguous"
);
assert!(!desc.broadcast, "no stride-0 axis here");
}
#[test]
fn contiguous_operand_projects_cleanly() {
let layout = Layout::contiguous(Shape::from(vec![8usize, 16]));
let desc = FdxOperandDesc::from_layout(&layout, DType::F16);
assert_eq!(desc.contiguity, Contiguity::Contiguous);
assert!(!desc.flipped);
assert!(!desc.broadcast);
assert_eq!(desc.dtype, DType::F16);
}
#[test]
fn broadcast_axis_sets_broadcast() {
let shape = Shape::from(vec![4usize, 3]);
let stride: StrideVec = [1isize, 0].into_iter().collect();
let layout = Layout::new(shape, stride, 0);
let desc = FdxOperandDesc::from_layout(&layout, DType::F32);
assert!(desc.broadcast, "stride-0 extent-3 axis is a broadcast");
}
#[test]
fn null_provider_yields_none() {
let p = NullStructureKeyProvider;
let operands = [FdxOperandDesc {
dtype: DType::F32,
contiguity: Contiguity::Contiguous,
broadcast: false,
flipped: false,
shape: vec![8, 16],
strides: vec![16, 1],
align_bytes: 256,
}];
assert!(p.structure_key("matmul", &operands, "sm_89").is_none());
}
#[test]
fn from_layout_carries_raw_shape_strides_and_align() {
let layout = Layout::contiguous(Shape::from(vec![4usize, 3]));
let desc = FdxOperandDesc::from_layout(&layout, DType::F32);
assert_eq!(desc.shape, vec![4i64, 3]);
assert_eq!(desc.strides, vec![3i64, 1]);
assert_eq!(desc.align_bytes, 256);
let flipped = Layout::new(
Shape::from(vec![4usize, 3]),
[3isize, -1].into_iter().collect(),
2,
);
let fdesc = FdxOperandDesc::from_layout(&flipped, DType::F32);
assert_eq!(fdesc.strides, vec![3i64, -1]);
assert!(fdesc.flipped);
}
#[test]
fn estimate_align_bytes_refines_by_offset() {
assert_eq!(estimate_align_bytes(0, DType::F32), 256);
assert_eq!(estimate_align_bytes(1, DType::F32), 4);
assert_eq!(estimate_align_bytes(2, DType::F32), 8);
assert_eq!(estimate_align_bytes(64, DType::F32), 256);
assert_eq!(estimate_align_bytes(3, DType::F4), 1);
}
}