use std::collections::HashMap;
use crate::Layout;
use crate::tensor::backend::Backend;
use crate::tensor::graph::NodeKind;
use crate::tensor::ops::def_op::OpKind;
use crate::tensor::planner::get_id;
pub(crate) struct Slot {
pub(crate) id: usize,
pub(crate) len: usize,
pub(crate) end: Option<usize>,
}
#[inline]
fn find_slot(slots: &[Slot], op_start: usize, len: usize) -> Option<usize> {
for (i, slot) in slots.iter().enumerate() {
if slot.len == len && slot.end.is_some_and(|e| e < op_start) {
return Some(i);
}
}
None
}
#[inline]
fn slot_is_free(slot: &Slot, op_location: usize, required_len: usize) -> bool {
slot.end
.is_some_and(|e| e < op_location && slot.len == required_len)
}
#[inline]
fn slot_is_last_read(slot: &Slot, op_location: usize, required_len: usize) -> bool {
slot.end == Some(op_location) && slot.len == required_len
}
#[inline]
fn assign_slot(slots: &[Slot], op_location: usize, output_layout: &Layout) -> ExecKind {
let slot = find_slot(slots, op_location, output_layout.len());
slot.map_or(ExecKind::Allocate, |slot| ExecKind::UseSlot {
slot_idx: slot,
})
}
pub(crate) enum ExecKind {
Allocate,
UseSlot { slot_idx: usize },
InPlace { slot_idx: usize, input_idx: usize },
ReferenceEternal { input_idx: usize },
ReferenceSlot { slot_idx: usize, input_idx: usize },
}
#[inline]
pub(crate) fn classify<T, B: Backend>(
op: &OpKind<T>,
inputs: &[&NodeKind<T, B>],
output_layout: &Layout,
op_location: usize,
slots: &[Slot],
id_slot_map: &HashMap<usize, usize>,
) -> ExecKind {
match op {
OpKind::Slice(_)
| OpKind::View(_)
| OpKind::Transpose
| OpKind::TransposeAxes(_)
| OpKind::Broadcast(_)
| OpKind::NoOp => match &inputs[0] {
NodeKind::Node(n) => match id_slot_map.get(&n.id) {
Some(slot_idx) => ExecKind::ReferenceSlot {
slot_idx: *slot_idx,
input_idx: 0,
},
None => ExecKind::ReferenceEternal { input_idx: 0 },
},
NodeKind::Baked(c) => match id_slot_map.get(&c.id) {
Some(slot_idx) => ExecKind::ReferenceSlot {
slot_idx: *slot_idx,
input_idx: 0,
},
None => ExecKind::ReferenceEternal { input_idx: 0 },
},
NodeKind::Cache(_) | NodeKind::Edge(_) | NodeKind::Slot(_) => {
ExecKind::ReferenceEternal { input_idx: 0 }
}
},
OpKind::AsContiguous => match &inputs[0] {
NodeKind::Node(n) => {
if n.layout().is_contiguous() {
match id_slot_map.get(&n.id) {
Some(slot_idx) => ExecKind::ReferenceSlot {
slot_idx: *slot_idx,
input_idx: 0,
},
None => ExecKind::ReferenceEternal { input_idx: 0 },
}
} else {
id_slot_map
.get(&n.id)
.filter(|&&s| slot_is_free(&slots[s], op_location, output_layout.len()))
.copied()
.map_or(assign_slot(slots, op_location, output_layout), |slot_idx| {
ExecKind::InPlace {
slot_idx,
input_idx: 0,
}
})
}
}
NodeKind::Cache(c) => {
let n = c.get_node();
if n.layout().is_contiguous() {
match id_slot_map.get(&n.id) {
Some(slot_idx) => ExecKind::ReferenceSlot {
slot_idx: *slot_idx,
input_idx: 0,
},
None => ExecKind::ReferenceEternal { input_idx: 0 },
}
} else {
id_slot_map
.get(&n.id)
.filter(|&&s| slot_is_free(&slots[s], op_location, output_layout.len()))
.copied()
.map_or(assign_slot(slots, op_location, output_layout), |slot_idx| {
ExecKind::InPlace {
slot_idx,
input_idx: 0,
}
})
}
}
NodeKind::Baked(c) => {
if c.layout().is_contiguous() {
match id_slot_map.get(&c.id) {
Some(slot_idx) => ExecKind::ReferenceSlot {
slot_idx: *slot_idx,
input_idx: 0,
},
None => ExecKind::ReferenceEternal { input_idx: 0 },
}
} else {
id_slot_map
.get(&c.id)
.filter(|&&s| slot_is_free(&slots[s], op_location, output_layout.len()))
.copied()
.map_or(assign_slot(slots, op_location, output_layout), |slot_idx| {
ExecKind::InPlace {
slot_idx,
input_idx: 0,
}
})
}
}
NodeKind::Edge(e) => {
if e.layout().is_contiguous() {
ExecKind::ReferenceEternal { input_idx: 0 }
} else {
assign_slot(slots, op_location, output_layout)
}
}
NodeKind::Slot(s) => {
if s.layout().is_contiguous() {
ExecKind::ReferenceEternal { input_idx: 0 }
} else {
assign_slot(slots, op_location, output_layout)
}
}
},
OpKind::ScalarOp(_) => {
let id = get_id(inputs[0]);
id_slot_map
.get(&id)
.filter(|&&s| slot_is_last_read(&slots[s], op_location, output_layout.len()))
.copied()
.map_or(assign_slot(slots, op_location, output_layout), |slot_idx| {
ExecKind::InPlace {
slot_idx,
input_idx: 0,
}
})
}
OpKind::FusedScalar(_) => {
let id = get_id(inputs[0]);
id_slot_map
.get(&id)
.filter(|&&s| slot_is_last_read(&slots[s], op_location, output_layout.len()))
.copied()
.map_or(assign_slot(slots, op_location, output_layout), |slot_idx| {
ExecKind::InPlace {
slot_idx,
input_idx: 0,
}
})
}
OpKind::Add | OpKind::Sub | OpKind::Mul | OpKind::Div => {
for (i, inp) in inputs.iter().enumerate() {
let id = get_id(inp);
if inputs
.iter()
.enumerate()
.any(|(j, other)| j != i && get_id(other) == id)
{
continue;
}
let slot_idx = id_slot_map.get(&id);
if let Some(idx) = slot_idx
&& slot_is_last_read(&slots[*idx], op_location, output_layout.len())
{
return ExecKind::InPlace {
slot_idx: *idx,
input_idx: i,
};
}
}
assign_slot(slots, op_location, output_layout)
}
_ => assign_slot(slots, op_location, output_layout),
}
}