use crate::lcnf::*;
use std::collections::HashMap;
use super::functions::ValueNumber;
use super::functions::*;
use std::collections::{HashSet, VecDeque};
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GVNAnalysisCache {
pub(super) entries: std::collections::HashMap<String, GVNCacheEntry>,
pub(super) max_size: usize,
pub(super) hits: u64,
pub(super) misses: u64,
}
impl GVNAnalysisCache {
#[allow(dead_code)]
pub fn new(max_size: usize) -> Self {
GVNAnalysisCache {
entries: std::collections::HashMap::new(),
max_size,
hits: 0,
misses: 0,
}
}
#[allow(dead_code)]
pub fn get(&mut self, key: &str) -> Option<&GVNCacheEntry> {
if self.entries.contains_key(key) {
self.hits += 1;
self.entries.get(key)
} else {
self.misses += 1;
None
}
}
#[allow(dead_code)]
pub fn insert(&mut self, key: String, data: Vec<u8>) {
if self.entries.len() >= self.max_size {
if let Some(oldest) = self.entries.keys().next().cloned() {
self.entries.remove(&oldest);
}
}
self.entries.insert(
key.clone(),
GVNCacheEntry {
key,
data,
timestamp: 0,
valid: true,
},
);
}
#[allow(dead_code)]
pub fn invalidate(&mut self, key: &str) {
if let Some(entry) = self.entries.get_mut(key) {
entry.valid = false;
}
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.entries.clear();
}
#[allow(dead_code)]
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
return 0.0;
}
self.hits as f64 / total as f64
}
#[allow(dead_code)]
pub fn size(&self) -> usize {
self.entries.len()
}
}
#[derive(Debug, Default)]
pub struct ExprCanonicaliser {
pub commutative_fns: std::collections::HashSet<String>,
pub canonicalisations: usize,
}
impl ExprCanonicaliser {
pub fn new() -> Self {
let mut c = ExprCanonicaliser::default();
c.commutative_fns.insert("add".to_string());
c.commutative_fns.insert("mul".to_string());
c.commutative_fns.insert("and".to_string());
c.commutative_fns.insert("or".to_string());
c
}
pub fn canonicalise(&mut self, expr: NormExpr) -> NormExpr {
match &expr {
NormExpr::App(NormArg::Vn(_), args) if args.len() == 2 => {
let mut sorted_args = args.clone();
sorted_args.sort_by_key(|a| match a {
NormArg::Vn(vn) => *vn,
NormArg::LitNat(n) => *n as ValueNumber,
_ => u32::MAX,
});
if sorted_args != *args {
self.canonicalisations += 1;
NormExpr::App(
match &expr {
NormExpr::App(f, _) => f.clone(),
_ => unreachable!(),
},
sorted_args,
)
} else {
expr
}
}
_ => expr,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GVNStatistics {
pub total_vns: usize,
pub lit_redundancies: usize,
pub proj_redundancies: usize,
pub ctor_redundancies: usize,
pub app_redundancies: usize,
pub fvar_redundancies: usize,
pub phi_translations: usize,
pub alg_simplifications: usize,
pub time_ns: u64,
}
impl GVNStatistics {
pub fn new() -> Self {
GVNStatistics::default()
}
pub fn total_redundancies(&self) -> usize {
self.lit_redundancies
+ self.proj_redundancies
+ self.ctor_redundancies
+ self.app_redundancies
+ self.fvar_redundancies
}
pub fn print_summary(&self) {
let _ = format!(
"GVN: {} VNs, {} redundancies ({} lit, {} proj, {} ctor, {} app, {} fvar)",
self.total_vns,
self.total_redundancies(),
self.lit_redundancies,
self.proj_redundancies,
self.ctor_redundancies,
self.app_redundancies,
self.fvar_redundancies,
);
}
}
#[derive(Debug, Clone, Default)]
pub struct GVNReport {
pub expressions_numbered: usize,
pub redundancies_eliminated: usize,
pub phi_translations: usize,
}
impl GVNReport {
pub fn merge(&mut self, other: &GVNReport) {
self.expressions_numbered += other.expressions_numbered;
self.redundancies_eliminated += other.redundancies_eliminated;
self.phi_translations += other.phi_translations;
}
}
#[derive(Debug, Default)]
pub struct CCPState {
pub known: HashMap<LcnfVarId, KnownConstant>,
pub folded: usize,
pub dead_branches: usize,
}
impl CCPState {
pub fn new() -> Self {
CCPState::default()
}
pub fn set_known(&mut self, var: LcnfVarId, val: KnownConstant) {
self.known.insert(var, val);
}
pub fn get_known(&self, var: &LcnfVarId) -> &KnownConstant {
self.known.get(var).unwrap_or(&KnownConstant::Top)
}
pub fn run(&mut self, decl: &mut LcnfFunDecl) {
self.propagate_in_expr(&mut decl.body);
}
pub(super) fn propagate_in_expr(&mut self, expr: &mut LcnfExpr) {
match expr {
LcnfExpr::Let {
id, value, body, ..
} => {
if let LcnfLetValue::Lit(lit) = value {
self.set_known(*id, KnownConstant::Lit(lit.clone()));
}
self.propagate_in_expr(body);
}
LcnfExpr::Case {
scrutinee,
alts,
default,
..
} => {
match self.get_known(scrutinee).clone() {
KnownConstant::Lit(LcnfLit::Nat(n)) => {
if let Some(alt) = alts.iter().find(|a| a.ctor_tag == n as u32) {
self.dead_branches += alts.len() - 1;
let matching_body = alt.body.clone();
*expr = matching_body;
self.folded += 1;
return;
}
}
_ => {}
}
for alt in alts.iter_mut() {
self.propagate_in_expr(&mut alt.body);
}
if let Some(d) = default {
self.propagate_in_expr(d);
}
}
_ => {}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GVNFunctionSummary {
pub return_vns: Vec<ValueNumber>,
pub is_pure_fn: bool,
pub param_equalities: Vec<(usize, usize)>,
}
impl GVNFunctionSummary {
pub fn new() -> Self {
GVNFunctionSummary::default()
}
pub fn mark_pure(&mut self) {
self.is_pure_fn = true;
}
}
#[derive(Debug, Default)]
pub struct ScopedValueContext {
pub stack: Vec<Vec<(LcnfVarId, ValueNumber)>>,
pub current: HashMap<LcnfVarId, ValueNumber>,
}
impl ScopedValueContext {
pub fn new() -> Self {
ScopedValueContext {
stack: vec![Vec::new()],
current: HashMap::new(),
}
}
pub fn push_scope(&mut self) {
self.stack.push(Vec::new());
}
pub fn pop_scope(&mut self) {
if let Some(scope) = self.stack.pop() {
for (var, _) in scope {
self.current.remove(&var);
}
}
}
pub fn bind(&mut self, var: LcnfVarId, vn: ValueNumber) {
self.current.insert(var, vn);
if let Some(scope) = self.stack.last_mut() {
scope.push((var, vn));
}
}
pub fn lookup(&self, var: &LcnfVarId) -> Option<ValueNumber> {
self.current.get(var).copied()
}
pub fn scope_depth(&self) -> usize {
self.stack.len()
}
}
#[derive(Debug, Default)]
pub struct HashConsingTable {
pub(super) table: HashMap<NormExpr, LcnfLetValue>,
}
impl HashConsingTable {
pub fn new() -> Self {
HashConsingTable::default()
}
pub fn intern(&mut self, key: NormExpr, value: LcnfLetValue) -> &LcnfLetValue {
self.table.entry(key).or_insert(value)
}
pub fn len(&self) -> usize {
self.table.len()
}
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
}
#[derive(Debug, Default)]
pub struct RedundancyCollector {
pub redundancies: Vec<Redundancy>,
}
impl RedundancyCollector {
pub fn new() -> Self {
RedundancyCollector::default()
}
pub fn collect(&mut self, decl: &LcnfFunDecl) {
let mut pass = GVNPass::default();
let mut table = ValueTable::new();
let mut fact = GVNFact::new();
pass.assign_value_numbers(decl, &mut table, &mut fact);
self.find_redundant(&decl.body, &table, &GVNFact::new());
}
pub(super) fn find_redundant(&mut self, expr: &LcnfExpr, table: &ValueTable, fact: &GVNFact) {
let mut fact = fact.clone();
match expr {
LcnfExpr::Let {
id, value, body, ..
} => {
let key = gvn_norm_value(value, &fact);
if let Some(vn) = table.lookup(&key) {
if let Some(canon) = table.canonical_var(vn) {
if canon != *id {
self.redundancies.push(Redundancy {
redundant_var: *id,
canonical_var: canon,
vn,
});
}
}
fact.insert(*id, vn);
}
self.find_redundant(body, table, &fact);
}
LcnfExpr::Case { alts, default, .. } => {
for alt in alts {
self.find_redundant(&alt.body, table, &fact);
}
if let Some(d) = default {
self.find_redundant(d, table, &fact);
}
}
_ => {}
}
}
pub fn num_redundancies(&self) -> usize {
self.redundancies.len()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GVNDominatorTree {
pub idom: Vec<Option<u32>>,
pub dom_children: Vec<Vec<u32>>,
pub dom_depth: Vec<u32>,
}
impl GVNDominatorTree {
#[allow(dead_code)]
pub fn new(size: usize) -> Self {
GVNDominatorTree {
idom: vec![None; size],
dom_children: vec![Vec::new(); size],
dom_depth: vec![0; size],
}
}
#[allow(dead_code)]
pub fn set_idom(&mut self, node: usize, idom: u32) {
self.idom[node] = Some(idom);
}
#[allow(dead_code)]
pub fn dominates(&self, a: usize, b: usize) -> bool {
if a == b {
return true;
}
let mut cur = b;
loop {
match self.idom[cur] {
Some(parent) if parent as usize == a => return true,
Some(parent) if parent as usize == cur => return false,
Some(parent) => cur = parent as usize,
None => return false,
}
}
}
#[allow(dead_code)]
pub fn depth(&self, node: usize) -> u32 {
self.dom_depth.get(node).copied().unwrap_or(0)
}
}
#[derive(Debug, Clone)]
pub struct GVNConfig {
pub do_phi_translation: bool,
pub max_depth: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Predicate {
EqLit(LcnfVarId, LcnfLit),
NeLit(LcnfVarId, LcnfLit),
VarEq(LcnfVarId, LcnfVarId),
}
#[derive(Debug, Clone, Default)]
pub struct AnticipationSet {
pub anticipated: std::collections::HashSet<NormExpr>,
}
impl AnticipationSet {
pub fn new() -> Self {
AnticipationSet::default()
}
pub fn add(&mut self, expr: NormExpr) {
self.anticipated.insert(expr);
}
pub fn contains(&self, expr: &NormExpr) -> bool {
self.anticipated.contains(expr)
}
pub fn meet(&self, other: &AnticipationSet) -> AnticipationSet {
AnticipationSet {
anticipated: self
.anticipated
.intersection(&other.anticipated)
.cloned()
.collect(),
}
}
pub fn is_empty(&self) -> bool {
self.anticipated.is_empty()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GVNLivenessInfo {
pub live_in: Vec<std::collections::HashSet<u32>>,
pub live_out: Vec<std::collections::HashSet<u32>>,
pub defs: Vec<std::collections::HashSet<u32>>,
pub uses: Vec<std::collections::HashSet<u32>>,
}
impl GVNLivenessInfo {
#[allow(dead_code)]
pub fn new(block_count: usize) -> Self {
GVNLivenessInfo {
live_in: vec![std::collections::HashSet::new(); block_count],
live_out: vec![std::collections::HashSet::new(); block_count],
defs: vec![std::collections::HashSet::new(); block_count],
uses: vec![std::collections::HashSet::new(); block_count],
}
}
#[allow(dead_code)]
pub fn add_def(&mut self, block: usize, var: u32) {
if block < self.defs.len() {
self.defs[block].insert(var);
}
}
#[allow(dead_code)]
pub fn add_use(&mut self, block: usize, var: u32) {
if block < self.uses.len() {
self.uses[block].insert(var);
}
}
#[allow(dead_code)]
pub fn is_live_in(&self, block: usize, var: u32) -> bool {
self.live_in
.get(block)
.map(|s| s.contains(&var))
.unwrap_or(false)
}
#[allow(dead_code)]
pub fn is_live_out(&self, block: usize, var: u32) -> bool {
self.live_out
.get(block)
.map(|s| s.contains(&var))
.unwrap_or(false)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct GVNPassStats {
pub total_runs: u32,
pub successful_runs: u32,
pub total_changes: u64,
pub time_ms: u64,
pub iterations_used: u32,
}
impl GVNPassStats {
#[allow(dead_code)]
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)]
pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
self.total_runs += 1;
self.successful_runs += 1;
self.total_changes += changes;
self.time_ms += time_ms;
self.iterations_used = iterations;
}
#[allow(dead_code)]
pub fn average_changes_per_run(&self) -> f64 {
if self.total_runs == 0 {
return 0.0;
}
self.total_changes as f64 / self.total_runs as f64
}
#[allow(dead_code)]
pub fn success_rate(&self) -> f64 {
if self.total_runs == 0 {
return 0.0;
}
self.successful_runs as f64 / self.total_runs as f64
}
#[allow(dead_code)]
pub fn format_summary(&self) -> String {
format!(
"Runs: {}/{}, Changes: {}, Time: {}ms",
self.successful_runs, self.total_runs, self.total_changes, self.time_ms
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum NormArg {
Vn(ValueNumber),
LitNat(u64),
LitStr(String),
Erased,
}
#[derive(Debug, Default)]
pub struct LoadEliminatorGVN {
pub eliminated: usize,
pub(super) load_cache: HashMap<(LcnfVarId, u32), LcnfVarId>,
}
impl LoadEliminatorGVN {
pub fn new() -> Self {
LoadEliminatorGVN::default()
}
pub fn run(&mut self, decl: &mut LcnfFunDecl) {
self.load_cache.clear();
self.elim_in_expr(&mut decl.body);
}
pub(super) fn elim_in_expr(&mut self, expr: &mut LcnfExpr) {
match expr {
LcnfExpr::Let {
id, value, body, ..
} => {
if let LcnfLetValue::Proj(_, idx, src) = value {
let key = (*src, *idx);
if let Some(&cached) = self.load_cache.get(&key) {
*value = LcnfLetValue::FVar(cached);
self.eliminated += 1;
} else {
self.load_cache.insert(key, *id);
}
}
if let LcnfLetValue::Reuse(slot, _, _, _) = value {
let slot_copy = *slot;
self.load_cache.retain(|&(src, _), _| src != slot_copy);
}
self.elim_in_expr(body);
}
LcnfExpr::Case { alts, default, .. } => {
let saved = self.load_cache.clone();
for alt in alts.iter_mut() {
self.load_cache = saved.clone();
self.elim_in_expr(&mut alt.body);
}
if let Some(d) = default {
self.load_cache = saved;
self.elim_in_expr(d);
}
}
_ => {}
}
}
}
#[derive(Debug, Default)]
pub struct PredicateGVN {
pub(super) active_preds: Vec<Predicate>,
pub equalities_derived: usize,
}
impl PredicateGVN {
pub fn new() -> Self {
PredicateGVN::default()
}
pub fn enter_branch(&mut self, scrutinee: LcnfVarId, ctor_tag: u32) {
self.active_preds
.push(Predicate::EqLit(scrutinee, LcnfLit::Nat(ctor_tag as u64)));
}
pub fn exit_branch(&mut self) {
self.active_preds.pop();
}
pub fn knows_eq_lit(&self, var: LcnfVarId, lit: &LcnfLit) -> bool {
self.active_preds
.contains(&Predicate::EqLit(var, lit.clone()))
}
pub fn run(&mut self, decl: &mut LcnfFunDecl, base_pass: &mut GVNPass) {
self.run_in_expr(&mut decl.body, base_pass);
}
pub(super) fn run_in_expr(&mut self, expr: &mut LcnfExpr, base_pass: &mut GVNPass) {
match expr {
LcnfExpr::Let { body, .. } => {
self.run_in_expr(body, base_pass);
}
LcnfExpr::Case {
scrutinee,
alts,
default,
..
} => {
for alt in alts.iter_mut() {
self.enter_branch(*scrutinee, alt.ctor_tag);
self.equalities_derived += 1;
self.run_in_expr(&mut alt.body, base_pass);
self.exit_branch();
}
if let Some(d) = default {
self.run_in_expr(d, base_pass);
}
}
_ => {}
}
}
}
#[derive(Debug)]
pub struct AlgebraicSimplifier {
pub rules: Vec<AlgRule>,
pub total_simplified: usize,
}
impl AlgebraicSimplifier {
pub fn new() -> Self {
AlgebraicSimplifier::default()
}
pub fn simplify(&mut self, expr: &NormExpr, fact: &GVNFact) -> Option<NormExpr> {
let _ = fact;
match expr {
NormExpr::App(NormArg::Vn(_), args) if args.last() == Some(&NormArg::LitNat(0)) => {
self.rules[0].applied += 1;
self.total_simplified += 1;
if let Some(NormArg::Vn(vn)) = args.first() {
Some(NormExpr::FVar(*vn))
} else {
None
}
}
NormExpr::App(NormArg::Vn(_), args)
if args.last() == Some(&NormArg::LitNat(1)) && args.len() == 2 =>
{
self.rules[1].applied += 1;
self.total_simplified += 1;
if let Some(NormArg::Vn(vn)) = args.first() {
Some(NormExpr::FVar(*vn))
} else {
None
}
}
_ => None,
}
}
pub fn run(&mut self, _decl: &mut LcnfFunDecl) {}
}
#[derive(Debug, Clone)]
pub struct DomTreeNode {
pub var: LcnfVarId,
pub children: Vec<LcnfVarId>,
pub depth: u32,
}
#[derive(Debug, Default)]
pub struct PhiNodeSet {
pub phis: Vec<PhiNode>,
pub(super) next_vn: ValueNumber,
}
impl PhiNodeSet {
pub fn new(start_vn: ValueNumber) -> Self {
PhiNodeSet {
phis: Vec::new(),
next_vn: start_vn,
}
}
pub fn add_phi(&mut self, var: LcnfVarId, operands: Vec<PhiOperand>) -> &PhiNode {
let vn = self.next_vn;
self.next_vn += 1;
self.phis.push(PhiNode::new(var, operands, vn));
self.phis
.last()
.expect("phis is non-empty after push; invariant guaranteed by add_phi")
}
pub fn remove_trivial(&mut self) -> usize {
let before = self.phis.len();
self.phis.retain(|p| !p.is_trivial());
before - self.phis.len()
}
pub fn num_phis(&self) -> usize {
self.phis.len()
}
}
#[allow(dead_code)]
pub struct GVNConstantFoldingHelper;
impl GVNConstantFoldingHelper {
#[allow(dead_code)]
pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
a.checked_add(b)
}
#[allow(dead_code)]
pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
a.checked_sub(b)
}
#[allow(dead_code)]
pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
a.checked_mul(b)
}
#[allow(dead_code)]
pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
if b == 0 {
None
} else {
a.checked_div(b)
}
}
#[allow(dead_code)]
pub fn fold_add_f64(a: f64, b: f64) -> f64 {
a + b
}
#[allow(dead_code)]
pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
a * b
}
#[allow(dead_code)]
pub fn fold_neg_i64(a: i64) -> Option<i64> {
a.checked_neg()
}
#[allow(dead_code)]
pub fn fold_not_bool(a: bool) -> bool {
!a
}
#[allow(dead_code)]
pub fn fold_and_bool(a: bool, b: bool) -> bool {
a && b
}
#[allow(dead_code)]
pub fn fold_or_bool(a: bool, b: bool) -> bool {
a || b
}
#[allow(dead_code)]
pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
a.checked_shl(b)
}
#[allow(dead_code)]
pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
a.checked_shr(b)
}
#[allow(dead_code)]
pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
if b == 0 {
None
} else {
Some(a % b)
}
}
#[allow(dead_code)]
pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
a & b
}
#[allow(dead_code)]
pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
a | b
}
#[allow(dead_code)]
pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
a ^ b
}
#[allow(dead_code)]
pub fn fold_bitnot_i64(a: i64) -> i64 {
!a
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GVNDepGraph {
pub(super) nodes: Vec<u32>,
pub(super) edges: Vec<(u32, u32)>,
}
impl GVNDepGraph {
#[allow(dead_code)]
pub fn new() -> Self {
GVNDepGraph {
nodes: Vec::new(),
edges: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_node(&mut self, id: u32) {
if !self.nodes.contains(&id) {
self.nodes.push(id);
}
}
#[allow(dead_code)]
pub fn add_dep(&mut self, dep: u32, dependent: u32) {
self.add_node(dep);
self.add_node(dependent);
self.edges.push((dep, dependent));
}
#[allow(dead_code)]
pub fn dependents_of(&self, node: u32) -> Vec<u32> {
self.edges
.iter()
.filter(|(d, _)| *d == node)
.map(|(_, dep)| *dep)
.collect()
}
#[allow(dead_code)]
pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
self.edges
.iter()
.filter(|(_, dep)| *dep == node)
.map(|(d, _)| *d)
.collect()
}
#[allow(dead_code)]
pub fn topological_sort(&self) -> Vec<u32> {
let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
for &n in &self.nodes {
in_degree.insert(n, 0);
}
for (_, dep) in &self.edges {
*in_degree.entry(*dep).or_insert(0) += 1;
}
let mut queue: std::collections::VecDeque<u32> = self
.nodes
.iter()
.filter(|&&n| in_degree[&n] == 0)
.copied()
.collect();
let mut result = Vec::new();
while let Some(node) = queue.pop_front() {
result.push(node);
for dep in self.dependents_of(node) {
let cnt = in_degree.entry(dep).or_insert(0);
*cnt = cnt.saturating_sub(1);
if *cnt == 0 {
queue.push_back(dep);
}
}
}
result
}
#[allow(dead_code)]
pub fn has_cycle(&self) -> bool {
self.topological_sort().len() < self.nodes.len()
}
}
#[derive(Debug, Default)]
pub struct FixpointGVN {
pub max_iter: usize,
pub iterations: usize,
pub converged: bool,
pub total_redundancies: usize,
}
impl FixpointGVN {
pub fn new(max_iter: usize) -> Self {
FixpointGVN {
max_iter,
iterations: 0,
converged: false,
total_redundancies: 0,
}
}
pub fn run(&mut self, decl: &mut LcnfFunDecl) {
let mut prev_state = FixpointState::new();
for iter in 0..self.max_iter {
self.iterations = iter + 1;
let mut pass = GVNPass::default();
let mut table = ValueTable::new();
let mut fact = GVNFact::new();
pass.assign_value_numbers(decl, &mut table, &mut fact);
let redundancies = pass.report().redundancies_eliminated;
self.total_redundancies += redundancies;
let curr_state = FixpointState {
table: table.clone(),
exit_fact: fact,
redundancies,
};
if curr_state.table.len() == prev_state.table.len() {
self.converged = true;
break;
}
pass.eliminate_redundant(decl, &mut table);
prev_state = curr_state;
}
}
}
#[derive(Debug, Clone)]
pub struct AlgRule {
pub name: String,
pub applied: usize,
}
impl AlgRule {
pub fn new(name: &str) -> Self {
AlgRule {
name: name.to_string(),
applied: 0,
}
}
}
#[derive(Debug, Default)]
pub struct DomTree {
pub nodes: HashMap<LcnfVarId, DomTreeNode>,
pub roots: Vec<LcnfVarId>,
}
impl DomTree {
pub fn new() -> Self {
DomTree::default()
}
pub fn build_from_decl(decl: &LcnfFunDecl) -> Self {
let mut dt = DomTree::new();
let mut parent: Option<LcnfVarId> = None;
dt.build_in_expr(&decl.body, &mut parent, 0);
dt
}
pub(super) fn build_in_expr(
&mut self,
expr: &LcnfExpr,
parent: &mut Option<LcnfVarId>,
depth: u32,
) {
match expr {
LcnfExpr::Let { id, body, .. } => {
let node = DomTreeNode {
var: *id,
children: Vec::new(),
depth,
};
self.nodes.insert(*id, node);
if let Some(p) = *parent {
if let Some(pn) = self.nodes.get_mut(&p) {
pn.children.push(*id);
}
} else {
self.roots.push(*id);
}
let prev = *parent;
*parent = Some(*id);
self.build_in_expr(body, parent, depth + 1);
*parent = prev;
}
LcnfExpr::Case { alts, default, .. } => {
for alt in alts {
let mut br_parent = *parent;
self.build_in_expr(&alt.body, &mut br_parent, depth);
}
if let Some(d) = default {
let mut br_parent = *parent;
self.build_in_expr(d, &mut br_parent, depth);
}
}
_ => {}
}
}
pub fn dominates(&self, a: LcnfVarId, b: LcnfVarId) -> bool {
if a == b {
return true;
}
if let Some(node_b) = self.nodes.get(&b) {
let _ = node_b;
}
self.is_ancestor(a, b)
}
pub(super) fn is_ancestor(&self, a: LcnfVarId, target: LcnfVarId) -> bool {
if let Some(node) = self.nodes.get(&a) {
for &child in &node.children {
if child == target || self.is_ancestor(child, target) {
return true;
}
}
}
false
}
pub fn num_nodes(&self) -> usize {
self.nodes.len()
}
}
#[derive(Debug, Default)]
pub struct GVNPrePass {
pub insertions: usize,
pub eliminations: usize,
pub anticipation: HashMap<LcnfVarId, AnticipationSet>,
}
impl GVNPrePass {
pub fn new() -> Self {
GVNPrePass::default()
}
pub fn compute_anticipation(&mut self, decl: &LcnfFunDecl) {
let mut anticipated = AnticipationSet::new();
self.anticip_in_expr(&decl.body, &mut anticipated);
}
pub(super) fn anticip_in_expr(&mut self, expr: &LcnfExpr, anticipated: &mut AnticipationSet) {
match expr {
LcnfExpr::Let {
id, value, body, ..
} => {
self.anticipation.insert(*id, anticipated.clone());
let key = norm_expr_from_value_conservative(value);
anticipated.add(key);
self.anticip_in_expr(body, anticipated);
}
LcnfExpr::Case { alts, default, .. } => {
let mut branch_sets: Vec<AnticipationSet> = Vec::new();
for alt in alts {
let mut br = anticipated.clone();
self.anticip_in_expr(&alt.body, &mut br);
branch_sets.push(br);
}
if let Some(d) = default {
let mut br = anticipated.clone();
self.anticip_in_expr(d, &mut br);
branch_sets.push(br);
}
if let Some(first) = branch_sets.first() {
let mut meet = first.clone();
for bs in branch_sets.iter().skip(1) {
meet = meet.meet(bs);
}
*anticipated = meet;
}
}
_ => {}
}
}
pub fn run(&mut self, decl: &mut LcnfFunDecl) {
self.compute_anticipation(decl);
self.insertions = self
.anticipation
.values()
.map(|a| a.anticipated.len())
.sum();
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PhiOperand {
pub branch_idx: usize,
pub vn: ValueNumber,
}
pub struct GVNPass {
pub(super) config: GVNConfig,
pub(super) report: GVNReport,
}
impl GVNPass {
pub fn new(config: GVNConfig) -> Self {
GVNPass {
config,
report: GVNReport::default(),
}
}
pub fn run(&mut self, decls: &mut [LcnfFunDecl]) {
for decl in decls.iter_mut() {
let mut table = ValueTable::new();
let mut fact = GVNFact::new();
self.assign_value_numbers(decl, &mut table, &mut fact);
self.eliminate_redundant(decl, &mut table);
}
}
pub fn assign_value_numbers(
&mut self,
decl: &LcnfFunDecl,
table: &mut ValueTable,
fact: &mut GVNFact,
) {
let mut depth = 0usize;
self.vn_expr(&decl.body, table, fact, &mut depth);
}
pub fn lookup_or_assign(
&mut self,
var: LcnfVarId,
value: &LcnfLetValue,
table: &mut ValueTable,
fact: &mut GVNFact,
) -> ValueNumber {
let key = self.normalise_let_value(value, fact);
if let Some(vn) = table.lookup(&key) {
fact.insert(var, vn);
vn
} else {
let vn = table.insert(key, value.clone(), var);
fact.insert(var, vn);
self.report.expressions_numbered += 1;
vn
}
}
pub fn eliminate_redundant(&mut self, decl: &mut LcnfFunDecl, table: &mut ValueTable) {
let mut fact = GVNFact::new();
self.rewrite_expr(&mut decl.body, table, &mut fact);
}
pub fn report(&self) -> GVNReport {
self.report.clone()
}
pub(super) fn vn_expr(
&mut self,
expr: &LcnfExpr,
table: &mut ValueTable,
fact: &mut GVNFact,
depth: &mut usize,
) {
if *depth >= self.config.max_depth {
return;
}
*depth += 1;
match expr {
LcnfExpr::Let {
id, value, body, ..
} => {
self.lookup_or_assign(*id, value, table, fact);
self.vn_expr(body, table, fact, depth);
}
LcnfExpr::Case { alts, default, .. } => {
for alt in alts {
let mut branch_fact = fact.clone();
let mut d = *depth;
self.vn_expr(&alt.body, table, &mut branch_fact, &mut d);
if self.config.do_phi_translation {
self.report.phi_translations += 1;
}
}
if let Some(def) = default {
let mut branch_fact = fact.clone();
let mut d = *depth;
self.vn_expr(def, table, &mut branch_fact, &mut d);
}
}
LcnfExpr::Return(_) | LcnfExpr::Unreachable | LcnfExpr::TailCall(..) => {}
}
*depth -= 1;
}
pub(super) fn rewrite_expr(
&mut self,
expr: &mut LcnfExpr,
table: &mut ValueTable,
fact: &mut GVNFact,
) {
match expr {
LcnfExpr::Let {
id,
value,
body,
ty,
..
} => {
let key = self.normalise_let_value(value, fact);
if let Some(vn) = table.lookup(&key) {
if let Some(canon) = table.canonical_var(vn) {
if canon != *id {
*value = LcnfLetValue::FVar(canon);
fact.insert(*id, vn);
self.report.redundancies_eliminated += 1;
} else {
fact.insert(*id, vn);
}
} else {
fact.insert(*id, vn);
}
} else {
let vn = table.insert(key, value.clone(), *id);
fact.insert(*id, vn);
let _ = ty;
}
self.rewrite_expr(body, table, fact);
}
LcnfExpr::Case { alts, default, .. } => {
for alt in alts.iter_mut() {
let mut branch_fact = fact.clone();
self.rewrite_expr(&mut alt.body, table, &mut branch_fact);
}
if let Some(def) = default {
let mut branch_fact = fact.clone();
self.rewrite_expr(def, table, &mut branch_fact);
}
}
LcnfExpr::Return(_) | LcnfExpr::Unreachable | LcnfExpr::TailCall(..) => {}
}
}
pub(super) fn normalise_let_value(&self, value: &LcnfLetValue, fact: &GVNFact) -> NormExpr {
match value {
LcnfLetValue::Lit(LcnfLit::Nat(n)) => NormExpr::Lit(*n),
LcnfLetValue::Lit(LcnfLit::Str(s)) => NormExpr::LitStr(s.clone()),
LcnfLetValue::Erased => NormExpr::Erased,
LcnfLetValue::FVar(v) => {
let vn = fact.get(v).unwrap_or(v.0 as ValueNumber + 1_000_000);
NormExpr::FVar(vn)
}
LcnfLetValue::Proj(name, idx, v) => {
let vn = fact.get(v).unwrap_or(v.0 as ValueNumber + 1_000_000);
NormExpr::Proj(name.clone(), *idx, vn)
}
LcnfLetValue::Ctor(name, tag, args) => {
let nargs = args.iter().map(|a| self.norm_arg(a, fact)).collect();
NormExpr::Ctor(name.clone(), *tag, nargs)
}
LcnfLetValue::App(f, args) => {
let nf = self.norm_arg(f, fact);
let nargs = args.iter().map(|a| self.norm_arg(a, fact)).collect();
NormExpr::App(nf, nargs)
}
LcnfLetValue::Reset(v) => {
let vn = fact.get(v).unwrap_or(v.0 as ValueNumber + 1_000_000);
NormExpr::Reset(vn)
}
LcnfLetValue::Reuse(..) => NormExpr::Unknown,
}
}
pub(super) fn norm_arg(&self, arg: &LcnfArg, fact: &GVNFact) -> NormArg {
match arg {
LcnfArg::Var(v) => {
let vn = fact.get(v).unwrap_or(v.0 as ValueNumber + 1_000_000);
NormArg::Vn(vn)
}
LcnfArg::Lit(LcnfLit::Nat(n)) => NormArg::LitNat(*n),
LcnfArg::Lit(LcnfLit::Str(s)) => NormArg::LitStr(s.clone()),
LcnfArg::Erased => NormArg::Erased,
LcnfArg::Type(_) => NormArg::Erased,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GVNWorklist {
pub(super) items: std::collections::VecDeque<u32>,
pub(super) in_worklist: std::collections::HashSet<u32>,
}
impl GVNWorklist {
#[allow(dead_code)]
pub fn new() -> Self {
GVNWorklist {
items: std::collections::VecDeque::new(),
in_worklist: std::collections::HashSet::new(),
}
}
#[allow(dead_code)]
pub fn push(&mut self, item: u32) -> bool {
if self.in_worklist.insert(item) {
self.items.push_back(item);
true
} else {
false
}
}
#[allow(dead_code)]
pub fn pop(&mut self) -> Option<u32> {
let item = self.items.pop_front()?;
self.in_worklist.remove(&item);
Some(item)
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.items.len()
}
#[allow(dead_code)]
pub fn contains(&self, item: u32) -> bool {
self.in_worklist.contains(&item)
}
}
#[derive(Debug, Default)]
pub struct CongruenceClosure {
pub(super) parent: HashMap<ValueNumber, ValueNumber>,
}
impl CongruenceClosure {
pub fn new() -> Self {
CongruenceClosure::default()
}
pub fn union(&mut self, a: ValueNumber, b: ValueNumber) {
let ra = self.find(a);
let rb = self.find(b);
if ra != rb {
self.parent.insert(ra, rb);
}
}
pub fn find(&mut self, vn: ValueNumber) -> ValueNumber {
match self.parent.get(&vn).copied() {
None => vn,
Some(p) if p == vn => vn,
Some(p) => {
let root = self.find(p);
self.parent.insert(vn, root);
root
}
}
}
pub fn are_equal(&mut self, a: ValueNumber, b: ValueNumber) -> bool {
self.find(a) == self.find(b)
}
pub fn num_classes(&self) -> usize {
self.parent.len()
}
}
#[derive(Debug, Clone, Default)]
pub struct GVNFact {
pub var_to_vn: HashMap<LcnfVarId, ValueNumber>,
}
impl GVNFact {
pub fn new() -> Self {
GVNFact::default()
}
pub fn get(&self, var: &LcnfVarId) -> Option<ValueNumber> {
self.var_to_vn.get(var).copied()
}
pub fn insert(&mut self, var: LcnfVarId, vn: ValueNumber) {
self.var_to_vn.insert(var, vn);
}
pub fn meet(&self, other: &GVNFact) -> GVNFact {
let mut result = GVNFact::new();
for (var, &vn) in &self.var_to_vn {
if other.var_to_vn.get(var) == Some(&vn) {
result.var_to_vn.insert(*var, vn);
}
}
result
}
}
#[derive(Debug, Default)]
pub struct LeaderFinder {
pub(super) leaders: HashMap<ValueNumber, LcnfVarId>,
pub(super) members: HashMap<ValueNumber, Vec<LcnfVarId>>,
}
impl LeaderFinder {
pub fn new() -> Self {
LeaderFinder::default()
}
pub fn record(&mut self, vn: ValueNumber, var: LcnfVarId) {
self.members.entry(vn).or_default().push(var);
self.leaders.entry(vn).or_insert(var);
}
pub fn leader(&self, vn: ValueNumber) -> Option<LcnfVarId> {
self.leaders.get(&vn).copied()
}
pub fn members(&self, vn: ValueNumber) -> &[LcnfVarId] {
self.members.get(&vn).map(|v| v.as_slice()).unwrap_or(&[])
}
pub fn num_redundancies(&self) -> usize {
self.members.values().filter(|v| v.len() > 1).count()
}
}
pub struct GVNPipeline {
pub do_base_gvn: bool,
pub do_load_elim: bool,
pub do_pre: bool,
pub do_ccp: bool,
pub do_fixpoint: bool,
pub max_fixpoint_iter: usize,
pub stats: GVNStatistics,
}
impl GVNPipeline {
pub fn new() -> Self {
GVNPipeline::default()
}
pub fn run(&mut self, decls: &mut [LcnfFunDecl]) {
for decl in decls.iter_mut() {
if self.do_base_gvn {
let mut pass = GVNPass::default();
pass.run(std::slice::from_mut(decl));
let r = pass.report();
self.stats.total_vns += r.expressions_numbered;
self.stats.phi_translations += r.phi_translations;
}
if self.do_load_elim {
let mut le = LoadEliminatorGVN::new();
le.run(decl);
self.stats.proj_redundancies += le.eliminated;
}
if self.do_pre {
let mut pre = GVNPrePass::new();
pre.run(decl);
}
if self.do_ccp {
let mut ccp = CCPState::new();
ccp.run(decl);
self.stats.lit_redundancies += ccp.folded;
}
if self.do_fixpoint {
let mut fp = FixpointGVN::new(self.max_fixpoint_iter);
fp.run(decl);
self.stats.total_vns += fp.total_redundancies;
}
}
}
pub fn total_redundancies(&self) -> usize {
self.stats.total_redundancies()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum GVNPassPhase {
Analysis,
Transformation,
Verification,
Cleanup,
}
impl GVNPassPhase {
#[allow(dead_code)]
pub fn name(&self) -> &str {
match self {
GVNPassPhase::Analysis => "analysis",
GVNPassPhase::Transformation => "transformation",
GVNPassPhase::Verification => "verification",
GVNPassPhase::Cleanup => "cleanup",
}
}
#[allow(dead_code)]
pub fn is_modifying(&self) -> bool {
matches!(self, GVNPassPhase::Transformation | GVNPassPhase::Cleanup)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GVNCacheEntry {
pub key: String,
pub data: Vec<u8>,
pub timestamp: u64,
pub valid: bool,
}
#[derive(Debug, Clone)]
pub struct FixpointState {
pub table: ValueTable,
pub exit_fact: GVNFact,
pub redundancies: usize,
}
impl FixpointState {
pub fn new() -> Self {
FixpointState {
table: ValueTable::new(),
exit_fact: GVNFact::new(),
redundancies: 0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ValueTable {
pub(super) expr_to_vn: HashMap<NormExpr, ValueNumber>,
pub(super) vn_to_expr: HashMap<ValueNumber, LcnfLetValue>,
pub(super) vn_to_var: HashMap<ValueNumber, LcnfVarId>,
pub(super) next_vn: ValueNumber,
}
impl ValueTable {
pub fn new() -> Self {
ValueTable::default()
}
pub fn lookup(&self, key: &NormExpr) -> Option<ValueNumber> {
self.expr_to_vn.get(key).copied()
}
pub fn canonical_var(&self, vn: ValueNumber) -> Option<LcnfVarId> {
self.vn_to_var.get(&vn).copied()
}
pub fn insert(&mut self, key: NormExpr, value: LcnfLetValue, var: LcnfVarId) -> ValueNumber {
let vn = self.next_vn;
self.next_vn += 1;
self.expr_to_vn.insert(key, vn);
self.vn_to_expr.insert(vn, value);
self.vn_to_var.insert(vn, var);
vn
}
pub fn len(&self) -> usize {
self.next_vn as usize
}
pub fn is_empty(&self) -> bool {
self.next_vn == 0
}
pub fn snapshot(&self) -> Vec<(NormExpr, ValueNumber)> {
self.expr_to_vn
.iter()
.map(|(k, v)| (k.clone(), *v))
.collect()
}
pub fn try_merge(&mut self, other: &ValueTable) -> bool {
for (key, &vn) in &other.expr_to_vn {
if let Some(&existing_vn) = self.expr_to_vn.get(key) {
if existing_vn != vn {
return false;
}
} else {
self.expr_to_vn.insert(key.clone(), vn);
if let Some(expr) = other.vn_to_expr.get(&vn) {
self.vn_to_expr.insert(vn, expr.clone());
}
if let Some(&cvar) = other.vn_to_var.get(&vn) {
self.vn_to_var.insert(vn, cvar);
}
if vn >= self.next_vn {
self.next_vn = vn + 1;
}
}
}
true
}
}
#[derive(Debug, Default)]
pub struct InterproceduralGVN {
pub summaries: HashMap<String, GVNFunctionSummary>,
pub cross_fn_equalities: usize,
}
impl InterproceduralGVN {
pub fn new() -> Self {
InterproceduralGVN::default()
}
pub fn add_summary(&mut self, name: String, summary: GVNFunctionSummary) {
self.summaries.insert(name, summary);
}
pub fn calls_are_equal(&self, fn_name: &str) -> bool {
self.summaries
.get(fn_name)
.map(|s| s.is_pure_fn)
.unwrap_or(false)
}
pub fn run(&mut self, decls: &mut [LcnfFunDecl]) {
for decl in decls.iter_mut() {
if let Some(summary) = self.summaries.get(&decl.name) {
if summary.is_pure_fn {
self.cross_fn_equalities += 1;
}
}
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GVNPassConfig {
pub phase: GVNPassPhase,
pub enabled: bool,
pub max_iterations: u32,
pub debug_output: bool,
pub pass_name: String,
}
impl GVNPassConfig {
#[allow(dead_code)]
pub fn new(name: impl Into<String>, phase: GVNPassPhase) -> Self {
GVNPassConfig {
phase,
enabled: true,
max_iterations: 10,
debug_output: false,
pass_name: name.into(),
}
}
#[allow(dead_code)]
pub fn disabled(mut self) -> Self {
self.enabled = false;
self
}
#[allow(dead_code)]
pub fn with_debug(mut self) -> Self {
self.debug_output = true;
self
}
#[allow(dead_code)]
pub fn max_iter(mut self, n: u32) -> Self {
self.max_iterations = n;
self
}
}
#[derive(Debug, Clone)]
pub struct PhiNode {
pub var: LcnfVarId,
pub operands: Vec<PhiOperand>,
pub vn: ValueNumber,
}
impl PhiNode {
pub fn new(var: LcnfVarId, operands: Vec<PhiOperand>, vn: ValueNumber) -> Self {
PhiNode { var, operands, vn }
}
pub fn is_trivial(&self) -> bool {
self.operands.windows(2).all(|w| w[0].vn == w[1].vn)
}
pub fn trivial_vn(&self) -> Option<ValueNumber> {
if self.is_trivial() {
self.operands.first().map(|op| op.vn)
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KnownConstant {
Lit(LcnfLit),
Top,
Bottom,
}
#[derive(Debug, Clone)]
pub struct Redundancy {
pub redundant_var: LcnfVarId,
pub canonical_var: LcnfVarId,
pub vn: ValueNumber,
}
#[allow(dead_code)]
pub struct GVNPassRegistry {
pub(super) configs: Vec<GVNPassConfig>,
pub(super) stats: std::collections::HashMap<String, GVNPassStats>,
}
impl GVNPassRegistry {
#[allow(dead_code)]
pub fn new() -> Self {
GVNPassRegistry {
configs: Vec::new(),
stats: std::collections::HashMap::new(),
}
}
#[allow(dead_code)]
pub fn register(&mut self, config: GVNPassConfig) {
self.stats
.insert(config.pass_name.clone(), GVNPassStats::new());
self.configs.push(config);
}
#[allow(dead_code)]
pub fn enabled_passes(&self) -> Vec<&GVNPassConfig> {
self.configs.iter().filter(|c| c.enabled).collect()
}
#[allow(dead_code)]
pub fn get_stats(&self, name: &str) -> Option<&GVNPassStats> {
self.stats.get(name)
}
#[allow(dead_code)]
pub fn total_passes(&self) -> usize {
self.configs.len()
}
#[allow(dead_code)]
pub fn enabled_count(&self) -> usize {
self.enabled_passes().len()
}
#[allow(dead_code)]
pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
if let Some(stats) = self.stats.get_mut(name) {
stats.record_run(changes, time_ms, iter);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum NormExpr {
Lit(u64),
LitStr(String),
Erased,
FVar(ValueNumber),
Proj(String, u32, ValueNumber),
Ctor(String, u32, Vec<NormArg>),
App(NormArg, Vec<NormArg>),
Reset(ValueNumber),
Unknown,
}