use std::collections::HashMap;
use rlx_fusion::pass::{IRStatus, Pass, PassResult};
use rlx_ir::{DType, Graph, NodeId, Op, OpKind};
use crate::const_fold::{evaluate, is_pure, static_dims};
pub struct SCCPPass;
#[derive(Debug, Clone)]
enum Lat {
Bottom,
Const(Vec<f32>),
Alias(NodeId),
}
fn decode_predicate(data: &[u8], dtype: DType, want: usize) -> Option<Vec<bool>> {
let bits: Vec<bool> = match dtype {
DType::Bool => data.iter().map(|&b| b != 0).collect(),
DType::F32 => data
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]) != 0.0)
.collect(),
_ => return None,
};
(bits.len() == want).then_some(bits)
}
fn decode_f32(data: &[u8]) -> Vec<f32> {
data.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect()
}
fn encode_f32(values: &[f32]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(values.len() * 4);
for &v in values {
bytes.extend_from_slice(&v.to_le_bytes());
}
bytes
}
fn predicate_bits(
graph: &Graph,
lattice: &HashMap<NodeId, Lat>,
pred: NodeId,
want: usize,
) -> Option<Vec<bool>> {
let id = resolve(lattice, pred);
if let Some(values) = value_of(lattice, id)
&& values.len() == want
{
return Some(values.iter().map(|&v| v != 0.0).collect());
}
let node = graph.node(id);
let Op::Constant { data } = &node.op else {
return None;
};
decode_predicate(data, node.shape.dtype(), want)
}
fn resolve(lattice: &HashMap<NodeId, Lat>, mut id: NodeId) -> NodeId {
for _ in 0..64 {
match lattice.get(&id) {
Some(Lat::Alias(target)) if *target != id => id = *target,
_ => break,
}
}
id
}
fn value_of(lattice: &HashMap<NodeId, Lat>, id: NodeId) -> Option<&Vec<f32>> {
match lattice.get(&resolve(lattice, id)) {
Some(Lat::Const(v)) => Some(v),
_ => None,
}
}
impl SCCPPass {
fn analyze(graph: &Graph) -> (HashMap<NodeId, Lat>, HashMap<NodeId, bool>) {
let mut lattice: HashMap<NodeId, Lat> = HashMap::with_capacity(graph.len());
let mut taken: HashMap<NodeId, bool> = HashMap::new();
for node in graph.nodes() {
let lat = match &node.op {
Op::Constant { data } if node.shape.dtype() == DType::F32 => {
Lat::Const(decode_f32(data))
}
Op::Where if node.inputs.len() == 3 => Self::transfer_where(graph, &lattice, node),
Op::If { .. } if !node.inputs.is_empty() => {
if let Some(b) = Self::resolve_predicate(graph, &lattice, node.inputs[0]) {
taken.insert(node.id, b);
}
Lat::Bottom
}
op if is_pure(op) => Self::transfer_pure(graph, &lattice, node),
_ => Lat::Bottom,
};
lattice.insert(node.id, lat);
}
(lattice, taken)
}
fn resolve_predicate(
graph: &Graph,
lattice: &HashMap<NodeId, Lat>,
pred: NodeId,
) -> Option<bool> {
let node = graph.node(resolve(lattice, pred));
let bits = predicate_bits(graph, lattice, pred, node.shape.num_elements()?)?;
if bits.is_empty() {
return None;
}
if bits.iter().all(|&b| b) {
Some(true)
} else if bits.iter().all(|&b| !b) {
Some(false)
} else {
None
}
}
fn transfer_where(graph: &Graph, lattice: &HashMap<NodeId, Lat>, node: &rlx_ir::Node) -> Lat {
let (cond_id, t_id, f_id) = (node.inputs[0], node.inputs[1], node.inputs[2]);
let cond_node = graph.node(resolve(lattice, cond_id));
let Some(elems) = cond_node.shape.num_elements() else {
return Lat::Bottom;
};
let Some(bits) = predicate_bits(graph, lattice, cond_id, elems) else {
return Lat::Bottom;
};
if bits.is_empty() {
return Lat::Bottom;
}
let all_true = bits.iter().all(|&b| b);
let all_false = bits.iter().all(|&b| !b);
if all_true || all_false {
let chosen = if all_true { t_id } else { f_id };
if graph.node(chosen).shape == node.shape {
return Lat::Alias(chosen);
}
}
if node.shape.dtype() != DType::F32 {
return Lat::Bottom;
}
let (Some(t), Some(f)) = (value_of(lattice, t_id), value_of(lattice, f_id)) else {
return Lat::Bottom;
};
if t.len() != elems || f.len() != elems || node.shape.num_elements() != Some(elems) {
return Lat::Bottom;
}
Lat::Const(
bits.iter()
.enumerate()
.map(|(i, &b)| if b { t[i] } else { f[i] })
.collect(),
)
}
fn transfer_pure(graph: &Graph, lattice: &HashMap<NodeId, Lat>, node: &rlx_ir::Node) -> Lat {
if node.inputs.is_empty() {
return Lat::Bottom;
}
let Some(values): Option<Vec<&Vec<f32>>> =
node.inputs.iter().map(|&i| value_of(lattice, i)).collect()
else {
return Lat::Bottom;
};
let Some(dims): Option<Vec<Vec<usize>>> = node
.inputs
.iter()
.map(|&i| static_dims(&graph.node(resolve(lattice, i)).shape))
.collect()
else {
return Lat::Bottom;
};
match evaluate(node, &values, &dims) {
Some(result) => Lat::Const(result),
None => Lat::Bottom,
}
}
}
impl Pass for SCCPPass {
fn name(&self) -> &str {
"sccp"
}
fn trigger_kinds(&self) -> &[OpKind] {
&[OpKind::Where, OpKind::If]
}
fn run(&self, graph: Graph) -> Graph {
self.run_with_status(graph).graph
}
fn run_with_status(&self, graph: Graph) -> PassResult {
if !self.can_fire(&graph) {
return PassResult::unchanged(graph);
}
let (lattice, taken_branch) = Self::analyze(&graph);
let actionable = graph.nodes().iter().any(|n| {
taken_branch.contains_key(&n.id)
|| match lattice.get(&n.id) {
Some(Lat::Alias(_)) => true,
Some(Lat::Const(_)) => !n.op.is_leaf() && n.shape.dtype() == DType::F32,
_ => false,
}
});
if !actionable {
return PassResult::unchanged(graph);
}
let mut out = Graph::new(&graph.name);
let mut id_map: HashMap<NodeId, NodeId> = HashMap::with_capacity(graph.len());
for node in graph.nodes() {
if let Some(&take_then) = taken_branch.get(&node.id)
&& let Op::If {
then_branch,
else_branch,
} = &node.op
{
let captures: Vec<NodeId> = node.inputs[1..]
.iter()
.map(|i| id_map[&resolve(&lattice, *i)])
.collect();
let body = if take_then { then_branch } else { else_branch };
let inlined =
rlx_fusion::control_flow::inline_subgraph_into(body, &captures, &mut out);
id_map.insert(node.id, inlined);
continue;
}
let new_id = match lattice.get(&node.id) {
Some(Lat::Alias(target)) => id_map[&resolve(&lattice, *target)],
Some(Lat::Const(values))
if !node.op.is_leaf() && node.shape.dtype() == DType::F32 =>
{
out.add_node(
Op::Constant {
data: encode_f32(values),
},
vec![],
node.shape.clone(),
)
}
_ => {
let inputs: Vec<NodeId> = node.inputs.iter().map(|i| id_map[i]).collect();
let id = out.add_node(node.op.clone(), inputs, node.shape.clone());
if node.name.is_some() || node.origin.is_some() {
let n = out.node_mut(id);
n.name = node.name.clone();
n.origin = node.origin.clone();
}
id
}
};
id_map.insert(node.id, new_id);
}
out.set_outputs(graph.outputs.iter().map(|o| id_map[o]).collect());
PassResult::from_status(out, IRStatus::Changed)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rlx_ir::op::{Activation, BinaryOp};
use rlx_ir::{DType, Shape};
fn f32s(dims: &[usize]) -> Shape {
Shape::new(dims, DType::F32)
}
fn f32_const(g: &mut Graph, values: &[f32]) -> NodeId {
g.add_node(
Op::Constant {
data: encode_f32(values),
},
vec![],
f32s(&[values.len()]),
)
}
fn bool_const(g: &mut Graph, bits: &[bool]) -> NodeId {
g.add_node(
Op::Constant {
data: bits.iter().map(|&b| u8::from(b)).collect(),
},
vec![],
Shape::new(&[bits.len()], DType::Bool),
)
}
fn constant_values(g: &Graph, id: NodeId) -> Option<Vec<f32>> {
match &g.node(id).op {
Op::Constant { data } => Some(decode_f32(data)),
_ => None,
}
}
#[test]
fn folds_a_where_with_a_runtime_arm() {
let mut g = Graph::new("where_true");
let x = g.input("x", f32s(&[2]));
let pred = bool_const(&mut g, &[true, true]);
let t = f32_const(&mut g, &[5.0, 5.0]);
let w = g.add_node(Op::Where, vec![pred, t, x], f32s(&[2]));
g.set_outputs(vec![w]);
let out = SCCPPass.run(g);
assert_eq!(constant_values(&out, out.outputs[0]), Some(vec![5.0, 5.0]));
}
#[test]
fn a_false_predicate_forwards_the_runtime_arm() {
let mut g = Graph::new("where_false");
let x = g.input("x", f32s(&[2]));
let pred = bool_const(&mut g, &[false, false]);
let c = f32_const(&mut g, &[9.0, 9.0]);
let w = g.add_node(Op::Where, vec![pred, c, x], f32s(&[2]));
g.set_outputs(vec![w]);
let out = SCCPPass.run(g);
assert!(
matches!(out.node(out.outputs[0]).op, Op::Input { .. }),
"the Where must collapse to its runtime arm"
);
}
#[test]
fn alias_works_for_dtypes_the_f32_folder_cannot_touch() {
let mut g = Graph::new("i32_where");
let x = g.input("x", Shape::new(&[3], DType::I32));
let y = g.input("y", Shape::new(&[3], DType::I32));
let pred = bool_const(&mut g, &[false, false, false]);
let w = g.add_node(Op::Where, vec![pred, x, y], Shape::new(&[3], DType::I32));
g.set_outputs(vec![w]);
let out = SCCPPass.run(g);
assert_eq!(
out.node(out.outputs[0]).name.as_deref().or_else(|| {
match &out.node(out.outputs[0]).op {
Op::Input { name } => Some(name.as_str()),
_ => None,
}
}),
Some("y"),
"false predicate selects the third operand regardless of dtype"
);
}
#[test]
fn constants_flow_through_a_resolved_where() {
let mut g = Graph::new("chain");
let x = g.input("x", f32s(&[2]));
let pred = bool_const(&mut g, &[true, true]);
let t = f32_const(&mut g, &[-2.0, 3.0]);
let w = g.add_node(Op::Where, vec![pred, t, x], f32s(&[2]));
let r = g.add_node(Op::Activation(Activation::Relu), vec![w], f32s(&[2]));
g.set_outputs(vec![r]);
let out = SCCPPass.run(g);
assert_eq!(constant_values(&out, out.outputs[0]), Some(vec![0.0, 3.0]));
}
#[test]
fn mixed_predicate_selects_elementwise() {
let mut g = Graph::new("mixed");
let pred = bool_const(&mut g, &[true, false, true]);
let t = f32_const(&mut g, &[1.0, 2.0, 3.0]);
let f = f32_const(&mut g, &[10.0, 20.0, 30.0]);
let w = g.add_node(Op::Where, vec![pred, t, f], f32s(&[3]));
g.set_outputs(vec![w]);
let out = SCCPPass.run(g);
assert_eq!(
constant_values(&out, out.outputs[0]),
Some(vec![1.0, 20.0, 3.0])
);
}
#[test]
fn a_predicate_computed_from_constants_resolves() {
let mut g = Graph::new("derived_pred");
let x = g.input("x", f32s(&[4]));
let seed = f32_const(&mut g, &[1.0]);
let pred = g.add_node(
Op::Expand {
target_shape: vec![4],
},
vec![seed],
f32s(&[4]),
);
let t = f32_const(&mut g, &[7.0, 7.0, 7.0, 7.0]);
let w = g.add_node(Op::Where, vec![pred, t, x], f32s(&[4]));
g.set_outputs(vec![w]);
let out = SCCPPass.run(g);
assert_eq!(
constant_values(&out, out.outputs[0]),
Some(vec![7.0, 7.0, 7.0, 7.0]),
"a predicate expanded from a constant must still resolve"
);
}
#[test]
fn a_predicate_from_a_constant_comparison_resolves() {
let mut g = Graph::new("cmp_pred");
let x = g.input("x", f32s(&[2]));
let lo = f32_const(&mut g, &[1.0, 1.0]);
let hi = f32_const(&mut g, &[2.0, 2.0]);
let pred = g.add_node(
Op::Compare(rlx_ir::op::CmpOp::Lt),
vec![lo, hi],
Shape::new(&[2], DType::Bool),
);
let t = f32_const(&mut g, &[5.0, 5.0]);
let w = g.add_node(Op::Where, vec![pred, t, x], f32s(&[2]));
g.set_outputs(vec![w]);
let out = SCCPPass.run(g);
assert_eq!(
constant_values(&out, out.outputs[0]),
Some(vec![5.0, 5.0]),
"1 < 2 is statically true, so the Where selects the constant arm"
);
}
#[test]
fn an_if_with_a_derived_predicate_prunes() {
let shape = f32s(&[4]);
let branch = |act: Activation| {
let mut b = Graph::new("branch");
let c = b.input("cap", shape.clone());
let y = b.add_node(Op::Activation(act), vec![c], shape.clone());
b.set_outputs(vec![y]);
Box::new(b)
};
let mut g = Graph::new("derived_if");
let x = g.input("x", shape.clone());
let seed = f32_const(&mut g, &[0.0]);
let pred = g.add_node(
Op::Expand {
target_shape: vec![4],
},
vec![seed],
shape.clone(),
);
let node = g.add_node(
Op::If {
then_branch: branch(Activation::Gelu),
else_branch: branch(Activation::Relu),
},
vec![pred, x],
shape,
);
g.set_outputs(vec![node]);
let out = SCCPPass.run(g);
assert!(
out.nodes()
.iter()
.any(|n| matches!(n.op, Op::Activation(Activation::Relu))),
"an all-zero derived predicate takes the else branch"
);
assert!(
!out.nodes()
.iter()
.any(|n| matches!(n.op, Op::Activation(Activation::Gelu)))
);
}
#[test]
fn a_runtime_predicate_is_left_alone() {
let mut g = Graph::new("dyn_pred");
let p = g.input("p", Shape::new(&[2], DType::Bool));
let x = g.input("x", f32s(&[2]));
let c = f32_const(&mut g, &[1.0, 1.0]);
let w = g.add_node(Op::Where, vec![p, c, x], f32s(&[2]));
g.set_outputs(vec![w]);
let before = g.len();
let result = SCCPPass.run_with_status(g);
assert_eq!(result.ir_changed, IRStatus::Unchanged);
assert_eq!(result.graph.len(), before);
}
#[test]
fn broadcasting_arms_are_not_forwarded() {
let mut g = Graph::new("bcast");
let x = g.input("x", f32s(&[4]));
let pred = bool_const(&mut g, &[true]);
let t = f32_const(&mut g, &[7.0]);
let w = g.add_node(Op::Where, vec![pred, t, x], f32s(&[4]));
g.set_outputs(vec![w]);
let out = SCCPPass.run(g);
assert_eq!(
out.node(out.outputs[0]).shape,
f32s(&[4]),
"output shape must survive"
);
}
#[test]
fn a_plain_constant_chain_is_left_to_the_constant_folder() {
let mut g = Graph::new("plain");
let a = f32_const(&mut g, &[2.0, 3.0]);
let b = f32_const(&mut g, &[4.0, 5.0]);
let sum = g.add_node(Op::Binary(BinaryOp::Add), vec![a, b], f32s(&[2]));
g.set_outputs(vec![sum]);
let result = SCCPPass.run_with_status(g);
assert_eq!(result.ir_changed, IRStatus::Unchanged);
assert!(matches!(
result.graph.node(result.graph.outputs[0]).op,
Op::Binary(BinaryOp::Add)
));
}
#[test]
fn constants_still_fold_when_a_where_gives_the_pass_a_reason_to_run() {
let mut g = Graph::new("via_where");
let a = f32_const(&mut g, &[2.0, 3.0]);
let b = f32_const(&mut g, &[4.0, 5.0]);
let sum = g.add_node(Op::Binary(BinaryOp::Add), vec![a, b], f32s(&[2]));
let x = g.input("x", f32s(&[2]));
let pred = bool_const(&mut g, &[true, true]);
let w = g.add_node(Op::Where, vec![pred, sum, x], f32s(&[2]));
g.set_outputs(vec![w]);
let out = SCCPPass.run(g);
assert_eq!(constant_values(&out, out.outputs[0]), Some(vec![6.0, 8.0]));
}
fn if_graph(pred: bool) -> Graph {
let shape = f32s(&[4]);
let branch = |act: Activation| {
let mut b = Graph::new("branch");
let c = b.input("cap", shape.clone());
let y = b.add_node(Op::Activation(act), vec![c], shape.clone());
b.set_outputs(vec![y]);
Box::new(b)
};
let mut g = Graph::new("cond");
let x = g.input("x", shape.clone());
let p = bool_const(&mut g, &[pred; 4]);
let node = g.add_node(
Op::If {
then_branch: branch(Activation::Gelu),
else_branch: branch(Activation::Relu),
},
vec![p, x],
shape,
);
g.set_outputs(vec![node]);
g
}
#[test]
fn a_constant_if_inlines_only_the_reachable_branch() {
let out = SCCPPass.run(if_graph(true));
assert!(
!out.nodes().iter().any(|n| matches!(n.op, Op::If { .. })),
"the If must be resolved away"
);
assert!(
out.nodes()
.iter()
.any(|n| matches!(n.op, Op::Activation(Activation::Gelu))),
"the taken branch must be inlined"
);
assert!(
!out.nodes()
.iter()
.any(|n| matches!(n.op, Op::Activation(Activation::Relu))),
"the unreachable branch must never be materialised — this is what \
LowerControlFlow cannot do, since it inlines both and selects"
);
}
#[test]
fn a_false_predicate_takes_the_other_branch() {
let out = SCCPPass.run(if_graph(false));
assert!(
out.nodes()
.iter()
.any(|n| matches!(n.op, Op::Activation(Activation::Relu)))
);
assert!(
!out.nodes()
.iter()
.any(|n| matches!(n.op, Op::Activation(Activation::Gelu)))
);
}
#[test]
fn a_runtime_if_is_left_for_lower_control_flow() {
let shape = f32s(&[4]);
let branch = |act: Activation| {
let mut b = Graph::new("branch");
let c = b.input("cap", shape.clone());
let y = b.add_node(Op::Activation(act), vec![c], shape.clone());
b.set_outputs(vec![y]);
Box::new(b)
};
let mut g = Graph::new("dyn_if");
let x = g.input("x", shape.clone());
let p = g.input("p", Shape::new(&[4], DType::Bool));
let node = g.add_node(
Op::If {
then_branch: branch(Activation::Gelu),
else_branch: branch(Activation::Relu),
},
vec![p, x],
shape,
);
g.set_outputs(vec![node]);
let result = SCCPPass.run_with_status(g);
assert_eq!(result.ir_changed, IRStatus::Unchanged);
assert!(
result
.graph
.nodes()
.iter()
.any(|n| matches!(n.op, Op::If { .. }))
);
}
#[test]
fn a_mixed_predicate_decides_nothing() {
let shape = f32s(&[4]);
let branch = |act: Activation| {
let mut b = Graph::new("branch");
let c = b.input("cap", shape.clone());
let y = b.add_node(Op::Activation(act), vec![c], shape.clone());
b.set_outputs(vec![y]);
Box::new(b)
};
let mut g = Graph::new("mixed_if");
let x = g.input("x", shape.clone());
let p = bool_const(&mut g, &[true, false, true, false]);
let node = g.add_node(
Op::If {
then_branch: branch(Activation::Gelu),
else_branch: branch(Activation::Relu),
},
vec![p, x],
shape,
);
g.set_outputs(vec![node]);
assert_eq!(
SCCPPass.run_with_status(g).ir_changed,
IRStatus::Unchanged,
"a mixed mask must not decide a branch"
);
}
#[test]
fn a_graph_with_nothing_to_prove_is_untouched() {
let mut g = Graph::new("runtime");
let x = g.input("x", f32s(&[2]));
let y = g.input("y", f32s(&[2]));
let s = g.add_node(Op::Binary(BinaryOp::Add), vec![x, y], f32s(&[2]));
g.set_outputs(vec![s]);
let result = SCCPPass.run_with_status(g);
assert_eq!(result.ir_changed, IRStatus::Unchanged);
}
}