milkir

Reusable Cranelift-like SSA intermediate representation

compiler
ir
ssa
optimization
moon add Milky2018/milkir@0.6.2
Download zip
Author
Version
0.6.2
License
Apache-2.0
Last updated
2 days ago
Downloads
153
README

#milkir

Reusable Cranelift-like SSA intermediate representation.

milkir provides compiler IR data structures, builders, verification, CFG helpers, printing, and optimization passes. It is intended as a reusable middle-end layer for MoonBit compiler projects.

#Package

  • Milky2018/milkir: SSA values, blocks, instructions, terminators, signatures, IRBuilder, verification, CFG utilities, and optimization drivers.

#When to use it

Use MilkIR when a frontend needs a target-independent SSA form before instruction selection. A typical pipeline builds a Function, verifies it, runs one or more optimization passes, then lowers it through isa_target into MachV.

#Example: build and verify SSA

IRBuilder owns the common workflow: declare parameters/results, create blocks, emit SSA values, and finish each block with a terminator.

///|
test "build an add function with IRBuilder" {
let builder = IRBuilder::new("add_i32")
let lhs = builder.add_param(I32)
let rhs = builder.add_param(I32)
builder.add_result(I32)
let entry = builder.create_block()
builder.switch_to_block(entry)
let sum = builder.iadd(lhs, rhs)
builder.return_([sum])
let func = builder.get_function()
inspect(func.verify(), content="()")
inspect(func.blocks.length(), content="1")
inspect(instruction_count(func), content="1")
}

#Example: run a small optimization pass

Optimization passes mutate the function and report whether they changed it. This example folds two constants and then verifies the optimized function.

///|
test "fold constants in a MilkIR function" {
let builder = IRBuilder::new("const_add")
builder.add_result(I32)
let entry = builder.create_block()
builder.switch_to_block(entry)
let lhs = builder.iconst_i32(10)
let rhs = builder.iconst_i32(20)
let sum = builder.iadd(lhs, rhs)
builder.return_([sum])
let func = builder.get_function()
let before = instruction_count(func)
let result = fold_constants(func)
inspect(result.changed, content="true")
inspect(instruction_count(func) <= before, content="true")
inspect(func.verify(), content="()")
}

#Boundary

milkir is generic compiler infrastructure. It should not depend on Wasmoon runtime concepts, embedding context layouts, WASI, or machine-code emission.

Dialect-specific operations should travel through the generic ExtOp extension hook. The WebAssembly dialect is owned by Milky2018/wasm_milkir, not by MilkIR core.

#
VerifyError

pub suberror VerifyError {
MissingTerminator(block_id~ : Int)
EmptyFunction
UndefinedValue(value_id~ : Int)
ArityMismatch(message~ : String)
TypeMismatch(message~ : String)
} derive(Eq,
Debug
)

impl Show for VerifyError

#
Block

pub struct Block {
id : Int
params : Array[(Value, Type)]
instructions : Array[Inst]
terminator : Terminator?
} derive(
Debug
)

Block - represents a basic block in the control flow graph A basic block is a sequence of instructions with:
  • One entry point (the first instruction)
  • One exit point (the last instruction, a terminator)

#
Block::add_inst

fn Block::add_inst(self : Block, inst : Inst) -> Unit

Add an instruction to this block

#
Block::add_param

fn Block::add_param(self : Block, value : Value, ty : Type) -> Unit

Add a parameter to this block (for SSA phi nodes)

#
Block::append_inst

fn Block::append_inst(self : Block, inst : Inst) -> Unit

#
Block::new_with_params

fn Block::new_with_params(id : Int, params : Array[Value]) -> Block

#
Block::set_terminator

fn Block::set_terminator(self : Block, term : Terminator) -> Unit

Set the terminator for this block

#
CFG

pub(all) struct CFG {
size : Int
num_blocks : Int
successors : Array[Array[Int]]
predecessors : Array[Array[Int]]
valid : Array[Bool]
} derive(
Debug
)

#
CFG::build

fn CFG::build(func : Function) -> CFG

#
CFG::compute_dominators

fn CFG::compute_dominators(self : CFG) -> Array[Int]

#
CFG::dominates

fn CFG::dominates(self : CFG, a : Int, b : Int) -> Bool

#
CFG::find_back_edges

fn CFG::find_back_edges(self : CFG) -> Array[(Int, Int)]

#
CFG::find_loops

fn CFG::find_loops(self : CFG) -> Array[Loop]

#
CFG::get_loop_preheader

fn CFG::get_loop_preheader(self : CFG, loop_ : Loop) -> Int?

#
CFG::get_predecessors

fn CFG::get_predecessors(self : CFG, block_id : Int) -> Array[Int]

#
CFG::get_successors

fn CFG::get_successors(self : CFG, block_id : Int) -> Array[Int]

#
CFG::is_entry_or_unreachable

fn CFG::is_entry_or_unreachable(self : CFG, block_id : Int) -> Bool

#
CFG::is_exit_block

fn CFG::is_exit_block(self : CFG, block_id : Int) -> Bool

#
CFG::is_valid

fn CFG::is_valid(self : CFG, block_id : Int) -> Bool

#
CFG::postorder

fn CFG::postorder(self : CFG) -> Array[Int]

#
CFG::reverse_postorder

fn CFG::reverse_postorder(self : CFG) -> Array[Int]

#
CFG::to_dot

fn CFG::to_dot(self : CFG, func_name : String) -> String

#
EGraphBuilder

type EGraphBuilder

Builder for constructing an EGraph from IR instructions

#
EGraphBuilder::add_value

Add an IR value to the e-graph, recursively adding its definition Uses eager optimization: rules are applied immediately when adding nodes

#
EGraphBuilder::extract

Extract the best expression for a given IR value

#
EGraphBuilder::get_egraph

Get the optimized e-graph

#
EGraphBuilder::get_simplified_opcode

fn EGraphBuilder::get_simplified_opcode(self : EGraphBuilder, value : Value) -> Opcode?

Check if the extracted expression is a constant folding result Returns Some(Iconst(c)) if constant folding found, None otherwise NOTE: Only handles constant folding. Complex rewrites (like x*3 -> (x<<1)+x) are not handled because they would require operand reconstruction.

#
EGraphBuilder::get_simplified_operand

fn EGraphBuilder::get_simplified_operand(self : EGraphBuilder, value : Value) -> Value?

Get the simplified value for an operand (for operand rewriting) If the operand's e-class has a simpler representation that maps to an existing IR value, return that value; otherwise return None.

#
EGraphBuilder::new

#
EGraphBuilder::new_with_limits

#
EGraphBuilder::new_with_limits_and_ruleset

#
EGraphBuilder::optimize

fn EGraphBuilder::optimize(self : EGraphBuilder) -> Unit

Run optimization on the e-graph With eager optimization, this only needs to rebuild to restore invariants

#
EGraphBuilder::register_def

fn EGraphBuilder::register_def(self : EGraphBuilder, inst : Inst) -> Unit

Register an instruction's definition

#
EGraphOptimizeStats

pub struct EGraphOptimizeStats {
changed : Bool
total_classes : Int
total_nodes : Int
total_rule_applications : Int
}

Apply e-graph optimization to a function Returns true if any optimization was applied

#
ExtOp

pub(all) struct ExtOp {
dialect : String
opcode : String
immediates : FixedArray[Int]
} derive(
Debug
)

impl Eq for ExtOp
impl Hash for ExtOp

#
ExtOp::new

fn ExtOp::new(dialect : String, opcode : String, immediates : FixedArray[Int]) -> ExtOp

#
ExternalSymbol

pub(all) struct ExternalSymbol {
name : String
} derive(Eq, Hash,
Debug
)

#
ExternalSymbol::new

fn ExternalSymbol::new(name : String) -> ExternalSymbol

#
FloatCC

pub(all) enum FloatCC {
Eq
Ne
Lt
Le
Gt
Ge
} derive(Eq, Hash,
Debug
)

Floating point comparison condition codes

#
Function

pub struct Function {
name : String
signature : Signature
params : Array[(Value, Type)]
results : Array[Type]
blocks : Array[Block]
stack_slots : Array[StackSlot]
external_symbols : Array[ExternalSymbol]
next_value_id : Int
next_inst_id : Int
next_block_id : Int
next_stack_slot_id : Int
} derive(
Debug
)

Function - a complete IR function

#
Function::add_param

fn Function::add_param(self : Function, ty : Type) -> Value

Add a parameter to the function

#
Function::add_result

fn Function::add_result(self : Function, ty : Type) -> Unit

Add a result type to the function

#
Function::declare_external_symbol

fn Function::declare_external_symbol(self : Function, name : String) -> ExternalSymbol

#
Function::new

fn Function::new(name : String, signature : Signature) -> Function

#
Function::new_block

fn Function::new_block(self : Function, params : Array[Value]) -> Block

Create a new basic block

#
Function::new_block0

fn Function::new_block0(self : Function) -> Block

#
Function::new_empty

fn Function::new_empty(name : String) -> Function

#
Function::new_inst

fn Function::new_inst(self : Function, opcode : Opcode, args : Array[Value], results : Array[Value]) -> Inst

#
Function::new_stack_slot

fn Function::new_stack_slot(self : Function, size : Int, align : Int) -> StackSlot

#
Function::new_value

fn Function::new_value(self : Function, ty : Type) -> Value

Create a new value with a unique ID

#
Function::print

fn Function::print(self : Function) -> String

Print a function

#
Function::verify

fn Function::verify(self : Function) -> Unit raise VerifyError

#
IRBuilder

type IRBuilder

IRBuilder - helps construct IR functions Tracks the current block and provides methods for emitting instructions

#
IRBuilder::add_block_param

fn IRBuilder::add_block_param(self : IRBuilder, block : Block, ty : Type) -> Value

Add a block parameter (for SSA phi nodes)

#
IRBuilder::add_param

fn IRBuilder::add_param(self : IRBuilder, ty : Type) -> Value

Add a parameter to the function

#
IRBuilder::add_result

fn IRBuilder::add_result(self : IRBuilder, ty : Type) -> Unit

Add a result type to the function

#
IRBuilder::band

fn IRBuilder::band(self : IRBuilder, a : Value, b : Value) -> Value

Bitwise and

#
IRBuilder::bitcast

fn IRBuilder::bitcast(self : IRBuilder, ty : Type, a : Value) -> Value

Bitcast (reinterpret bits)

#
IRBuilder::bnot

fn IRBuilder::bnot(self : IRBuilder, a : Value) -> Value

Bitwise not

#
IRBuilder::bor

fn IRBuilder::bor(self : IRBuilder, a : Value, b : Value) -> Value

Bitwise or

#
IRBuilder::br_table

fn IRBuilder::br_table(self : IRBuilder, index : Value, targets : Array[Block], default_target : Block) -> Unit

Branch table (switch)

#
IRBuilder::brnz

fn IRBuilder::brnz(self : IRBuilder, cond : Value, then_block : Block, else_block : Block) -> Unit

Conditional branch (branch if non-zero)

#
IRBuilder::brz

fn IRBuilder::brz(self : IRBuilder, cond : Value, then_block : Block, else_block : Block) -> Unit

Conditional branch (branch if zero)

#
IRBuilder::bxor

fn IRBuilder::bxor(self : IRBuilder, a : Value, b : Value) -> Value

Bitwise xor

#
IRBuilder::call_ptr

fn IRBuilder::call_ptr(self : IRBuilder, func_ptr : Value, callee_env : Value, args : Array[Value], result_types : Array[Type]) -> Array[Value]

Call via function pointer with multiple return values For calls through an indirect callee address.

Design: a callee environment operand is passed explicitly before user arguments; callers can provide a null/sentinel value when their convention does not use an environment.

Operand layout: [func_ptr, callee_env, user_args...]

#
IRBuilder::call_symbol

fn IRBuilder::call_symbol(self : IRBuilder, symbol : ExternalSymbol, result_ty : Type?, args : Array[Value]) -> Value?

#
IRBuilder::call_symbol_multi

fn IRBuilder::call_symbol_multi(self : IRBuilder, symbol : ExternalSymbol, result_types : Array[Type], args : Array[Value]) -> Array[Value]

#
IRBuilder::clz

fn IRBuilder::clz(self : IRBuilder, a : Value) -> Value

Count leading zeros

#
IRBuilder::copy

fn IRBuilder::copy(self : IRBuilder, a : Value) -> Value

Copy value (for register allocation)

#
IRBuilder::create_block

fn IRBuilder::create_block(self : IRBuilder) -> Block

Create a new block and make it the current block

#
IRBuilder::ctz

fn IRBuilder::ctz(self : IRBuilder, a : Value) -> Value

Count trailing zeros

#
IRBuilder::current_block

fn IRBuilder::current_block(self : IRBuilder) -> Block?

Get the current block

#
IRBuilder::emit_ext_inst

fn IRBuilder::emit_ext_inst(self : IRBuilder, ty : Type, opcode : ExtOp, operands : Array[Value]) -> Value

#
IRBuilder::emit_inst

fn IRBuilder::emit_inst(self : IRBuilder, ty : Type, opcode : Opcode, operands : Array[Value]) -> Value

Emit an instruction that produces a result

#
IRBuilder::emit_void_ext_inst

fn IRBuilder::emit_void_ext_inst(self : IRBuilder, opcode : ExtOp, operands : Array[Value]) -> Unit

#
IRBuilder::fabs

fn IRBuilder::fabs(self : IRBuilder, a : Value) -> Value

Float absolute value

#
IRBuilder::fadd

fn IRBuilder::fadd(self : IRBuilder, a : Value, b : Value) -> Value

Float add

#
IRBuilder::fceil

fn IRBuilder::fceil(self : IRBuilder, a : Value) -> Value

Float ceiling

#
IRBuilder::fcmp

fn IRBuilder::fcmp(self : IRBuilder, cc : FloatCC, a : Value, b : Value) -> Value

Float comparison (returns i32 0 or 1)

#
IRBuilder::fconst

fn IRBuilder::fconst(self : IRBuilder, ty : Type, value : Double) -> Value

Emit a float constant

#
IRBuilder::fconst_f32

fn IRBuilder::fconst_f32(self : IRBuilder, value : Float) -> Value

Emit an f32 constant Note: We pack the f32 bits into the Double's bit representation to preserve NaN payloads. Using value.to_double() would go through the FPU and convert signaling NaNs to quiet NaNs.

#
IRBuilder::fconst_f64

fn IRBuilder::fconst_f64(self : IRBuilder, value : Double) -> Value

Emit an f64 constant

#
IRBuilder::fcvt_to_sint

fn IRBuilder::fcvt_to_sint(self : IRBuilder, ty : Type, a : Value) -> Value

Float to signed int

#
IRBuilder::fcvt_to_sint_sat

fn IRBuilder::fcvt_to_sint_sat(self : IRBuilder, ty : Type, a : Value) -> Value

Float to signed int (saturating - NaN->0, overflow->max/min)

#
IRBuilder::fcvt_to_uint

fn IRBuilder::fcvt_to_uint(self : IRBuilder, ty : Type, a : Value) -> Value

Float to unsigned int

#
IRBuilder::fcvt_to_uint_sat

fn IRBuilder::fcvt_to_uint_sat(self : IRBuilder, ty : Type, a : Value) -> Value

Float to unsigned int (saturating - NaN->0, overflow->max, negative->0)

#
IRBuilder::fdemote

fn IRBuilder::fdemote(self : IRBuilder, a : Value) -> Value

Demote float (f64 -> f32)

#
IRBuilder::fdiv

fn IRBuilder::fdiv(self : IRBuilder, a : Value, b : Value) -> Value

Float divide

#
IRBuilder::ffloor

fn IRBuilder::ffloor(self : IRBuilder, a : Value) -> Value

Float floor

#
IRBuilder::fmax

fn IRBuilder::fmax(self : IRBuilder, a : Value, b : Value) -> Value

Float maximum

#
IRBuilder::fmin

fn IRBuilder::fmin(self : IRBuilder, a : Value, b : Value) -> Value

Float minimum

#
IRBuilder::fmul

fn IRBuilder::fmul(self : IRBuilder, a : Value, b : Value) -> Value

Float multiply

#
IRBuilder::fnearest

fn IRBuilder::fnearest(self : IRBuilder, a : Value) -> Value

Float nearest (round to nearest even)

#
IRBuilder::fneg

fn IRBuilder::fneg(self : IRBuilder, a : Value) -> Value

Float negate

#
IRBuilder::fpromote

fn IRBuilder::fpromote(self : IRBuilder, a : Value) -> Value

Promote float (f32 -> f64)

#
IRBuilder::fsqrt

fn IRBuilder::fsqrt(self : IRBuilder, a : Value) -> Value

Float square root

#
IRBuilder::fsub

fn IRBuilder::fsub(self : IRBuilder, a : Value, b : Value) -> Value

Float subtract

#
IRBuilder::ftrunc

fn IRBuilder::ftrunc(self : IRBuilder, a : Value) -> Value

Float truncate

#
IRBuilder::get_const_value

fn IRBuilder::get_const_value(self : IRBuilder, v : Value) -> Int64?

Get the constant value if a Value was defined by an Iconst instruction. Searches all blocks in the function to find the defining instruction. Returns None if the value is not a constant or not found.

#
IRBuilder::get_function

fn IRBuilder::get_function(self : IRBuilder) -> Function

Get the function being built

#
IRBuilder::iadd

fn IRBuilder::iadd(self : IRBuilder, a : Value, b : Value) -> Value

Integer add

#
IRBuilder::icmp

fn IRBuilder::icmp(self : IRBuilder, cc : IntCC, a : Value, b : Value) -> Value

Integer comparison (returns i32 0 or 1)

#
IRBuilder::icmp_eq

fn IRBuilder::icmp_eq(self : IRBuilder, a : Value, b : Value) -> Value

Integer equal

#
IRBuilder::icmp_ne

fn IRBuilder::icmp_ne(self : IRBuilder, a : Value, b : Value) -> Value

Integer not equal

#
IRBuilder::icmp_sge

fn IRBuilder::icmp_sge(self : IRBuilder, a : Value, b : Value) -> Value

Signed greater than or equal

#
IRBuilder::icmp_sgt

fn IRBuilder::icmp_sgt(self : IRBuilder, a : Value, b : Value) -> Value

Signed greater than

#
IRBuilder::icmp_sle

fn IRBuilder::icmp_sle(self : IRBuilder, a : Value, b : Value) -> Value

Signed less than or equal

#
IRBuilder::icmp_slt

fn IRBuilder::icmp_slt(self : IRBuilder, a : Value, b : Value) -> Value

Signed less than

#
IRBuilder::icmp_uge

fn IRBuilder::icmp_uge(self : IRBuilder, a : Value, b : Value) -> Value

Unsigned greater than or equal

#
IRBuilder::icmp_ugt

fn IRBuilder::icmp_ugt(self : IRBuilder, a : Value, b : Value) -> Value

Unsigned greater than

#
IRBuilder::icmp_ule

fn IRBuilder::icmp_ule(self : IRBuilder, a : Value, b : Value) -> Value

Unsigned less than or equal

#
IRBuilder::icmp_ult

fn IRBuilder::icmp_ult(self : IRBuilder, a : Value, b : Value) -> Value

Unsigned less than

#
IRBuilder::iconst

fn IRBuilder::iconst(self : IRBuilder, ty : Type, value : Int64) -> Value

Emit an integer constant

#
IRBuilder::iconst_i32

fn IRBuilder::iconst_i32(self : IRBuilder, value : Int) -> Value

Emit an i32 constant

#
IRBuilder::iconst_i64

fn IRBuilder::iconst_i64(self : IRBuilder, value : Int64) -> Value

Emit an i64 constant

#
IRBuilder::imul

fn IRBuilder::imul(self : IRBuilder, a : Value, b : Value) -> Value

Integer multiply

#
IRBuilder::ireduce

fn IRBuilder::ireduce(self : IRBuilder, ty : Type, a : Value) -> Value

Reduce integer width (e.g., i64 -> i32)

#
IRBuilder::ishl

fn IRBuilder::ishl(self : IRBuilder, a : Value, b : Value) -> Value

Shift left

#
IRBuilder::isub

fn IRBuilder::isub(self : IRBuilder, a : Value, b : Value) -> Value

Integer subtract

#
IRBuilder::jump

fn IRBuilder::jump(self : IRBuilder, target : Block, args : Array[Value]) -> Unit

Unconditional jump

#
IRBuilder::load_ptr

fn IRBuilder::load_ptr(self : IRBuilder, ty : Type, base : Value, offset : Value) -> Value

Load from raw pointer (no bounds checking) For trampoline code that operates on host memory

#
IRBuilder::load_ptr_narrow

fn IRBuilder::load_ptr_narrow(self : IRBuilder, result_ty : Type, bits : Int, signed : Bool, base : Value, offset : Value) -> Value

Load narrow value from raw pointer (no bounds checking) Loads 'bits' bits from memory and extends to result_ty

#
IRBuilder::new

fn IRBuilder::new(name : String) -> IRBuilder

#
IRBuilder::popcnt

fn IRBuilder::popcnt(self : IRBuilder, a : Value) -> Value

Population count (count number of 1 bits)

#
IRBuilder::print

fn IRBuilder::print(self : IRBuilder) -> String

Print using IRBuilder

#
IRBuilder::return_

fn IRBuilder::return_(self : IRBuilder, values : Array[Value]) -> Unit

Return from function

#
IRBuilder::rotl

fn IRBuilder::rotl(self : IRBuilder, a : Value, b : Value) -> Value

Rotate left

#
IRBuilder::rotr

fn IRBuilder::rotr(self : IRBuilder, a : Value, b : Value) -> Value

Rotate right

#
IRBuilder::sdiv

fn IRBuilder::sdiv(self : IRBuilder, a : Value, b : Value) -> Value

Signed integer divide

#
IRBuilder::select

fn IRBuilder::select(self : IRBuilder, cond : Value, a : Value, b : Value) -> Value

Conditional select: cond ? a : b

#
IRBuilder::sextend

fn IRBuilder::sextend(self : IRBuilder, ty : Type, a : Value) -> Value

Sign extend (e.g., i32 -> i64)

#
IRBuilder::sextend16

fn IRBuilder::sextend16(self : IRBuilder, ty : Type, a : Value) -> Value

Sign extend from 16 bits (in-place, keeps the same type) Similar to ireduce(I16) + sextend(ty)

#
IRBuilder::sextend32

fn IRBuilder::sextend32(self : IRBuilder, a : Value) -> Value

Sign extend from 32 bits to 64 bits Similar to ireduce(I32) + sextend(I64)

#
IRBuilder::sextend8

fn IRBuilder::sextend8(self : IRBuilder, ty : Type, a : Value) -> Value

Sign extend from 8 bits (in-place, keeps the same type) Similar to ireduce(I8) + sextend(ty)

#
IRBuilder::sint_to_fcvt

fn IRBuilder::sint_to_fcvt(self : IRBuilder, ty : Type, a : Value) -> Value

Signed int to float

#
IRBuilder::smulh

fn IRBuilder::smulh(self : IRBuilder, a : Value, b : Value) -> Value

Signed multiply high (i64 only)

#
IRBuilder::srem

fn IRBuilder::srem(self : IRBuilder, a : Value, b : Value) -> Value

Signed integer remainder

#
IRBuilder::sshr

fn IRBuilder::sshr(self : IRBuilder, a : Value, b : Value) -> Value

Signed shift right

#
IRBuilder::store_ptr

fn IRBuilder::store_ptr(self : IRBuilder, ty : Type, base : Value, value : Value, offset : Value) -> Unit

Store to raw pointer (no bounds checking) For trampoline code that operates on host memory

#
IRBuilder::store_ptr_narrow

fn IRBuilder::store_ptr_narrow(self : IRBuilder, bits : Int, base : Value, value : Value, offset : Value) -> Unit

Store narrow value to raw pointer (no bounds checking) Stores the low 'bits' bits of value to memory

#
IRBuilder::switch_to_block

fn IRBuilder::switch_to_block(self : IRBuilder, block : Block) -> Unit

Switch to a different block for emitting instructions

#
IRBuilder::trap

fn IRBuilder::trap(self : IRBuilder, reason : String) -> Unit

Trap/unreachable

#
IRBuilder::udiv

fn IRBuilder::udiv(self : IRBuilder, a : Value, b : Value) -> Value

Unsigned integer divide

#
IRBuilder::uextend

fn IRBuilder::uextend(self : IRBuilder, ty : Type, a : Value) -> Value

Zero extend (e.g., i32 -> i64)

#
IRBuilder::uint_to_fcvt

fn IRBuilder::uint_to_fcvt(self : IRBuilder, ty : Type, a : Value) -> Value

Unsigned int to float

#
IRBuilder::umulh

fn IRBuilder::umulh(self : IRBuilder, a : Value, b : Value) -> Value

Unsigned multiply high (i64 only)

#
IRBuilder::urem

fn IRBuilder::urem(self : IRBuilder, a : Value, b : Value) -> Value

Unsigned integer remainder

#
IRBuilder::ushr

fn IRBuilder::ushr(self : IRBuilder, a : Value, b : Value) -> Value

Unsigned shift right

#
IRBuilder::v128_and

fn IRBuilder::v128_and(self : IRBuilder, a : Value, b : Value) -> Value

#
IRBuilder::v128_andnot

fn IRBuilder::v128_andnot(self : IRBuilder, a : Value, b : Value) -> Value

#
IRBuilder::v128_anytrue

fn IRBuilder::v128_anytrue(self : IRBuilder, a : Value) -> Value

#
IRBuilder::v128_binary

fn IRBuilder::v128_binary(self : IRBuilder, opcode : Opcode, a : Value, b : Value) -> Value

#
IRBuilder::v128_bitselect

fn IRBuilder::v128_bitselect(self : IRBuilder, a : Value, b : Value, c : Value) -> Value

#
IRBuilder::v128_const

fn IRBuilder::v128_const(self : IRBuilder, bytes : Bytes) -> Value

v128_const - emit a V128 constant

#
IRBuilder::v128_extract16s

fn IRBuilder::v128_extract16s(self : IRBuilder, vec : Value, lane : Int) -> Value

#
IRBuilder::v128_extract16u

fn IRBuilder::v128_extract16u(self : IRBuilder, vec : Value, lane : Int) -> Value

#
IRBuilder::v128_extract32

fn IRBuilder::v128_extract32(self : IRBuilder, vec : Value, lane : Int) -> Value

#
IRBuilder::v128_extract64

fn IRBuilder::v128_extract64(self : IRBuilder, vec : Value, lane : Int) -> Value

#
IRBuilder::v128_extract8s

fn IRBuilder::v128_extract8s(self : IRBuilder, vec : Value, lane : Int) -> Value

Extract a lane from a v128 value

#
IRBuilder::v128_extract8u

fn IRBuilder::v128_extract8u(self : IRBuilder, vec : Value, lane : Int) -> Value

#
IRBuilder::v128_extract_f32

fn IRBuilder::v128_extract_f32(self : IRBuilder, vec : Value, lane : Int) -> Value

#
IRBuilder::v128_extract_f64

fn IRBuilder::v128_extract_f64(self : IRBuilder, vec : Value, lane : Int) -> Value

#
IRBuilder::v128_load_lane_with_addr

fn IRBuilder::v128_load_lane_with_addr(self : IRBuilder, opcode : Opcode, effective_addr : Value, vec : Value) -> Value

SIMD load lane with effective address and existing vector

#
IRBuilder::v128_load_with_addr

fn IRBuilder::v128_load_with_addr(self : IRBuilder, opcode : Opcode, effective_addr : Value) -> Value

SIMD load with effective address (for complex SIMD loads)

#
IRBuilder::v128_not

fn IRBuilder::v128_not(self : IRBuilder, a : Value) -> Value

Bitwise operations on v128

#
IRBuilder::v128_or

fn IRBuilder::v128_or(self : IRBuilder, a : Value, b : Value) -> Value

#
IRBuilder::v128_replace16

fn IRBuilder::v128_replace16(self : IRBuilder, vec : Value, val : Value, lane : Int) -> Value

#
IRBuilder::v128_replace32

fn IRBuilder::v128_replace32(self : IRBuilder, vec : Value, val : Value, lane : Int) -> Value

#
IRBuilder::v128_replace64

fn IRBuilder::v128_replace64(self : IRBuilder, vec : Value, val : Value, lane : Int) -> Value

#
IRBuilder::v128_replace8

fn IRBuilder::v128_replace8(self : IRBuilder, vec : Value, val : Value, lane : Int) -> Value

Replace a lane in a v128 value

#
IRBuilder::v128_replace_f32

fn IRBuilder::v128_replace_f32(self : IRBuilder, vec : Value, val : Value, lane : Int) -> Value

#
IRBuilder::v128_replace_f64

fn IRBuilder::v128_replace_f64(self : IRBuilder, vec : Value, val : Value, lane : Int) -> Value

#
IRBuilder::v128_shift

fn IRBuilder::v128_shift(self : IRBuilder, opcode : Opcode, vec : Value, shift : Value) -> Value

#
IRBuilder::v128_shuffle

fn IRBuilder::v128_shuffle(self : IRBuilder, a : Value, b : Value, lanes : FixedArray[Int]) -> Value

Shuffle lanes from two v128 values

#
IRBuilder::v128_splat16

fn IRBuilder::v128_splat16(self : IRBuilder, val : Value) -> Value

#
IRBuilder::v128_splat32

fn IRBuilder::v128_splat32(self : IRBuilder, val : Value) -> Value

#
IRBuilder::v128_splat64

fn IRBuilder::v128_splat64(self : IRBuilder, val : Value) -> Value

#
IRBuilder::v128_splat8

fn IRBuilder::v128_splat8(self : IRBuilder, val : Value) -> Value

v128_splat - broadcast a scalar to all lanes

#
IRBuilder::v128_splat_f32

fn IRBuilder::v128_splat_f32(self : IRBuilder, val : Value) -> Value

#
IRBuilder::v128_splat_f64

fn IRBuilder::v128_splat_f64(self : IRBuilder, val : Value) -> Value

#
IRBuilder::v128_store_lane_with_addr

fn IRBuilder::v128_store_lane_with_addr(self : IRBuilder, opcode : Opcode, effective_addr : Value, vec : Value) -> Unit

SIMD store lane with effective address and vector (void)

#
IRBuilder::v128_swizzle

fn IRBuilder::v128_swizzle(self : IRBuilder, a : Value, b : Value) -> Value

Swizzle lanes using indices from another v128

#
IRBuilder::v128_to_i32

fn IRBuilder::v128_to_i32(self : IRBuilder, opcode : Opcode, a : Value) -> Value

#
IRBuilder::v128_unary

fn IRBuilder::v128_unary(self : IRBuilder, opcode : Opcode, a : Value) -> Value

Generic SIMD instruction emitter Use this for operations that follow the pattern: opcode, operands -> V128/I32

#
IRBuilder::v128_xor

fn IRBuilder::v128_xor(self : IRBuilder, a : Value, b : Value) -> Value

#
Inst

pub struct Inst {
id : Int
results : Array[Value]
opcode : Opcode
args : Array[Value]
operands : Array[Value]
metadata : Array[Metadata]
} derive(
Debug
)

Instruction - an SSA instruction that produces a value
impl Eq for Inst
impl Hash for Inst

#
Inst::add_metadata

fn Inst::add_metadata(self : Inst, metadata : Metadata) -> Unit

#
Inst::all_results

fn Inst::all_results(self : Inst) -> Array[Value]

Get all results of this instruction

#
Inst::first_result

fn Inst::first_result(self : Inst) -> Value?

Get primary result of this instruction (first result or None)

#
Inst::new_multi

fn Inst::new_multi(results : Array[Value], opcode : Opcode, operands : Array[Value]) -> Inst

Create an instruction with multiple results (for multi-value call)

#
Inst::new_with_id

fn Inst::new_with_id(id : Int, opcode : Opcode, args : Array[Value], results : Array[Value]) -> Inst

#
IntCC

pub(all) enum IntCC {
Eq
Ne
Slt
Sle
Sgt
Sge
Ult
Ule
Ugt
Uge
} derive(Eq, Hash,
Debug
)

Integer comparison condition codes

#
Loop

pub(all) struct Loop {
header : Int
blocks : Array[Int]
back_edges : Array[(Int, Int)]
} derive(
Debug
)

#
Loop::contains

fn Loop::contains(self : Loop, block_id : Int) -> Bool

#
Metadata

pub(all) enum Metadata {
SourceLoc(String)
Comment(String)
} derive(Eq,
Debug
)

#
Opcode

pub(all) enum Opcode {
Iconst(Int64)
Fconst(Double)
Iadd
Isub
Imul
Umulh
Smulh
Sdiv
Udiv
Srem
Urem
Band
Bor
Bxor
Bnot
Ishl
Sshr
Ushr
Rotl
Rotr
Clz
Ctz
Popcnt
Icmp(IntCC)
IcmpEq
Fadd
Fsub
Fmul
Fdiv
Fmin
Fmax
Fcmp(FloatCC)
Fneg
Fabs
Fsqrt
Fceil
Ffloor
Ftrunc
Fnearest
Ireduce
Sextend
Uextend
Fpromote
Fdemote
FcvtToSint
FcvtToUint
FcvtToSintSat
FcvtToUintSat
SintToFcvt
UintToFcvt
Bitcast
Sextend8
Sextend16
Sextend32
Select
Copy
Load
Store
StackAddr(StackSlot)
Call(ExternalSymbol)
CallIndirect(Signature)
LoadPtr(Type)
StorePtr(Type)
LoadPtrNarrow(Type, Int, Bool)
StorePtrNarrow(Int)
CallPtr(Int, Int)
Trap(String)
Custom(String)
Ext(ExtOp)
V128Const(Bytes)
V128Splat8
V128Splat16
V128Splat32
V128Splat64
V128SplatF32
V128SplatF64
V128ExtractLane8S(Int)
V128ExtractLane8U(Int)
V128ExtractLane16S(Int)
V128ExtractLane16U(Int)
V128ExtractLane32(Int)
V128ExtractLane64(Int)
V128ExtractLaneF32(Int)
V128ExtractLaneF64(Int)
V128ReplaceLane8(Int)
V128ReplaceLane16(Int)
V128ReplaceLane32(Int)
V128ReplaceLane64(Int)
V128ReplaceLaneF32(Int)
V128ReplaceLaneF64(Int)
V128Shuffle(FixedArray[Int])
V128Swizzle
V128Not
V128And
V128AndNot
V128Or
V128Xor
V128Bitselect
V128AnyTrue
V128AllTrue8
V128AllTrue16
V128AllTrue32
V128AllTrue64
V128Bitmask8
V128Bitmask16
V128Bitmask32
V128Bitmask64
V128Add8
V128Add16
V128Add32
V128Add64
V128Sub8
V128Sub16
V128Sub32
V128Sub64
V128Mul16
V128Mul32
V128Mul64
V128AddSat8S
V128AddSat8U
V128AddSat16S
V128AddSat16U
V128SubSat8S
V128SubSat8U
V128SubSat16S
V128SubSat16U
V128Min8S
V128Min8U
V128Min16S
V128Min16U
V128Min32S
V128Min32U
V128Max8S
V128Max8U
V128Max16S
V128Max16U
V128Max32S
V128Max32U
V128Avgr8U
V128Avgr16U
V128Abs8
V128Abs16
V128Abs32
V128Abs64
V128Neg8
V128Neg16
V128Neg32
V128Neg64
V128Popcnt8
V128Shl8
V128Shl16
V128Shl32
V128Shl64
V128Shr8S
V128Shr8U
V128Shr16S
V128Shr16U
V128Shr32S
V128Shr32U
V128Shr64S
V128Shr64U
V128Eq8
V128Eq16
V128Eq32
V128Eq64
V128Ne8
V128Ne16
V128Ne32
V128Ne64
V128Lt8S
V128Lt8U
V128Lt16S
V128Lt16U
V128Lt32S
V128Lt32U
V128Lt64S
V128Gt8S
V128Gt8U
V128Gt16S
V128Gt16U
V128Gt32S
V128Gt32U
V128Gt64S
V128Le8S
V128Le8U
V128Le16S
V128Le16U
V128Le32S
V128Le32U
V128Le64S
V128Ge8S
V128Ge8U
V128Ge16S
V128Ge16U
V128Ge32S
V128Ge32U
V128Ge64S
V128Narrow16to8S
V128Narrow16to8U
V128Narrow32to16S
V128Narrow32to16U
V128ExtendLow8to16S
V128ExtendHigh8to16S
V128ExtendLow8to16U
V128ExtendHigh8to16U
V128ExtendLow16to32S
V128ExtendHigh16to32S
V128ExtendLow16to32U
V128ExtendHigh16to32U
V128ExtendLow32to64S
V128ExtendHigh32to64S
V128ExtendLow32to64U
V128ExtendHigh32to64U
V128ExtMulLow8to16S
V128ExtMulHigh8to16S
V128ExtMulLow8to16U
V128ExtMulHigh8to16U
V128ExtMulLow16to32S
V128ExtMulHigh16to32S
V128ExtMulLow16to32U
V128ExtMulHigh16to32U
V128ExtMulLow32to64S
V128ExtMulHigh32to64S
V128ExtMulLow32to64U
V128ExtMulHigh32to64U
V128ExtAddPairwise8to16S
V128ExtAddPairwise8to16U
V128ExtAddPairwise16to32S
V128ExtAddPairwise16to32U
V128Dot16to32S
V128Q15MulrSat16S
V128AddF32
V128AddF64
V128SubF32
V128SubF64
V128MulF32
V128MulF64
V128DivF32
V128DivF64
V128MinF32
V128MinF64
V128MaxF32
V128MaxF64
V128PMinF32
V128PMinF64
V128PMaxF32
V128PMaxF64
V128AbsF32
V128AbsF64
V128NegF32
V128NegF64
V128SqrtF32
V128SqrtF64
V128CeilF32
V128CeilF64
V128FloorF32
V128FloorF64
V128TruncF32
V128TruncF64
V128NearestF32
V128NearestF64
V128EqF32
V128EqF64
V128NeF32
V128NeF64
V128LtF32
V128LtF64
V128GtF32
V128GtF64
V128LeF32
V128LeF64
V128GeF32
V128GeF64
V128TruncSatF32toI32S
V128TruncSatF32toI32U
V128TruncSatF64toI32SZero
V128TruncSatF64toI32UZero
V128ConvertI32toF32S
V128ConvertI32toF32U
V128ConvertLowI32toF64S
V128ConvertLowI32toF64U
V128DemoteF64toF32Zero
V128PromoteLowF32toF64
V128Load8x8S(Int, Int, Int64)
V128Load8x8U(Int, Int, Int64)
V128Load16x4S(Int, Int, Int64)
V128Load16x4U(Int, Int, Int64)
V128Load32x2S(Int, Int, Int64)
V128Load32x2U(Int, Int, Int64)
V128Load8Splat(Int, Int, Int64)
V128Load16Splat(Int, Int, Int64)
V128Load32Splat(Int, Int, Int64)
V128Load64Splat(Int, Int, Int64)
V128Load32Zero(Int, Int, Int64)
V128Load64Zero(Int, Int, Int64)
V128Load8Lane(Int, Int, Int64, Int)
V128Load16Lane(Int, Int, Int64, Int)
V128Load32Lane(Int, Int, Int64, Int)
V128Load64Lane(Int, Int, Int64, Int)
V128Store8Lane(Int, Int, Int64, Int)
V128Store16Lane(Int, Int, Int64, Int)
V128Store32Lane(Int, Int, Int64, Int)
V128Store64Lane(Int, Int, Int64, Int)
V128RelaxedSwizzle
V128RelaxedTruncF32toI32S
V128RelaxedTruncF32toI32U
V128RelaxedTruncF64toI32SZero
V128RelaxedTruncF64toI32UZero
V128RelaxedMaddF32
V128RelaxedNmaddF32
V128RelaxedMaddF64
V128RelaxedNmaddF64
V128RelaxedLaneselect8
V128RelaxedLaneselect16
V128RelaxedLaneselect32
V128RelaxedLaneselect64
V128RelaxedMinF32
V128RelaxedMaxF32
V128RelaxedMinF64
V128RelaxedMaxF64
V128RelaxedQ15MulrS
V128RelaxedDot8to16S
V128RelaxedDot8to32AddS
} derive(Eq, Hash,
Debug
)

Opcode - the operation performed by an instruction
impl Show for Opcode

#
OptLevel

pub(all) enum OptLevel {
O0
O1
O2
O3
}

Optimization level
  • 0: No optimization
  • 1: Basic optimizations (constant folding, copy propagation, CSE, DCE)
  • 2: Default optimizations (includes control flow optimizations)
  • 3: Aggressive optimizations (includes loop optimizations)

#
OptLevel::from_int

fn OptLevel::from_int(n : Int) -> OptLevel

Parse optimization level from integer

#
OptResult

pub struct OptResult {
changed : Bool
} derive(Eq)

Result of an optimization pass

#
OptResult::mark_changed

fn OptResult::mark_changed(self : OptResult) -> Unit

Mark that the IR was changed

#
OptResult::new

fn OptResult::new() -> OptResult

#
RepKey

Key used for mapping an e-class to an in-scope IR value of a specific type. Note: an e-class may contain values of multiple IR types (e.g., shared constants), so the Type must be part of the key to avoid producing ill-typed IR.

#
Signature

pub(all) struct Signature {
params : Array[Type]
results : Array[Type]
} derive(Eq, Hash,
Debug
)

#
Signature::new

fn Signature::new(params : Array[Type], results : Array[Type]) -> Signature

#
StackSlot

pub(all) struct StackSlot {
id : Int
size : Int
align : Int
} derive(Eq, Hash,
Debug
)

#
Terminator

pub(all) enum Terminator {
Jump(Int, Array[Value])
Branch(Value, Int, Array[Value], Int, Array[Value])
Brz(Value, Int, Int)
Brnz(Value, Int, Int)
BrTable(Value, Array[Int], Int)
Return(Array[Value])
Trap(String)
TrapExit(String)
} derive(
Debug
)

Terminator - how a basic block ends

#
Type

pub(all) enum Type {
I32
I64
F32
F64
V128
Ptr
Ref
CallableRef
OpaqueRef
} derive(Eq, Hash,
Debug
)

IR type system for scalar, vector, pointer, and reference-typed values.
impl Show for Type

#
ValidationResult

pub struct ValidationResult {
valid : Bool
errors : Array[String]
}

Result of IR validation

#
ValidationResult::new

#
Value

pub(all) struct Value {
id : Int
ty : Type
} derive(Eq, Hash,
Debug
)

IR Value - represents a virtual register in SSA form Each value is defined exactly once and can be used multiple times

#
build_dominator_tree

fn build_dominator_tree(idom : Array[Int]) -> Array[Array[Int]]

#
canonicalize_aliases

fn canonicalize_aliases(func : Function) -> OptResult

Alias/copy canonicalization pass. Resolves visible copy chains along the dominator tree and rewrites operands to canonical values (Cranelift analogue: resolve_all_aliases()).

#
cse_gvn_global

fn cse_gvn_global(func : Function) -> OptResult

Unified global optimization pass that combines pure CSE and alias-aware GVN in a single dominator-tree walk.

#
eliminate_common_subexpressions

fn eliminate_common_subexpressions(func : Function) -> OptResult

Common Subexpression Elimination (CSE) Replaces duplicate computations with references to the first computation Note: This is local CSE - expressions are only reused within the same basic block to avoid incorrectly using values from non-dominating blocks (e.g., sibling branches)

#
eliminate_common_subexpressions_global

fn eliminate_common_subexpressions_global(func : Function) -> OptResult

Global Common Subexpression Elimination using dominance analysis Expressions in dominating blocks can be reused in dominated blocks

#
eliminate_constant_block_params

fn eliminate_constant_block_params(func : Function) -> OptResult

Constant Block Parameter Elimination Removes block parameters that always take the same value across all incoming edges. This mirrors Cranelift's constant-phi removal, but operates on IR block params.

#
eliminate_dead_block_params

fn eliminate_dead_block_params(func : Function) -> OptResult

Dead Block Parameter Elimination Removes block parameters that are never used This is crucial for eliminating unused locals that get SSA-converted to block params

#
eliminate_dead_code

fn eliminate_dead_code(func : Function) -> OptResult

Dead Code Elimination (DCE) Removes instructions whose results are never used

#
eliminate_unreachable_blocks

fn eliminate_unreachable_blocks(func : Function) -> OptResult

#
eliminate_unreachable_code

fn eliminate_unreachable_code(func : Function) -> OptResult

Unreachable Code Elimination Removes blocks that cannot be reached from the entry block

#
fold_constants

fn fold_constants(func : Function) -> OptResult

Constant Folding Evaluates constant expressions at compile time

#
gvn

fn gvn(func : Function) -> OptResult

Global Value Numbering with Load CSE Extends CSE to handle memory loads by tracking when stores invalidate loads

#
gvn_global

fn gvn_global(func : Function) -> OptResult

Global Value Numbering with dominance analysis Expressions in dominating blocks can be reused in dominated blocks, with proper invalidation of memory-based expressions

#
hoist_loop_invariants

fn hoist_loop_invariants(func : Function) -> OptResult

Loop Invariant Code Motion Moves loop-invariant computations out of loops to the preheader

#
instruction_count

fn instruction_count(func : Function) -> Int

Count IR instructions in a function.

#
merge_blocks

fn merge_blocks(func : Function) -> OptResult

Basic Block Merging Merges a block with its unique predecessor if the predecessor has only one successor

#
optimize

fn optimize(func : Function) -> OptResult

Run default optimizations (Cranelift-aligned O2 path).

#
optimize_block

fn optimize_block(block : Block) -> Map[Int, (Int,
ENode
)]

Optimize a single basic block's arithmetic expressions Returns a map from original Value id to optimized ENode

#
optimize_block_with_limits

fn optimize_block_with_limits(block : Block, limits :
SaturationLimits
) -> Map[Int, (Int,
ENode
)]

#
optimize_function

fn optimize_function(func : Function) -> Bool

Apply e-graph optimization to a function. Returns true if any optimization was applied.

#
optimize_function_with_limits

fn optimize_function_with_limits(func : Function, limits :
SaturationLimits
) -> Bool

#
optimize_function_with_stats

fn optimize_function_with_stats(func : Function) -> EGraphOptimizeStats

Apply e-graph optimization to a function and return aggregate stats.

#
optimize_function_with_stats_with_limits

fn optimize_function_with_stats_with_limits(func : Function, limits :
SaturationLimits
) -> EGraphOptimizeStats

#
optimize_function_with_stats_with_limits_and_ruleset

#
optimize_with_level

fn optimize_with_level(func : Function, level : OptLevel) -> OptResult

Run optimizations based on level

#
propagate_copies

fn propagate_copies(func : Function) -> OptResult

Copy Propagation Replaces uses of copied values with the original value Note: This is local copy propagation - only propagates within the same basic block to avoid incorrectly using values from non-dominating blocks (e.g., sibling branches)

#
propagate_copies_global

fn propagate_copies_global(func : Function) -> OptResult

Cross-basic-block copy propagation using dominance analysis Compatibility wrapper around canonicalize_aliases.

#
reduce_strength

fn reduce_strength(func : Function) -> OptResult

Strength Reduction Replaces expensive operations with cheaper equivalents Examples: multiplication by power of 2 -> shift, division by power of 2 -> shift

#
rematerialize_across_blocks

fn rematerialize_across_blocks(func : Function) -> OptResult

Rematerialize cheap, pure SSA defs into each use block (once per block).

This pass is intentionally non-recursive (it does not attempt to remat the operands of a rematted instruction), matching Cranelift's current behavior.

#
simplify_branches

fn simplify_branches(func : Function) -> OptResult

Branch Simplification Simplifies conditional branches when the condition is a known constant

#
thread_jumps

fn thread_jumps(func : Function) -> OptResult

Jump Threading Bypasses blocks that only contain an unconditional jump

#
unroll_loops

fn unroll_loops(func : Function, unroll_factor : Int) -> OptResult

Loop Unrolling Duplicates the loop body to reduce loop overhead and enable further optimizations This is a simple unrolling that only handles loops with known trip counts

#
validate_function

fn validate_function(func : Function) -> ValidationResult

Validate a function's IR