tenferro-runtime 0.1.0

Concrete tensor helpers, traced tensors, graph execution, and extension runtime support for tenferro.
Documentation
use tenferro_cpu::CpuBackend;
use tenferro_tensor::{DType, Tensor, TensorRead, TensorValue, TypedTensor};

use super::GraphExecutor;
use crate::{Error, GraphCompiler, TracedTensor};

#[test]
fn borrowed_input_execution_retains_executor_slot_workspace_capacity() {
    let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
    let y = (&x + &x).unwrap();
    let mut compiler = GraphCompiler::new();
    let program = compiler
        .compile_with_input_specs(&y, &[(&x, DType::F64, &[4])])
        .unwrap();
    let input =
        Tensor::F64(TypedTensor::from_vec_col_major(vec![4], vec![1.0, 2.0, 3.0, 4.0]).unwrap());
    let mut executor = GraphExecutor::new(CpuBackend::new());

    assert_eq!(executor.borrowed_slot_workspace_capacity, 0);

    let outputs = executor
        .run_many_with_input_reads(&program, &[(&x, TensorRead::from_tensor(&input))])
        .unwrap();

    assert_eq!(outputs[0].as_slice::<f64>().unwrap(), &[2.0, 4.0, 6.0, 8.0]);
    assert_eq!(input.as_slice::<f64>().unwrap(), &[1.0, 2.0, 3.0, 4.0]);
    assert!(
        executor.borrowed_slot_workspace_capacity >= program.exec.n_slots,
        "borrowed execution should retain reusable slot capacity; capacity={}, n_slots={}",
        executor.borrowed_slot_workspace_capacity,
        program.exec.n_slots
    );
}

#[test]
fn borrowed_input_value_execution_retains_workspace_and_lazy_output() {
    let x = TracedTensor::input_symbolic_shape(DType::F64, 2).unwrap();
    let y = x.transpose(&[1, 0]).unwrap();
    let mut compiler = GraphCompiler::new();
    let program = compiler
        .compile_with_input_specs(&y, &[(&x, DType::F64, &[2, 2])])
        .unwrap();
    let input =
        Tensor::F64(TypedTensor::from_vec_col_major(vec![2, 2], vec![1.0, 2.0, 3.0, 4.0]).unwrap());
    let mut executor = GraphExecutor::new(CpuBackend::new());

    let outputs = executor
        .run_many_values_with_input_reads(&program, &[(&x, TensorRead::from_tensor(&input))])
        .unwrap();

    assert!(matches!(outputs[0], TensorValue::View(_)));
    assert_eq!(input.as_slice::<f64>().unwrap(), &[1.0, 2.0, 3.0, 4.0]);
    assert!(
        executor.borrowed_slot_workspace_capacity >= program.exec.n_slots,
        "borrowed value execution should retain reusable slot capacity; capacity={}, n_slots={}",
        executor.borrowed_slot_workspace_capacity,
        program.exec.n_slots
    );
}

#[test]
fn borrowed_input_workspace_does_not_retype_static_slot_vec() {
    let source = include_str!("../executor.rs");

    assert!(
        !source.contains("Vec::from_raw_parts"),
        "borrowed input workspace must not retype Vec allocations across ExecSlot lifetimes"
    );
    assert!(
        !source.contains("BorrowedSlotWorkspace"),
        "borrowed input execution should use a lifetime-local workspace"
    );
}

#[test]
fn compile_with_input_specs_rejects_computed_placeholder_specs() {
    let x = TracedTensor::input_symbolic_shape(DType::F64, 1).unwrap();
    let y = (&x + &x).unwrap();
    let mut compiler = GraphCompiler::new();

    let err = compiler
        .compile_with_input_specs(&y, &[(&y, DType::F64, &[2])])
        .unwrap_err();

    assert!(matches!(err, Error::UnexpectedBinding { binding_index: 0 }));
}