machv

Target-neutral semantic machine IR

compiler
machine-ir
codegen
jit
moon add Milky2018/machv@0.8.2
Download zip
Author
Version
0.8.2
License
Apache-2.0
Last updated
2 days ago
Downloads
164
README

#machv

Reusable virtual-register machine IR.

machv models low-level machine code before final register allocation and emission. It provides virtual registers, physical-register descriptions, machine instructions, blocks, ABI data, target ISA descriptors, printing, and verification helpers.

#Packages

  • Milky2018/machv: root facade for machine functions and common helpers.
  • Milky2018/machv/abi: ABI locations, registers, calling-convention data, and runtime-context layout abstractions.
  • Milky2018/machv/instr: virtual machine instruction and terminator model.
  • Milky2018/machv/block: basic-block representation.
  • Milky2018/machv/isa: generic ISA descriptions and target selection.
  • Milky2018/machv/isa/aarch64, Milky2018/machv/isa/amd64: target-specific register descriptions.

#When to use it

Use MachV after instruction selection, but before final physical register allocation and encoding. It is the place to model virtual registers, machine instructions, ABI locations, block successors, clobbers, stack slots, and terminators.

#Example: build a virtual-register function

The AbstractFunction API is useful for target-independent tests, adapters, and simple machine-IR construction. A real target lowering usually fills the same concepts from MilkIR.

///|
test "build and verify a virtual-register copy" {
let func = AbstractFunction::new("copy")
let entry = func.new_block()
let src = func.add_param(Int)
let dst = func.new_vreg(Int)
let mov = func.new_inst(Move)
mov.add_operand(Operand::use_reg(Virtual(src)))
mov.add_operand(Operand::def(Virtual(dst)))
entry.append(mov)
entry.set_terminator(TermReturn([Virtual(dst)]))
inspect(func.verify(), content="()")
inspect(func.blocks.length(), content="1")
inspect(entry.instructions.length(), content="1")
}

#Example: record call-side ABI effects

Call instructions can declare clobbers and stack effects so allocation and emission can preserve live values correctly.

///|
test "represent a call clobber and outgoing stack frame" {
let func = AbstractFunction::new("call_host")
let entry = func.new_block()
let call = func.new_inst(Call("host.print"))
call.add_clobber({ index: 0, class: Int })
call.set_stack_effect(CallFrame(16))
entry.append(call)
inspect(call.clobbers.length(), content="1")
debug_inspect(call.stack_effect, content="CallFrame(16)")
}

#Boundary

machv should remain independent from Wasmoon. Product-specific runtime symbols, WASI, embedding context fields, and native FFI glue belong in embedding modules such as wasmoon_jit.

#
VerifyError

pub suberror VerifyError {
EmptyFunction
EmptyBlock(block_id~ : Int)
} derive(Eq,
Debug
)

impl Show for VerifyError

#
AbiReturnArea

pub(all) struct AbiReturnArea {
size : Int
align : Int
} derive(Eq,
Debug
)

#
AbiValueLocation

pub(all) enum AbiValueLocation {
Reg(PReg)
Stack(offset~ : Int, size~ : Int, align~ : Int)
ReturnArea(offset~ : Int, size~ : Int, align~ : Int)
} derive(Eq,
Debug
)

#
AbstractFunction

pub(all) struct AbstractFunction {
name : String
params : Array[VReg]
results : Array[RegClass]
blocks : Array[Block]
stack_slots : Array[StackSlot]
num_spill_slots : Int
param_pregs : Array[PReg?]
param_locations : Array[AbiValueLocation]
result_locations : Array[AbiValueLocation]
return_area : AbiReturnArea?
int_stack_params : Int
max_outgoing_args_size : Int
next_block_id : Int
next_inst_id : Int
next_vreg_id : Int
next_stack_slot_id : Int
} derive(
Debug
)

#
AbstractFunction::add_param

fn AbstractFunction::add_param(self : AbstractFunction, class : RegClass) -> VReg

#
AbstractFunction::add_param_location

fn AbstractFunction::add_param_location(self : AbstractFunction, location : AbiValueLocation) -> Unit

#
AbstractFunction::add_param_preg

fn AbstractFunction::add_param_preg(self : AbstractFunction, preg : PReg?) -> Unit

#
AbstractFunction::add_result

fn AbstractFunction::add_result(self : AbstractFunction, class : RegClass) -> Unit

#
AbstractFunction::add_result_location

fn AbstractFunction::add_result_location(self : AbstractFunction, location : AbiValueLocation) -> Unit

#
AbstractFunction::get_max_outgoing_args_size

fn AbstractFunction::get_max_outgoing_args_size(self : AbstractFunction) -> Int

#
AbstractFunction::get_num_spill_slots

fn AbstractFunction::get_num_spill_slots(self : AbstractFunction) -> Int

#
AbstractFunction::new

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

#
AbstractFunction::new_block

fn AbstractFunction::new_block(self : AbstractFunction) -> Block

#
AbstractFunction::new_inst

fn AbstractFunction::new_inst(self : AbstractFunction, opcode : Opcode) -> Instruction

#
AbstractFunction::new_stack_slot

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

#
AbstractFunction::new_vreg

fn AbstractFunction::new_vreg(self : AbstractFunction, class : RegClass) -> VReg

#
AbstractFunction::print

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

#
AbstractFunction::push_param

fn AbstractFunction::push_param(self : AbstractFunction, vreg : VReg) -> Unit

#
AbstractFunction::set_int_stack_params

fn AbstractFunction::set_int_stack_params(self : AbstractFunction, n : Int) -> Unit

#
AbstractFunction::set_num_spill_slots

fn AbstractFunction::set_num_spill_slots(self : AbstractFunction, n : Int) -> Unit

#
AbstractFunction::set_return_area

fn AbstractFunction::set_return_area(self : AbstractFunction, area : AbiReturnArea) -> Unit

#
AbstractFunction::update_max_outgoing_args_size

fn AbstractFunction::update_max_outgoing_args_size(self : AbstractFunction, size : Int) -> Unit

#
AbstractFunction::verify

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

#
Block

pub(all) struct Block {
id : Int
params : Array[VReg]
instructions : Array[Instruction]
successors : Array[Int]
terminator : Terminator?
} derive(
Debug
)

#
Block::add_param

fn Block::add_param(self : Block, param : VReg) -> Unit

#
Block::add_successor

fn Block::add_successor(self : Block, block_id : Int) -> Unit

#
Block::append

fn Block::append(self : Block, inst : Instruction) -> Unit

#
Block::new

fn Block::new(id : Int) -> Block

#
Block::set_terminator

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

#
CFG

pub(all) struct CFG {
size : Int
preds : Array[Array[Int]]
succs : Array[Array[Int]]
}

#
CFG::build

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

#
CFG::compute_dominators

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

#
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::reverse_postorder

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

#
Cond

pub(all) enum Cond {
Eq
Ne
Lt
Le
Gt
Ge
Ult
Ule
Ugt
Uge
Mi
Parity
NotParity
Custom(String)
} derive(Eq,
Debug
)

#
Constraint

pub(all) struct Constraint {
operand_index : Int
allowed_regs : Array[PReg]
} derive(Eq,
Debug
)

#
Function

pub(all) struct Function {
name : String
params : Array[
VReg
]
results : Array[
RegClass
]
result_kinds : Array[ValueKind]
blocks : Array[
MachVBlock
]
next_vreg_id : Int
num_spill_slots : Int
param_pregs : Array[
PReg
?]
int_stack_params : Int
max_outgoing_args_size : Int
uses_context_cache_0_source : Bool
}

MachV function - a complete machine-level function in virtual-register form.
impl Show for Function

#
Function::add_param_preg

fn Function::add_param_preg(self : Function, preg :
PReg
?) -> Unit

Add a parameter physical register mapping (called by register allocator)

#
Function::add_result

fn Function::add_result(self : Function, class :
RegClass
) -> Unit

#
Function::add_result_kind

fn Function::add_result_kind(self : Function, ty : ValueKind) -> Unit

Add a result kind with full kind information for multi-value returns

#
Function::calls_multi_value_function

fn Function::calls_multi_value_function(self : Function) -> Bool

Check if this function calls any function that returns more than 2 values In that case, we need to allocate a local buffer for receiving extra results

#
Function::clone_base

fn Function::clone_base(self : Function) -> Function

Clone the base structure of a function for regalloc transformations. Copies: name, next_vreg_id, int_stack_params, max_outgoing_args_size, uses_context_cache_0_source Empty: params, results, result_kinds, blocks, param_pregs Zero: num_spill_slots

#
Function::get_blocks

#
Function::get_max_outgoing_args_size

fn Function::get_max_outgoing_args_size(self : Function) -> Int

#
Function::get_name

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

#
Function::get_num_spill_slots

fn Function::get_num_spill_slots(self : Function) -> Int

#
Function::get_param_pregs

fn Function::get_param_pregs(self : Function) -> Array[
PReg
?]

#
Function::get_params

#
Function::get_result_kinds

fn Function::get_result_kinds(self : Function) -> Array[ValueKind]

#
Function::has_calls

fn Function::has_calls(self : Function) -> Bool

Returns true if this function contains any call-like instruction.

#
Function::mark_uses_context_cache_0_source

fn Function::mark_uses_context_cache_0_source(self : Function) -> Unit

#
Function::needs_extra_results_ptr

fn Function::needs_extra_results_ptr(self : Function) -> Bool

Check if this function needs a hidden pointer for extra return values Returns true if there are more than 2 integer or 2 float returns

#
Function::new

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

#
Function::new_block

#
Function::print

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

#
Function::push_param

fn Function::push_param(self : Function, vreg :
VReg
) -> Unit

Push a parameter vreg directly (used during regalloc reconstruction)

#
Function::set_int_stack_params

fn Function::set_int_stack_params(self : Function, n : Int) -> Unit

Set the number of integer stack parameters (called during lowering)

#
Function::set_num_spill_slots

fn Function::set_num_spill_slots(self : Function, n : Int) -> Unit

Set the number of spill slots (called by register allocator)

#
Function::should_reserve_context_cache_0

fn Function::should_reserve_context_cache_0(self : Function) -> Bool

Returns true if context cache 0 caching should be enabled for this function.

#
Function::should_reserve_context_cache_1

fn Function::should_reserve_context_cache_1(self : Function) -> Bool

Returns true if context cache 1 pointer caching should be enabled for this function.

#
Function::update_max_outgoing_args_size

fn Function::update_max_outgoing_args_size(self : Function, size : Int) -> Unit

Update max outgoing args size if the new size is larger (called during lowering)

#
Function::uses_context_cache_0_source

fn Function::uses_context_cache_0_source(self : Function) -> Bool

#
Function::uses_context_cache_1

fn Function::uses_context_cache_1(self : Function) -> Bool

Returns true if the function loads the module context cache 1 from embedding context. Used to reserve a dedicated register for caching the context cache 1 pointer.

#
Instruction

pub(all) struct Instruction {
id : Int
opcode : Opcode
operands : Array[Operand]
clobbers : Array[PReg]
constraints : Array[Constraint]
abi_arg_locations : Array[AbiValueLocation]
abi_result_locations : Array[AbiValueLocation]
stack_effect : StackEffect
} derive(Eq,
Debug
)

#
Instruction::add_abi_arg_location

fn Instruction::add_abi_arg_location(self : Instruction, location : AbiValueLocation) -> Unit

#
Instruction::add_abi_result_location

fn Instruction::add_abi_result_location(self : Instruction, location : AbiValueLocation) -> Unit

#
Instruction::add_clobber

fn Instruction::add_clobber(self : Instruction, preg : PReg) -> Unit

#
Instruction::add_constraint

fn Instruction::add_constraint(self : Instruction, constraint : Constraint) -> Unit

#
Instruction::add_operand

fn Instruction::add_operand(self : Instruction, operand : Operand) -> Unit

#
Instruction::new

fn Instruction::new(id : Int, opcode : Opcode) -> Instruction

#
Instruction::set_stack_effect

fn Instruction::set_stack_effect(self : Instruction, effect : StackEffect) -> Unit

#
Loop

pub(all) struct Loop {
header : Int
body : Array[Int]
latch : Int
}

#
MemType

pub(all) enum MemType {
MemI32
MemI64
MemF32
MemF64
MemV128
MemPtr
} derive(Eq,
Debug
)

#
Opcode

pub(all) enum Opcode {
Target(String)
Move
IntConst(Int64)
FloatConst(Double)
IntAdd
IntSub
IntMul
IntMulHigh(Bool)
IntDiv(Bool)
IntRem(Bool)
IntAnd
IntOr
IntXor
IntNot
IntShl
IntShr(Bool)
IntRotl
IntRotr
IntClz
IntCtz
IntPopcnt
IntCmp(Cond)
FloatAdd
FloatSub
FloatMul
FloatDiv
FloatMin
FloatMax
FloatCmp(Cond)
FloatNeg
FloatAbs
FloatSqrt
FloatCeil
FloatFloor
FloatTrunc
FloatNearest
Ireduce
Sextend
Uextend
Fpromote
Fdemote
FcvtToSint
FcvtToUint
FcvtToSintSat
FcvtToUintSat
SintToFcvt
UintToFcvt
Bitcast
Sextend8
Sextend16
Sextend32
Select
LoadMemory(MemType, Int)
StoreMemory(MemType, Int)
LoadPtr(MemType, Int)
LoadPtrNarrow(Int, Bool, Int)
StorePtr(MemType, Int)
StorePtrNarrow(Int, Int)
StackAddr(Int)
CallIndirect
CallPtr(Int, Int)
Custom(String)
Load(StackSlot)
Store(StackSlot)
Call(String)
Trap(Int)
Jump(Int)
Branch(Int, Int)
Return
} derive(Eq,
Debug
)

#
Operand

pub(all) struct Operand {
reg : Reg
role : OperandRole
tie_id : Int
} derive(Eq,
Debug
)

#
Operand::def

fn Operand::def(reg : Reg) -> Operand

#
Operand::use_def

fn Operand::use_def(reg : Reg) -> Operand

#
Operand::use_reg

fn Operand::use_reg(reg : Reg) -> Operand

#
Operand::with_tie

fn Operand::with_tie(self : Operand, tie_id : Int) -> Operand

#
OperandConstraint

pub(all) enum OperandConstraint {
Any
FixedReg(PReg)
} derive(Eq,
Debug
)

#
OperandRole

pub(all) enum OperandRole {
Use
Def
UseDef
} derive(Eq,
Debug
)

#
PReg

pub(all) struct PReg {
index : Int
class : RegClass
} derive(Eq,
Debug
)

impl Show for PReg

#
PReg::get_spill_slot

fn PReg::get_spill_slot(self : PReg) -> Int

#
PReg::is_spilled

fn PReg::is_spilled(self : PReg) -> Bool

#
PReg::spilled

fn PReg::spilled(slot : Int, class : RegClass) -> PReg

#
Reg

pub(all) enum Reg {
Virtual(VReg)
Physical(PReg)
} derive(Eq,
Debug
)

impl Show for Reg

#
RegClass

pub(all) enum RegClass {
Int
Float32
Float64
Vector
} derive(Eq,
Debug
)

impl Show for RegClass

#
StackEffect

pub(all) enum StackEffect {
None
Adjust(Int)
CallFrame(Int)
} derive(Eq,
Debug
)

#
StackSlot

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

#
Terminator

pub(all) enum Terminator {
TermJump(Int, Array[Reg])
TermBranch(Reg, Int, Array[Reg], Int, Array[Reg])
TermBranchCmp(Reg, Reg, Cond, Bool, Int, Array[Reg], Int, Array[Reg])
TermBranchZero(Reg, Bool, Bool, Int, Array[Reg], Int, Array[Reg])
TermBranchCmpImm(Reg, Int, Cond, Bool, Int, Array[Reg], Int, Array[Reg])
TermReturn(Array[Reg])
TermTrap(Int)
TermBrTable(Reg, Array[Int], Int)
} derive(Eq,
Debug
)

#
VReg

pub(all) struct VReg {
id : Int
class : RegClass
} derive(Eq,
Debug
)

impl Show for VReg

#
ValueKind

pub(all) enum ValueKind {
I32
I64
F32
F64
V128
Ptr
} derive(Eq,
Debug
)

Machine-level value kind needed after MilkIR lowering.

Keep this in MachV instead of carrying @milkir.Type through register allocation and emission.

#
Writable

pub(all) struct Writable {
reg : Reg
} derive(Eq,
Debug
)

impl Show for Writable

#
abi_location_stack_align

fn abi_location_stack_align(location : AbiValueLocation) -> Int

#
abi_location_stack_size

fn abi_location_stack_size(location : AbiValueLocation) -> Int

#
spill_slot_base

let spill_slot_base : Int