Target-neutral semantic machine IR
MilkIR
|
| target lowering and instruction selection
v
MachV with virtual registers
|
| machv_regalloc
v
allocation locations and edits, or rewritten MachV
|
| machv_emit
v
machine code and relocation metadata///|
test "build a 64-bit add function" {
let builder = FunctionBuilder::FunctionBuilder("add64")
let lhs = builder.add_param(Int)
let rhs = builder.add_param(Int)
builder.add_result(I64)
let sum = builder.new_vreg(Int)
builder.append(Add(true), uses=[Virtual(lhs), Virtual(rhs)], defs=[
{ reg: Virtual(sum) },
])
|> ignore
builder.terminate(Return([Virtual(sum)]))
let func = builder.finish()
inspect(
func.print(),
content=(
#|machv add64(v0:int, v1:int) {
#|block0:
#| v2 = add v0, v1
#| ret v2
#|}
#|
),
)
debug_inspect(func.get_result_kinds(), content="[I64]")
}machv add64(v0:int, v1:int) { two integer-register parameters
block0: execution starts in block0
v2 = add v0, v1 read v0 and v1, then define v2
ret v2 return the value in v2
}| MilkIR | MachV |
|---|---|
| Represents target-independent program semantics. | Represents instructions selected for a machine target. |
| Values have language-level types such as I32, F64, and Ptr. | Registers have allocation classes such as Int, Float64, and Vector. |
| An instruction carries SSA operands and results. | An instruction carries explicit register uses, definitions, and allocation constraints. |
| Control flow uses typed SSA block parameters and jump arguments. | Control flow uses register-level block arguments and machine branch forms. |
| Most semantic optimization happens before instruction selection. | Machine peephole optimization, register allocation, and encoding follow instruction selection. |
| Type | Meaning | Example |
|---|---|---|
| VReg | A virtual register that still needs a location. | v2 |
| PReg | A physical register identified by target register index and class. | { index: 0, class: Int } |
| Reg | Either Virtual(vreg) or Physical(preg). | Virtual(sum) |
| Writable | A register used as an instruction destination. | { reg: Virtual(sum) } |
uses = [v0, v1]
defs = [v2]| RegClass | Register bank |
|---|---|
| Int | General-purpose integer and pointer registers. |
| Float32 | 32-bit floating-point registers. |
| Float64 | 64-bit floating-point registers. |
| Vector | 128-bit SIMD registers. |
Add(true) uses [lhs, rhs] defs [result]
Load(I64, 8) uses [base] defs [loaded]
Store(I64, 8) uses [value, base] defs []
Move uses [source] defs [destination]///|
test "attach a fixed-register constraint" {
let builder = FunctionBuilder::FunctionBuilder("fixed_result")
let src = builder.add_param(Int)
let dst = builder.new_vreg(Int)
let required : PReg = { index: 1, class: Int }
builder.append(Move, uses=[Virtual(src)], defs=[{ reg: Virtual(dst) }], def_constraints=[
FixedReg(required),
])
|> ignore
builder.terminate(Return([Virtual(dst)]))
let func = builder.finish()
inspect(
func.blocks[0].insts[0].def_constraints[0] == FixedReg(required),
content="true",
)
}///|
test "build conditional control flow" {
let builder = FunctionBuilder::FunctionBuilder("choose_path")
let condition = builder.add_param(Int)
let then_block = builder.create_block()
let else_block = builder.create_block()
builder.terminate(Branch(Virtual(condition), then_block, else_block))
builder.switch_to_block(then_block)
builder.terminate(Return([]))
builder.switch_to_block(else_block)
builder.terminate(Return([]))
let func = builder.finish()
inspect(func.blocks.length(), content="3")
inspect(func.blocks[0].terminator is Some(Branch(_, _, _)), content="true")
inspect(func.blocks[1].terminator is Some(Return([])), content="true")
inspect(func.blocks[2].terminator is Some(Return([])), content="true")
}| Terminator | Purpose |
|---|---|
| Jump(target, args) | Transfer control to one block and pass register arguments. |
| Branch(condition, then_id, else_id) | Choose between two blocks. |
| BranchCmp(...) | Compare two registers and branch without materializing a Boolean value. |
| BranchZero(...) | Branch on a zero or nonzero register value. |
| BranchCmpImm(...) | Compare a register with an immediate and branch. |
| BrTable(index, targets, default) | Dispatch through a jump table. |
| Return(values) | Return registers to the caller. |
| Trap(payload) | End execution with an embedding-defined trap payload. |
| Package | Purpose |
|---|---|
| Milky2018/machv | Function model, FunctionBuilder, common instruction types, and printing. |
| Milky2018/machv/abi | Virtual and physical registers, operand constraints, calling conventions, and embedding ABI data. |
| Milky2018/machv/instr | Machine instructions, calls, traps, and terminators. |
| Milky2018/machv/block | Basic-block representation. |
| Milky2018/machv/isa | ISA descriptors and target selection. |
| Milky2018/machv/isa/aarch64 | AArch64 register descriptions. |
| Milky2018/machv/isa/amd64 | AMD64 register descriptions. |
fn Function::calls_multi_value_function_for_call_conv(self : Function, call_conv : CallConventionLayout) -> Boolfn Function::needs_extra_results_ptr_for_call_conv(self : Function, call_conv : CallConventionLayout) -> Boolfn FunctionBuilder::append(self : FunctionBuilder, opcode : Opcode, uses? : Array[Reg], defs? : Array[Writable], use_constraints? : Array[OperandConstraint], def_constraints? : Array[OperandConstraint]) -> IntTarget-neutral semantic machine IR