use crate::model::{ExpressionsInfo, StarkInfo};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub enum Operand {
Tmp { id: u64, dim: u64 },
Num(u64),
Cm { stage: u64, pos: u64, dim: u64, stride: i64 },
Const { id: u64, stride: i64 },
Zi,
Ch { base: u64 },
Av { pos: u64, dim: u64 },
Agv { pos: u64, dim: u64 },
Pub { id: u64 },
}
impl Operand {
pub fn dim(&self) -> u64 {
match self {
Operand::Tmp { dim, .. } | Operand::Cm { dim, .. } | Operand::Av { dim, .. } | Operand::Agv { dim, .. } => {
*dim
}
Operand::Ch { .. } => 3,
Operand::Num(_) | Operand::Const { .. } | Operand::Zi | Operand::Pub { .. } => 1,
}
}
pub fn as_tmp(&self) -> Option<(u64, u64)> {
if let Operand::Tmp { id, dim } = self {
Some((*id, *dim))
} else {
None
}
}
}
#[derive(Debug)]
pub struct Instr {
pub op: String,
pub a: Operand,
pub b: Operand,
pub dst_is_tmp: bool,
pub dst_id: Option<u64>,
pub ddim: u64,
pub idx: usize,
}
pub struct Ir {
pub instrs: Vec<Instr>,
pub ncols: HashMap<u64, u64>,
pub n_constants: u64,
pub n_bits: u64,
}
impl Ir {
pub fn uses_zi(&self) -> bool {
self.instrs.iter().any(|i| matches!(i.a, Operand::Zi) || matches!(i.b, Operand::Zi))
}
pub fn out_dim(&self) -> Option<u64> {
let last = self.instrs.last()?;
let early_out = self.instrs[..self.instrs.len() - 1].iter().any(|i| !i.dst_is_tmp);
if early_out {
None
} else {
Some(last.ddim)
}
}
}
#[derive(Debug)]
pub struct UnhandledOperand(pub String);
impl std::fmt::Display for UnhandledOperand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unhandled operand {}", self.0)
}
}
impl std::error::Error for UnhandledOperand {}
pub fn build_ir(stark_info: &StarkInfo, expr_info: &ExpressionsInfo) -> anyhow::Result<Ir> {
build_ir_expr(stark_info, expr_info, stark_info.c_exp_id, true)
}
pub fn build_ir_expr(
stark_info: &StarkInfo,
expr_info: &ExpressionsInfo,
exp_id: i64,
extended: bool,
) -> anyhow::Result<Ir> {
let opening = &stark_info.opening_points;
let blowup = if extended { stark_info.blowup() } else { 1 };
let n_constants = stark_info.n_constants;
let mut ncols = HashMap::new();
for stage in 1..=(stark_info.n_stages + 1) {
let n = stark_info.map_sections_n.get(&format!("cm{stage}")).copied().unwrap_or(0);
ncols.insert(stage, n);
}
let code = &expr_info
.expressions_code
.iter()
.find(|e| e.exp_id == exp_id)
.ok_or_else(|| anyhow::anyhow!("expId {exp_id} not found in expressionsCode"))?
.code;
let stride = |prime: i64| -> anyhow::Result<i64> {
let pos = opening
.iter()
.position(|&x| x == prime)
.ok_or_else(|| anyhow::anyhow!("opening point {prime} not in openingPoints"))?;
Ok(opening[pos] * blowup)
};
let pos_of = |idx: u64, map: &[crate::model::ValueMapEntry]| -> u64 {
(0..idx as usize).map(|j| if map[j].stage.unwrap_or(1) != 1 { 3 } else { 1 }).sum()
};
let operand = |src: &crate::model::Src| -> anyhow::Result<Operand> {
let dim = src.dim.unwrap_or(1);
Ok(match src.op_type.as_str() {
"tmp" => Operand::Tmp { id: src.id.unwrap(), dim },
"number" => Operand::Num(src.number_value()?),
"cm" => {
let cm = &stark_info.cm_pols_map[src.id.unwrap() as usize];
Operand::Cm { stage: cm.stage, pos: cm.stage_pos, dim: cm.dim, stride: stride(src.prime.unwrap_or(0))? }
}
"const" => Operand::Const { id: src.id.unwrap(), stride: stride(src.prime.unwrap_or(0))? },
"Zi" => Operand::Zi,
"challenge" => Operand::Ch { base: 3 * src.id.unwrap() },
"airvalue" => Operand::Av { pos: pos_of(src.id.unwrap(), &stark_info.air_values_map), dim },
"airgroupvalue" => Operand::Agv { pos: pos_of(src.id.unwrap(), &stark_info.airgroup_values_map), dim },
"public" => Operand::Pub { id: src.id.unwrap() },
other => return Err(anyhow::Error::new(UnhandledOperand(other.to_string()))),
})
};
let mut instrs = Vec::with_capacity(code.len());
for (idx, step) in code.iter().enumerate() {
instrs.push(Instr {
op: step.op.clone(),
a: operand(&step.src[0])?,
b: operand(&step.src[1])?,
dst_is_tmp: step.dest.dest_type == "tmp",
dst_id: step.dest.id,
ddim: step.dest.dim,
idx,
});
}
Ok(Ir { instrs, ncols, n_constants, n_bits: stark_info.stark_struct.n_bits })
}
pub struct ChunkPlan {
pub chunk: usize,
pub n_chunks: usize,
pub out_dim: u64,
pub def_idx: HashMap<u64, usize>,
pub dim_of: HashMap<u64, u64>,
pub cut_temps: HashSet<u64>,
pub total_slots: u64,
slot_index: HashMap<u64, u64>,
}
impl ChunkPlan {
pub fn chunk_of(&self, op_idx: usize) -> usize {
op_idx / self.chunk
}
pub fn slot_index(&self, t: u64) -> u64 {
self.slot_index[&t]
}
}
pub fn plan_chunks(ir: &Ir, chunk_req: usize, sym: &str) -> anyhow::Result<ChunkPlan> {
let n_ops = ir.instrs.len();
let mut def_idx: HashMap<u64, usize> = HashMap::new();
let mut last_use: HashMap<u64, usize> = HashMap::new();
let mut dim_of: HashMap<u64, u64> = HashMap::new();
for (i, instr) in ir.instrs.iter().enumerate() {
if instr.dst_is_tmp {
let id = instr.dst_id.expect("tmp dest without id");
if def_idx.contains_key(&id) {
anyhow::bail!("{sym}: temp t{id} written twice -- IR is not SSA, chunk liveness would be wrong");
}
def_idx.insert(id, i);
dim_of.insert(id, instr.ddim);
}
for opnd in [&instr.a, &instr.b] {
if let Some((tid, tdim)) = opnd.as_tmp() {
last_use.insert(tid, i);
dim_of.insert(tid, tdim);
}
}
}
let chunk = chunk_req.clamp(1, n_ops.max(1));
let n_chunks = n_ops.div_ceil(chunk);
let chunk_of = |op_idx: usize| op_idx / chunk;
let mut out_dim = 3u64;
for instr in &ir.instrs {
if !instr.dst_is_tmp {
out_dim = instr.ddim;
}
}
let cut_temps: HashSet<u64> = def_idx
.keys()
.copied()
.filter(|t| last_use.get(t).is_some_and(|&lu| chunk_of(lu) > chunk_of(def_idx[t])))
.collect();
let color = |temps: &[u64], width: u64| -> (HashMap<u64, u64>, u64) {
let mut sorted: Vec<u64> = temps.to_vec();
sorted.sort_by_key(|&t| (chunk_of(def_idx[&t]), t));
let mut slot_of: HashMap<u64, u64> = HashMap::new();
let mut active: Vec<(usize, u64)> = Vec::new(); let mut free_bases: Vec<u64> = Vec::new(); let mut next_base: u64 = 0;
for t in sorted {
let def_chunk = chunk_of(def_idx[&t]);
let end_chunk = chunk_of(last_use[&t]);
let mut still = Vec::with_capacity(active.len());
for &(end, base) in &active {
if end < def_chunk {
free_bases.push(base);
} else {
still.push((end, base));
}
}
active = still;
let base = free_bases.pop().unwrap_or_else(|| {
let b = next_base;
next_base += width;
b
});
slot_of.insert(t, base);
active.push((end_chunk, base));
}
(slot_of, next_base)
};
let temps1: Vec<u64> = cut_temps.iter().copied().filter(|t| dim_of[t] == 1).collect();
let temps3: Vec<u64> = cut_temps.iter().copied().filter(|t| dim_of[t] == 3).collect();
let (slot1, n_slots1) = color(&temps1, 1);
let (slot3, n_slots3) = color(&temps3, 3);
let base3 = n_slots1;
let total_slots = n_slots1 + n_slots3;
let mut slot_index: HashMap<u64, u64> = HashMap::new();
for &t in &cut_temps {
let s = if dim_of[&t] == 1 { slot1[&t] } else { base3 + slot3[&t] };
slot_index.insert(t, s);
}
Ok(ChunkPlan { chunk, n_chunks, out_dim, def_idx, dim_of, cut_temps, total_slots, slot_index })
}