use std::collections::HashMap;
use fuel_graph::jit::match_region_extract;
use fuel_graph::registry::FusedOpId;
use fuel_graph::runtime_fused::runtime_entries;
use fuel_graph::{Graph, NodeId};
use fuel_ir::Result;
use fuel_ir::probe::BackendId;
use crate::driver::{OptimizationContext, Pathfinder};
use crate::kernel::KernelBindingTable;
use crate::runtime_fused_arm::{RuntimeFusedSpec, offer_runtime_fused_arm};
struct RegionMatch {
runtime_id: FusedOpId,
inputs: Vec<NodeId>,
scalars: Vec<f64>,
primitive_sink: NodeId,
reconverge: NodeId,
}
pub fn emit_runtime_fused_arms(graph: &mut Graph, table: &KernelBindingTable) -> Result<usize> {
let entries = runtime_entries();
if entries.is_empty() {
return Ok(0);
}
let mut consumer_count: HashMap<NodeId, usize> = HashMap::new();
let mut sole_consumer: HashMap<NodeId, NodeId> = HashMap::new();
let len = graph.len();
for i in 0..len {
let id = NodeId(i);
for &input in &graph.node(id).inputs {
*consumer_count.entry(input).or_insert(0) += 1;
sole_consumer.insert(input, id);
}
}
let consumers = |n: NodeId| consumer_count.get(&n).copied().unwrap_or(0);
let mut claimed: std::collections::HashSet<NodeId> = std::collections::HashSet::new();
let mut matches: Vec<RegionMatch> = Vec::new();
for entry in &entries {
for i in 0..len {
let root = NodeId(i);
if claimed.contains(&root) {
continue;
}
let Some((inputs, scalars)) =
match_region_extract(graph, root, &entry.region, &consumers)
else {
continue;
};
if consumers(root) != 1 {
continue;
}
claimed.insert(root);
matches.push(RegionMatch {
runtime_id: entry.id,
inputs,
scalars,
primitive_sink: root,
reconverge: sole_consumer[&root],
});
}
}
let mut emitted = 0usize;
for m in matches {
let spec = RuntimeFusedSpec {
runtime_id: m.runtime_id,
inputs: m.inputs,
primitive_sink: m.primitive_sink,
reconverge: m.reconverge,
backend: BackendId::Cpu,
scalars: m.scalars,
};
if offer_runtime_fused_arm(graph, &spec, table)?.is_some() {
emitted += 1;
}
}
Ok(emitted)
}
pub struct RuntimeFusedArmPathfinder;
impl Pathfinder for RuntimeFusedArmPathfinder {
fn name(&self) -> &'static str {
"RuntimeFusedArm"
}
fn propose(&self, graph: &mut Graph, ctx: &OptimizationContext<'_>) -> Result<()> {
emit_runtime_fused_arms(graph, ctx.bindings())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime_fused_kernels::adopt_runtime_fused;
use fuel_graph::jit::{OpAttrs, OpTag, PatternNode};
use fuel_graph::{Node, Op};
use fuel_ir::{DType, Layout, Shape};
use std::sync::{Arc, RwLock as StdRwLock};
fn noop_kernel(
_inputs: &[Arc<StdRwLock<fuel_memory::Storage>>],
_outputs: &mut [Arc<StdRwLock<fuel_memory::Storage>>],
_layouts: &[Layout],
_params: &crate::kernel::OpParams,
) -> fuel_ir::Result<()> {
Ok(())
}
fn tanh_mul_region() -> PatternNode {
PatternNode::Op {
op: OpTag::Tanh,
attrs: OpAttrs::default(),
operands: vec![PatternNode::Op {
op: OpTag::Mul,
attrs: OpAttrs::default(),
operands: vec![
PatternNode::Bind { index: 0 },
PatternNode::Bind { index: 1 },
],
}],
}
}
fn graph_with_region() -> (Graph, NodeId) {
let mut g = Graph::new();
let s = Shape::from_dims(&[4]);
let leaf = |g: &mut Graph| {
g.push(Node {
op: Op::Const,
inputs: vec![],
shape: s.clone(),
dtype: DType::F32,
})
};
let a = leaf(&mut g);
let b = leaf(&mut g);
let mul = g.push(Node {
op: Op::Mul,
inputs: vec![a, b],
shape: s.clone(),
dtype: DType::F32,
});
let tanh = g.push(Node {
op: Op::Tanh,
inputs: vec![mul],
shape: s.clone(),
dtype: DType::F32,
});
let _neg = g.push(Node {
op: Op::Neg,
inputs: vec![tanh],
shape: s.clone(),
dtype: DType::F32,
});
(g, tanh)
}
#[test]
fn emits_a_fused_arm_for_an_adopted_region_match() {
let rid = adopt_runtime_fused(
"test::pathfinder::tanh_mul",
tanh_mul_region(),
noop_kernel as crate::kernel::KernelRef,
vec![DType::F32, DType::F32, DType::F32],
BackendId::Cpu,
)
.expect("registrable region");
let (mut g, tanh) = graph_with_region();
let before = g.len();
let emitted = emit_runtime_fused_arms(&mut g, &crate::dispatch::global_bindings())
.expect("no build-time error");
assert_eq!(emitted, 1, "exactly one match ⇒ one arm emitted");
assert!(g.len() > before, "the fused node + branch were appended");
let mut saw_branch = false;
let mut saw_fused = false;
for i in before..g.len() {
match &g.node(NodeId(i)).op {
Op::Branch { .. } => saw_branch = true,
Op::Fused(id, _) if *id == rid => saw_fused = true,
_ => {}
}
}
assert!(saw_branch, "an Op::Branch decision point was emitted");
assert!(saw_fused, "an Op::Fused(rid, ..) arm was emitted");
assert!(matches!(g.node(tanh).op, Op::Tanh));
}
#[test]
fn no_adopted_op_emits_nothing() {
let mut g = Graph::new();
let s = Shape::from_dims(&[4]);
let a = g.push(Node {
op: Op::Const,
inputs: vec![],
shape: s.clone(),
dtype: DType::F32,
});
let _n = g.push(Node {
op: Op::Neg,
inputs: vec![a],
shape: s.clone(),
dtype: DType::F32,
});
let before = g.len();
let emitted = emit_runtime_fused_arms(&mut g, &crate::dispatch::global_bindings())
.expect("no build-time error");
assert_eq!(emitted, 0, "no structural match ⇒ no arm emitted");
assert_eq!(g.len(), before, "graph untouched");
}
#[test]
fn emitted_arm_carries_the_extracted_live_scalar() {
let region = PatternNode::Op {
op: OpTag::Sigmoid,
attrs: OpAttrs::default(),
operands: vec![PatternNode::Op {
op: OpTag::AddScalar,
attrs: OpAttrs::default(), operands: vec![PatternNode::Bind { index: 0 }],
}],
};
let rid = adopt_runtime_fused(
"test::pathfinder::sigmoid_add_scalar",
region,
noop_kernel as crate::kernel::KernelRef,
vec![DType::F32, DType::F32],
BackendId::Cpu,
)
.expect("slot template is registrable");
let mut g = Graph::new();
let s = Shape::from_dims(&[4]);
let x = g.push(Node {
op: Op::Const,
inputs: vec![],
shape: s.clone(),
dtype: DType::F32,
});
let asn = g.push(Node {
op: Op::AddScalar(7.25),
inputs: vec![x],
shape: s.clone(),
dtype: DType::F32,
});
let sig = g.push(Node {
op: Op::Sigmoid,
inputs: vec![asn],
shape: s.clone(),
dtype: DType::F32,
});
let _neg = g.push(Node {
op: Op::Neg,
inputs: vec![sig],
shape: s.clone(),
dtype: DType::F32,
});
let before = g.len();
let emitted = emit_runtime_fused_arms(&mut g, &crate::dispatch::global_bindings())
.expect("no build-time error");
assert_eq!(emitted, 1, "the slot region matched once");
let mut found = false;
for i in before..g.len() {
if let Op::Fused(fid, fuel_graph::registry::FusedOpParams::Runtime { scalars }) =
&g.node(NodeId(i)).op
&& *fid == rid
{
assert_eq!(scalars, &vec![7.25], "the LIVE value rode into the arm");
found = true;
}
}
assert!(found, "an Op::Fused(rid, Runtime) arm was emitted");
}
}