Sign in

    machv_regalloc

    MachV adapter for the reusable register allocator

    compiler
    register-allocation
    machv
    jit
    Download zip
    Author
    Version
    0.2.1
    License
    Apache-2.0
    Last updated
    3 months ago
    Downloads
    191

    #machv_regalloc

    MachV adapter for the reusable register allocator.

    machv_regalloc bridges Milky2018/machv virtual-register functions to the target-independent Milky2018/regalloc algorithm. It projects MachV into the generic allocation model, applies allocation output, and keeps edge-copy and layout behavior compatible with MachV emission.

    #Packages

    • Milky2018/machv_regalloc: projection, allocation entry points, application, validation, spill handling, and output construction.
    • Milky2018/machv_regalloc/layout: block layout utilities for MachV functions.

    #When to use it

    Use machv_regalloc when your code is already in MachV and you want to reuse the target-independent allocator. This package keeps MachV as the machine IR, invokes the allocator, and returns either a rewritten function or a Cranelift-style allocation Output that the emitter can consume without mutating the original virtual-register instructions.

    #Example: allocate a MachV function

    Embedders provide their calling-convention data explicitly; MachV and machv_regalloc do not know Wasmoon-specific runtime layouts.

    ///|
    fn example_abi() -> @abi.EmbeddingABI {
    let call_conv : @abi.CallConventionLayout = {
    context_arg: { index: 27, class: Int },
    user_arg_gprs: [{ index: 0, class: Int }, { index: 1, class: Int }],
    arg_fprs: [{ index: 0, class: Float64 }, { index: 1, class: Float64 }],
    ret_gprs: [{ index: 0, class: Int }],
    ret_fprs: [{ index: 0, class: Float64 }],
    }
    EmbeddingABI(call_conv, reserve_context_role=false)
    }

    ///|
    test "allocate a canonical MachV copy" {
    let builder = @machv.FunctionBuilder::FunctionBuilder("copy")
    let src = builder.add_param(Int)
    let dst = builder.new_vreg(Int)
    builder.append(Move, uses=[Virtual(src)], defs=[{ reg: Virtual(dst) }])
    |> ignore
    builder.terminate(Return([Virtual(dst)]))
    let allocated = allocate_registers_backtracking_with_isa(
    builder.finish(),
    AArch64,
    embedding_abi=Some(example_abi()),
    )
    inspect(allocated.blocks.length(), content="1")
    inspect(allocated.blocks[0].terminator is Some(Return(_)), content="true")
    }

    #Example: consume Cranelift-style output

    The output API records per-operand locations and inserted edits. This is the preferred path for emitters that materialize moves while encoding instructions.

    ///|
    test "read operand locations from regalloc output" {
    let builder = @machv.FunctionBuilder::FunctionBuilder("rewrite")
    let src = builder.add_param(Int)
    let dst = builder.new_vreg(Int)
    builder.append(Move, uses=[Virtual(src)], defs=[{ reg: Virtual(dst) }])
    |> ignore
    builder.terminate(Return([Virtual(dst)]))
    let (_func, output) = allocate_registers_backtracking_output_with_isa(
    builder.finish(),
    AArch64,
    embedding_abi=Some(example_abi()),
    )
    inspect(output.get_num_spillslots(), content="0")
    inspect(output.inst_def_loc(0, 0, false, 0) is Reg(_), content="true")
    inspect(output.inst_use_loc(0, 0, false, 0) is Reg(_), content="true")
    }

    #Boundary

    This module may depend on MachV and regalloc, but should not depend on product runtime packages or native embedding details.

    RegallocAlgorithm

    Register allocation algorithm policy.

    • Backtracking: Ion-style backtracking + eviction/splitting (better codegen).
    • SinglePass: no eviction/backtracking, spills earlier (faster compile).

    SpillSlot

    type SpillSlot = Int

    A spill slot index (8-byte slot).

    AArch64StackFrame

    type AArch64StackFrame

    AArch64-specific stack frame configuration

    AArch64StackFrame::AArch64StackFrame

    fn AArch64StackFrame::AArch64StackFrame() -> AArch64StackFrame

    AArch64StackFrame::alloc_spill

    Allocate a spill slot

    AArch64StackFrame::finalize

    fn AArch64StackFrame::finalize(self : AArch64StackFrame) -> Unit

    Finalize the frame

    AArch64StackFrame::gen_epilogue

    Generate epilogue instructions

    AArch64StackFrame::gen_prologue

    Generate prologue instructions

    AArch64StackFrame::save_callee_reg

    fn AArch64StackFrame::save_callee_reg(self : AArch64StackFrame, preg :
    PReg
    ) -> Int

    Add a callee-saved register

    AArch64StackFrame::setup

    fn AArch64StackFrame::setup(self : AArch64StackFrame) -> Unit

    Setup the frame with FP and LR saves

    AArch64StackFrame::size

    fn AArch64StackFrame::size(self : AArch64StackFrame) -> Int

    Get frame size

    AllocStats

    type AllocStats

    Allocation statistics for comparison
    impl Show for AllocStats

    Allocation

    pub enum Allocation {
    Reg(
    PReg
    )
    Spill(Int)
    Unallocated
    }

    Allocation result for a LiveRange or Bundle
    impl Show for Allocation

    Bundle

    type Bundle

    A Bundle groups related LiveRanges that should ideally be allocated to the same physical register. Bundles are formed by merging LiveRanges connected by Move instructions, block arguments, or tied operands.

    Bundle::Bundle

    fn Bundle::Bundle(id : Int, reg_class :
    RegClass
    ) -> Bundle

    Bundle::contains_range

    fn Bundle::contains_range(self : Bundle, range_id : Int) -> Bool

    Check if this bundle contains a specific LiveRange

    Bundle::crosses_call

    fn Bundle::crosses_call(self : Bundle, ranges : LiveRangeSet) -> Bool

    Check if any range in this bundle crosses a function call

    Bundle::crosses_foreign_call

    fn Bundle::crosses_foreign_call(self : Bundle, ranges : LiveRangeSet) -> Bool

    Check if any range in this bundle crosses a foreign/helper call

    Bundle::crosses_internal_call

    fn Bundle::crosses_internal_call(self : Bundle, ranges : LiveRangeSet) -> Bool

    Check if any range in this bundle crosses an internal call

    Bundle::get_fixed_reg

    fn Bundle::get_fixed_reg(self : Bundle, ranges : LiveRangeSet) ->
    PReg
    ?

    Get the fixed register if all ranges in bundle require same fixed reg

    Bundle::has_fixed_constraint

    fn Bundle::has_fixed_constraint(self : Bundle, ranges : LiveRangeSet) -> Bool

    Bundle::overlaps

    fn Bundle::overlaps(self : Bundle, other : Bundle, ranges : LiveRangeSet) -> Bool

    Check if this bundle overlaps with another bundle

    Bundle::to_string

    fn Bundle::to_string(self : Bundle, _ranges : LiveRangeSet) -> String

    Bundle::total_length

    fn Bundle::total_length(self : Bundle, ranges : LiveRangeSet) -> Int

    Get total length of all ranges in this bundle

    BundleSet

    type BundleSet

    Collection of Bundles

    BundleSet::BundleSet

    fn BundleSet::BundleSet() -> BundleSet

    BundleSet::get

    fn BundleSet::get(self : BundleSet, id : Int) -> Bundle

    Get bundle by ID

    BundleSet::length

    fn BundleSet::length(self : BundleSet) -> Int

    Get number of bundles

    Edit

    pub(all) enum Edit {
    Move(Loc, Loc,
    RegClass
    )
    }

    An edit produced by regalloc to be inserted at a program point.

    Equivalent to regalloc2::Edit::Move in Cranelift: move between two locations.
    impl Show for Edit

    InstEdits

    type InstEdits

    Edits to insert before/after an instruction.

    LiveInterval

    pub struct LiveInterval {
    vreg :
    VReg

    start : ProgPoint
    end : ProgPoint
    uses : Array[ProgPoint]
    hint :
    PReg
    ?
    assigned :
    PReg
    ?
    spill_slot : Int?
    crosses_call : Bool
    crosses_foreign_call : Bool
    }

    A live interval - the range where a virtual register is live

    LiveRange

    type LiveRange

    A LiveRange represents the liveness of a single virtual register with precise span information and use constraints.
    impl Show for LiveRange

    LiveRange::LiveRange

    fn LiveRange::LiveRange(id : Int, vreg :
    VReg
    ) -> LiveRange

    LiveRange::end

    fn LiveRange::end(self : LiveRange, block_order : FixedArray[Int]) -> ProgPoint?

    Get the end point (latest point in all ranges)

    LiveRange::get_fixed_reg

    fn LiveRange::get_fixed_reg(self : LiveRange) ->
    PReg
    ?

    Get the fixed register constraint if all uses require the same fixed reg

    LiveRange::has_fixed_constraint

    fn LiveRange::has_fixed_constraint(self : LiveRange) -> Bool

    Check if this LiveRange has any fixed register constraint

    LiveRange::overlaps

    fn LiveRange::overlaps(self : LiveRange, other : LiveRange, block_order : FixedArray[Int]) -> Bool

    Check if this LiveRange overlaps with another

    LiveRange::start

    fn LiveRange::start(self : LiveRange, block_order : FixedArray[Int]) -> ProgPoint?

    Get the start point (earliest point in all ranges)

    LiveRange::total_length

    fn LiveRange::total_length(self : LiveRange) -> Int

    Compute total length of all ranges (in instruction count)

    LiveRangeSet

    type LiveRangeSet

    Collection of LiveRanges built from liveness analysis

    LiveRangeSet::get

    fn LiveRangeSet::get(self : LiveRangeSet, idx : Int) -> LiveRange

    Get LiveRange by index

    LiveRangeSet::get_by_vreg

    fn LiveRangeSet::get_by_vreg(self : LiveRangeSet, vreg_id : Int) -> LiveRange?

    Get LiveRange by vreg id

    LiveRangeSet::length

    fn LiveRangeSet::length(self : LiveRangeSet) -> Int

    Get number of ranges

    LivenessResult

    pub struct LivenessResult {
    intervals : Map[Int, LiveInterval]
    use_def : Map[Int, UseDefInfo]
    use_def_dense : Array[UseDefInfo?]
    live_in : Array[
    Set
    [Int]]
    live_out : Array[
    Set
    [Int]]
    live_in_dense : Array[Array[Int]]?
    live_out_dense : Array[Array[Int]]?
    block_order : FixedArray[Int]
    call_points : Array[(ProgPoint,
    CallClobberClass
    )]
    }

    Liveness analysis result

    Loc

    pub(all) enum Loc {
    Reg(
    PReg
    )
    Spill(Int)
    }

    A location for a value at a program point: either in a register or in a spill slot.
    impl Show for Loc

    OperandConstraint

    pub enum OperandConstraint {
    AnyReg
    FixedReg(
    PReg
    )
    }

    Operand constraint for a use position

    Output

    pub struct Output {
    param_locs : Array[Loc]
    allocs : Array[Loc]
    operand_ranges : Array[(Int, Int, Bool, Int, Int, Int)]
    inst_operand_range_index : Array[Array[Int]]
    term_operand_range_index : Array[Int]
    edits : Array[((Int, Int, ProgPos), Edit)]
    edits_before_dense : Array[Array[Array[Edit]]]
    edits_after_dense : Array[Array[Array[Edit]]]
    used_int_pregs_any : Array[Bool]
    used_fp_pregs_any : Array[Bool]
    num_spillslots : Int
    }

    Regalloc output.

    • allocs is a flat array of operand allocations, aligned with operand_ranges.
    • edits is a list of edits keyed by a program point (block/inst/pos).
    • num_spillslots is used by stack-frame layout.

    Output::Output

    fn Output::Output() -> Output

    Output::begin_inst_allocs

    fn Output::begin_inst_allocs(self : Output, block_id : Int, inst_idx : Int, is_terminator : Bool, def_count : Int, use_count : Int) -> (Int, Int)

    Start recording operand allocations for one instruction/terminator. Returns (start, total) where start is the first index in allocs.

    Output::edits_at

    fn Output::edits_at(self : Output, block_id : Int, inst_idx : Int, pos : ProgPos) -> Array[Edit]?

    Output::end_inst_allocs

    fn Output::end_inst_allocs(self : Output, start : Int, total : Int) -> Unit

    Output::finalize_edits

    fn Output::finalize_edits(self : Output) -> Unit

    Output::get_num_params

    fn Output::get_num_params(self : Output) -> Int

    Output::get_num_spillslots

    fn Output::get_num_spillslots(self : Output) -> Int

    Output::get_param_loc

    fn Output::get_param_loc(self : Output, idx : Int) -> Loc

    Output::inst_def_loc

    fn Output::inst_def_loc(self : Output, block_id : Int, inst_idx : Int, is_terminator : Bool, def_idx : Int) -> Loc

    Output::inst_use_loc

    fn Output::inst_use_loc(self : Output, block_id : Int, inst_idx : Int, is_terminator : Bool, use_idx : Int) -> Loc

    Output::iter_allocs

    fn Output::iter_allocs(self : Output) -> Array[Loc]

    Output::iter_edits

    fn Output::iter_edits(self : Output) -> Array[((Int, Int, ProgPos), Edit)]

    Output::push_edit

    fn Output::push_edit(self : Output, block_id : Int, inst_idx : Int, pos : ProgPos, edit : Edit) -> Unit

    Output::push_inst_alloc_loc

    fn Output::push_inst_alloc_loc(self : Output, loc : Loc) -> Unit

    Output::push_inst_allocs

    fn Output::push_inst_allocs(self : Output, block_id : Int, inst_idx : Int, is_terminator : Bool, def_count : Int, use_count : Int, locs : Array[Loc]) -> Unit

    Record operand allocations for one instruction/terminator. locs must have length def_count + use_count, in that order.

    Output::push_param_loc

    fn Output::push_param_loc(self : Output, loc : Loc) -> Unit

    Output::rebuild_edits_index

    fn Output::rebuild_edits_index(self : Output) -> Unit

    Output::sort_edits

    fn Output::sort_edits(self : Output) -> Unit

    Output::spill_reload_stats

    fn Output::spill_reload_stats(self : Output) -> (Int, Int, Int, Int)

    Return stack-traffic counts inferred from regalloc edits: (spills, reloads, reg_moves, spill_to_spill).

    Output::summary

    fn Output::summary(self : Output) -> String

    A compact human-readable summary for debugging (used by the CLI explore command).

    Output::uses_preg_index_any

    fn Output::uses_preg_index_any(self : Output, preg_idx : Int, is_int_class : Bool) -> Bool

    Output::validate_for_isa

    fn Output::validate_for_isa(self : Output, isa :
    ISA
    ) -> Unit

    Validate that the regalloc output only uses legal physical registers for the selected ISA.

    This is a fail-fast guard: silent truncation in x86 encoders can clobber rsp/rbp if an out-of-range preg leaks into emission (e.g. x20 -> rsp).

    ProgPoint

    pub struct ProgPoint {
    block : Int
    inst : Int
    pos : ProgPos
    } derive(Eq, Hash,
    Debug
    )

    A program point - identifies a position in the MachV function Using #valtype for stack allocation - this struct is created/compared frequently
    impl Show for ProgPoint

    ProgPointRange

    type ProgPointRange

    A contiguous program point range (start inclusive, end exclusive)

    ProgPointRange::contains

    fn ProgPointRange::contains(self : ProgPointRange, point : ProgPoint, block_order : FixedArray[Int]) -> Bool

    Check if a point is within this range

    ProgPos

    pub(all) enum ProgPos {
    Before
    After
    } derive(Eq, Hash,
    Debug
    )

    Position relative to an instruction

    RegAllocResult

    pub struct RegAllocResult {
    assignments : Map[Int,
    PReg
    ]
    spill_slots : Map[Int, Int]
    num_spill_slots : Int
    inst_edits : Array[(Int, Int, InstEdits)]
    }

    Register allocation result

    RegMove

    type RegMove

    A move between two locations (register or spill slot).
    impl Show for RegMove

    SpillBundle

    type SpillBundle

    A SpillBundle tracks a shared spill slot for split bundles. When a bundle is split, both parts share the same SpillBundle so they use the same stack slot.

    SpillBundle::SpillBundle

    fn SpillBundle::SpillBundle(id : Int, slot : Int) -> SpillBundle

    StackFrame

    type StackFrame

    Stack frame layout for a function
    impl Show for StackFrame

    StackFrame::StackFrame

    fn StackFrame::StackFrame(frame_align : Int) -> StackFrame

    StackFrame::add_callee_saved

    fn StackFrame::add_callee_saved(self : StackFrame, preg :
    PReg
    ) -> Int

    Record a callee-saved register that needs to be saved

    StackFrame::alloc_local

    fn StackFrame::alloc_local(self : StackFrame, local_idx : Int, size : Int, align : Int) -> Int

    Allocate a slot for a local variable

    StackFrame::alloc_outgoing_arg

    fn StackFrame::alloc_outgoing_arg(self : StackFrame, arg_idx : Int, size : Int, align : Int) -> Int

    Allocate a slot for an outgoing argument

    StackFrame::alloc_spill_slot

    fn StackFrame::alloc_spill_slot(self : StackFrame, vreg :
    VReg
    , size : Int, align : Int) -> Int

    Allocate a spill slot for a virtual register

    StackFrame::finalize

    fn StackFrame::finalize(self : StackFrame) -> Unit

    Finalize the frame layout and compute total size

    StackFrame::get_callee_saved

    fn StackFrame::get_callee_saved(self : StackFrame) -> Array[
    PReg
    ]

    Get all callee-saved registers that were used

    StackFrame::print

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

    Print the frame layout

    StackFrame::size

    fn StackFrame::size(self : StackFrame) -> Int

    Get the total frame size

    StackSlot

    type StackSlot

    A stack slot - a location on the stack
    impl Show for StackSlot

    StackSlotKind

    pub enum StackSlotKind {
    Spill(
    VReg
    )
    Local(Int)
    OutgoingArg(Int)
    CalleeSaved(
    PReg
    )
    }

    Kind of stack slot

    UseDefInfo

    type UseDefInfo

    Use-def information for a single vreg

    UseKind

    type UseKind

    Kind of use at a program point Note: For tied operands (same reg as both def and use), add a DefUse variant when Inst supports tied operand representation.
    impl Show for UseKind

    UsePosition

    type UsePosition

    A use position within a LiveRange
    impl Show for UsePosition

    allocate_registers_backtracking

    Allocate MachV virtual registers through the production backtracking path.

    machv_regalloc builds MachV liveness, register pools, and output edits; reusable allocation-loop, probe, eviction, split, and spill policies live in regalloc.

    allocate_registers_backtracking_output

    fn allocate_registers_backtracking_output(func :
    Function
    , embedding_abi? :
    EmbeddingABI
    ?) -> (
    Function
    , Output)

    Allocate MachV virtual registers and return a Cranelift-style regalloc Output.

    The returned @machv.Function is not rewritten to physical registers; the emitter consumes the returned Output to materialize edits and operand allocations on the fly.

    allocate_registers_backtracking_output_with_isa

    Allocate MachV virtual registers and return Output using a specific ISA policy implementation.

    allocate_registers_backtracking_with_isa

    Allocate registers using a specific ISA policy implementation.

    apply_allocation

    Apply register allocation results to a MachV function Handles spilled registers by inserting StackLoad/StackStore instructions

    build_bundles_with_merging

    fn build_bundles_with_merging(func :
    Function
    , ranges : LiveRangeSet) -> BundleSet

    Build bundles with merging from Move instructions and block arguments

    build_initial_bundles

    fn build_initial_bundles(ranges : LiveRangeSet) -> BundleSet

    Build initial bundles from LiveRanges Each LiveRange starts in its own bundle

    build_live_ranges

    fn build_live_ranges(func :
    Function
    , liveness : LivenessResult) -> LiveRangeSet

    Build LiveRanges from liveness analysis result This is Phase 2 of the Ion allocator

    build_stack_layout_aarch64

    fn build_stack_layout_aarch64(alloc : RegAllocResult, func :
    Function
    ) -> AArch64StackFrame

    Build stack layout from register allocation result

    compute_liveness

    fn compute_liveness(func :
    Function
    ) -> LivenessResult

    Compute full liveness information including interval maps.

    compute_liveness_for_regalloc

    fn compute_liveness_for_regalloc(func :
    Function
    ) -> LivenessResult

    Compute liveness for regalloc hot path (no interval map construction).

    compute_loop_depths

    fn compute_loop_depths(func :
    Function
    ) -> Array[Int]

    Pre-compute per-block approximate loop depth, aligned with regalloc2's CFG heuristic (cfg.rs::approx_loop_depth): count backedge entries/exits over linear block order and maintain a small nesting stack.

    compute_spill_weight

    fn compute_spill_weight(bundle : Bundle, ranges : LiveRangeSet, loop_depths : Array[Int]) -> Int

    Compute spill weight for a bundle using pre-computed loop depths Higher weight = more important to keep in register

    debug_liveness

    fn debug_liveness(liveness : LivenessResult) -> String

    Debug: print liveness info

    eliminate_dead_code

    Eliminate dead code from a MachV function Removes instructions that define vregs which are never used

    get_alloc_stats

    Get allocation statistics

    process_constraints

    fn process_constraints(func :
    Function
    , alloc : RegAllocResult) -> Unit

    Process operand constraints and generate RegMove edits Design: constraints are processed after allocation and moves are inserted when the allocated register doesn't match the constraint

    rematerialize_cross_block_constants

    fn rematerialize_cross_block_constants(func :
    Function
    ) ->
    Function

    Rematerialize cross-block cheap defs:
    • Detect vregs defined by rematerializable opcodes.
    • If such a vreg is used in a different block than its def, clone the defining opcode into each such use block and rewrite uses to a block-local vreg.

    This is intentionally conservative and mirrors Cranelift’s “clone remat values into the block where used” strategy.

    rematerialize_long_distance_constants

    fn rematerialize_long_distance_constants(func :
    Function
    ) ->
    Function

    Clone long-distance rematerializable defs within the same block.

    This mirrors Cranelift's remat intent for cheap constants: shorten very long live ranges by re-defining constants near far-away uses.

    verify_allocation