use super::{IrVisitorMut, OperationIr, ScalarIr, TensorId, TensorIr, TensorStatus};
use crate::{ExecutionError, Slice};
use alloc::{collections::{BTreeMap, BTreeSet}, vec, vec::Vec};
use ruda_core::backtrace::BackTrace;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct GraphId(pub u64);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GraphIr {
pub operations: Vec<OperationIr>,
pub inputs: Vec<TensorId>,
pub outputs: Vec<TensorId>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphBindings {
pub tensors: Vec<(TensorId, TensorId)>,
pub shapes: Vec<usize>,
pub scalars: Vec<ScalarIr>,
pub ranges: Vec<Slice>,
}
impl GraphIr {
pub fn capture(mut operations: Vec<OperationIr>) -> (Self, GraphBindings) {
let (inputs, outputs) = Self::classify(&operations);
let mut normalizer = Normalizer {
tensors: BTreeMap::new(),
shapes: BTreeMap::from([(1, 0)]),
bindings: GraphBindings { tensors: Vec::new(), shapes: vec![1], scalars: Vec::new(), ranges: Vec::new() },
};
for operation in &mut operations { operation.visit_mut(&mut normalizer); }
let inputs: Vec<_> = inputs.into_iter().map(|id| {
let relative = normalizer.tensors[&id];
normalizer.bindings.tensors.push((relative, id));
relative
}).collect();
let outputs: Vec<_> = outputs.into_iter().map(|id| {
let relative = normalizer.tensors[&id];
normalizer.bindings.tensors.push((relative, id));
relative
}).collect();
(Self { operations, inputs, outputs }, normalizer.bindings)
}
pub fn classify(operations: &[OperationIr]) -> (Vec<TensorId>, Vec<TensorId>) {
let mut referenced = Vec::new();
let mut referenced_set = BTreeSet::new();
let mut produced = Vec::new();
let mut produced_set = BTreeSet::new();
let mut consumed = BTreeSet::new();
for operation in operations {
if let OperationIr::Drop(tensor) = operation { consumed.insert(tensor.id); }
if !matches!(operation, OperationIr::Init(_)) {
for tensor in operation.outputs() {
if produced_set.insert(tensor.id) { produced.push(tensor.id); }
}
}
for tensor in operation.inputs().chain(operation.outputs()) {
if referenced_set.insert(tensor.id) { referenced.push(tensor.id); }
if tensor.status == TensorStatus::ReadWrite { consumed.insert(tensor.id); }
}
}
let inputs = referenced.into_iter().filter(|id| !produced_set.contains(id)).collect();
let outputs = produced.into_iter().filter(|id| !consumed.contains(id)).collect();
(inputs, outputs)
}
pub fn bindings_for(&self, operations: Vec<OperationIr>) -> Result<GraphBindings, ExecutionError> {
let (graph, bindings) = Self::capture(operations);
if &graph != self {
return Err(ExecutionError::Generic { reason: "graph structure, dtype, status or static options changed".into(), backtrace: BackTrace::capture() });
}
Ok(bindings)
}
pub fn len(&self) -> usize { self.operations.len() }
pub fn is_empty(&self) -> bool { self.operations.is_empty() }
}
struct Normalizer {
tensors: BTreeMap<TensorId, TensorId>,
shapes: BTreeMap<usize, usize>,
bindings: GraphBindings,
}
impl IrVisitorMut for Normalizer {
fn visit_tensor_mut(&mut self, tensor: &mut TensorIr) {
let next = TensorId::new(self.tensors.len() as u64);
tensor.id = *self.tensors.entry(tensor.id).or_insert(next);
for dimension in tensor.shape.iter_mut() {
let next = self.bindings.shapes.len();
*dimension = *self.shapes.entry(*dimension).or_insert_with(|| {
self.bindings.shapes.push(*dimension);
next
});
}
}
fn visit_scalar_mut(&mut self, scalar: &mut ScalarIr) {
let next = self.bindings.scalars.len() as u64;
self.bindings.scalars.push(*scalar);
*scalar = ScalarIr::UInt(next);
}
fn visit_range_mut(&mut self, range: &mut Slice) {
let next = self.bindings.ranges.len();
self.bindings.ranges.push(*range);
*range = Slice::from(next..next + 1);
}
}