use crate::ir::analysis::{ConstraintInfo, ConstraintShape};
use crate::ir::chunker::Chunk;
use crate::ir::dag::{ConstraintDag, NodeId};
#[derive(Debug, Clone)]
pub enum Lowering {
Sequential(SequentialPlan),
ColumnTile(ColumnTilePlan),
}
#[derive(Debug, Clone)]
pub struct SequentialPlan {
pub topo_order: Vec<NodeId>,
pub max_live: u32,
pub topo_work: u32,
}
#[derive(Debug, Clone)]
pub struct ColumnTilePlan {
pub terms: Vec<ColumnTilePlanTerm>,
}
#[derive(Debug, Clone, Copy)]
pub struct ColumnTilePlanTerm {
pub coeff_node: NodeId,
pub leaf_node: NodeId,
pub alpha_idx: u32,
pub negate: bool,
}
pub fn enumerate_lowerings(
chunk: &Chunk,
constraints: &[ConstraintInfo],
dag: &ConstraintDag,
) -> Vec<Lowering> {
let mut out = Vec::new();
out.push(Lowering::Sequential(build_sequential(chunk, constraints, dag)));
if matches!(chunk.shape, ConstraintShape::LinearWeightedSum) {
if let Some(plan) = build_column_tile(chunk, constraints, dag) {
out.push(Lowering::ColumnTile(plan));
}
}
out
}
fn build_sequential(
chunk: &Chunk,
constraints: &[ConstraintInfo],
dag: &ConstraintDag,
) -> SequentialPlan {
use std::collections::HashSet;
let roots: Vec<NodeId> =
chunk.constraint_indices.iter().map(|&ci| constraints[ci].root).collect();
let mut visited: HashSet<NodeId> = HashSet::new();
let mut topo_order: Vec<NodeId> = Vec::new();
for &root in &roots {
post_order(dag, root, &mut visited, &mut topo_order);
}
let topo_work =
topo_order.iter().filter(|&&n| is_arithmetic(&dag.nodes[n as usize])).count() as u32;
let max_live = compute_max_live(&topo_order, dag);
SequentialPlan { topo_order, max_live, topo_work }
}
fn post_order(
dag: &ConstraintDag,
node_id: NodeId,
visited: &mut std::collections::HashSet<NodeId>,
out: &mut Vec<NodeId>,
) {
if !visited.insert(node_id) {
return;
}
for child in children(&dag.nodes[node_id as usize]).into_iter().flatten() {
post_order(dag, child, visited, out);
}
out.push(node_id);
}
fn children(node: &crate::ir::dag::DagNode) -> [Option<NodeId>; 2] {
use crate::ir::dag::DagNode::*;
match *node {
InputLeaf { .. }
| PublicValue { .. }
| GlobalCumulativeSum { .. }
| ConstF { .. }
| ConstEF { .. }
| IsFirstRow
| IsLastRow
| IsTransition => [None, None],
AddF { a, b }
| SubF { a, b }
| MulF { a, b }
| AddEF { a, b }
| SubEF { a, b }
| MulEF { a, b }
| EFAddF { a, b }
| EFSubF { a, b }
| EFMulF { a, b } => [Some(a), Some(b)],
NegF { a } | NegEF { a } | EFFromF { a } => [Some(a), None],
}
}
fn is_arithmetic(node: &crate::ir::dag::DagNode) -> bool {
use crate::ir::dag::DagNode::*;
!matches!(
node,
InputLeaf { .. }
| PublicValue { .. }
| GlobalCumulativeSum { .. }
| ConstF { .. }
| ConstEF { .. }
| IsFirstRow
| IsLastRow
| IsTransition
)
}
fn compute_max_live(topo: &[NodeId], dag: &ConstraintDag) -> u32 {
let pos_of: std::collections::HashMap<NodeId, usize> =
topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
let mut last_use: std::collections::HashMap<NodeId, usize> =
topo.iter().map(|&n| (n, 0)).collect();
for (i, &n) in topo.iter().enumerate() {
for c in children(&dag.nodes[n as usize]).into_iter().flatten() {
if let Some(&p) = pos_of.get(&c) {
let _ = p; let e = last_use.entry(c).or_insert(0);
if i > *e {
*e = i;
}
}
}
}
let mut max_live: u32 = 0;
let mut live: u32 = 0;
let mut end_at: std::collections::HashMap<usize, Vec<NodeId>> = Default::default();
for (i, &n) in topo.iter().enumerate() {
live += 1;
end_at.entry(*last_use.get(&n).unwrap_or(&i)).or_default().push(n);
max_live = max_live.max(live);
if let Some(ending) = end_at.get(&i) {
live = live.saturating_sub(ending.len() as u32);
}
}
max_live
}
fn build_column_tile(
chunk: &Chunk,
constraints: &[ConstraintInfo],
dag: &ConstraintDag,
) -> Option<ColumnTilePlan> {
let mut terms: Vec<ColumnTilePlanTerm> = Vec::new();
for &ci in &chunk.constraint_indices {
let c = &constraints[ci];
if !matches!(c.shape, ConstraintShape::LinearWeightedSum) {
return None;
}
let mut raw: Vec<FlattenedTerm> = Vec::new();
flatten_linear(dag, c.root, false, &mut raw)?;
for FlattenedTerm { coeff, leaf, negate } in raw {
terms.push(ColumnTilePlanTerm {
coeff_node: coeff,
leaf_node: leaf,
alpha_idx: c.alpha_index,
negate,
});
}
}
Some(ColumnTilePlan { terms })
}
struct FlattenedTerm {
coeff: NodeId,
leaf: NodeId,
negate: bool,
}
fn flatten_linear(
dag: &ConstraintDag,
node_id: NodeId,
negate: bool,
out: &mut Vec<FlattenedTerm>,
) -> Option<()> {
use crate::ir::dag::DagNode::*;
match dag.nodes[node_id as usize] {
AddF { a, b } => {
flatten_linear(dag, a, negate, out)?;
flatten_linear(dag, b, negate, out)?;
Some(())
}
SubF { a, b } => {
flatten_linear(dag, a, negate, out)?;
flatten_linear(dag, b, !negate, out)?;
Some(())
}
MulF { a, b } => {
let a_is_coeff = is_coefficient(dag, a);
let b_is_coeff = is_coefficient(dag, b);
match (a_is_coeff, b_is_coeff) {
(true, false) => {
out.push(FlattenedTerm { coeff: a, leaf: b, negate });
Some(())
}
(false, true) => {
out.push(FlattenedTerm { coeff: b, leaf: a, negate });
Some(())
}
_ => None,
}
}
InputLeaf { .. } => {
None
}
_ => None,
}
}
fn is_coefficient(dag: &ConstraintDag, node_id: NodeId) -> bool {
use crate::ir::dag::DagNode::*;
matches!(
dag.nodes[node_id as usize],
ConstF { .. }
| ConstEF { .. }
| PublicValue { .. }
| GlobalCumulativeSum { .. }
| IsFirstRow
| IsLastRow
| IsTransition
)
}