README

Milky2018/milkir/egraph does not have a README file

#
EClassId

pub(all) struct EClassId(Int) derive(Compare, Eq, Hash,
Debug
)

E-class identifier (index into the e-graph's class array)
impl Show for EClassId

#
EGraph

type EGraph

The main E-graph data structure

#
EGraph::EGraph

fn EGraph::EGraph() -> EGraph

#
EGraph::add

fn EGraph::add(self : EGraph, node : ENode) -> EClassId

Add an e-node to the e-graph, returning its e-class id. If an equivalent node already exists, returns the existing class id. Rewriting happens later, in saturate_indexed.

#
EGraph::add_add

fn EGraph::add_add(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_and

fn EGraph::add_and(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_binop

fn EGraph::add_binop(self : EGraph, op : EOpcode, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_bitrev

fn EGraph::add_bitrev(self : EGraph, operand : EClassId) -> EClassId

Add a bit reverse node

#
EGraph::add_bmask

fn EGraph::add_bmask(self : EGraph, operand : EClassId) -> EClassId

Add a boolean mask node: 0 -> 0, nonzero -> -1

#
EGraph::add_bnot

fn EGraph::add_bnot(self : EGraph, operand : EClassId) -> EClassId

Add a bitwise NOT node: ~x

#
EGraph::add_bswap

fn EGraph::add_bswap(self : EGraph, operand : EClassId) -> EClassId

Add a byte swap node

#
EGraph::add_clz

fn EGraph::add_clz(self : EGraph, operand : EClassId) -> EClassId

Add a count leading zeros node

#
EGraph::add_const

fn EGraph::add_const(self : EGraph, value : Int64) -> EClassId

#
EGraph::add_ctz

fn EGraph::add_ctz(self : EGraph, operand : EClassId) -> EClassId

Add a count trailing zeros node

#
EGraph::add_eq

fn EGraph::add_eq(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

Add an equality comparison node

#
EGraph::add_fabs

fn EGraph::add_fabs(self : EGraph, operand : EClassId) -> EClassId

Add a float absolute value node

#
EGraph::add_fadd

fn EGraph::add_fadd(self : EGraph, left : EClassId, right : EClassId) -> EClassId

Add a float add node

#
EGraph::add_fceil

fn EGraph::add_fceil(self : EGraph, operand : EClassId) -> EClassId

Add a float ceiling node

#
EGraph::add_fcmp

fn EGraph::add_fcmp(self : EGraph, cc : Int, left : EClassId, right : EClassId) -> EClassId

Add a float compare node

#
EGraph::add_fconst

fn EGraph::add_fconst(self : EGraph, bits : UInt64) -> EClassId

Add a float constant node (bits stored as UInt64)

#
EGraph::add_fcopysign

fn EGraph::add_fcopysign(self : EGraph, mag : EClassId, sign : EClassId) -> EClassId

Add a float copysign node

#
EGraph::add_fcvt_to_sint

fn EGraph::add_fcvt_to_sint(self : EGraph, operand : EClassId) -> EClassId

Add a float to signed int conversion node

#
EGraph::add_fcvt_to_uint

fn EGraph::add_fcvt_to_uint(self : EGraph, operand : EClassId) -> EClassId

Add a float to unsigned int conversion node

#
EGraph::add_fdemote

fn EGraph::add_fdemote(self : EGraph, operand : EClassId) -> EClassId

Add a float demote (f64 -> f32) node

#
EGraph::add_fdiv

fn EGraph::add_fdiv(self : EGraph, left : EClassId, right : EClassId) -> EClassId

Add a float divide node

#
EGraph::add_ffloor

fn EGraph::add_ffloor(self : EGraph, operand : EClassId) -> EClassId

Add a float floor node

#
EGraph::add_fmax

fn EGraph::add_fmax(self : EGraph, left : EClassId, right : EClassId) -> EClassId

Add a float max node

#
EGraph::add_fmin

fn EGraph::add_fmin(self : EGraph, left : EClassId, right : EClassId) -> EClassId

Add a float min node

#
EGraph::add_fmul

fn EGraph::add_fmul(self : EGraph, left : EClassId, right : EClassId) -> EClassId

Add a float multiply node

#
EGraph::add_fnearest

fn EGraph::add_fnearest(self : EGraph, operand : EClassId) -> EClassId

Add a float round to nearest node

#
EGraph::add_fneg

fn EGraph::add_fneg(self : EGraph, operand : EClassId) -> EClassId

Add a float negate node

#
EGraph::add_fpromote

fn EGraph::add_fpromote(self : EGraph, operand : EClassId) -> EClassId

Add a float promote (f32 -> f64) node

#
EGraph::add_fsqrt

fn EGraph::add_fsqrt(self : EGraph, operand : EClassId) -> EClassId

Add a float square root node

#
EGraph::add_fsub

fn EGraph::add_fsub(self : EGraph, left : EClassId, right : EClassId) -> EClassId

Add a float subtract node

#
EGraph::add_ftrunc

fn EGraph::add_ftrunc(self : EGraph, operand : EClassId) -> EClassId

Add a float truncate node

#
EGraph::add_icmp

fn EGraph::add_icmp(self : EGraph, cc : Int, lhs : EClassId, rhs : EClassId) -> EClassId

Add an integer compare node with condition code

#
EGraph::add_ireduce

fn EGraph::add_ireduce(self : EGraph, operand : EClassId, from_bits? : Int, to_bits? : Int) -> EClassId

Add an integer reduce (truncate) node with type info

#
EGraph::add_mul

fn EGraph::add_mul(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_ne

fn EGraph::add_ne(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

Add a not-equal comparison node

#
EGraph::add_neg

fn EGraph::add_neg(self : EGraph, operand : EClassId) -> EClassId

Add a negation node: -x

#
EGraph::add_or

fn EGraph::add_or(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_popcnt

fn EGraph::add_popcnt(self : EGraph, operand : EClassId) -> EClassId

Add a population count node

#
EGraph::add_rotl

fn EGraph::add_rotl(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_rotr

fn EGraph::add_rotr(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_sdiv

fn EGraph::add_sdiv(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_select

fn EGraph::add_select(self : EGraph, cond : EClassId, a : EClassId, b : EClassId) -> EClassId

Add a select node: select(cond, a, b) = cond ? a : b

#
EGraph::add_sextend

fn EGraph::add_sextend(self : EGraph, operand : EClassId, from_bits? : Int, to_bits? : Int) -> EClassId

Add a signed extend node with type info

#
EGraph::add_shl

fn EGraph::add_shl(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_sint_to_fcvt

fn EGraph::add_sint_to_fcvt(self : EGraph, operand : EClassId) -> EClassId

Add a signed int to float conversion node

#
EGraph::add_splat

fn EGraph::add_splat(self : EGraph, scalar : EClassId) -> EClassId

Add a vector splat node (broadcast scalar to all lanes)

#
EGraph::add_srem

fn EGraph::add_srem(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_sshr

fn EGraph::add_sshr(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_sub

fn EGraph::add_sub(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_typed

fn EGraph::add_typed(self : EGraph, node : ENode, bits : Int) -> EClassId

Add an e-node with type information bits: the bit width of the value (8, 16, 32, 64, 128)

#
EGraph::add_udiv

fn EGraph::add_udiv(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_uextend

fn EGraph::add_uextend(self : EGraph, operand : EClassId, from_bits? : Int, to_bits? : Int) -> EClassId

Add an unsigned extend node with type info

#
EGraph::add_uint_to_fcvt

fn EGraph::add_uint_to_fcvt(self : EGraph, operand : EClassId) -> EClassId

Add an unsigned int to float conversion node

#
EGraph::add_urem

fn EGraph::add_urem(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_ushr

fn EGraph::add_ushr(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::add_var

fn EGraph::add_var(self : EGraph, var_id : Int) -> EClassId

#
EGraph::add_vconst

fn EGraph::add_vconst(self : EGraph, bits : Bytes) -> EClassId

Add a vector constant node

#
EGraph::add_xor

fn EGraph::add_xor(self : EGraph, lhs : EClassId, rhs : EClassId) -> EClassId

#
EGraph::best_nodes

fn EGraph::best_nodes(self : EGraph) -> Map[Int, ENode]

Best node per class root, computed once for the whole e-graph.

Elaboration needs every class's chosen node, not one class's, so it pays for the shared fixpoint once instead of re-running extraction per value. Classes with no finite extraction (every node cyclic) are absent.

#
EGraph::const_conflict_count

fn EGraph::const_conflict_count(self : EGraph) -> Int

Number of constant-cache conflict observations so far (diagnostic). A nonzero value means some rule merged classes carrying different constants — an unsound union whose constant is deliberately not harvested.

#
EGraph::equiv

fn EGraph::equiv(self : EGraph, a : EClassId, b : EClassId) -> Bool

Check if two e-class ids are equivalent

#
EGraph::extract

fn EGraph::extract(self : EGraph, id : EClassId) -> (Int, ENode)

Extract the best (lowest cost) expression from an e-class Returns the total cost and the best e-node

Costs are computed with a bottom-up fixpoint over the whole e-graph (egg-style relaxation) rather than a memoized DFS: a DFS that prices re-entered classes as infinite while an ancestor is on the stack can memoize that context-dependent infinity and poison later queries that reach the class through a cycle-free route. The fixpoint has no evaluation context, so every class settles to its true best cost.

#
EGraph::find

fn EGraph::find(self : EGraph, id : EClassId) -> EClassId

Find the canonical EClassId for a given id

#
EGraph::get_bits

fn EGraph::get_bits(self : EGraph, id : EClassId) -> Int?

Get the type (bit width) for an equivalence class Returns None if no type information is available

#
EGraph::get_const

fn EGraph::get_const(self : EGraph, id : EClassId) -> Int64?

Get cached constant value for an e-class (O(1) lookup)

#
EGraph::get_fconst

fn EGraph::get_fconst(self : EGraph, id : EClassId) -> UInt64?

Get cached float constant value for an e-class (O(1) lookup)

#
EGraph::get_nodes

fn EGraph::get_nodes(self : EGraph, id : EClassId) -> Array[ENode]

Get all nodes in an e-class

#
EGraph::last_eclass_size_limit_hits

fn EGraph::last_eclass_size_limit_hits(self : EGraph) -> Int

Number of classes skipped/truncated due to eclass size limit in the most recent saturate / saturate_indexed* invocation.

#
EGraph::last_matches_limit_hits

fn EGraph::last_matches_limit_hits(self : EGraph) -> Int

Number of classes/rule streams truncated by the rewrite matches limit in the most recent saturate / saturate_indexed* invocation.

#
EGraph::last_rebuild_repairs

fn EGraph::last_rebuild_repairs(self : EGraph) -> Int

Number of classes repaired by the most recent rebuild (diagnostic).

#
EGraph::last_rule_applications

fn EGraph::last_rule_applications(self : EGraph) -> Int

Number of successful rewrite rule applications in the most recent saturate / saturate_indexed invocation.

#
EGraph::merge

fn EGraph::merge(self : EGraph, a : EClassId, b : EClassId) -> EClassId

Merge two e-classes, returning the canonical id of the merged class

#
EGraph::merge_changed

fn EGraph::merge_changed(self : EGraph, a : EClassId, b : EClassId) -> Bool

Merge two e-classes, reporting whether they were distinct beforehand.

Rewrite rules use this to answer "did I make progress?" directly, rather than inferring it from which id merge returned. Keeping progress explicit lets the union-find choose roots freely.

#
EGraph::num_classes

fn EGraph::num_classes(self : EGraph) -> Int

Get the number of e-classes

#
EGraph::num_nodes

fn EGraph::num_nodes(self : EGraph) -> Int

Get the total number of e-nodes

#
EGraph::rebuild

fn EGraph::rebuild(self : EGraph) -> Unit

Restore e-graph invariants after unions.

Repair is driven by a worklist of classes whose contents changed, not by a walk of the whole graph: when a union moves a class, only the nodes that reference it hold a stale child id, and parents names exactly the classes holding those nodes. Repairing a class can expose new congruences, whose merges enqueue more work, so the loop runs until the worklist drains.

#
EGraph::saturate_indexed

fn EGraph::saturate_indexed(self : EGraph, ruleset : IndexedRuleSet, max_iterations : Int) -> Int

Apply rules using opcode index for efficiency

#
EGraph::saturate_indexed_with_limits

fn EGraph::saturate_indexed_with_limits(self : EGraph, ruleset : IndexedRuleSet, max_iterations : Int, limits : SaturationLimits) -> Int

Apply rules using opcode index for efficiency with explicit rewrite caps.

#
EGraph::set_type

fn EGraph::set_type(self : EGraph, id : EClassId, bits : Int) -> Unit

Set the type (bit width) for an equivalence class. A conflicting assignment poisons the class: it reports no width from then on, so width-dependent rules skip it rather than rewriting with whichever width happened to be recorded first.

#
EGraph::subsume

fn EGraph::subsume(self : EGraph, a : EClassId, b : EClassId) -> EClassId

Subsume: replace node in class a with node from class b Unlike merge, this doesn't add the original node to the equivalence class. Used to avoid infinite loops in associativity/commutativity rules. Returns the canonical id of the result (same as b).

#
EGraph::subsume_changed

fn EGraph::subsume_changed(self : EGraph, a : EClassId, b : EClassId) -> Bool

Subsume a into b, reporting whether they were distinct beforehand.

#
EGraph::type_conflict_count

fn EGraph::type_conflict_count(self : EGraph) -> Int

Number of width-conflict observations so far (diagnostic).

#
ENode

pub(all) struct ENode {
op : EOpcode
children : Array[EClassId]
} derive(Compare, Eq, Hash,
Debug
)

E-node: an expression node in the e-graph Children are EClassIds, not direct node references

#
EOpcode

pub(all) enum EOpcode {
Const(Int64)
Fconst(UInt64)
Add
Sub
Mul
Sdiv
Udiv
Srem
Urem
And
Or
Xor
Shl
Sshr
Ushr
Rotl
Rotr
Neg
Bnot
Clz
Ctz
Popcnt
Bswap
Bitrev
Icmp(Int)
Eq
Ne
Select
Bmask
Smin
Smax
Umin
Umax
Iabs
SpaceshipS
SpaceshipU
Ireduce(Int, Int)
Uextend(Int, Int)
Sextend(Int, Int)
Fadd
Fsub
Fmul
Fdiv
Fmin
Fmax
Fcopysign
Fneg
Fabs
Fsqrt
Fceil
Ffloor
Ftrunc
Fnearest
Fcmp(Int)
Fpromote
Fdemote
FcvtToSint
FcvtToUint
SintToFcvt
UintToFcvt
Splat
Vconst(Bytes)
Var(Int)
} derive(Compare, Eq, Hash,
Debug
)

E-node opcode for e-graph expressions Standard IR opcodes for optimization
impl Show for EOpcode

#
EOpcode::tag

fn EOpcode::tag(self : EOpcode) -> EOpcodeTag

Get the tag of an opcode (for indexing)

#
EOpcodeTag

type EOpcodeTag derive(Eq, Hash,
Debug
)

E-opcode tag for indexing (ignores parameters like Const value)

#
IndexedRuleSet

type IndexedRuleSet

An indexed rule set for efficient rule application

#
SaturationLimits

pub struct SaturationLimits {
matches_limit : Int
eclass_enode_limit : Int
} derive(
Debug
)

Saturation bounds (Cranelift-style):
  • matches_limit: max successful rewrites to keep per class per pass
  • eclass_enode_limit: max eclass node budget before skipping further rewrites

#
SaturationLimits::SaturationLimits

fn SaturationLimits::SaturationLimits(matches_limit : Int, eclass_enode_limit : Int) -> SaturationLimits

#
SaturationLimits::cranelift_default

fn SaturationLimits::cranelift_default() -> SaturationLimits

#
SaturationLimits::eclass_enode_limit

fn SaturationLimits::eclass_enode_limit(self : SaturationLimits) -> Int

#
SaturationLimits::matches_limit

fn SaturationLimits::matches_limit(self : SaturationLimits) -> Int

#
get_compact_ruleset

fn get_compact_ruleset() -> IndexedRuleSet

Get a compact ruleset for large functions where compile-time predictability matters more than chasing deep algebraic rewrites.

#
get_global_ruleset

fn get_global_ruleset() -> IndexedRuleSet

Get the global indexed ruleset (singleton pattern for performance)

#
legality_sensitive_rules

fn legality_sensitive_rules() -> IndexedRuleSet

Rules that require frontend- or target-provided definedness facts.

These are intentionally not part of get_global_ruleset because generic MilkIR does not yet model whether division/remainder operations are total, trapping, or poison-producing. A frontend adapter may use this collection only after proving the required non-zero and overflow preconditions.

#
ty_smax

fn ty_smax(bits : Int) -> Int64

Get the maximum signed value for a given bit width

#
ty_smin

fn ty_smin(bits : Int) -> Int64

Get the minimum signed value for a given bit width

#
ty_umax

fn ty_umax(bits : Int) -> Int64

Get the maximum unsigned value for a given bit width