use onnx_runtime_ep_api::{EpError, Kernel, KernelFactory, Result, TensorMut, TensorView};
use onnx_runtime_ir::{compute_contiguous_strides, Node};
use super::{check_arity, elem_size, to_dense_bytes, to_dense_i64};
use crate::strided::{next_index, numel};
pub struct SliceKernel;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SliceAxisPlan {
pub start: i64,
pub step: i64,
pub count: usize,
}
pub fn slice_plan(
in_shape: &[usize],
starts: &[i64],
ends: &[i64],
axes: &[i64],
steps: &[i64],
) -> Result<Vec<SliceAxisPlan>> {
let rank = in_shape.len();
if starts.len() != ends.len() || starts.len() != axes.len() || starts.len() != steps.len() {
return Err(EpError::KernelFailed(
"Slice: starts/ends/axes/steps length mismatch".into(),
));
}
let mut plan = vec![
SliceAxisPlan {
start: 0,
step: 1,
count: 0,
};
rank
];
for (ax, p) in plan.iter_mut().enumerate() {
p.count = in_shape[ax];
}
for i in 0..starts.len() {
let raw_axis = axes[i];
let ax = if raw_axis < 0 {
raw_axis + rank as i64
} else {
raw_axis
};
if ax < 0 || ax as usize >= rank {
return Err(EpError::KernelFailed(format!(
"Slice: axis {raw_axis} out of range for rank {rank}"
)));
}
let ax = ax as usize;
let d = in_shape[ax] as i64;
let s = steps[i];
if s == 0 {
return Err(EpError::KernelFailed("Slice: step must be non-zero".into()));
}
let mut b = starts[i];
let mut e = ends[i];
if b < 0 {
b += d;
}
if e < 0 {
e += d;
}
let (b, e) = if s < 0 {
(b.clamp(0, d - 1), e.clamp(-1, d - 1))
} else {
(b.clamp(0, d), e.clamp(0, d))
};
let c = if s > 0 {
if e > b {
((e - b + s - 1) / s) as usize
} else {
0
}
} else if b > e {
((b - e + (-s) - 1) / (-s)) as usize
} else {
0
};
plan[ax] = SliceAxisPlan {
start: b,
step: s,
count: c,
};
}
Ok(plan)
}
pub fn slice_axes_steps(
starts_len: usize,
axes: Option<&[i64]>,
steps: Option<&[i64]>,
) -> (Vec<i64>, Vec<i64>) {
let axes = match axes {
Some(a) => a.to_vec(),
None => (0..starts_len as i64).collect(),
};
let steps = match steps {
Some(s) => s.to_vec(),
None => vec![1; starts_len],
};
(axes, steps)
}
pub struct SliceFactory;
impl KernelFactory for SliceFactory {
fn create(&self, _node: &Node, _shapes: &[Vec<usize>]) -> Result<Box<dyn Kernel>> {
Ok(Box::new(SliceKernel))
}
}
impl Kernel for SliceKernel {
fn execute(&self, inputs: &[TensorView], outputs: &mut [TensorMut]) -> Result<()> {
check_arity("Slice", inputs, outputs, 3, 5, 1)?;
let esize = elem_size(inputs[0].dtype)?;
let src = to_dense_bytes(&inputs[0])?;
let in_shape = inputs[0].shape;
let rank = in_shape.len();
let starts = to_dense_i64(&inputs[1])?;
let ends = to_dense_i64(&inputs[2])?;
let axes_in = if inputs.len() >= 4 {
Some(to_dense_i64(&inputs[3])?)
} else {
None
};
let steps_in = if inputs.len() >= 5 {
Some(to_dense_i64(&inputs[4])?)
} else {
None
};
let (axes, steps) =
slice_axes_steps(starts.len(), axes_in.as_deref(), steps_in.as_deref());
let plan = slice_plan(in_shape, &starts, &ends, &axes, &steps)?;
let start: Vec<i64> = plan.iter().map(|p| p.start).collect();
let step: Vec<i64> = plan.iter().map(|p| p.step).collect();
let count: Vec<usize> = plan.iter().map(|p| p.count).collect();
let out_shape = count;
let in_strides = compute_contiguous_strides(in_shape);
let n = numel(&out_shape);
let mut out = vec![0u8; n * esize];
if n > 0 {
let mut idx = vec![0usize; rank];
let mut w = 0usize;
loop {
let mut in_off = 0i64;
for d in 0..rank {
let coord = start[d] + step[d] * idx[d] as i64;
in_off += in_strides[d] * coord;
}
let s = in_off as usize * esize;
out[w..w + esize].copy_from_slice(&src[s..s + esize]);
w += esize;
if !next_index(&out_shape, &mut idx) {
break;
}
}
}
super::write_dense_bytes(&mut outputs[0], &out)
}
fn supports_strided_input(&self, _input_idx: usize) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernels::testutil::Owned;
#[test]
fn slice_plan_matches_reference_geometry() {
let plan = slice_plan(&[4, 2], &[1], &[3], &[0], &[1]).unwrap();
assert_eq!(
plan,
vec![
SliceAxisPlan { start: 1, step: 1, count: 2 },
SliceAxisPlan { start: 0, step: 1, count: 2 },
]
);
let rev = slice_plan(&[5], &[4], &[-6], &[0], &[-1]).unwrap();
assert_eq!(rev, vec![SliceAxisPlan { start: 4, step: -1, count: 5 }]);
assert!(slice_plan(&[5], &[0], &[5], &[0], &[0]).is_err());
assert!(slice_plan(&[5], &[0, 1], &[5], &[0], &[1]).is_err());
}
#[test]
fn slice_basic_axis0() {
let data = Owned::f32(&[4, 2], &[1., 2., 3., 4., 5., 6., 7., 8.]);
let starts = Owned::i64(&[1], &[1]);
let ends = Owned::i64(&[1], &[3]);
let axes = Owned::i64(&[1], &[0]);
let mut out = Owned::zeros_f32(&[2, 2]);
SliceKernel
.execute(
&[data.view(), starts.view(), ends.view(), axes.view()],
&mut [out.view_mut()],
)
.unwrap();
assert_eq!(out.to_f32(), vec![3., 4., 5., 6.]);
}
#[test]
fn slice_negative_indices() {
let data = Owned::f32(&[5], &[1., 2., 3., 4., 5.]);
let starts = Owned::i64(&[1], &[-3]);
let ends = Owned::i64(&[1], &[-1]);
let mut out = Owned::zeros_f32(&[2]);
SliceKernel
.execute(
&[data.view(), starts.view(), ends.view()],
&mut [out.view_mut()],
)
.unwrap();
assert_eq!(out.to_f32(), vec![3., 4.]);
}
#[test]
fn slice_step_two() {
let data = Owned::f32(&[6], &[1., 2., 3., 4., 5., 6.]);
let starts = Owned::i64(&[1], &[0]);
let ends = Owned::i64(&[1], &[6]);
let axes = Owned::i64(&[1], &[0]);
let steps = Owned::i64(&[1], &[2]);
let mut out = Owned::zeros_f32(&[3]);
SliceKernel
.execute(
&[
data.view(),
starts.view(),
ends.view(),
axes.view(),
steps.view(),
],
&mut [out.view_mut()],
)
.unwrap();
assert_eq!(out.to_f32(), vec![1., 3., 5.]);
}
#[test]
fn slice_negative_step_reverse() {
let data = Owned::f32(&[5], &[1., 2., 3., 4., 5.]);
let starts = Owned::i64(&[1], &[4]);
let ends = Owned::i64(&[1], &[-6]);
let axes = Owned::i64(&[1], &[0]);
let steps = Owned::i64(&[1], &[-1]);
let mut out = Owned::zeros_f32(&[5]);
SliceKernel
.execute(
&[
data.view(),
starts.view(),
ends.view(),
axes.view(),
steps.view(),
],
&mut [out.view_mut()],
)
.unwrap();
assert_eq!(out.to_f32(), vec![5., 4., 3., 2., 1.]);
}
#[test]
fn slice_int64_data_multi_axis() {
let data = Owned::i64(&[3, 3], &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
let starts = Owned::i64(&[2], &[0, 1]);
let ends = Owned::i64(&[2], &[2, 3]);
let axes = Owned::i64(&[2], &[0, 1]);
let mut out = Owned::zeros(onnx_runtime_ir::DataType::Int64, &[2, 2]);
SliceKernel
.execute(
&[data.view(), starts.view(), ends.view(), axes.view()],
&mut [out.view_mut()],
)
.unwrap();
assert_eq!(out.to_i64(), vec![2, 3, 5, 6]);
}
}