pub mod convert;
pub mod enode;
pub mod extract;
pub mod rules;
pub use enode::CompilerENode;
use std::collections::HashMap;
use logicaffeine_base::union_find::UnionFind;
use crate::optimize::ScalarKind;
pub type NodeId = usize;
pub const SATURATION_ITERS: usize = 8;
pub const MAX_NODES: usize = 10_000;
#[derive(Debug, Clone, Copy, Default)]
pub struct ClassFact {
pub scalar: Option<ScalarKind>,
pub range: Option<(i64, i64)>,
pub collection: bool,
pub list: bool,
}
pub struct CompilerEGraph {
uf: UnionFind,
nodes: Vec<CompilerENode>,
memo: HashMap<CompilerENode, NodeId>,
class_nodes: HashMap<NodeId, Vec<NodeId>>,
uses: HashMap<NodeId, Vec<NodeId>>,
facts: HashMap<NodeId, ClassFact>,
dirty: Vec<NodeId>,
}
impl CompilerEGraph {
pub fn new() -> Self {
CompilerEGraph {
uf: UnionFind::new(),
nodes: Vec::new(),
memo: HashMap::new(),
class_nodes: HashMap::new(),
uses: HashMap::new(),
facts: HashMap::new(),
dirty: Vec::new(),
}
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn find(&mut self, id: NodeId) -> NodeId {
self.uf.find(id)
}
pub fn canonical_node(&mut self, id: NodeId) -> CompilerENode {
let node = self.nodes[id];
node.map_children(|c| self.uf.find(c))
}
pub fn class_members(&mut self, id: NodeId) -> Vec<NodeId> {
let root = self.find(id);
self.class_nodes.get(&root).cloned().unwrap_or_default()
}
pub fn add(&mut self, node: CompilerENode) -> NodeId {
let canonical = node.map_children(|c| self.uf.find(c));
if let Some(&existing) = self.memo.get(&canonical) {
return self.uf.find(existing);
}
let id = self.uf.make_set();
debug_assert_eq!(id, self.nodes.len());
self.nodes.push(canonical);
self.memo.insert(canonical, id);
self.class_nodes.insert(id, vec![id]);
for child in canonical.children() {
self.uses.entry(child).or_default().push(id);
}
match canonical {
CompilerENode::Int(k) => {
self.facts.insert(
id,
ClassFact {
scalar: Some(ScalarKind::Int),
range: Some((k, k)),
..ClassFact::default()
},
);
}
CompilerENode::Bool(_) => {
self.facts.insert(
id,
ClassFact { scalar: Some(ScalarKind::Bool), ..ClassFact::default() },
);
}
CompilerENode::Float(_) => {
self.facts.insert(
id,
ClassFact { scalar: Some(ScalarKind::Float), ..ClassFact::default() },
);
}
CompilerENode::Slice(..) => {
self.facts.insert(
id,
ClassFact { collection: true, list: true, ..ClassFact::default() },
);
}
_ => {}
}
id
}
pub fn union(&mut self, a: NodeId, b: NodeId) -> bool {
let ra = self.uf.find(a);
let rb = self.uf.find(b);
if ra == rb {
return false;
}
let fa = self.facts.remove(&ra);
let fb = self.facts.remove(&rb);
self.uf.union(ra, rb);
let root = self.uf.find(ra);
let loser = if root == ra { rb } else { ra };
let mut members = self.class_nodes.remove(&loser).unwrap_or_default();
self.class_nodes.entry(root).or_default().append(&mut members);
let mut moved_uses = self.uses.remove(&loser).unwrap_or_default();
self.uses.entry(root).or_default().append(&mut moved_uses);
let merged = match (fa, fb) {
(Some(x), Some(y)) => ClassFact {
scalar: x.scalar.or(y.scalar),
range: match (x.range, y.range) {
(Some((al, ah)), Some((bl, bh))) => {
let lo = al.max(bl);
let hi = ah.min(bh);
debug_assert!(lo <= hi, "contradictory ranges merged — unsound rule?");
if lo <= hi { Some((lo, hi)) } else { Some((al, ah)) }
}
(r, None) | (None, r) => r,
},
collection: x.collection || y.collection,
list: x.list || y.list,
},
(Some(x), None) | (None, Some(x)) => x,
(None, None) => ClassFact::default(),
};
self.facts.insert(root, merged);
self.dirty.push(root);
true
}
pub fn rebuild(&mut self) {
while let Some(r) = self.dirty.pop() {
let root = self.uf.find(r);
let parents = self.uses.remove(&root).unwrap_or_default();
let mut kept: Vec<NodeId> = Vec::with_capacity(parents.len());
for p in parents {
let canon = self.nodes[p].map_children(|c| self.uf.find(c));
if let Some(&q) = self.memo.get(&canon) {
if self.uf.find(q) != self.uf.find(p) {
self.union(p, q);
}
}
let rep = self.uf.find(p);
self.memo.insert(canon, rep);
kept.push(p);
}
let now = self.uf.find(root);
self.uses.entry(now).or_default().extend(kept);
}
}
pub fn set_scalar(&mut self, id: NodeId, kind: ScalarKind) {
let root = self.find(id);
self.facts.entry(root).or_default().scalar = Some(kind);
}
pub fn set_int_range(&mut self, id: NodeId, lo: i64, hi: i64) {
let root = self.find(id);
let fact = self.facts.entry(root).or_default();
fact.scalar = Some(ScalarKind::Int);
fact.range = Some((lo, hi));
}
pub fn scalar_of(&mut self, id: NodeId) -> Option<ScalarKind> {
let root = self.find(id);
self.facts.get(&root).and_then(|f| f.scalar)
}
pub fn int_range(&mut self, id: NodeId) -> Option<(i64, i64)> {
let root = self.find(id);
self.facts.get(&root).and_then(|f| f.range)
}
pub fn int_value(&mut self, id: NodeId) -> Option<i64> {
match self.int_range(id) {
Some((lo, hi)) if lo == hi => Some(lo),
_ => None,
}
}
pub fn proven_nonneg(&mut self, id: NodeId) -> bool {
matches!(self.int_range(id), Some((lo, _)) if lo >= 0)
}
pub fn set_collection(&mut self, id: NodeId) {
let root = self.find(id);
self.facts.entry(root).or_default().collection = true;
}
pub fn set_list(&mut self, id: NodeId) {
let root = self.find(id);
let fact = self.facts.entry(root).or_default();
fact.collection = true;
fact.list = true;
}
pub fn proven_collection(&mut self, id: NodeId) -> bool {
let root = self.find(id);
self.facts.get(&root).is_some_and(|f| f.collection)
}
pub fn proven_list(&mut self, id: NodeId) -> bool {
let root = self.find(id);
self.facts.get(&root).is_some_and(|f| f.list)
}
pub fn class_has_bool(&mut self, id: NodeId, b: bool) -> bool {
self.class_members(id)
.into_iter()
.any(|m| self.nodes[m] == CompilerENode::Bool(b))
}
pub fn provably_total(&mut self, id: NodeId) -> bool {
let mut total: HashMap<NodeId, bool> = HashMap::new();
loop {
let mut changed = false;
for n in 0..self.nodes.len() {
let node = self.nodes[n];
let root = self.uf.find(n);
if *total.get(&root).unwrap_or(&false) {
continue;
}
let op_total = match node {
CompilerENode::Len(c)
| CompilerENode::Copy(c)
| CompilerENode::Contains(c, _) => self.proven_collection(c),
_ => node.op_is_total(),
};
if op_total
&& node
.children()
.iter()
.all(|&c| *total.get(&self.uf.find(c)).unwrap_or(&false))
{
total.insert(root, true);
changed = true;
}
}
if !changed {
break;
}
}
let root = self.find(id);
*total.get(&root).unwrap_or(&false)
}
pub fn saturate(&mut self, rules: &[rules::Rewrite]) {
self.rebuild();
'outer: for _ in 0..SATURATION_ITERS {
let snapshot = self.nodes.len();
let mut changed = false;
for id in 0..snapshot {
for rule in rules {
if self.nodes.len() + 4 > MAX_NODES {
self.rebuild();
break 'outer;
}
if let Some(replacement) = (rule.apply)(self, id) {
changed |= self.union(id, replacement);
}
}
}
self.rebuild();
if !changed && self.nodes.len() == snapshot {
break;
}
}
}
}
impl Default for CompilerEGraph {
fn default() -> Self {
Self::new()
}
}