use crate::HashSet;
use crate::ir::statement::{ProtoForBound, ProtoForRange, ProtoSystemFunctionCall};
use crate::ir::{ProtoExpression, ProtoStatement, VarOffset};
use std::sync::OnceLock;
pub fn enabled() -> bool {
static EN: OnceLock<bool> = OnceLock::new();
*EN.get_or_init(|| std::env::var("VERYL_DEAD_VAR_DCE").ok().as_deref() != Some("0"))
}
#[derive(Default, Clone, Copy)]
struct Liveness {
full_writes: u32,
partial_writes: u32,
inherited_writes: u32,
reads: u32,
}
#[derive(Clone, Copy)]
struct ArrayRange {
base: VarOffset,
num: usize,
stride: isize,
}
impl ArrayRange {
#[inline]
fn contains(&self, off: VarOffset) -> bool {
if off.is_ff() != self.base.is_ff() {
return false;
}
if self.stride == 0 || self.num == 0 {
return false;
}
let delta = off.raw() - self.base.raw();
if delta < 0 {
return false;
}
if delta % self.stride != 0 {
return false;
}
let idx = delta / self.stride;
idx >= 0 && idx < self.num as isize
}
}
#[derive(Default)]
struct Census {
live: crate::HashMap<VarOffset, Liveness>,
ranges: Vec<ArrayRange>,
}
fn walk_expr_reads(expr: &ProtoExpression, c: &mut Census) {
match expr {
ProtoExpression::Variable {
var_offset,
dynamic_select,
..
} => {
let e = c.live.entry(*var_offset).or_default();
e.reads = e.reads.saturating_add(1);
if let Some(dyn_sel) = dynamic_select {
walk_expr_reads(&dyn_sel.index_expr, c);
}
}
ProtoExpression::Value { .. } => {}
ProtoExpression::Unary { x, .. } => walk_expr_reads(x, c),
ProtoExpression::Binary { x, y, .. } => {
walk_expr_reads(x, c);
walk_expr_reads(y, c);
}
ProtoExpression::Concatenation { elements, .. } => {
for (e, _, _) in elements {
walk_expr_reads(e, c);
}
}
ProtoExpression::Ternary {
cond,
true_expr,
false_expr,
..
} => {
walk_expr_reads(cond, c);
walk_expr_reads(true_expr, c);
walk_expr_reads(false_expr, c);
}
ProtoExpression::DynamicVariable {
base_offset,
stride,
index_expr,
num_elements,
dynamic_select,
..
} => {
c.ranges.push(ArrayRange {
base: *base_offset,
num: *num_elements,
stride: *stride,
});
walk_expr_reads(index_expr, c);
if let Some(dyn_sel) = dynamic_select {
walk_expr_reads(&dyn_sel.index_expr, c);
}
}
}
}
fn walk_for_range(range: &ProtoForRange, c: &mut Census) {
let (start, end) = match range {
ProtoForRange::Forward { start, end, .. }
| ProtoForRange::Reverse { start, end, .. }
| ProtoForRange::Stepped { start, end, .. } => (start, end),
};
for bound in [start, end] {
if let ProtoForBound::Dynamic(expr) = bound {
walk_expr_reads(expr, c);
}
}
}
fn walk_stmt_liveness(stmt: &ProtoStatement, c: &mut Census) {
match stmt {
ProtoStatement::Assign(x) => {
walk_expr_reads(&x.expr, c);
if let Some(dyn_sel) = &x.dynamic_select {
walk_expr_reads(&dyn_sel.index_expr, c);
}
let e = c.live.entry(x.dst).or_default();
if x.select.is_some() || x.dynamic_select.is_some() || x.rhs_select.is_some() {
e.partial_writes = e.partial_writes.saturating_add(1);
} else {
e.full_writes = e.full_writes.saturating_add(1);
}
}
ProtoStatement::AssignDynamic(x) => {
walk_expr_reads(&x.dst_index_expr, c);
walk_expr_reads(&x.expr, c);
if let Some(dyn_sel) = &x.dynamic_select {
walk_expr_reads(&dyn_sel.index_expr, c);
}
c.ranges.push(ArrayRange {
base: x.dst_base,
num: x.dst_num_elements,
stride: x.dst_stride,
});
}
ProtoStatement::If(x) => {
if let Some(cond) = &x.cond {
walk_expr_reads(cond, c);
}
for s in &x.true_side {
walk_stmt_liveness(s, c);
}
for s in &x.false_side {
walk_stmt_liveness(s, c);
}
}
ProtoStatement::For(x) => {
let e = c.live.entry(x.var_offset).or_default();
e.partial_writes = e.partial_writes.saturating_add(1);
walk_for_range(&x.range, c);
for s in &x.body {
walk_stmt_liveness(s, c);
}
}
ProtoStatement::SequentialBlock(body) => {
for s in body {
walk_stmt_liveness(s, c);
}
}
ProtoStatement::SystemFunctionCall(x) => match x {
ProtoSystemFunctionCall::Display { args, .. }
| ProtoSystemFunctionCall::Write { args, .. } => {
for a in args {
walk_expr_reads(a, c);
}
}
ProtoSystemFunctionCall::Readmemh { .. } => {}
ProtoSystemFunctionCall::Assert {
condition, args, ..
} => {
walk_expr_reads(condition, c);
for a in args {
walk_expr_reads(a, c);
}
}
ProtoSystemFunctionCall::Finish => {}
},
ProtoStatement::CompiledBlock(x) => {
for s in &x.original_stmts {
walk_stmt_liveness(s, c);
}
}
ProtoStatement::TbMethodCall { .. } => {}
ProtoStatement::Break => {}
}
}
pub fn collect_dead_offsets(slices: &[&[ProtoStatement]]) -> HashSet<VarOffset> {
if !enabled() {
return HashSet::default();
}
let mut census = Census::default();
for slice in slices {
for s in *slice {
walk_stmt_liveness(s, &mut census);
}
}
let mut dead = HashSet::default();
for (off, s) in &census.live {
if off.is_ff() {
continue;
}
if s.reads != 0 {
continue;
}
if s.partial_writes != 0 || s.inherited_writes != 0 {
continue;
}
if s.full_writes == 0 {
continue;
}
if census.ranges.iter().any(|r| r.contains(*off)) {
continue;
}
dead.insert(*off);
}
dead
}
pub fn apply_counting(
stmts: Vec<ProtoStatement>,
dead: &HashSet<VarOffset>,
) -> (Vec<ProtoStatement>, usize) {
if dead.is_empty() {
return (stmts, 0);
}
let mut out: Vec<ProtoStatement> = Vec::with_capacity(stmts.len());
let mut dropped = 0usize;
for s in stmts {
match s {
ProtoStatement::Assign(ref a)
if a.select.is_none()
&& a.dynamic_select.is_none()
&& a.rhs_select.is_none()
&& dead.contains(&a.dst) =>
{
dropped += 1;
}
ProtoStatement::SequentialBlock(body) => {
let (body, d) = apply_counting(body, dead);
dropped += d;
out.push(ProtoStatement::SequentialBlock(body));
}
ProtoStatement::If(mut if_stmt) => {
let (ts, d1) = apply_counting(if_stmt.true_side, dead);
let (fs, d2) = apply_counting(if_stmt.false_side, dead);
dropped += d1 + d2;
if_stmt.true_side = ts;
if_stmt.false_side = fs;
out.push(ProtoStatement::If(if_stmt));
}
ProtoStatement::For(mut for_stmt) => {
let (body, d) = apply_counting(for_stmt.body, dead);
dropped += d;
for_stmt.body = body;
out.push(ProtoStatement::For(for_stmt));
}
other => out.push(other),
}
}
(out, dropped)
}