#![doc(hidden)]
use strided_kernel::{
erased_map_into, erased_zip_into, ErasedMapOp, ErasedZipOp, Exec, KernelDType,
};
use tenferro_tensor::backend::{validate_read_into_destination, ElementwiseReadOp};
use tenferro_tensor::{
DType, Tensor, TensorRead, TensorView, TensorViewMut, TensorWrite, TypedTensorView,
TypedTensorViewMut,
};
use tinyvec::TinyVec;
use crate::Error;
use tenferro_cpu_basic::{erased_raw_strided_mut, erased_raw_strided_ptr};
fn validate_elementwise_output_disjoint(
op: ElementwiseReadOp,
inputs: &[TensorRead<'_>],
out: &TensorWrite<'_>,
) -> crate::Result<()> {
validate_read_into_destination(op.label(), inputs, out)
}
fn read_is_host(input: &TensorRead<'_>) -> bool {
match input {
TensorRead::Tensor(tensor) => !tensor.is_backend_buffer(),
TensorRead::View(view) => match view {
TensorView::F32(view) => view.backend_buffer().is_none(),
TensorView::F64(view) => view.backend_buffer().is_none(),
TensorView::I32(view) => view.backend_buffer().is_none(),
TensorView::I64(view) => view.backend_buffer().is_none(),
TensorView::Bool(view) => view.backend_buffer().is_none(),
TensorView::C32(view) => view.backend_buffer().is_none(),
TensorView::C64(view) => view.backend_buffer().is_none(),
},
}
}
fn write_is_host(out: &TensorWrite<'_>) -> bool {
read_is_host(&out.as_read())
}
fn one_shot_supports(op: ElementwiseReadOp, dtype: DType) -> bool {
match op {
ElementwiseReadOp::Conj => true,
ElementwiseReadOp::Add
| ElementwiseReadOp::Subtract
| ElementwiseReadOp::Multiply
| ElementwiseReadOp::Divide
| ElementwiseReadOp::Negate => !matches!(dtype, DType::Bool),
_ => false,
}
}
fn one_shot_eligible(
op: ElementwiseReadOp,
inputs: &[TensorRead<'_>],
out: &TensorWrite<'_>,
) -> bool {
let dtype = out.dtype();
write_is_host(out)
&& one_shot_supports(op, dtype)
&& inputs.iter().all(|input| {
read_is_host(input) && input.dtype() == dtype && input.shape() == out.shape()
})
}
fn write_typed<T: tenferro_tensor::TensorScalar>(
tensor: &mut Tensor,
) -> crate::Result<&mut tenferro_tensor::TypedTensor<T>> {
let dtype = tensor.dtype();
tensor.as_typed_mut::<T>().ok_or_else(|| {
crate::Error::unsupported_dtype(
"tensor_write_view",
dtype,
"the write view covers the preset scalars",
)
})
}
fn tensor_write_view(out: TensorWrite<'_>) -> crate::Result<TensorViewMut<'_>> {
match out {
TensorWrite::Tensor(tensor) => {
let dtype = tensor.dtype();
match dtype {
DType::F32 => Ok(TensorViewMut::F32(
write_typed::<f32>(tensor)?.as_view_mut(),
)),
DType::F64 => Ok(TensorViewMut::F64(
write_typed::<f64>(tensor)?.as_view_mut(),
)),
DType::I32 => Ok(TensorViewMut::I32(
write_typed::<i32>(tensor)?.as_view_mut(),
)),
DType::I64 => Ok(TensorViewMut::I64(
write_typed::<i64>(tensor)?.as_view_mut(),
)),
DType::Bool => Ok(TensorViewMut::Bool(
write_typed::<bool>(tensor)?.as_view_mut(),
)),
DType::C32 => Ok(TensorViewMut::C32(
write_typed::<tenferro_tensor::Complex32>(tensor)?.as_view_mut(),
)),
DType::C64 => Ok(TensorViewMut::C64(
write_typed::<tenferro_tensor::Complex64>(tensor)?.as_view_mut(),
)),
DType::External(..) => Err(crate::Error::unsupported_dtype(
"tensor_write_view",
dtype,
"the write view covers the preset scalars",
)),
}
}
TensorWrite::View(view) => Ok(view),
}
}
fn typed_bytes<T>(data: &[T]) -> &[u8] {
unsafe { std::slice::from_raw_parts(data.as_ptr().cast(), std::mem::size_of_val(data)) }
}
fn typed_bytes_mut<T>(data: &mut [T]) -> &mut [u8] {
let len = std::mem::size_of_val(data);
unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr().cast(), len) }
}
fn execute_one_shot_map<T: 'static>(
dtype: KernelDType,
op: ErasedMapOp,
input: TypedTensorView<'_, T>,
mut out: TypedTensorViewMut<'_, T>,
exec: Exec<'_>,
) -> crate::Result<()> {
let input_data = input.host_storage()?;
let input_descriptor = unsafe {
erased_raw_strided_ptr(
dtype,
typed_bytes(input_data),
input.shape(),
input.strides(),
input.offset(),
)
}
.map_err(|error| Error::backend_source("elementwise_read_into", error))?;
let out_dims = TinyVec::<[usize; 8]>::from(out.shape());
let out_strides = TinyVec::<[isize; 8]>::from(out.strides());
let out_offset = out.offset();
let out_data = out.host_storage_mut()?;
let mut out_descriptor = unsafe {
erased_raw_strided_mut(
dtype,
typed_bytes_mut(out_data),
&out_dims,
&out_strides,
out_offset,
)
}
.map_err(|error| Error::backend_source("elementwise_read_into", error))?;
erased_map_into(dtype, op, &mut out_descriptor, &input_descriptor, exec)
.map_err(|error| Error::backend_source("elementwise_read_into", error))
}
fn execute_one_shot_zip<T: 'static>(
dtype: KernelDType,
op: ErasedZipOp,
lhs: TypedTensorView<'_, T>,
rhs: TypedTensorView<'_, T>,
mut out: TypedTensorViewMut<'_, T>,
exec: Exec<'_>,
) -> crate::Result<()> {
let lhs_data = lhs.host_storage()?;
let lhs_descriptor = unsafe {
erased_raw_strided_ptr(
dtype,
typed_bytes(lhs_data),
lhs.shape(),
lhs.strides(),
lhs.offset(),
)
}
.map_err(|error| Error::backend_source("elementwise_read_into", error))?;
let rhs_data = rhs.host_storage()?;
let rhs_descriptor = unsafe {
erased_raw_strided_ptr(
dtype,
typed_bytes(rhs_data),
rhs.shape(),
rhs.strides(),
rhs.offset(),
)
}
.map_err(|error| Error::backend_source("elementwise_read_into", error))?;
let out_dims = TinyVec::<[usize; 8]>::from(out.shape());
let out_strides = TinyVec::<[isize; 8]>::from(out.strides());
let out_offset = out.offset();
let out_data = out.host_storage_mut()?;
let mut out_descriptor = unsafe {
erased_raw_strided_mut(
dtype,
typed_bytes_mut(out_data),
&out_dims,
&out_strides,
out_offset,
)
}
.map_err(|error| Error::backend_source("elementwise_read_into", error))?;
erased_zip_into(
dtype,
op,
&mut out_descriptor,
&lhs_descriptor,
&rhs_descriptor,
exec,
)
.map_err(|error| Error::backend_source("elementwise_read_into", error))
}
fn execute_one_shot_elementwise(
op: ElementwiseReadOp,
inputs: &[TensorRead<'_>],
out: TensorWrite<'_>,
exec: Exec<'_>,
) -> crate::Result<()> {
let out = tensor_write_view(out)?;
macro_rules! dispatch_map {
($map_op:expr) => {{
let input = inputs[0].clone().tensor_view();
match (input, out) {
(TensorView::F32(input), TensorViewMut::F32(out)) => {
execute_one_shot_map(KernelDType::F32, $map_op, input, out, exec)
}
(TensorView::F64(input), TensorViewMut::F64(out)) => {
execute_one_shot_map(KernelDType::F64, $map_op, input, out, exec)
}
(TensorView::I32(input), TensorViewMut::I32(out)) => {
execute_one_shot_map(KernelDType::I32, $map_op, input, out, exec)
}
(TensorView::I64(input), TensorViewMut::I64(out)) => {
execute_one_shot_map(KernelDType::I64, $map_op, input, out, exec)
}
(TensorView::Bool(input), TensorViewMut::Bool(out)) => {
execute_one_shot_map(KernelDType::Bool, $map_op, input, out, exec)
}
(TensorView::C32(input), TensorViewMut::C32(out)) => {
execute_one_shot_map(KernelDType::C32, $map_op, input, out, exec)
}
(TensorView::C64(input), TensorViewMut::C64(out)) => {
execute_one_shot_map(KernelDType::C64, $map_op, input, out, exec)
}
_ => unreachable!("one-shot eligibility validates matching dtypes"),
}
}};
}
macro_rules! dispatch_zip {
($zip_op:expr) => {{
let lhs = inputs[0].clone().tensor_view();
let rhs = inputs[1].clone().tensor_view();
match (lhs, rhs, out) {
(TensorView::F32(lhs), TensorView::F32(rhs), TensorViewMut::F32(out)) => {
execute_one_shot_zip(KernelDType::F32, $zip_op, lhs, rhs, out, exec)
}
(TensorView::F64(lhs), TensorView::F64(rhs), TensorViewMut::F64(out)) => {
execute_one_shot_zip(KernelDType::F64, $zip_op, lhs, rhs, out, exec)
}
(TensorView::I32(lhs), TensorView::I32(rhs), TensorViewMut::I32(out)) => {
execute_one_shot_zip(KernelDType::I32, $zip_op, lhs, rhs, out, exec)
}
(TensorView::I64(lhs), TensorView::I64(rhs), TensorViewMut::I64(out)) => {
execute_one_shot_zip(KernelDType::I64, $zip_op, lhs, rhs, out, exec)
}
(TensorView::C32(lhs), TensorView::C32(rhs), TensorViewMut::C32(out)) => {
execute_one_shot_zip(KernelDType::C32, $zip_op, lhs, rhs, out, exec)
}
(TensorView::C64(lhs), TensorView::C64(rhs), TensorViewMut::C64(out)) => {
execute_one_shot_zip(KernelDType::C64, $zip_op, lhs, rhs, out, exec)
}
_ => unreachable!("one-shot eligibility validates matching dtypes"),
}
}};
}
match op {
ElementwiseReadOp::Add => dispatch_zip!(ErasedZipOp::Add),
ElementwiseReadOp::Subtract => dispatch_zip!(ErasedZipOp::Subtract),
ElementwiseReadOp::Multiply => dispatch_zip!(ErasedZipOp::Multiply),
ElementwiseReadOp::Divide => dispatch_zip!(ErasedZipOp::Divide),
ElementwiseReadOp::Negate => dispatch_map!(ErasedMapOp::Negate),
ElementwiseReadOp::Conj => dispatch_map!(ErasedMapOp::Conj),
_ => Err(Error::unsupported(
"elementwise_read_into",
"unsupported operation",
)),
}
}
#[doc(hidden)]
pub fn elementwise_read_into_with_exec(
op: ElementwiseReadOp,
inputs: &[TensorRead<'_>],
out: TensorWrite<'_>,
fallback: impl FnOnce(&[TensorRead<'_>], TensorWrite<'_>, Exec<'_>) -> crate::Result<()>,
exec: Exec<'_>,
) -> crate::Result<()> {
if inputs.len() != op.arity() {
return Err(Error::invalid_argument(
op.label(),
"inputs",
format!("expected {} inputs, got {}", op.arity(), inputs.len()),
));
}
validate_elementwise_output_disjoint(op, inputs, &out)?;
if one_shot_eligible(op, inputs, &out) {
execute_one_shot_elementwise(op, inputs, out, exec)
} else {
fallback(inputs, out, exec)
}
}
#[cfg(test)]
#[path = "read_into/tests.rs"]
mod tests;