Skip to main content

logicaffeine_compile/optimize/egraph/
enode.rs

1//! The Architect's term language — flat, multi-arity, `Copy`.
2//!
3//! Children are `NodeId`s (e-class references after canonicalization).
4//! `Var` carries `(symbol index, version)` — versions partition reads of a
5//! mutable name so equality never leaks across a write. `Opaque` is the
6//! escape hatch: any expression the e-graph does not model (calls, indexing,
7//! collection literals, text) becomes an opaque leaf whose ORIGINAL `&Expr`
8//! pointer is held by the converter, so extraction reproduces it verbatim —
9//! effects and error behavior preserved by construction.
10
11use super::NodeId;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum CompilerENode {
15    Int(i64),
16    Bool(bool),
17    /// f64 bit pattern. Floats are NEVER arithmetic-rewritten (no identity,
18    /// no reassociation — bit-exactness is the contract); the variant exists
19    /// so float-valued subtrees still participate in congruence.
20    Float(u64),
21    /// (symbol index, version)
22    Var(u32, u32),
23    /// Index into the converter's originals table.
24    Opaque(u32),
25
26    Add(NodeId, NodeId),
27    Sub(NodeId, NodeId),
28    Mul(NodeId, NodeId),
29    Div(NodeId, NodeId),
30    Mod(NodeId, NodeId),
31    Shl(NodeId, NodeId),
32    Shr(NodeId, NodeId),
33    BitXor(NodeId, NodeId),
34    /// Bitwise `&`/`|` on Int (the surface `&`/`|` symbols; also what the
35    /// mod-pow2 mask rule synthesizes).
36    BitAnd(NodeId, NodeId),
37    BitOr(NodeId, NodeId),
38    /// Logical `and`/`or`: truthiness in, Bool out, short-circuit. Rules
39    /// consult class facts (`is_bool`) before rewriting.
40    And(NodeId, NodeId),
41    Or(NodeId, NodeId),
42    Not(NodeId),
43
44    Eq(NodeId, NodeId),
45    Ne(NodeId, NodeId),
46    Lt(NodeId, NodeId),
47    Le(NodeId, NodeId),
48    Gt(NodeId, NodeId),
49    Ge(NodeId, NodeId),
50
51    Concat(NodeId, NodeId),
52    Len(NodeId),
53    Index(NodeId, NodeId),
54    /// `copy of xs` — a fresh, unaliased deep copy. Total.
55    Copy(NodeId),
56    /// `xs sliced from a to b` (1-based, inclusive). NON-total: raises on
57    /// out-of-bounds, so rewrites may never delete one without proofs.
58    Slice(NodeId, NodeId, NodeId),
59    /// `xs contains x` — membership. Total; modeled for congruence/CSE
60    /// only (no rewrite touches its semantics).
61    Contains(NodeId, NodeId),
62}
63
64impl CompilerENode {
65    /// Children in evaluation order.
66    pub fn children(&self) -> Vec<NodeId> {
67        use CompilerENode::*;
68        match *self {
69            Int(_) | Bool(_) | Float(_) | Var(..) | Opaque(_) => vec![],
70            Not(a) | Len(a) | Copy(a) => vec![a],
71            Add(a, b) | Sub(a, b) | Mul(a, b) | Div(a, b) | Mod(a, b) | Shl(a, b)
72            | Shr(a, b) | BitXor(a, b) | BitAnd(a, b) | BitOr(a, b) | And(a, b) | Or(a, b)
73            | Eq(a, b) | Ne(a, b) | Lt(a, b) | Le(a, b) | Gt(a, b) | Ge(a, b)
74            | Concat(a, b) | Index(a, b) | Contains(a, b) => {
75                vec![a, b]
76            }
77            Slice(a, b, c) => vec![a, b, c],
78        }
79    }
80
81    /// Rebuild the node with each child mapped (canonicalization).
82    pub fn map_children(self, mut f: impl FnMut(NodeId) -> NodeId) -> Self {
83        use CompilerENode::*;
84        match self {
85            Int(_) | Bool(_) | Float(_) | Var(..) | Opaque(_) => self,
86            Not(a) => Not(f(a)),
87            Len(a) => Len(f(a)),
88            Add(a, b) => Add(f(a), f(b)),
89            Sub(a, b) => Sub(f(a), f(b)),
90            Mul(a, b) => Mul(f(a), f(b)),
91            Div(a, b) => Div(f(a), f(b)),
92            Mod(a, b) => Mod(f(a), f(b)),
93            Shl(a, b) => Shl(f(a), f(b)),
94            Shr(a, b) => Shr(f(a), f(b)),
95            BitXor(a, b) => BitXor(f(a), f(b)),
96            BitAnd(a, b) => BitAnd(f(a), f(b)),
97            BitOr(a, b) => BitOr(f(a), f(b)),
98            And(a, b) => And(f(a), f(b)),
99            Or(a, b) => Or(f(a), f(b)),
100            Eq(a, b) => Eq(f(a), f(b)),
101            Ne(a, b) => Ne(f(a), f(b)),
102            Lt(a, b) => Lt(f(a), f(b)),
103            Le(a, b) => Le(f(a), f(b)),
104            Gt(a, b) => Gt(f(a), f(b)),
105            Ge(a, b) => Ge(f(a), f(b)),
106            Concat(a, b) => Concat(f(a), f(b)),
107            Index(a, b) => Index(f(a), f(b)),
108            Copy(a) => Copy(f(a)),
109            Slice(a, b, c) => Slice(f(a), f(b), f(c)),
110            Contains(a, b) => Contains(f(a), f(b)),
111        }
112    }
113
114    /// Ops whose evaluation can never raise a runtime error, given total
115    /// children. Div/Mod (zero divisor), Index/Slice (bounds), and Opaque
116    /// (arbitrary effects) are the non-total ones — a rewrite may only
117    /// DELETE a subterm whose whole tree is total, or it would erase the
118    /// program's error/effect behavior. Len/Copy/Contains raise KIND
119    /// errors over non-collections, so they are non-total HERE and earn
120    /// totality through class facts in
121    /// [`super::CompilerEGraph::provably_total`].
122    pub fn op_is_total(&self) -> bool {
123        !matches!(
124            self,
125            CompilerENode::Div(..)
126                | CompilerENode::Mod(..)
127                | CompilerENode::Index(..)
128                | CompilerENode::Slice(..)
129                | CompilerENode::Len(..)
130                | CompilerENode::Copy(..)
131                | CompilerENode::Contains(..)
132                | CompilerENode::Opaque(_)
133        )
134    }
135}