use std::any::Any;
use std::hash::Hasher;
use std::num::NonZeroU64;
use std::sync::Arc;
use tenferro_core_ops::{all_primitive_descriptors, PrimitiveOpKind};
use tenferro_ops::dim_expr::DimExpr;
use tenferro_ops::ext_op::ExtensionOp;
use tenferro_ops::{ShapeExtent, SymDim};
use tenferro_tensor::{
CompareDir, DType, DotGeneralConfig, Engine, GatherConfig, PadConfig, ScatterConfig,
SliceConfig, Tensor,
};
use super::dispatch::{
backend_dispatch_entry, host_dispatch_entry, FfiDispatchKey, HostDispatchKey,
BACKEND_DISPATCH_TABLE,
};
use super::{
constant_tensor, get, initialize_exec_slots_in, validate_exec_program, ExecInstruction, ExecOp,
ExecProgram, ExecSlot, InputView,
};
use crate::runtime::{
EngineId, ExecutionContextIdentity, HardwareClassId, InputSignature, PreparedOperation,
PreparedOperationBinding, PreparedOperationPlan, RegistrationIdentity, RuntimeEpoch, RuntimeId,
SpecializationProjection, SpecializationRequirements,
};
use crate::ExtensionCacheStore;
use tenferro_cpu::CpuEngine;
#[test]
fn exec_op_maps_to_catalog_kind() {
assert_eq!(ExecOp::Add.primitive_kind(), Some(PrimitiveOpKind::Add));
assert_eq!(
ExecOp::Multiply.primitive_kind(),
Some(PrimitiveOpKind::Mul)
);
assert_eq!(ExecOp::Negate.primitive_kind(), Some(PrimitiveOpKind::Neg));
assert_eq!(ExecOp::Divide.primitive_kind(), Some(PrimitiveOpKind::Div));
assert_eq!(
ExecOp::ShapeOf { axis: 0 }.primitive_kind(),
Some(PrimitiveOpKind::ShapeOf)
);
assert_eq!(
ExecOp::Extension(Arc::new(TestExtension)).primitive_kind(),
None
);
}
#[test]
fn every_catalog_descriptor_has_an_exec_op_variant_with_matching_arity() {
for descriptor in all_primitive_descriptors() {
let op = ExecOp::sample_from_kind(descriptor.kind);
assert_eq!(
op.primitive_kind(),
Some(descriptor.kind),
"ExecOp variant for {:?} disagrees with descriptor",
descriptor.kind
);
assert_eq!(
op.input_arity_bounds(),
Some((descriptor.min_inputs, descriptor.max_inputs)),
"ExecOp arity for {:?} disagrees with descriptor",
descriptor.kind
);
}
}
#[test]
fn backend_dispatch_table_covers_all_backend_exec_ops() {
let cases = backend_dispatch_cases();
assert_eq!(cases.len(), BACKEND_DISPATCH_TABLE.len());
for (op, expected) in cases {
assert_eq!(op.primitive_kind(), Some(expected), "{op:?}");
let entry = backend_dispatch_entry(&op).unwrap_or_else(|| {
panic!("missing backend dispatch table entry for {expected:?}: {op:?}")
});
assert_eq!(entry.key, expected);
}
}
#[test]
fn backend_dispatch_table_excludes_host_and_ffi_exec_ops() {
for op in non_backend_dispatch_cases() {
assert!(backend_dispatch_entry(&op).is_none(), "{op:?}");
}
}
#[test]
fn ffi_dispatch_table_covers_dot_and_extension_exec_ops() {
let cases = ffi_dispatch_cases();
assert_eq!(cases.len(), FfiDispatchKey::COUNT);
for (op, expected) in cases {
assert_eq!(FfiDispatchKey::for_op(&op), Some(expected), "{op:?}");
}
}
#[test]
fn ffi_dispatch_keys_exclude_host_and_backend_exec_ops() {
for op in non_ffi_dispatch_cases() {
assert_eq!(FfiDispatchKey::for_op(&op), None, "{op:?}");
}
}
#[test]
fn host_dispatch_table_covers_host_exec_ops() {
let cases = host_dispatch_cases();
assert_eq!(cases.len(), HostDispatchKey::COUNT);
for (op, expected) in cases {
assert_eq!(HostDispatchKey::for_op(&op), Some(expected), "{op:?}");
let entry = host_dispatch_entry::<dyn Engine>(&op).unwrap_or_else(|| {
panic!("missing host dispatch table entry for {expected:?}: {op:?}")
});
assert_eq!(entry.key, expected);
}
}
#[test]
fn host_dispatch_table_excludes_backend_and_ffi_exec_ops() {
for op in non_host_dispatch_cases() {
assert_eq!(HostDispatchKey::for_op(&op), None, "{op:?}");
assert!(host_dispatch_entry::<dyn Engine>(&op).is_none(), "{op:?}");
}
}
#[test]
fn exec_instruction_single_output_metadata_stays_inline() {
let instr = ExecInstruction {
op: ExecOp::Negate,
semantic_operation_index: None,
input_slots: vec![0],
output_slots: vec![1],
dtype: DType::F64,
output_shapes: vec![vec![DimExpr::Const(2)]].into_iter().collect(),
output_extents: vec![vec![ShapeExtent::exact(DimExpr::Const(2))]]
.into_iter()
.collect(),
last_use: vec![true],
input_views: Vec::new(),
};
assert!(!instr.output_shapes.is_heap());
assert!(!instr.output_extents.is_heap());
}
#[test]
fn exec_slot_owned_and_read_tensor_conversions_preserve_shape_and_data() {
let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap();
let typed = tensor.as_typed::<f64>().expect("f64 tensor");
let view = typed.as_view().reshape_view(&[1, 2]).expect("reshape view");
let owned = ExecSlot::Owned(tensor.duplicate().unwrap());
let read = ExecSlot::Read(tenferro_tensor::TensorRead::from_tensor(&tensor));
assert_eq!(owned.as_tensor("test").unwrap().shape(), &[2]);
assert_eq!(read.as_tensor("test").unwrap().shape(), &[2]);
assert_eq!(owned.shape(), &[2]);
assert_eq!(read.shape(), &[2]);
assert_eq!(owned.as_read().as_slice::<f64>().unwrap(), &[1.0_f64, 2.0]);
assert_eq!(read.as_read().as_slice::<f64>().unwrap(), &[1.0_f64, 2.0]);
let view_slot = ExecSlot::Read(view.into_tensor_read().expect("view read"));
assert!(matches!(
view_slot.as_tensor("test"),
Err(crate::Error::Internal(message)) if message.contains("borrowed TensorView")
));
assert_eq!(view_slot.shape(), &[1, 2]);
}
#[test]
fn exec_accessors_reject_bad_input_slot_index_without_panicking() {
let slots = [Some(ExecSlot::Owned(
Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap(),
))];
let err = get(&slots, &negate_instruction(vec![]), 0).unwrap_err();
assert!(matches!(err, crate::Error::Internal(message) if message.contains("input index 0")));
}
#[test]
fn exec_accessors_reject_out_of_range_slot_without_panicking() {
let slots = [Some(ExecSlot::Owned(
Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap(),
))];
let err = get(&slots, &negate_instruction(vec![4]), 0).unwrap_err();
let message = err.to_string();
assert!(message.contains("slot 4"), "{message}");
assert!(message.contains("out of range"), "{message}");
}
#[test]
fn initialize_exec_slots_rejects_input_count_mismatch_without_panicking() {
let program = empty_program(1);
let mut slots = Vec::new();
let inputs = vec![
ExecSlot::Owned(Tensor::from_vec_col_major(vec![1], vec![1.0_f64]).unwrap()),
ExecSlot::Owned(Tensor::from_vec_col_major(vec![1], vec![2.0_f64]).unwrap()),
];
let err = initialize_exec_slots_in(&program, inputs, &mut slots).unwrap_err();
let message = err.to_string();
assert!(message.contains("expected 1 inputs"), "{message}");
assert!(message.contains("got 2"), "{message}");
}
#[test]
fn validate_exec_program_rejects_out_of_range_output_slot_without_panicking() {
let mut program = empty_program(1);
program.output_slots = vec![3];
let err = validate_exec_program(&program, "test executor").unwrap_err();
let message = err.to_string();
assert!(message.contains("slot 3"), "{message}");
assert!(message.contains("1 slots"), "{message}");
}
#[test]
fn validate_exec_program_rejects_empty_instruction_outputs() {
let mut program = empty_program(1);
program.instructions.push(ExecInstruction {
op: ExecOp::Negate,
semantic_operation_index: None,
input_slots: vec![0],
output_slots: vec![],
dtype: DType::F64,
output_shapes: Vec::new().into_iter().collect(),
output_extents: Vec::new().into_iter().collect(),
last_use: vec![true],
input_views: Vec::new(),
});
let err = validate_exec_program(&program, "test").unwrap_err();
let message = err.to_string();
assert!(message.contains("instruction 0"), "{message}");
assert!(message.contains("no output slots"), "{message}");
}
#[test]
fn validate_exec_program_rejects_terminal_value_instruction_input_arity() {
let mut program = empty_program(0);
program.n_slots = 1;
program.output_slots = vec![0];
program.instructions.push(ExecInstruction {
op: ExecOp::Transpose { perm: vec![0] },
semantic_operation_index: None,
input_slots: vec![],
output_slots: vec![0],
dtype: DType::F64,
output_shapes: vec![vec![DimExpr::Const(1)]].into_iter().collect(),
output_extents: vec![vec![ShapeExtent::exact(DimExpr::Const(1))]]
.into_iter()
.collect(),
last_use: vec![],
input_views: Vec::new(),
});
let err = validate_exec_program(&program, "test").unwrap_err();
let message = err.to_string();
assert!(message.contains("instruction 0"), "{message}");
assert!(message.contains("input slots"), "{message}");
}
#[test]
fn validate_exec_program_accepts_symbolic_shape_reference_inputs() {
let mut program = empty_program(2);
program.n_slots = 3;
program.output_slots = vec![2];
program.instructions.push(ExecInstruction {
op: ExecOp::BroadcastInDim {
shape: vec![DimExpr::InputDim {
input_idx: 1,
axis: 0,
}],
dims: vec![],
},
semantic_operation_index: None,
input_slots: vec![0, 1],
output_slots: vec![2],
dtype: DType::F64,
output_shapes: vec![vec![DimExpr::InputDim {
input_idx: 1,
axis: 0,
}]]
.into_iter()
.collect(),
output_extents: vec![vec![ShapeExtent::exact(DimExpr::InputDim {
input_idx: 1,
axis: 0,
})]]
.into_iter()
.collect(),
last_use: vec![true],
input_views: Vec::new(),
});
validate_exec_program(&program, "test").unwrap();
}
#[test]
fn constant_tensor_rejects_payload_length_mismatch_without_panicking() {
let err = constant_tensor(DType::F64, &[0; 4]).unwrap_err();
let message = err.to_string();
assert!(
message.contains("constant F64 expected 8 bytes"),
"{message}"
);
assert!(message.contains("got 4"), "{message}");
}
fn negate_instruction(input_slots: Vec<usize>) -> ExecInstruction {
ExecInstruction {
op: ExecOp::Negate,
semantic_operation_index: None,
input_slots,
output_slots: vec![0],
dtype: DType::F64,
output_shapes: Default::default(),
output_extents: Default::default(),
last_use: Vec::new(),
input_views: Vec::new(),
}
}
fn empty_program(input_count: usize) -> ExecProgram {
ExecProgram {
instructions: Vec::new(),
input_slots: (0..input_count).collect(),
output_slots: Vec::new(),
n_slots: input_count,
shape_guards: Vec::new(),
}
}
fn backend_dispatch_cases() -> Vec<(ExecOp, PrimitiveOpKind)> {
vec![
(
ExecOp::Transpose { perm: vec![0] },
PrimitiveOpKind::Transpose,
),
(
ExecOp::Reshape {
shape: vec![DimExpr::Const(1)],
},
PrimitiveOpKind::Reshape,
),
(
ExecOp::BroadcastInDim {
shape: vec![DimExpr::Const(1)],
dims: vec![0],
},
PrimitiveOpKind::BroadcastInDim,
),
(ExecOp::Convert { to: DType::F64 }, PrimitiveOpKind::Convert),
(
ExecOp::ReduceSum { axes: vec![0] },
PrimitiveOpKind::ReduceSum,
),
(
ExecOp::ReduceSumSquares { axes: vec![0] },
PrimitiveOpKind::ReduceSumSquares,
),
(
ExecOp::ExtractDiag {
axis_a: 0,
axis_b: 1,
},
PrimitiveOpKind::ExtractDiag,
),
(
ExecOp::EmbedDiag {
axis_a: 0,
axis_b: 1,
},
PrimitiveOpKind::EmbedDiag,
),
(ExecOp::Tril { k: 0 }, PrimitiveOpKind::Tril),
(ExecOp::Triu { k: 0 }, PrimitiveOpKind::Triu),
(ExecOp::Add, PrimitiveOpKind::Add),
(ExecOp::Subtract, PrimitiveOpKind::Sub),
(ExecOp::Multiply, PrimitiveOpKind::Mul),
(ExecOp::Negate, PrimitiveOpKind::Neg),
(ExecOp::Conj, PrimitiveOpKind::Conj),
(ExecOp::Divide, PrimitiveOpKind::Div),
(ExecOp::Remainder, PrimitiveOpKind::Rem),
(ExecOp::Abs, PrimitiveOpKind::Abs),
(ExecOp::Sign, PrimitiveOpKind::Sign),
(ExecOp::Maximum, PrimitiveOpKind::Maximum),
(ExecOp::Minimum, PrimitiveOpKind::Minimum),
(ExecOp::Compare(CompareDir::Eq), PrimitiveOpKind::Compare),
(ExecOp::Select, PrimitiveOpKind::Select),
(ExecOp::Clamp, PrimitiveOpKind::Clamp),
(ExecOp::Exp, PrimitiveOpKind::Exp),
(ExecOp::Log, PrimitiveOpKind::Log),
(ExecOp::Sin, PrimitiveOpKind::Sin),
(ExecOp::Cos, PrimitiveOpKind::Cos),
(ExecOp::Tanh, PrimitiveOpKind::Tanh),
(ExecOp::Sqrt, PrimitiveOpKind::Sqrt),
(ExecOp::Rsqrt, PrimitiveOpKind::Rsqrt),
(ExecOp::Pow, PrimitiveOpKind::Pow),
(ExecOp::Expm1, PrimitiveOpKind::Expm1),
(ExecOp::Log1p, PrimitiveOpKind::Log1p),
(ExecOp::Erf, PrimitiveOpKind::Erf),
(ExecOp::Gather(gather_config()), PrimitiveOpKind::Gather),
(
ExecOp::GatherDynamicSliceSizes {
offset_dims: vec![],
collapsed_slice_dims: vec![0],
start_index_map: vec![0],
index_vector_dim: 1,
slice_sizes: vec![DimExpr::Const(1)],
},
PrimitiveOpKind::GatherDynamicSliceSizes,
),
(ExecOp::Scatter(scatter_config()), PrimitiveOpKind::Scatter),
(ExecOp::Slice(slice_config()), PrimitiveOpKind::Slice),
(
ExecOp::DynamicSlice {
slice_sizes: vec![1],
},
PrimitiveOpKind::DynamicSlice,
),
(
ExecOp::DynamicUpdateSlice,
PrimitiveOpKind::DynamicUpdateSlice,
),
(ExecOp::Pad(pad_config()), PrimitiveOpKind::Pad),
(
ExecOp::Concatenate { axis: 0 },
PrimitiveOpKind::Concatenate,
),
(ExecOp::Reverse { axes: vec![0] }, PrimitiveOpKind::Reverse),
(
ExecOp::ReduceProd { axes: vec![0] },
PrimitiveOpKind::ReduceProd,
),
(
ExecOp::ReduceMax { axes: vec![0] },
PrimitiveOpKind::ReduceMax,
),
(
ExecOp::ReduceMin { axes: vec![0] },
PrimitiveOpKind::ReduceMin,
),
]
}
fn ffi_dispatch_cases() -> Vec<(ExecOp, FfiDispatchKey)> {
vec![
(ExecOp::DotGeneral(dot_config()), FfiDispatchKey::DotGeneral),
(
ExecOp::DotGeneralWithConj {
config: dot_config(),
lhs_conj: true,
rhs_conj: false,
},
FfiDispatchKey::DotGeneralWithConj,
),
(
ExecOp::Extension(Arc::new(TestExtension)),
FfiDispatchKey::Extension,
),
]
}
fn host_dispatch_cases() -> Vec<(ExecOp, HostDispatchKey)> {
vec![
(ExecOp::ShapeOf { axis: 0 }, HostDispatchKey::ShapeOf),
(
ExecOp::DynamicTruncate { axis: 0 },
HostDispatchKey::DynamicTruncate,
),
(ExecOp::PadToMatch { axis: 0 }, HostDispatchKey::PadToMatch),
(
ExecOp::Constant {
dtype: DType::F64,
bytes: 1.0_f64.to_le_bytes().to_vec(),
},
HostDispatchKey::Constant,
),
]
}
fn non_backend_dispatch_cases() -> Vec<ExecOp> {
vec![
ExecOp::ShapeOf { axis: 0 },
ExecOp::DynamicTruncate { axis: 0 },
ExecOp::PadToMatch { axis: 0 },
ExecOp::Constant {
dtype: DType::F64,
bytes: 1.0_f64.to_le_bytes().to_vec(),
},
ExecOp::DotGeneral(dot_config()),
ExecOp::DotGeneralWithConj {
config: dot_config(),
lhs_conj: true,
rhs_conj: false,
},
ExecOp::Extension(Arc::new(TestExtension)),
]
}
fn non_ffi_dispatch_cases() -> Vec<ExecOp> {
let mut cases = backend_dispatch_cases()
.into_iter()
.map(|(op, _)| op)
.collect::<Vec<_>>();
cases.extend([
ExecOp::ShapeOf { axis: 0 },
ExecOp::DynamicTruncate { axis: 0 },
ExecOp::PadToMatch { axis: 0 },
ExecOp::Constant {
dtype: DType::F64,
bytes: 1.0_f64.to_le_bytes().to_vec(),
},
]);
cases
}
fn non_host_dispatch_cases() -> Vec<ExecOp> {
let mut cases = backend_dispatch_cases()
.into_iter()
.map(|(op, _)| op)
.collect::<Vec<_>>();
cases.extend(ffi_dispatch_cases().into_iter().map(|(op, _)| op));
cases
}
fn dot_config() -> DotGeneralConfig {
DotGeneralConfig {
lhs_contracting_dims: [0].as_slice().into(),
rhs_contracting_dims: [0].as_slice().into(),
lhs_batch_dims: [].as_slice().into(),
rhs_batch_dims: [].as_slice().into(),
}
}
fn gather_config() -> GatherConfig {
GatherConfig {
offset_dims: vec![],
collapsed_slice_dims: vec![0],
start_index_map: vec![0],
index_vector_dim: 1,
slice_sizes: vec![1],
}
}
fn scatter_config() -> ScatterConfig {
ScatterConfig {
update_window_dims: vec![],
inserted_window_dims: vec![0],
scatter_dims_to_operand_dims: vec![0],
index_vector_dim: 1,
}
}
fn slice_config() -> SliceConfig {
SliceConfig {
starts: vec![0],
limits: vec![1],
strides: vec![1],
}
}
fn pad_config() -> PadConfig {
PadConfig {
edge_padding_low: vec![0],
edge_padding_high: vec![1],
interior_padding: vec![0],
}
}
#[derive(Clone, Debug)]
struct TestExtension;
impl ExtensionOp for TestExtension {
fn family_id(&self) -> &'static str {
"runtime.dispatch-test.v1"
}
fn payload_hash(&self, hasher: &mut dyn Hasher) {
hasher.write_u8(0);
}
fn payload_eq(&self, other: &dyn ExtensionOp) -> bool {
other.as_any().is::<Self>()
}
fn clone_arc(&self) -> Arc<dyn ExtensionOp> {
Arc::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn input_count(&self) -> usize {
1
}
fn output_count(&self) -> usize {
1
}
fn infer_output_meta(
&self,
ctx: &mut tenferro_ops::ExtensionShapeContext<'_>,
) -> tenferro_tensor::Result<Vec<(DType, Vec<SymDim>)>> {
Ok(vec![(ctx.input_dtype(0)?, ctx.input_shape(0)?.to_vec())])
}
}
#[derive(Debug)]
struct MetadataOnlyPreparedOperation {
binding: PreparedOperationBinding,
specialization: SpecializationProjection,
}
impl PreparedOperation for MetadataOnlyPreparedOperation {
fn binding(&self) -> &PreparedOperationBinding {
&self.binding
}
fn specialization(&self) -> &SpecializationProjection {
&self.specialization
}
fn retained_bytes(&self) -> usize {
0
}
}
fn metadata_only_prepared_operation() -> Arc<dyn PreparedOperation> {
Arc::new(MetadataOnlyPreparedOperation {
binding: PreparedOperationBinding::new(
RuntimeId::from_nonzero(NonZeroU64::new(1).expect("runtime id")),
RuntimeEpoch::from_nonzero(NonZeroU64::new(1).expect("runtime epoch")),
EngineId::new("tenferro-test.metadata-only-engine").expect("engine id"),
RegistrationIdentity::new(
NonZeroU64::new(1).expect("registration issuer"),
NonZeroU64::new(1).expect("registration ordinal"),
),
ExecutionContextIdentity::of::<CpuEngine>(),
HardwareClassId::new("tenferro-test.metadata-only-hardware").expect("hardware class"),
),
specialization: SpecializationRequirements::polymorphic(0)
.project(&InputSignature::new(Vec::new()))
.expect("empty specialization projection"),
})
}
fn assert_missing_extension_executor(error: crate::Error) {
let crate::Error::RuntimeStateSource { source, .. } = error else {
panic!("missing extension executor must retain a typed source");
};
let missing = source
.downcast_ref::<super::MissingPreparedOperationExecutorError>()
.expect("typed missing extension executor source");
assert_eq!(missing.family_id, TestExtension.family_id());
assert_eq!(missing.operation_index, 0);
}
#[test]
fn missing_extension_executor_reports_typed_fields_on_both_execution_paths() {
let instruction = ExecInstruction {
op: ExecOp::Extension(Arc::new(TestExtension)),
semantic_operation_index: Some(0),
input_slots: Vec::new(),
output_slots: Vec::new(),
dtype: DType::F64,
output_shapes: Vec::new().into_iter().collect(),
output_extents: Vec::new().into_iter().collect(),
last_use: Vec::new(),
input_views: Vec::new(),
};
let operations = vec![PreparedOperationPlan::metadata(
metadata_only_prepared_operation(),
)];
let mut caches = ExtensionCacheStore::new();
let mut engine = CpuEngine::serial();
let error = super::execute_prepared_extension_instruction(
&mut engine,
ExecutionContextIdentity::of::<CpuEngine>(),
&[],
&instruction,
&TestExtension,
&operations,
&mut caches,
)
.expect_err("metadata-only extension must not execute");
assert_missing_extension_executor(error);
let error = super::execute_prepared_extension_instruction_in_session(
&mut engine,
&[],
&instruction,
&TestExtension,
&operations,
&mut caches,
)
.expect_err("metadata-only extension must not execute in a session");
assert_missing_extension_executor(error);
}
#[test]
fn get_read_applies_a_folded_view_to_an_owned_slot() {
let tensor =
Tensor::from_vec_col_major(vec![2, 3], vec![1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
let slots = [Some(ExecSlot::Owned(tensor))];
let mut instruction = negate_instruction(vec![0]);
instruction.input_views = vec![(0, InputView::Transpose { perm: vec![1, 0] })];
let mut engine = CpuEngine::serial();
let read = super::get_read(&slots, &instruction, 0).unwrap();
assert_eq!(read.shape(), &[3, 2]);
let compact = tenferro_tensor::TensorStructural::to_contiguous_read(&mut engine, read).unwrap();
assert_eq!(
compact.as_slice::<f64>().unwrap(),
&[1.0, 3.0, 5.0, 2.0, 4.0, 6.0]
);
instruction.input_views.push((
0,
InputView::Slice(SliceConfig {
starts: vec![0, 0],
limits: vec![1, 2],
strides: vec![1, 1],
}),
));
let read = super::get_read(&slots, &instruction, 0).unwrap();
assert_eq!(read.shape(), &[1, 2]);
let compact = tenferro_tensor::TensorStructural::to_contiguous_read(&mut engine, read).unwrap();
assert_eq!(compact.as_slice::<f64>().unwrap(), &[1.0, 2.0]);
}
#[test]
fn owned_accessors_reject_a_folded_view_typed() {
let tensor = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 2.0, 3.0, 4.0]).unwrap();
let mut slots = [Some(ExecSlot::Owned(tensor))];
let mut instruction = negate_instruction(vec![0]);
instruction.input_views = vec![(0, InputView::Transpose { perm: vec![1, 0] })];
let mut engine = CpuEngine::serial();
assert!(matches!(
get(&slots, &instruction, 0),
Err(crate::Error::Internal(message)) if message.contains("folded view")
));
assert!(matches!(
super::ensure_owned(&mut engine, &mut slots, &instruction, 0),
Err(crate::Error::Internal(message)) if message.contains("folded view")
));
}
#[test]
fn input_view_definition_matches_the_view_ops_and_read_entries() {
for descriptor in all_primitive_descriptors() {
let op = ExecOp::sample_from_kind(descriptor.kind);
let is_view = matches!(
descriptor.kind,
PrimitiveOpKind::Transpose
| PrimitiveOpKind::Reshape
| PrimitiveOpKind::BroadcastInDim
| PrimitiveOpKind::Slice
);
assert_eq!(InputView::from_op(&op).is_some(), is_view, "{op:?}");
}
for entry in BACKEND_DISPATCH_TABLE {
let op = ExecOp::sample_from_kind(entry.key);
assert_eq!(
super::reads_input_view(&op, 0),
entry.key != PrimitiveOpKind::Convert,
"{:?}",
entry.key
);
}
assert!(super::reads_input_view(
&ExecOp::Extension(Arc::new(TestExtension)),
0
));
assert!(super::reads_input_view(&ExecOp::ShapeOf { axis: 0 }, 0));
for input in 0..2 {
assert!(super::reads_input_view(
&ExecOp::DynamicTruncate { axis: 0 },
input
));
assert!(super::reads_input_view(
&ExecOp::PadToMatch { axis: 0 },
input
));
}
assert!(!super::reads_input_view(
&ExecOp::Constant {
dtype: DType::F64,
bytes: 1.0_f64.to_le_bytes().to_vec(),
},
0
));
}
#[test]
fn validate_exec_program_rejects_a_view_on_a_missing_input() {
let mut program = empty_program(1);
program.n_slots = 2;
let mut instruction = negate_instruction(vec![0]);
instruction.output_slots = vec![1];
instruction.output_shapes = vec![vec![DimExpr::Const(1)]].into_iter().collect();
instruction.output_extents = vec![vec![ShapeExtent::exact(DimExpr::Const(1))]]
.into_iter()
.collect();
instruction.input_views = vec![(1, InputView::Transpose { perm: vec![0] })];
program.instructions.push(instruction);
let err = validate_exec_program(&program, "test").unwrap_err();
assert!(
err.to_string().contains("folds a view onto input 1"),
"{err}"
);
}