Reusable Cranelift-like SSA intermediate representation
frontend IR or bytecode
|
v
MilkIR SSA -- verify and optimize here
|
v
MachV IR -- select instructions and allocate registers lateradd_one(x: i32) -> i32 = x + 1///|
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
#|}
#|
),
)
}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
}| Concept | Meaning | In add_one |
|---|---|---|
| Function | One compilable function, including its signature and blocks. | add_one |
| Type | The static type of an IR value. | I32 |
| Value | A typed name for a function parameter, block parameter, or instruction result. | v0, v1, v2 |
| Block | A straight-line sequence of instructions with one exit. | block0 |
| Inst | An operation that may produce one or more values. | iconst, iadd |
| Terminator | The final control-flow operation in a block. | return |
| Family | Responsibility |
|---|---|
| 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. |
v0:i32 = ... old x
v1:i32 = iconst 1
v2:i32 = iadd v0, v1 new x///|
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")
}| Builder method | Meaning |
|---|---|
| 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. |
+--------------+
| block0 |
| test cond |
+------+-------+
|
+---------+---------+
| |
v v
+-----------+ +-----------+
| then_block| | else_block|
+-----+-----+ +-----+-----+
| |
+---------+---------+
|
v
+------------+
| join_block |
+------------+///|
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")
}add_block --jump [incremented]--+
>-- join_block(result) -- return result
unchanged_block --jump [input]----------+let func = builder.finalize()
func.verify()///|
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="()")
}| Level | Intended use |
|---|---|
| O0 | Minimal pipeline: removes dead code plus constant and unused block parameters. |
| O1 | The standard Cranelift-style simplification pipeline. |
| O2 | The default level and an alias for the standard O1 pass set. |
| O3 | The O2 pipeline, loop-invariant code motion, checked counted-loop unrolling, strength reduction, and a final O2 cleanup. |
///|
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")
}| Compiler stage | Package |
|---|---|
| SSA construction, verification, CFGs, and optimization | Milky2018/milkir |
| Optional WebAssembly extension operations | Milky2018/wasm_milkir |
| Lowering from MilkIR to machine-oriented IR | Milky2018/milkir_machv |
| Target instruction selection and ABI details | Milky2018/aarch64_target and Milky2018/x64_target |
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 VerifyErrorfn ExtOpDescriptor::ExtOpDescriptor(dialect : String, opcode : String, immediate_count : Int) -> ExtOpDescriptorfn ExtOpDescriptor::with_immediate_range(dialect : String, opcode : String, min_immediates : Int, max_immediates : Int) -> ExtOpDescriptortype FunctionBuilderfn FunctionBuilder::append_block_params_for_function_params(self : FunctionBuilder, block : Block) -> Unitfn FunctionBuilder::append_block_params_for_function_returns(self : FunctionBuilder, block : Block) -> Unitfn FunctionBuilder::br_table(self : FunctionBuilder, index : Value, targets : Array[Block], default_target : Block) -> Unitfn FunctionBuilder::brnz(self : FunctionBuilder, cond : Value, then_block : Block, else_block : Block) -> Unitfn FunctionBuilder::brz(self : FunctionBuilder, cond : Value, then_block : Block, else_block : Block) -> Unitfn FunctionBuilder::call_pointer(self : FunctionBuilder, func_ptr : Value, args : Array[Value], result_types : Array[Type]) -> Array[Value]fn FunctionBuilder::call_symbol(self : FunctionBuilder, symbol : ExternalSymbol, result_ty : Type?, args : Array[Value]) -> Value?fn FunctionBuilder::call_symbol_multi(self : FunctionBuilder, symbol : ExternalSymbol, result_types : Array[Type], args : Array[Value]) -> Array[Value]fn FunctionBuilder::emit_ext_inst(self : FunctionBuilder, ty : Type, opcode : ExtOp, operands : Array[Value]) -> Valuefn FunctionBuilder::emit_inst(self : FunctionBuilder, ty : Type, opcode : Opcode, operands : Array[Value]) -> Valuefn FunctionBuilder::emit_multi_ext_inst(self : FunctionBuilder, result_types : Array[Type], opcode : ExtOp, operands : Array[Value]) -> Array[Value]fn FunctionBuilder::emit_multi_inst(self : FunctionBuilder, result_types : Array[Type], opcode : Opcode, operands : Array[Value]) -> Array[Value]fn FunctionBuilder::emit_void_ext_inst(self : FunctionBuilder, opcode : ExtOp, operands : Array[Value]) -> Unitfn FunctionBuilder::emit_void_inst(self : FunctionBuilder, opcode : Opcode, operands : Array[Value]) -> Unitfn FunctionBuilder::load_ptr(self : FunctionBuilder, ty : Type, base : Value, offset : Value) -> Valuefn FunctionBuilder::load_ptr_narrow(self : FunctionBuilder, result_ty : Type, bits : Int, signed : Bool, base : Value, offset : Value) -> Valuefn FunctionBuilder::store_ptr(self : FunctionBuilder, ty : Type, base : Value, value : Value, offset : Value) -> Unitfn FunctionBuilder::store_ptr_narrow(self : FunctionBuilder, bits : Int, base : Value, value : Value, offset : Value) -> Unitfn FunctionBuilder::v128_bitselect(self : FunctionBuilder, a : Value, b : Value, c : Value) -> Valuefn FunctionBuilder::v128_load_lane_with_addr(self : FunctionBuilder, opcode : VectorMemoryOp, effective_addr : Value, vec : Value) -> Valuefn FunctionBuilder::v128_load_with_addr(self : FunctionBuilder, opcode : VectorMemoryOp, effective_addr : Value) -> Valuefn FunctionBuilder::v128_replace16(self : FunctionBuilder, vec : Value, val : Value, lane : Int) -> Valuefn FunctionBuilder::v128_replace32(self : FunctionBuilder, vec : Value, val : Value, lane : Int) -> Valuefn FunctionBuilder::v128_replace64(self : FunctionBuilder, vec : Value, val : Value, lane : Int) -> Valuefn FunctionBuilder::v128_replace8(self : FunctionBuilder, vec : Value, val : Value, lane : Int) -> Valuefn FunctionBuilder::v128_replace_f32(self : FunctionBuilder, vec : Value, val : Value, lane : Int) -> Valuefn FunctionBuilder::v128_replace_f64(self : FunctionBuilder, vec : Value, val : Value, lane : Int) -> Valuefn FunctionBuilder::v128_shuffle(self : FunctionBuilder, a : Value, b : Value, lanes : FixedArray[Int]) -> Valuefn FunctionBuilder::v128_store_lane_with_addr(self : FunctionBuilder, opcode : VectorMemoryOp, effective_addr : Value, vec : Value) -> Unitpub(all) enum OptLevel {
O0
O1
O2
O3
}type OptimizationMetricsSinkfn OptimizationMetricsSink::new(now_us : () -> Int64, record : (OptimizationPassMetric) -> Unit) -> OptimizationMetricsSinkpub 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?
}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)pub(all) enum VectorConversionOp {
TruncSatF32ToI32(VectorSignedness)
TruncSatF64ToI32Zero(VectorSignedness)
ConvertI32ToF32(VectorSignedness)
ConvertLowI32ToF64(VectorSignedness)
DemoteF64ToF32Zero
PromoteLowF32ToF64
} derive(Eq, Hash, Debug)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)pub(all) enum VectorIntCompareOp {
Eq
Ne
Lt(VectorSignedness)
Gt(VectorSignedness)
Le(VectorSignedness)
Ge(VectorSignedness)
} derive(Eq, Hash, Debug)pub(all) enum VectorIntUnaryOp {
Abs
Neg
Popcnt
Extend(VectorHalf, VectorSignedness)
ExtAddPairwise(VectorSignedness)
} derive(Eq, Hash, Debug)pub(all) enum VectorMemoryOp {
LoadExtend(VectorIntLane, VectorSignedness)
LoadSplat(VectorIntLane)
LoadZero(VectorIntLane)
LoadLane(VectorIntLane, Int)
StoreLane(VectorIntLane, Int)
} derive(Eq, Hash, Debug)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)pub(all) enum VectorPredicateOp {
AnyTrue
AllTrue(VectorIntLane)
Bitmask(VectorIntLane)
} derive(Eq, Hash, Debug)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)Reusable Cranelift-like SSA intermediate representation