use super::{
Scalar, arg, as_dtype, as_i64, as_shape, as_usize, at_most, bad, by_dtype, charge, cost,
element_error, finish, finish_bytes, finish_slice, numel_of, opt_arg, strides_of, wrap,
};
use crate::arena::{ContextStack, DataValue, bvec};
use crate::{CompiledNode, Engine, Result};
use bumpalo::Bump;
use datavalue::DataTensor;
fn is_tagged(v: &DataValue<'_>) -> bool {
matches!(v, DataValue::Object([(k, _)]) if *k == DataTensor::JSON_TAG)
}
fn is_wire_body(v: &DataValue<'_>) -> bool {
matches!(v, DataValue::Object(pairs)
if pairs.iter().any(|(k, _)| *k == "dtype")
&& pairs.iter().all(|(k, _)| matches!(*k, "dtype" | "shape" | "data")))
}
fn decode_body<'a>(v: &DataValue<'a>, arena: &'a Bump) -> Result<DataTensor<'a>> {
let tagged = arena.alloc([(DataTensor::JSON_TAG, *v)]);
DataTensor::from_json_value_in(&DataValue::Object(&tagged[..]), arena).map_err(wrap)
}
pub(crate) fn evaluate_tensor<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
at_most(args, 2)?;
let v = arg(args, 0, ctx, engine, arena)?;
let t = if let DataValue::Tensor(t) = v {
**t
} else if is_tagged(v) {
DataTensor::from_json_value_in(v, arena).map_err(wrap)?
} else if is_wire_body(v) {
decode_body(v, arena)?
} else {
let dtype = as_dtype(opt_arg(args, 1, ctx, engine, arena)?.ok_or_else(|| {
bad("tensor: nested-array input needs a dtype, e.g. {\"tensor\": [[[1,2]], \"f32\"]}")
})?)?;
DataTensor::from_nested_in(v, dtype, arena).map_err(wrap)?
};
charge(ctx, t.numel() as u64)?;
finish(t, arena)
}
pub(crate) fn evaluate_zeros<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
at_most(args, 2)?;
let shape = as_shape(arg(args, 0, ctx, engine, arena)?, arena)?;
let dtype = as_dtype(arg(args, 1, ctx, engine, arena)?)?;
charge(ctx, numel_of(shape)? as u64)?;
let buf = DataTensor::zeroed_bytes_in(dtype, shape, arena).map_err(wrap)?;
finish_bytes(dtype, shape, buf, arena)
}
pub(crate) fn evaluate_full<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
at_most(args, 3)?;
let shape = as_shape(arg(args, 0, ctx, engine, arena)?, arena)?;
let dtype = as_dtype(arg(args, 1, ctx, engine, arena)?)?;
let value = arg(args, 2, ctx, engine, arena)?;
charge(ctx, numel_of(shape)? as u64)?;
by_dtype!(dtype, full_impl, shape, value, arena)
}
fn full_impl<'a, T: Scalar>(
shape: &'a [usize],
value: &DataValue<'_>,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let fill = T::from_value(value).ok_or_else(|| element_error(0, T::DTYPE))?;
let numel = numel_of(shape)?;
let mut data = bvec::<T>(arena, numel);
data.resize(numel, fill);
finish_slice(shape, data, arena)
}
pub(crate) fn evaluate_scatter<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
at_most(args, 4)?;
let points = arg(args, 0, ctx, engine, arena)?;
let shape = as_shape(arg(args, 1, ctx, engine, arena)?, arena)?;
let dtype = as_dtype(arg(args, 2, ctx, engine, arena)?)?;
let value = opt_arg(args, 3, ctx, engine, arena)?;
let DataValue::Array(points) = points else {
return Err(bad("scatter: points must be an array of coordinate arrays"));
};
charge(ctx, cost(points.len(), numel_of(shape)?))?;
by_dtype!(dtype, scatter_impl, points, shape, value, arena)
}
fn scatter_impl<'a, T: Scalar>(
points: &[DataValue<'_>],
shape: &'a [usize],
value: Option<&DataValue<'_>>,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let default = match value {
None => T::ONE,
Some(v) => T::from_value(v).ok_or_else(|| element_error(0, T::DTYPE))?,
};
let rank = shape.len();
let strides = strides_of(shape, arena);
let numel = numel_of(shape)?;
let mut data = bvec::<T>(arena, numel);
data.resize(numel, T::ZERO);
for (n, point) in points.iter().enumerate() {
let DataValue::Array(coords) = point else {
return Err(bad("scatter: each point must be an array"));
};
let (coords, written) = match coords.len() {
l if l == rank => (&coords[..], default),
l if l == rank + 1 => (
&coords[..rank],
T::from_value(&coords[rank]).ok_or_else(|| element_error(n, T::DTYPE))?,
),
_ => {
return Err(bad(
"scatter: point length must match the rank, or rank + 1",
));
}
};
let mut offset = 0usize;
let mut inside = true;
for (ax, c) in coords.iter().enumerate() {
let c = as_i64(c)?;
if c < 0 || c as usize >= shape[ax] {
inside = false;
break;
}
offset += c as usize * strides[ax];
}
if inside {
data[offset] = written;
}
}
finish_slice(shape, data, arena)
}
pub(crate) fn evaluate_rle_expand<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
at_most(args, 3)?;
let runs = arg(args, 0, ctx, engine, arena)?;
let shape = as_shape(arg(args, 1, ctx, engine, arena)?, arena)?;
let dtype = as_dtype(arg(args, 2, ctx, engine, arena)?)?;
let DataValue::Array(runs) = runs else {
return Err(bad(
"rle_expand: runs must be a flat [value, count, …] array",
));
};
if !runs.len().is_multiple_of(2) {
return Err(bad("rle_expand: runs must have an even length"));
}
charge(ctx, cost(runs.len(), numel_of(shape)?))?;
by_dtype!(dtype, rle_impl, runs, shape, arena)
}
fn rle_impl<'a, T: Scalar>(
runs: &[DataValue<'_>],
shape: &'a [usize],
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let numel = numel_of(shape)?;
let mut data = bvec::<T>(arena, numel);
for (pair, chunk) in runs.as_chunks::<2>().0.iter().enumerate() {
let value = T::from_value(&chunk[0]).ok_or_else(|| element_error(pair, T::DTYPE))?;
let count = as_usize(&chunk[1])?;
if data.len() + count > numel {
return Err(bad(
"rle_expand: runs decode to more elements than the shape holds",
));
}
data.resize(data.len() + count, value);
}
if data.len() != numel {
return Err(bad(
"rle_expand: runs decode to fewer elements than the shape holds",
));
}
finish_slice(shape, data, arena)
}
pub(crate) fn evaluate_one_hot<'a>(
args: &'a [CompiledNode],
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
at_most(args, 3)?;
let indices = arg(args, 0, ctx, engine, arena)?;
let depth = as_usize(arg(args, 1, ctx, engine, arena)?)?;
let dtype = as_dtype(arg(args, 2, ctx, engine, arena)?)?;
let DataValue::Array(indices) = indices else {
return Err(bad("one_hot: indices must be an array"));
};
let shape = arena.alloc_slice_copy(&[indices.len(), depth]);
charge(ctx, numel_of(shape)? as u64)?;
by_dtype!(dtype, one_hot_impl, indices, shape, arena)
}
fn one_hot_impl<'a, T: Scalar>(
indices: &[DataValue<'_>],
shape: &'a [usize],
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let depth = shape[1];
let numel = numel_of(shape)?;
let mut data = bvec::<T>(arena, numel);
data.resize(numel, T::ZERO);
for (row, idx) in indices.iter().enumerate() {
let i = as_i64(idx)?;
if i >= 0 && (i as usize) < depth {
data[row * depth + i as usize] = T::ONE;
}
}
finish_slice(shape, data, arena)
}