Sign in

    milkir

    Reusable Cranelift-like SSA intermediate representation

    compiler
    ir
    ssa
    optimization
    Download zip
    Author
    Version
    0.3.0
    License
    Apache-2.0
    Last updated
    3 months ago
    Downloads
    362

    #MilkIR

    MilkIR is a reusable, target-independent intermediate representation for compiler middle ends. A frontend translates source operations into MilkIR, optimization passes simplify the MilkIR function, and a lowering package such as Milky2018/milkir_machv turns it into machine-oriented IR.

    Target-independent means that MilkIR operations do not encode a particular instruction set or calling convention; it does not mean that every data width is target-configurable. MilkIR pointer/reference carriers are always 64-bit: Ptr, Ref, CallableRef, and OpaqueRef each have a fixed 64-bit representation. A backend for a non-64-bit target would require an explicit IR contract change rather than interpreting these types using the host pointer width.

    frontend IR or bytecode | v MilkIR SSA -- verify and optimize here | v MachV IR -- select instructions and allocate registers later

    #Start with one small function

    Suppose the source program contains this function:

    add_one(x: i32) -> i32 = x + 1

    The following test builds the same function in MilkIR:

    ///|
    test "build add_one" {
    let builder = FunctionBuilder::FunctionBuilder("add_one")

    let x = builder.add_param(I32)
    builder.add_result(I32)

    let one = builder.iconst_i32(1)
    let answer = builder.iadd(x, one)
    builder.return_([answer])

    let func = builder.finalize()
    inspect(func.verify(), content="()")
    inspect(
    func.print(),
    content=(
    #|function add_one(v0:i32) -> i32 {
    #|block0:
    #| v1:i32 = iconst 1
    #| v2:i32 = iadd v0, v1
    #| return v2
    #|}
    #|
    ),
    )
    }

    Read the generated IR from top to bottom:

    function add_one(v0:i32) -> i32 { function parameter v0 has type i32 block0: execution starts in block0 v1:i32 = iconst 1 create the constant 1 v2:i32 = iadd v0, v1 add x and 1, producing a new value return v2 return that value }

    finalize() verifies the current function before returning it and raises VerifyError for malformed SSA, CFG, or generic instruction contracts. For extension instructions, generic verification checks only the dialect/opcode envelope and the explicit operand/result signature; the owning adapter validates dialect semantics. Finalization is a validation checkpoint, not a freeze operation: the returned Function remains mutable and does not carry a persistent "verified" state. get_function() remains the explicit escape hatch for in-progress construction and transformation code. Callers may continue transforming either value, but every consuming adapter must verify the function after its final mutation and immediately before lowering it.

    The important detail is that x, one, and answer are not runtime integers in the MoonBit program that builds the IR. They are MilkIR Values: typed handles that name values in the function being compiled.

    #The six concepts to know

    ConceptMeaningIn add_one
    FunctionOne compilable function, including its signature and blocks.add_one
    TypeThe static type of an IR value.I32
    ValueA typed name for a function parameter, block parameter, or instruction result.v0, v1, v2
    BlockA straight-line sequence of instructions with one exit.block0
    InstAn operation that may produce one or more values.iconst, iadd
    TerminatorThe final control-flow operation in a block.return

    FunctionBuilder is the normal construction API. It creates an entry block automatically and keeps track of the block that receives new instructions. For a straight-line function, the usual order is:

    1. Create a builder.
    2. Declare function parameters and result types.
    3. Emit instructions.
    4. End the current block with return_, jump, a branch, or trap.
    5. Call finalize, then verify the function.

    Lower-level producers that already have a complete Signature can use Function::with_signature. It eagerly materializes every declared function parameter in Function.params; retrieve those values with func.param(index) before emitting instructions. Function::signature() returns a snapshot derived from the same explicit parameter and result arrays. Do not recreate parameters with new_value(): that method allocates instruction results, block values, and other values after the declared function parameters.

    #Opcode families

    Every instruction belongs to one semantic family. Opcode has no source-language instruction variants or target-machine operations of its own:

    FamilyResponsibility
    Scalar(ScalarOp)Constants, arithmetic, comparisons, conversions, selection, and copies.
    Memory(MemoryOp)Full-width and narrow loads and stores over an explicit base and offset value.
    Call(CallOp)Direct external-symbol calls and function-pointer calls with explicit contracts.
    Vector(VectorOp)Language-neutral V128 lane, arithmetic, comparison, conversion, and effective-address memory operations.
    Ext(ExtOp, Signature)Typed operations whose semantics belong to a separately owned dialect.

    Frontends must consume source-only metadata before constructing a core instruction. For example, a WebAssembly frontend resolves a SIMD memory index, alignment hint, and immediate offset while computing the effective address; MilkIR receives that address and the vector load/store semantics. A frontend uses Ext only when the operation genuinely requires dialect-owned validation and lowering.

    #Semantic ownership

    MilkIR records the optimizer-visible facts of each built-in opcode in one semantic summary: whether it may trap, whether it reads or writes memory, and whether it has another observable effect. Dead-code elimination, loop optimization, global value numbering, and e-graph admission derive their safety decisions from that summary instead of maintaining independent opcode lists. Unknown extension operations are conservatively treated as trapping and effectful.

    Other concerns remain with the adapter that implements them. The verifier owns operand and result contracts, the printer owns textual syntax, MilkIR-MachV Lowering owns instruction selection, and the e-graph adapter owns which operations have an e-graph encoding. These are different responsibilities rather than duplicate semantic facts.

    When adding a built-in opcode:

    1. Add its operand and result contract to the verifier.
    2. Classify its trap, memory, and observable-effect behavior in the opcode semantic summary.
    3. Add its textual representation to the printer.
    4. Add its instruction selection to MilkIR-MachV Lowering.
    5. Add an e-graph encoding only if the e-graph node language represents it.

    The built-in family matches in those stages are exhaustive, so adding a new family or operation leaves a compiler error until its required behavior is supplied. E-graph admission is intentionally conservative: an operation without an explicit encoding remains outside the e-graph.

    #Why values are called SSA values

    SSA means static single assignment: each MilkIR Value is defined exactly once. An instruction never changes an existing value; it creates a new one.

    For example, a source-language assignment such as x = x + 1 should not overwrite the MilkIR value for the old x. The frontend emits a fresh value instead:

    v0:i32 = ... old x v1:i32 = iconst 1 v2:i32 = iadd v0, v1 new x

    This makes data dependencies explicit. An optimizer can see that v2 depends on v0 and v1 without reconstructing the history of a mutable variable.

    Every value belongs to one function and has one Type. The core types are I32, I64, F32, F64, V128, Ptr, Ref, CallableRef, and OpaqueRef. The four pointer/reference carrier types are fixed-width 64-bit values, not aliases for the host's native pointer type.

    #Comparisons do not require extra blocks

    Use select when both candidate values are already available. The next function returns the larger of two signed i32 values:

    ///|
    test "build max_i32 with select" {
    let builder = FunctionBuilder::FunctionBuilder("max_i32")
    let lhs = builder.add_param(I32)
    let rhs = builder.add_param(I32)
    builder.add_result(I32)

    let lhs_is_greater = builder.icmp_sgt(lhs, rhs)
    let result = builder.select(lhs_is_greater, lhs, rhs)
    builder.return_([result])

    let func = builder.finalize()
    inspect(func.verify(), content="()")
    inspect(func.print().contains("icmp.sgt"), content="true")
    inspect(func.print().contains("select"), content="true")
    }

    icmp_sgt produces an I32 condition. select(condition, when_nonzero, when_zero) chooses a value without changing control flow.

    #Control flow: blocks and terminators

    A block contains zero or more instructions and exactly one terminator. Once a terminator has been emitted, switch to a different block before emitting more instructions.

    The common terminators are:

    Builder methodMeaning
    return_(values)Return values to the caller.
    jump(target, args)Continue in another block and pass its arguments.
    brnz(condition, then_block, else_block)Branch to the first target when the condition is nonzero.
    brz(condition, then_block, else_block)Branch to the first target when the condition is zero.
    br_table(index, targets, default)Choose one of several targets.
    trap(reason)Stop execution abnormally.

    Here is the shape of a conditional function before considering how values cross block boundaries:

    +--------------+ | block0 | | test cond | +------+-------+ | +---------+---------+ | | v v +-----------+ +-----------+ | then_block| | else_block| +-----+-----+ +-----+-----+ | | +---------+---------+ | v +------------+ | join_block | +------------+

    #Passing values between blocks

    MilkIR uses block parameters instead of phi instructions. A block parameter is a value defined at the start of a block. Every jump to that block supplies the argument that the parameter receives on that edge.

    The following function computes input + 1 when condition is nonzero and returns input otherwise:

    ///|
    test "pass a value into a join block" {
    let builder = FunctionBuilder::FunctionBuilder("add_if")
    let input = builder.add_param(I32)
    let condition = builder.add_param(I32)
    builder.add_result(I32)

    let add_block = builder.create_block()
    let unchanged_block = builder.create_block()
    let join_block = builder.create_block()
    let result = builder.add_block_param(join_block, I32)

    builder.brnz(condition, add_block, unchanged_block)

    builder.switch_to_block(add_block)
    let one = builder.iconst_i32(1)
    let incremented = builder.iadd(input, one)
    builder.jump(join_block, [incremented])

    builder.switch_to_block(unchanged_block)
    builder.jump(join_block, [input])

    builder.switch_to_block(join_block)
    builder.return_([result])

    let func = builder.finalize()
    inspect(func.verify(), content="()")
    inspect(func.blocks.length(), content="4")
    inspect(func.blocks[3].params.length(), content="1")
    }

    Focus on the three names around the join:

    add_block --jump [incremented]--+ >-- join_block(result) -- return result unchanged_block --jump [input]----------+

    result is defined by join_block, not by either predecessor. On the add_block edge it receives incremented; on the other edge it receives input. If a join block has multiple parameters, each incoming jump must pass arguments in the same order.

    #Verification

    Always verify a function after construction and after transformations that may change its structure:

    let func = builder.finalize()
    func.verify()

    Function::verify checks core structural and local typing rules, including:

    • the function contains at least one block;
    • each referenced operand has been defined;
    • each block has a terminator;
    • core instructions have the expected operand arity;
    • operands that must agree have matching types;
    • comparison results have type I32.

    Verification is a useful construction guard, not a complete proof of every frontend, dialect, dominance, calling-convention, or embedding invariant. Frontends and dialects should perform any additional semantic checks their input requires.

    #Optimization

    Optimization mutates a Function and returns an OptResult whose changed field reports whether any pass changed the IR.

    ///|
    test "fold a constant expression" {
    let builder = FunctionBuilder::FunctionBuilder("constant_answer")
    builder.add_result(I32)
    let ten = builder.iconst_i32(10)
    let twenty = builder.iconst_i32(20)
    let answer = builder.iadd(ten, twenty)
    builder.return_([answer])

    let func = builder.finalize()
    let result = optimize_with_level(func, O1)

    inspect(result.changed, content="true")
    inspect(instruction_count(func), content="1")
    inspect(func.print().contains("iconst 30"), content="true")
    inspect(func.verify(), content="()")
    }

    The optimization levels are:

    LevelIntended use
    O0Minimal pipeline: removes dead code plus constant and unused block parameters.
    O1Inexpensive simplification: mandatory cleanup, constant folding, alias canonicalization, and dead-code elimination.
    O2The default pipeline: O1-style cleanup plus e-graph rewriting, budgeted global value numbering, and CFG simplification.
    O3The O2 pipeline, loop-invariant code motion, checked counted-loop unrolling, strength reduction, and a final O2 cleanup.

    Use optimize(func) for the default pipeline or optimize_with_level(func, level) when the caller chooses the level explicitly. Optimizers assume a valid SSA and block-parameter structure. Verify before optimization when the input comes from a frontend, then verify again after developing a new transformation.

    #Counted-loop unrolling at O3

    O3 unrolls only natural loops for which analysis produces a complete plan. The supported form has one preheader, one header comparison, one body path, one latch/back edge, and one exit; body and latch blocks must not introduce additional block parameters. Initial values, bounds, and steps must resolve to constants through any loop-external copy chain. The induction value may be I32 or I64 and may use signed or unsigned <, <=, >, or >= comparisons. Reversed comparison operands and inverted conditional-branch polarity are normalized before analysis. Increasing and decreasing updates use checked arithmetic, so a loop whose final update would wrap is rejected.

    All header block parameters are treated as loop-carried state. Full unrolling is limited to at most eight source iterations, while larger proven loops use factor-two unrolling with one peeled iteration for odd trip counts. Both strategies cap newly cloned instructions at 64. Cloning assigns fresh instruction and value IDs, remaps zero-result and multi-result instructions, preserves metadata and effect order, and verifies correctly with calls, loads, stores, and traps.

    The pass leaves the function unchanged when the CFG shape is unsupported, a bound or step is dynamic, the trip count exceeds the analysis limit, an operand or latch edge cannot be mapped, arithmetic may wrap, or code growth exceeds the budget.

    #Calls, memory, and traps

    The concepts below matter when a frontend moves beyond pure arithmetic into embedding and effect semantics.

    #External calls

    ExternalSymbol names a symbol outside the IR function. CallOp::Direct(symbol, signature) and CallOp::Pointer(num_args, num_results) are treated conservatively by core optimizations because calls may observe or change state. Direct calls carry operand/result signatures that the verifier checks; pointer calls carry explicit argument and result counts.

    CallOp::Pointer has a generic operand contract:

    1. operand 0 is the function pointer;
    2. operands 1 and later are ordinary arguments;
    3. num_args counts every operand after the callee pointer.

    An embedding adapter may assign roles such as VMContext to those arguments when it chooses a calling convention, but that role is not part of the core opcode contract.

    #Traps and effects

    Trap(reason) and TrapExit(reason) terminators end execution without normal results. Stores, calls, traps, and unknown extension operations are observable or potentially observable. Optimizations must not delete or reorder them unless a stronger analysis proves that doing so preserves behavior.

    #Dialect-specific operations

    Ext(ExtOp, Signature) represents dialect-specific operations. MilkIR stores only a dialect name, opcode name, integer immediates, and an explicit operand/result contract. Validator closures are not part of Function; the dialect package owns builders, semantic validation, decoding, and lowering.

    Function::verify and FunctionBuilder::finalize validate generic IR structure without interpreting dialect semantics. At the adapter seam, Function::verify_with_extension_validator receives the owned dialect name and validator explicitly, rejects extensions owned by another dialect, and converts diagnostics into VerifyError::UnverifiableInstruction. Dialect lowering likewise requires an explicit adapter verifier, so validation behavior depends only on the adapter selected by the consumer, never on function construction history.

    ///|
    test "validate a dialect opcode descriptor" {
    let descriptor = ExtOpDescriptor::ExtOpDescriptor("demo", "checked_add", 1)
    let opcode = ExtOp::ExtOp("demo", "checked_add", FixedArray::make(1, 32))

    inspect(opcode.matches_descriptor(descriptor), content="true")
    inspect(descriptor.expected_immediate_count(), content="1")
    }

    Core optimizations treat extension operations conservatively because MilkIR cannot infer whether an unknown operation reads memory, writes memory, traps, or depends on embedding state.

    #Typical frontend workflow

    A frontend using MilkIR normally follows this sequence:

    1. Map source types to MilkIR Types.
    2. Create a FunctionBuilder, then declare the function parameters and results.
    3. Translate each source basic block, keeping a map from source values to MilkIR Values.
    4. Represent merges and loop-carried values with block parameters and jump arguments.
    5. Finalize and verify the function.
    6. Optimize it at the desired level.
    7. Lower it to the next IR, for example with Milky2018/milkir_machv.

    For debugging, Function::print produces a readable textual view and CFG::to_dot produces Graphviz DOT for the control-flow graph. CFG also provides predecessors, successors, traversal orders, dominators, back edges, and loop discovery.

    MilkIR focuses on target-independent SSA construction, verification, control-flow analysis, and optimization. Other compiler stages build on it through separate packages:

    Compiler stagePackage
    SSA construction, verification, CFGs, and optimizationMilky2018/milkir
    Optional WebAssembly extension operationsMilky2018/wasm_milkir
    Lowering from MilkIR to machine-oriented IRMilky2018/milkir_machv
    Target instruction selection and ABI detailsMilky2018/aarch64_target and Milky2018/x64_target

    VerifyError

    pub suberror VerifyError {
    MissingTerminator(block_id~ : Int)
    EmptyFunction
    UndefinedValue(value_id~ : Int)
    ForeignValue(value_id~ : Int)
    ForeignBlock(block_id~ : Int)
    ForeignInstruction(inst_id~ : Int)
    DuplicateBlockId(block_id~ : Int)
    DuplicateValueDefinition(value_id~ : Int)
    DuplicateInstructionId(inst_id~ : Int)
    UseBeforeDefinition(value_id~ : Int)
    NonDominatingUse(value_id~ : Int, defining_block~ : Int, use_block~ : Int)
    InstructionOperandMismatch(inst_id~ : Int)
    InvalidBlockTarget(block_id~ : Int)
    ArityMismatch(message~ : String)
    TypeMismatch(message~ : String)
    UnverifiableInstruction(message~ : String)
    } derive(Eq,
    Debug
    )

    impl Show for VerifyError

    VerifyError::unverifiable_instruction

    fn VerifyError::unverifiable_instruction(message : String) -> VerifyError

    Construct a structured error for a consumer seam that cannot validate or lower an otherwise well-formed MilkIR function.

    Block

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

    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)

    The public fields are exposed for zero-copy inspection. Callers must not mutate params, instructions, or terminator directly. Use FunctionBuilder, append_inst, and set_terminator so ownership checks run, then verify the containing function before optimization or lowering.

    Block::append_inst

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

    Block::set_terminator

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

    Set the terminator for this block

    CFG

    pub 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

    CallOp

    pub(all) enum CallOp {
    Direct(ExternalSymbol, Signature)
    Pointer(Int, Int)
    } derive(Eq, Hash,
    Debug
    )

    Generic call semantics. Pointer-call operands are laid out as the callee pointer followed by ordinary arguments.

    ConversionOp

    pub(all) enum ConversionOp {
    IntReduce
    SignedExtend
    UnsignedExtend
    FloatPromote
    FloatDemote
    FloatToSignedInt
    FloatToUnsignedInt
    FloatToSignedIntSaturating
    FloatToUnsignedIntSaturating
    SignedIntToFloat
    UnsignedIntToFloat
    Bitcast
    } derive(Eq, Hash,
    Debug
    )

    ExtOp

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

    impl Eq for ExtOp
    impl Hash for ExtOp

    ExtOp::ExtOp

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

    ExtOp::matches_descriptor

    fn ExtOp::matches_descriptor(self : ExtOp, descriptor : ExtOpDescriptor) -> Bool

    ExtOpDescriptor

    pub(all) struct ExtOpDescriptor {
    dialect : String
    opcode : String
    min_immediates : Int
    max_immediates : Int
    } derive(Eq, Hash,
    Debug
    )

    ExtOpDescriptor::ExtOpDescriptor

    fn ExtOpDescriptor::ExtOpDescriptor(dialect : String, opcode : String, immediate_count : Int) -> ExtOpDescriptor

    ExtOpDescriptor::accepts_immediate_count

    fn ExtOpDescriptor::accepts_immediate_count(self : ExtOpDescriptor, count : Int) -> Bool

    ExtOpDescriptor::expected_immediate_count

    fn ExtOpDescriptor::expected_immediate_count(self : ExtOpDescriptor) -> String

    ExtOpDescriptor::with_immediate_range

    fn ExtOpDescriptor::with_immediate_range(dialect : String, opcode : String, min_immediates : Int, max_immediates : Int) -> ExtOpDescriptor

    ExtensionInstView

    pub struct ExtensionInstView {
    op : ExtOp
    operand_types : ReadOnlyArray[Type]
    result_types : ReadOnlyArray[Type]
    } derive(
    Debug
    )

    Read-only instruction shape exposed to an extension dialect validator.

    ExternalSymbol

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

    ExternalSymbol::ExternalSymbol

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

    FloatBinaryOp

    pub(all) enum FloatBinaryOp {
    Add
    Sub
    Mul
    Div
    Min
    Max
    } derive(Eq, Hash,
    Debug
    )

    FloatCC

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

    Floating point comparison condition codes

    FloatUnaryOp

    pub(all) enum FloatUnaryOp {
    Neg
    Abs
    Sqrt
    Ceil
    Floor
    Trunc
    Nearest
    } derive(Eq, Hash,
    Debug
    )

    Function

    pub struct Function {
    name : String
    params : Array[(Value, Type)]
    results : Array[Type]
    blocks : Array[Block]
    external_symbols : Array[ExternalSymbol]
    next_value_id : Int
    next_inst_id : Int
    next_block_id : Int
    // private fields
    }

    Function - a complete mutable IR function.

    The public fields are exposed for zero-copy inspection. Callers must not mutate params, results, blocks, external_symbols, or the ID counters directly. Use FunctionBuilder and the checked Function/Block methods for structural changes. A function must be verified after its final mutation and immediately before optimization or lowering.

    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_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_value

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

    Create a new value with a unique ID

    Function::param

    fn Function::param(self : Function, index : Int) -> Value?

    Return a declared function parameter by position.

    Function::print

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

    Print a function

    Function::signature

    fn Function::signature(self : Function) -> Signature

    Return a signature snapshot derived from the explicit function contract.

    Function::verify

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

    Function::verify_core

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

    Verify that a function is self-contained core MilkIR. Dialect-bearing instructions must be validated and lowered by their owning adapter.

    Function::verify_with_extension_validator

    fn Function::verify_with_extension_validator(self : Function, dialect : String, validate : (ExtensionInstView) -> String?) -> Unit raise VerifyError

    Verify generic MilkIR structure, require every extension to belong to the owning dialect, and apply that adapter's validator before crossing its seam.

    Function::with_signature

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

    Create a function whose declared signature is materialized as explicit parameter values and result types.

    FunctionBuilder

    type FunctionBuilder

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

    FunctionBuilder::FunctionBuilder

    fn FunctionBuilder::FunctionBuilder(name : String) -> FunctionBuilder

    FunctionBuilder::add_block_param

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

    Add a block parameter (for SSA phi nodes)

    FunctionBuilder::add_param

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

    Add a parameter to the function

    FunctionBuilder::add_result

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

    Add a result type to the function

    FunctionBuilder::append_block_params_for_function_params

    fn FunctionBuilder::append_block_params_for_function_params(self : FunctionBuilder, block : Block) -> Unit

    Append block parameters matching the function parameter types.

    FunctionBuilder::append_block_params_for_function_returns

    fn FunctionBuilder::append_block_params_for_function_returns(self : FunctionBuilder, block : Block) -> Unit

    Append block parameters matching the function result types.

    FunctionBuilder::band

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

    Bitwise and

    FunctionBuilder::bitcast

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

    Bitcast (reinterpret bits)

    FunctionBuilder::block_params

    fn FunctionBuilder::block_params(self : FunctionBuilder, block : Block) -> Array[Value]

    Return the SSA values used as block parameters.

    FunctionBuilder::bnot

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

    Bitwise not

    FunctionBuilder::bor

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

    Bitwise or

    FunctionBuilder::br_table

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

    Branch table (switch)

    FunctionBuilder::brnz

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

    Conditional branch (branch if non-zero)

    FunctionBuilder::brz

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

    Conditional branch (branch if zero)

    FunctionBuilder::bxor

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

    Bitwise xor

    FunctionBuilder::call_pointer

    fn FunctionBuilder::call_pointer(self : FunctionBuilder, func_ptr : Value, args : Array[Value], result_types : Array[Type]) -> Array[Value]

    Call a function pointer with ordinary arguments.

    FunctionBuilder::call_symbol

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

    FunctionBuilder::call_symbol_multi

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

    FunctionBuilder::clz

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

    Count leading zeros

    FunctionBuilder::copy

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

    Copy value (for register allocation)

    FunctionBuilder::create_block

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

    Create a new block.

    FunctionBuilder::ctz

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

    Count trailing zeros

    FunctionBuilder::current_block

    fn FunctionBuilder::current_block(self : FunctionBuilder) -> Block

    Get the current block

    FunctionBuilder::emit_ext_inst

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

    FunctionBuilder::emit_inst

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

    Emit an instruction that produces a result

    FunctionBuilder::emit_multi_ext_inst

    fn FunctionBuilder::emit_multi_ext_inst(self : FunctionBuilder, result_types : Array[Type], opcode : ExtOp, operands : Array[Value]) -> Array[Value]

    FunctionBuilder::emit_multi_inst

    fn FunctionBuilder::emit_multi_inst(self : FunctionBuilder, result_types : Array[Type], opcode : Opcode, operands : Array[Value]) -> Array[Value]

    Emit an instruction with multiple results.

    FunctionBuilder::emit_void_ext_inst

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

    FunctionBuilder::emit_void_inst

    fn FunctionBuilder::emit_void_inst(self : FunctionBuilder, opcode : Opcode, operands : Array[Value]) -> Unit

    Emit an instruction without a result

    FunctionBuilder::fabs

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

    Float absolute value

    FunctionBuilder::fadd

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

    Float add

    FunctionBuilder::fceil

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

    Float ceiling

    FunctionBuilder::fcmp

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

    Float comparison (returns i32 0 or 1)

    FunctionBuilder::fconst_f32

    fn FunctionBuilder::fconst_f32(self : FunctionBuilder, 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.

    FunctionBuilder::fconst_f64

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

    Emit an f64 constant

    FunctionBuilder::fcvt_to_sint

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

    Float to signed int

    FunctionBuilder::fcvt_to_sint_sat

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

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

    FunctionBuilder::fcvt_to_uint

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

    Float to unsigned int

    FunctionBuilder::fcvt_to_uint_sat

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

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

    FunctionBuilder::fdemote

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

    Demote float (f64 -> f32)

    FunctionBuilder::fdiv

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

    Float divide

    FunctionBuilder::ffloor

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

    Float floor

    FunctionBuilder::finalize

    fn FunctionBuilder::finalize(self : FunctionBuilder) -> Function raise VerifyError

    Verify generic MilkIR structure and return the same mutable function.

    Finalization is a validation checkpoint, not a frozen or persistent validated state. Extension semantics remain the owning adapter's responsibility. Any later mutation requires validation at the next consuming seam.

    FunctionBuilder::fmax

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

    Float maximum

    FunctionBuilder::fmin

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

    Float minimum

    FunctionBuilder::fmul

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

    Float multiply

    FunctionBuilder::fnearest

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

    Float nearest (round to nearest even)

    FunctionBuilder::fneg

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

    Float negate

    FunctionBuilder::fpromote

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

    Promote float (f32 -> f64)

    FunctionBuilder::fsqrt

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

    Float square root

    FunctionBuilder::fsub

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

    Float subtract

    FunctionBuilder::ftrunc

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

    Float truncate

    FunctionBuilder::get_const_value

    fn FunctionBuilder::get_const_value(self : FunctionBuilder, 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.

    FunctionBuilder::get_function

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

    Get the mutable function being built without validating it.

    This is an explicit construction and transformation escape hatch. Consumers must verify the function after the final mutation and before lowering it.

    FunctionBuilder::iadd

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

    Integer add

    FunctionBuilder::icmp

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

    Integer comparison (returns i32 0 or 1)

    FunctionBuilder::icmp_eq

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

    Integer equal

    FunctionBuilder::icmp_ne

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

    Integer not equal

    FunctionBuilder::icmp_sge

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

    Signed greater than or equal

    FunctionBuilder::icmp_sgt

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

    Signed greater than

    FunctionBuilder::icmp_sle

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

    Signed less than or equal

    FunctionBuilder::icmp_slt

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

    Signed less than

    FunctionBuilder::icmp_uge

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

    Unsigned greater than or equal

    FunctionBuilder::icmp_ugt

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

    Unsigned greater than

    FunctionBuilder::icmp_ule

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

    Unsigned less than or equal

    FunctionBuilder::icmp_ult

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

    Unsigned less than

    FunctionBuilder::iconst

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

    Emit an integer constant

    FunctionBuilder::iconst_i32

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

    Emit an i32 constant

    FunctionBuilder::iconst_i64

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

    Emit an i64 constant

    FunctionBuilder::imul

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

    Integer multiply

    FunctionBuilder::ireduce

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

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

    FunctionBuilder::ishl

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

    Shift left

    FunctionBuilder::isub

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

    Integer subtract

    FunctionBuilder::jump

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

    Unconditional jump

    FunctionBuilder::load_ptr

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

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

    FunctionBuilder::load_ptr_narrow

    fn FunctionBuilder::load_ptr_narrow(self : FunctionBuilder, 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

    FunctionBuilder::popcnt

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

    Population count (count number of 1 bits)

    FunctionBuilder::print

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

    Print using FunctionBuilder

    FunctionBuilder::return_

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

    Return from function

    FunctionBuilder::rotl

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

    Rotate left

    FunctionBuilder::rotr

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

    Rotate right

    FunctionBuilder::sdiv

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

    Signed integer divide

    FunctionBuilder::select

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

    Conditional select: cond ? a : b

    FunctionBuilder::sextend

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

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

    FunctionBuilder::sextend16

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

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

    FunctionBuilder::sextend32

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

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

    FunctionBuilder::sextend8

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

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

    FunctionBuilder::sint_to_fcvt

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

    Signed int to float

    FunctionBuilder::smulh

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

    Signed multiply high (i64 only)

    FunctionBuilder::srem

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

    Signed integer remainder

    FunctionBuilder::sshr

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

    Signed shift right

    FunctionBuilder::store_ptr

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

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

    FunctionBuilder::store_ptr_narrow

    fn FunctionBuilder::store_ptr_narrow(self : FunctionBuilder, 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

    FunctionBuilder::switch_to_block

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

    Switch to a different block for emitting instructions

    FunctionBuilder::trap

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

    Trap/unreachable

    FunctionBuilder::udiv

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

    Unsigned integer divide

    FunctionBuilder::uextend

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

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

    FunctionBuilder::uint_to_fcvt

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

    Unsigned int to float

    FunctionBuilder::umulh

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

    Unsigned multiply high (i64 only)

    FunctionBuilder::urem

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

    Unsigned integer remainder

    FunctionBuilder::ushr

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

    Unsigned shift right

    FunctionBuilder::v128_and

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

    FunctionBuilder::v128_andnot

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

    FunctionBuilder::v128_anytrue

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

    FunctionBuilder::v128_bitselect

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

    FunctionBuilder::v128_const

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

    v128_const - emit a V128 constant

    FunctionBuilder::v128_extract16s

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

    FunctionBuilder::v128_extract16u

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

    FunctionBuilder::v128_extract32

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

    FunctionBuilder::v128_extract64

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

    FunctionBuilder::v128_extract8s

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

    Extract a lane from a v128 value

    FunctionBuilder::v128_extract8u

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

    FunctionBuilder::v128_extract_f32

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

    FunctionBuilder::v128_extract_f64

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

    FunctionBuilder::v128_load_lane_with_addr

    fn FunctionBuilder::v128_load_lane_with_addr(self : FunctionBuilder, opcode : VectorMemoryOp, effective_addr : Value, vec : Value) -> Value

    SIMD load lane with effective address and existing vector

    FunctionBuilder::v128_load_with_addr

    fn FunctionBuilder::v128_load_with_addr(self : FunctionBuilder, opcode : VectorMemoryOp, effective_addr : Value) -> Value

    SIMD load with effective address (for complex SIMD loads)

    FunctionBuilder::v128_not

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

    Bitwise operations on v128

    FunctionBuilder::v128_or

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

    FunctionBuilder::v128_replace16

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

    FunctionBuilder::v128_replace32

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

    FunctionBuilder::v128_replace64

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

    FunctionBuilder::v128_replace8

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

    Replace a lane in a v128 value

    FunctionBuilder::v128_replace_f32

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

    FunctionBuilder::v128_replace_f64

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

    FunctionBuilder::v128_shuffle

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

    Shuffle lanes from two v128 values

    FunctionBuilder::v128_splat16

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

    FunctionBuilder::v128_splat32

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

    FunctionBuilder::v128_splat64

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

    FunctionBuilder::v128_splat8

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

    v128_splat - broadcast a scalar to all lanes

    FunctionBuilder::v128_splat_f32

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

    FunctionBuilder::v128_splat_f64

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

    FunctionBuilder::v128_store_lane_with_addr

    fn FunctionBuilder::v128_store_lane_with_addr(self : FunctionBuilder, opcode : VectorMemoryOp, effective_addr : Value, vec : Value) -> Unit

    SIMD store lane with effective address and vector (void)

    FunctionBuilder::v128_swizzle

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

    Swizzle lanes using indices from another v128

    FunctionBuilder::v128_xor

    fn FunctionBuilder::v128_xor(self : FunctionBuilder, 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]
    // private fields
    }

    Instruction - an SSA instruction that produces a value.

    The public fields are exposed for zero-copy inspection. Callers must not mutate results, opcode, args, operands, or metadata directly. Construct instructions through FunctionBuilder or Function::new_inst and add metadata through add_metadata.
    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)

    IntBinaryOp

    pub(all) enum IntBinaryOp {
    Add
    Sub
    Mul
    UnsignedMulHigh
    SignedMulHigh
    SignedDiv
    UnsignedDiv
    SignedRem
    UnsignedRem
    And
    Or
    Xor
    ShiftLeft
    SignedShiftRight
    UnsignedShiftRight
    RotateLeft
    RotateRight
    } derive(Eq, Hash,
    Debug
    )

    IntCC

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

    Integer comparison condition codes

    IntUnaryOp

    pub(all) enum IntUnaryOp {
    Not
    CountLeadingZeros
    CountTrailingZeros
    PopulationCount
    } derive(Eq, Hash,
    Debug
    )

    Loop

    pub 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

    MemoryOp

    pub(all) enum MemoryOp {
    Load(Type)
    Store(Type)
    LoadNarrow(Type, Int, Bool)
    StoreNarrow(Int)
    Vector(VectorMemoryOp)
    } derive(Eq, Hash,
    Debug
    )

    Generic memory semantics over an explicit address and offset operand.

    Metadata

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

    Opcode

    pub(all) enum Opcode {
    Scalar(ScalarOp)
    Call(CallOp)
    Memory(MemoryOp)
    Ext(ExtOp, Signature)
    Vector(VectorOp)
    } 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: Inexpensive optimizations (constant folding, alias cleanup, DCE)
    • 2: Default optimizations (adds e-graph, global GVN, and CFG transforms)
    • 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

    OptimizationMetricsSink

    type OptimizationMetricsSink

    Optional embedding hook for optimization metrics.

    The embedder owns the clock and recorder so MilkIR remains independent of any runtime, operating system, or metrics output format.

    OptimizationMetricsSink::new

    fn OptimizationMetricsSink::new(now_us : () -> Int64, record : (OptimizationPassMetric) -> Unit) -> OptimizationMetricsSink

    OptimizationPassMetric

    pub struct OptimizationPassMetric {
    name : String
    duration_us : Int64
    before_insts : Int
    after_insts : Int
    changed : Bool
    egraph_classes : Int?
    egraph_nodes : Int?
    egraph_rule_apps : Int?
    work_done : Int?
    budget_exhausted : Bool?
    }

    Metrics emitted for one optimization pass.

    ScalarOp

    pub(all) enum ScalarOp {
    IntConst(Int64)
    FloatConst32(UInt)
    FloatConst64(UInt64)
    IntBinary(IntBinaryOp)
    IntUnary(IntUnaryOp)
    IntCompare(IntCC)
    FloatBinary(FloatBinaryOp)
    FloatUnary(FloatUnaryOp)
    FloatCompare(FloatCC)
    Convert(ConversionOp)
    SignExtendFrom(Int)
    Select
    Copy
    } derive(Eq, Hash,
    Debug
    )

    Signature

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

    Signature::Signature

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

    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.

    Ptr, Ref, CallableRef, and OpaqueRef are fixed-width 64-bit carriers. Their representation does not vary with the host pointer width.
    impl Show for Type

    Value

    pub struct Value {
    id : Int
    ty : Type
    // private fields
    }

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

    VectorBitwiseOp

    pub(all) enum VectorBitwiseOp {
    Not
    And
    AndNot
    Or
    Xor
    Bitselect
    } derive(Eq, Hash,
    Debug
    )

    VectorConversionOp

    pub(all) enum VectorConversionOp {
    TruncSatF32ToI32(VectorSignedness)
    TruncSatF64ToI32Zero(VectorSignedness)
    ConvertI32ToF32(VectorSignedness)
    ConvertLowI32ToF64(VectorSignedness)
    DemoteF64ToF32Zero
    PromoteLowF32ToF64
    } derive(Eq, Hash,
    Debug
    )

    VectorExtension

    pub(all) enum VectorExtension {
    None
    Signed
    Unsigned
    } derive(Eq, Hash,
    Debug
    )

    VectorFloatBinaryOp

    pub(all) enum VectorFloatBinaryOp {
    Add
    Sub
    Mul
    Div
    Min
    Max
    PseudoMin
    PseudoMax
    } derive(Eq, Hash,
    Debug
    )

    VectorFloatCompareOp

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

    VectorFloatLane

    pub(all) enum VectorFloatLane {
    F32
    F64
    } derive(Eq, Hash,
    Debug
    )

    VectorFloatUnaryOp

    pub(all) enum VectorFloatUnaryOp {
    Abs
    Neg
    Sqrt
    Ceil
    Floor
    Trunc
    Nearest
    } derive(Eq, Hash,
    Debug
    )

    VectorFmaOp

    pub(all) enum VectorFmaOp {
    Add
    NegatedAdd
    } derive(Eq, Hash,
    Debug
    )

    VectorHalf

    pub(all) enum VectorHalf {
    Low
    High
    } derive(Eq, Hash,
    Debug
    )

    VectorIntBinaryOp

    pub(all) enum VectorIntBinaryOp {
    Add
    Sub
    Mul
    AddSaturating(VectorSignedness)
    SubSaturating(VectorSignedness)
    Min(VectorSignedness)
    Max(VectorSignedness)
    AverageUnsigned
    ExtMul(VectorHalf, VectorSignedness)
    Dot16To32Signed
    Q15MulrSaturating
    } derive(Eq, Hash,
    Debug
    )

    VectorIntCompareOp

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

    VectorIntLane

    pub(all) enum VectorIntLane {
    I8
    I16
    I32
    I64
    } derive(Eq, Hash,
    Debug
    )

    VectorIntShiftOp

    pub(all) enum VectorIntShiftOp {
    Left
    Right(VectorSignedness)
    } derive(Eq, Hash,
    Debug
    )

    VectorIntUnaryOp

    pub(all) enum VectorIntUnaryOp {
    Abs
    Neg
    Popcnt
    Extend(VectorHalf, VectorSignedness)
    ExtAddPairwise(VectorSignedness)
    } derive(Eq, Hash,
    Debug
    )

    VectorLane

    pub(all) enum VectorLane {
    I8
    I16
    I32
    I64
    F32
    F64
    } derive(Eq, Hash,
    Debug
    )

    VectorMemoryOp

    pub(all) enum VectorMemoryOp {
    LoadExtend(VectorIntLane, VectorSignedness)
    LoadSplat(VectorIntLane)
    LoadZero(VectorIntLane)
    LoadLane(VectorIntLane, Int)
    StoreLane(VectorIntLane, Int)
    } derive(Eq, Hash,
    Debug
    )

    Vector memory operations consume an already checked effective address. Source-language memory indices, alignment hints, and offsets belong to the frontend.

    VectorOp

    pub(all) enum VectorOp {
    Const(Bytes)
    Splat(VectorLane)
    ExtractLane(VectorLane, VectorExtension, Int)
    ReplaceLane(VectorLane, Int)
    Shuffle(FixedArray[Int])
    Swizzle
    Bitwise(VectorBitwiseOp)
    Predicate(VectorPredicateOp)
    IntUnary(VectorIntUnaryOp, VectorIntLane)
    IntBinary(VectorIntBinaryOp, VectorIntLane)
    IntShift(VectorIntShiftOp, VectorIntLane)
    IntCompare(VectorIntCompareOp, VectorIntLane)
    Narrow(VectorIntLane, VectorSignedness)
    FloatUnary(VectorFloatUnaryOp, VectorFloatLane)
    FloatBinary(VectorFloatBinaryOp, VectorFloatLane)
    FloatCompare(VectorFloatCompareOp, VectorFloatLane)
    Convert(VectorConversionOp)
    Relaxed(VectorRelaxedOp)
    } derive(Eq, Hash,
    Debug
    )

    Language-neutral V128 operations normalized by semantic family.

    VectorPredicateOp

    pub(all) enum VectorPredicateOp {
    AnyTrue
    AllTrue(VectorIntLane)
    Bitmask(VectorIntLane)
    } derive(Eq, Hash,
    Debug
    )

    VectorRelaxedOp

    pub(all) enum VectorRelaxedOp {
    Swizzle
    TruncF32ToI32(VectorSignedness)
    TruncF64ToI32Zero(VectorSignedness)
    Fma(VectorFloatLane, VectorFmaOp)
    LaneSelect(VectorIntLane)
    Min(VectorFloatLane)
    Max(VectorFloatLane)
    Q15MulrSigned
    Dot8To16Signed
    Dot8To32AddSigned
    } derive(Eq, Hash,
    Debug
    )

    VectorSignedness

    pub(all) enum VectorSignedness {
    Signed
    Unsigned
    } derive(Eq, Hash,
    Debug
    )

    clear_optimization_metrics_sink

    fn clear_optimization_metrics_sink() -> Unit

    install_optimization_metrics_sink

    fn install_optimization_metrics_sink(sink : OptimizationMetricsSink) -> Unit

    instruction_count

    fn instruction_count(func : Function) -> Int

    Count IR instructions in a function.

    optimize

    fn optimize(func : Function) -> OptResult

    Run default optimizations (Cranelift-aligned O2 path).

    optimize_with_level

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

    Run optimizations based on level