use crate::HashMap;
use crate::ir::{Declaration, FfTable, Statement, VarId, VarKind, Variable};
#[derive(Clone)]
pub struct HoistPlan {
pub var_id: VarId,
pub var_index: usize,
pub comb_decl_idx: usize,
pub ff_decl_idx: usize,
pub var_kind: VarKind,
}
pub fn plan_hoists(
declarations: &[Declaration],
ff_table: &FfTable,
variables: &HashMap<VarId, Variable>,
) -> Vec<HoistPlan> {
let mut ff_decls: HashMap<usize, ()> = HashMap::default();
for (i, decl) in declarations.iter().enumerate() {
if matches!(decl, Declaration::Ff(_)) {
ff_decls.insert(i, ());
}
}
let mut plans = Vec::new();
for ((var_id, var_index), entry) in &ff_table.table {
let Some(comb_decl_idx) = entry.assigned_comb else {
continue;
};
if entry.refered.is_empty() {
continue;
}
let mut reader_decls: Vec<usize> = entry
.refered
.iter()
.filter_map(|(d, _, from_ff)| if *from_ff { Some(*d) } else { None })
.collect();
if reader_decls.len() != entry.refered.len() {
continue;
}
reader_decls.sort_unstable();
reader_decls.dedup();
if reader_decls.len() != 1 {
continue;
}
let ff_decl_idx = reader_decls[0];
if !ff_decls.contains_key(&ff_decl_idx) {
continue;
}
let var_kind = match variables.get(var_id) {
Some(v) => v.kind,
None => continue,
};
plans.push(HoistPlan {
var_id: *var_id,
var_index: *var_index,
comb_decl_idx,
ff_decl_idx,
var_kind,
});
}
plans
}
pub fn apply_hoists(
declarations: &mut [Declaration],
plans: &[HoistPlan],
variables: &HashMap<VarId, Variable>,
) -> usize {
use std::cell::Cell;
thread_local! {
static GLOBAL_APPLIED: Cell<usize> = const { Cell::new(0) };
static GLOBAL_SEEN: Cell<usize> = const { Cell::new(0) };
static GLOBAL_LIMIT: Cell<Option<usize>> = const { Cell::new(None) };
static GLOBAL_SKIP: Cell<usize> = const { Cell::new(0) };
static LIMIT_READ: Cell<bool> = const { Cell::new(false) };
}
if !LIMIT_READ.with(|c| c.get()) {
GLOBAL_LIMIT.with(|c| {
c.set(
std::env::var("VERYL_COMB_HOIST_LIMIT")
.ok()
.and_then(|s| s.parse::<usize>().ok()),
)
});
GLOBAL_SKIP.with(|c| {
c.set(
std::env::var("VERYL_COMB_HOIST_SKIP")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(0),
)
});
LIMIT_READ.with(|c| c.set(true));
}
let mut by_ff: HashMap<usize, Vec<(VarId, usize, usize)>> = HashMap::default();
for p in plans {
if !matches!(p.var_kind, VarKind::Let) {
continue;
}
let seen = GLOBAL_SEEN.with(|c| {
let v = c.get();
c.set(v + 1);
v
});
if seen < GLOBAL_SKIP.with(|c| c.get()) {
continue;
}
if let Some(lim) = GLOBAL_LIMIT.with(|c| c.get())
&& GLOBAL_APPLIED.with(|c| c.get()) >= lim
{
break;
}
by_ff
.entry(p.ff_decl_idx)
.or_default()
.push((p.var_id, p.var_index, p.comb_decl_idx));
GLOBAL_APPLIED.with(|c| c.set(c.get() + 1));
}
let trace = std::env::var("VERYL_COMB_HOIST_TRACE").ok().as_deref() == Some("1");
let mut applied = 0usize;
for (ff_idx, items) in by_ff {
let mut hoisted_stmts: Vec<Statement> = Vec::new();
for (var_id, var_index, comb_idx) in &items {
let extracted = if let Some(Declaration::Comb(comb)) = declarations.get_mut(*comb_idx) {
extract_top_level_assign(&mut comb.statements, *var_id, *var_index)
} else {
None
};
if let Some(stmt) = extracted {
if trace {
let path = variables
.get(var_id)
.map(|v| v.path.to_string())
.unwrap_or_else(|| "?".to_string());
eprintln!(
"[CombHoistTrace] hoist {path} var={:?}[{}] comb_idx={} ff_idx={}",
var_id, var_index, comb_idx, ff_idx
);
}
hoisted_stmts.push(stmt);
applied += 1;
} else if trace {
eprintln!(
"[CombHoistTrace] FAILED to extract var={:?}[{}] comb_idx={}",
var_id, var_index, comb_idx
);
}
}
if hoisted_stmts.is_empty() {
continue;
}
if let Some(Declaration::Ff(ff)) = declarations.get_mut(ff_idx) {
inject_into_ff_body(&mut ff.statements, hoisted_stmts);
}
}
applied
}
fn extract_top_level_assign(
stmts: &mut Vec<Statement>,
var_id: VarId,
_var_index: usize,
) -> Option<Statement> {
let pos = stmts.iter().position(|s| match s {
Statement::Assign(a) => a.dst.first().map(|d| d.id == var_id).unwrap_or(false),
_ => false,
})?;
Some(stmts.remove(pos))
}
fn inject_into_ff_body(stmts: &mut Vec<Statement>, hoisted: Vec<Statement>) {
if let Some(Statement::IfReset(ifr)) = stmts.first_mut() {
let mut new_false = hoisted;
new_false.append(&mut ifr.false_side);
ifr.false_side = new_false;
return;
}
let mut new_stmts = hoisted;
new_stmts.append(stmts);
*stmts = new_stmts;
}