use crate::ir::analysis::ConstraintInfo;
use crate::ir::chunker::Chunk;
use crate::ir::dag::{ConstraintDag, DagNode, NodeId, TraceSource};
use crate::ir::lowering::SequentialPlan;
use crate::F;
use std::collections::HashMap;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BcOp {
LoadLeaf = 0,
LoadConst = 1,
LoadPublic = 2,
AddF = 3,
SubF = 4,
MulF = 5,
NegF = 6,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DagInstr {
pub opcode: u8,
pub _pad: u8,
pub out: u16,
pub a: u16,
pub b: u16,
}
impl DagInstr {
pub fn new(op: BcOp, out: u16, a: u16, b: u16) -> Self {
Self { opcode: op as u8, _pad: 0, out, a, b }
}
}
pub const LEAF_SOURCE_PREPROCESSED_LOCAL: u8 = 2;
pub const LEAF_SOURCE_MAIN_LOCAL: u8 = 4;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LeafRef {
pub source: u8,
pub _pad: u8,
pub col: u32,
}
#[derive(Debug, Default, Clone)]
pub struct ChunkBytecode {
pub leaves: Vec<LeafRef>,
pub consts: Vec<F>,
pub publics: Vec<u32>,
pub instrs: Vec<DagInstr>,
pub asserts: Vec<(u16, u32)>,
pub max_reg: u16,
pub n_constraints: u32,
pub gkr_main_width: u32,
pub gkr_prep_width: u32,
}
pub fn lower_sequential(
chunk: &Chunk,
constraints: &[ConstraintInfo],
dag: &ConstraintDag,
plan: &SequentialPlan,
) -> ChunkBytecode {
let mut bc = ChunkBytecode {
n_constraints: chunk.constraint_indices.len() as u32,
..ChunkBytecode::default()
};
let phys_of = liveness_allocate(chunk, constraints, dag, plan);
let mut leaf_of: HashMap<(u8, u32), u16> = HashMap::new();
let mut const_of: HashMap<u32, u16> = HashMap::new();
let mut public_of: HashMap<u32, u16> = HashMap::new();
let reg = |n: NodeId| -> u16 { *phys_of.get(&n).expect("topo order broken") };
for &node_id in &plan.topo_order {
let node = &dag.nodes[node_id as usize];
match *node {
DagNode::InputLeaf { source, col } => {
let src_byte = match source {
TraceSource::PreprocessedLocal => LEAF_SOURCE_PREPROCESSED_LOCAL,
TraceSource::MainLocal => LEAF_SOURCE_MAIN_LOCAL,
};
let leaf_idx = *leaf_of.entry((src_byte, col)).or_insert_with(|| {
let i = bc.leaves.len() as u16;
bc.leaves.push(LeafRef { source: src_byte, _pad: 0, col });
i
});
bc.instrs.push(DagInstr::new(BcOp::LoadLeaf, reg(node_id), leaf_idx, 0));
}
DagNode::ConstF { value } => {
use slop_algebra::PrimeField32;
let key = value.as_canonical_u32();
let cidx = *const_of.entry(key).or_insert_with(|| {
let i = bc.consts.len() as u16;
bc.consts.push(value);
i
});
bc.instrs.push(DagInstr::new(BcOp::LoadConst, reg(node_id), cidx, 0));
}
DagNode::PublicValue { idx } => {
let pidx = *public_of.entry(idx).or_insert_with(|| {
let i = bc.publics.len() as u16;
bc.publics.push(idx);
i
});
bc.instrs.push(DagInstr::new(BcOp::LoadPublic, reg(node_id), pidx, 0));
}
DagNode::AddF { a, b } => {
bc.instrs.push(DagInstr::new(BcOp::AddF, reg(node_id), reg(a), reg(b)));
}
DagNode::SubF { a, b } => {
bc.instrs.push(DagInstr::new(BcOp::SubF, reg(node_id), reg(a), reg(b)));
}
DagNode::MulF { a, b } => {
bc.instrs.push(DagInstr::new(BcOp::MulF, reg(node_id), reg(a), reg(b)));
}
DagNode::NegF { a } => {
bc.instrs.push(DagInstr::new(BcOp::NegF, reg(node_id), reg(a), 0));
}
_ => {
panic!(
"Sequential kernel cannot lower node kind {:?} (node id {}); \
a base-field asserted root reached a non-base-field DAG node \
for the first time",
node, node_id
);
}
}
}
for &ci in &chunk.constraint_indices {
let info = &constraints[ci];
bc.asserts.push((reg(info.root), info.alpha_index));
}
bc.max_reg = phys_of.values().copied().max().map(|m| m + 1).unwrap_or(0);
bc
}
fn liveness_allocate(
chunk: &Chunk,
constraints: &[ConstraintInfo],
dag: &ConstraintDag,
plan: &SequentialPlan,
) -> HashMap<NodeId, u16> {
let topo = &plan.topo_order;
let pos_of: HashMap<NodeId, usize> = topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
let mut last_use: HashMap<NodeId, usize> = HashMap::new();
for (i, &node_id) in topo.iter().enumerate() {
last_use.insert(node_id, i);
}
for (i, &node_id) in topo.iter().enumerate() {
let node = &dag.nodes[node_id as usize];
for child in node_children(node).into_iter().flatten() {
if pos_of.contains_key(&child) {
let e = last_use.entry(child).or_insert(0);
if i > *e {
*e = i;
}
}
}
}
let end = topo.len();
for &ci in &chunk.constraint_indices {
let root = constraints[ci].root;
if pos_of.contains_key(&root) {
last_use.insert(root, end);
}
}
let mut active: Vec<(u16, NodeId)> = Vec::new(); let mut free_pool: Vec<u16> = Vec::new();
let mut next_phys: u16 = 0;
let mut phys_of: HashMap<NodeId, u16> = HashMap::new();
for (i, &node_id) in topo.iter().enumerate() {
active.retain(|&(p, n)| {
if last_use[&n] < i {
free_pool.push(p);
false
} else {
true
}
});
let phys = free_pool.pop().unwrap_or_else(|| {
let p = next_phys;
next_phys += 1;
p
});
active.push((phys, node_id));
phys_of.insert(node_id, phys);
}
phys_of
}
fn node_children(node: &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],
}
}