///|
/// Call type - classifies instructions that behave like calls
/// CallType enum for different call variants
pub(all) enum CallType {
None // Not a call instruction
Regular // Regular call that returns to caller
TailCall // Tail call that doesn't return to caller
}
///|
/// Call-site clobber class for register allocation and optimization barriers.
pub(all) enum CallClobberClass {
Internal // Same-embedding ABI call.
Foreign // Foreign/helper ABI call with conservative clobbers.
} derive(Eq, Debug)
///|
/// Symbolic name for a runtime or external helper address.
///
/// MachV treats this as an opaque relocation key. Embedding packages own the
/// mapping from names to native addresses.
pub(all) struct ExternalName {
name : String
} derive(Eq, Debug, Hash)
///|
/// Symbolic name for code owned by the embedding.
///
/// MachV treats this as an opaque relocation key. Frontends may use the
/// scope and ordinal to map their own function identifiers to this generic
/// code target without exposing those identifiers in MachV opcodes.
pub(all) struct CodeSymbol {
scope : String
ordinal : Int
} derive(Eq, Debug, Hash)
///|
/// Target for a relocatable code address or direct call.
pub(all) enum RelocTarget {
Code(CodeSymbol)
External(ExternalName)
} derive(Eq, Debug)
///|
pub impl Show for CallClobberClass with fn output(self, logger) {
match self {
Internal => logger.write_string("Internal")
Foreign => logger.write_string("Foreign")
}
}
///|
pub fn ExternalName::ExternalName(name : String) -> ExternalName {
{ name, }
}
///|
pub fn CodeSymbol::CodeSymbol(scope : String, ordinal : Int) -> CodeSymbol {
{ scope, ordinal }
}
///|
pub impl Show for ExternalName with fn output(self, logger) {
logger.write_string(self.name)
}
///|
pub impl Show for CodeSymbol with fn output(self, logger) {
if self.scope == "" {
logger.write_string("code.\{self.ordinal}")
} else {
logger.write_string("\{self.scope}.\{self.ordinal}")
}
}
///|
pub impl Show for RelocTarget with fn output(self, logger) {
match self {
Code(symbol) => logger.write_string(symbol.to_string())
External(symbol) => logger.write_string(symbol.name)
}
}
///|
pub fn RelocTarget::key(self : RelocTarget) -> String {
match self {
Code(symbol) => "code:\{symbol.scope}:\{symbol.ordinal}"
External(symbol) => "external:\{symbol.name}"
}
}
///|
/// MachV instruction - a machine-level instruction with virtual registers
/// Operand constraints for fixed register allocation
pub struct Inst {
opcode : Opcode
defs : Array[@abi.Writable] // Registers defined (written)
uses : Array[@abi.Reg] // Registers used (read)
use_constraints : Array[@abi.OperandConstraint] // Constraints for uses
def_constraints : Array[@abi.OperandConstraint] // Constraints for defs
}
///|
pub fn Inst::Inst(opcode : Opcode) -> Inst {
{ opcode, defs: [], uses: [], use_constraints: [], def_constraints: [] }
}
///|
pub fn Inst::add_def(self : Inst, reg : @abi.Writable) -> Unit {
self.defs.push(reg)
self.def_constraints.push(Any)
}
///|
pub fn Inst::add_use(self : Inst, reg : @abi.Reg) -> Unit {
self.uses.push(reg)
self.use_constraints.push(Any)
}
///|
/// Add a use operand with a fixed register constraint
pub fn Inst::add_use_fixed(
self : Inst,
reg : @abi.Reg,
preg : @abi.PReg,
) -> Unit {
self.uses.push(reg)
self.use_constraints.push(FixedReg(preg))
}
///|
/// Add a def operand with a fixed register constraint
pub fn Inst::add_def_fixed(
self : Inst,
reg : @abi.Writable,
preg : @abi.PReg,
) -> Unit {
self.defs.push(reg)
self.def_constraints.push(FixedReg(preg))
}
///|
fn Inst::to_string(self : Inst) -> String {
let mut result = ""
// Print definitions (skip clobbers for call instructions)
let defs_to_print = match self.opcode {
ReturnCallIndirect(_, num_results) =>
// Only print result registers, not clobbers
self.defs.iter().take(num_results).collect()
CallPtr(_, result_classes, _)
| CallDirect(_, _, result_classes, _)
| CallExternal(_, _, result_classes, _) =>
// Only print result registers, not clobbers
self.defs.iter().take(result_classes.length()).collect()
CallExternalIfI32NeImm(_, _) =>
// Only clobbers are attached (no results); keep output compact.
[]
_ => self.defs
}
if defs_to_print.length() > 0 {
for i, def in defs_to_print {
if i > 0 {
result = result + ", "
}
result = result + def.to_string()
}
result = result + " = "
}
// Print opcode
result = result + self.opcode.to_string()
// Print uses (simplified for call instructions)
if self.opcode is ReturnCallIndirect(_, _) || self.opcode is CallPtr(_, _, _) {
// For call instructions, only print the function pointer (first use)
if self.uses.length() > 0 {
result = result + " " + self.uses[0].to_string()
}
} else if self.uses.length() > 0 {
result = result + " "
for i, use_ in self.uses {
if i > 0 {
result = result + ", "
}
result = result + use_.to_string()
}
}
result
}
///|
pub impl Show for Inst with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// MachV opcode - machine-level operation (target-independent subset)
pub(all) enum Opcode {
// Integer arithmetic (Bool = true for 64-bit, false for 32-bit)
Add(Bool) // ADD with size: true = 64-bit, false = 32-bit
AddImm(Int, Bool) // ADD Xd, Xn, #imm with size: true = 64-bit, false = 32-bit
Sub(Bool) // SUB with size: true = 64-bit, false = 32-bit
SubImm(Int, Bool) // SUB Xd, Xn, #imm with size: true = 64-bit, false = 32-bit
Mul(Bool) // MUL with size: true = 64-bit, false = 32-bit
SDiv(Bool) // SDIV with size: true = 64-bit, false = 32-bit
UDiv(Bool) // UDIV with size: true = 64-bit, false = 32-bit
// x86_64: explicit remainder opcodes (lowered only on x86_64).
// This matches Cranelift's checked remainder sequences to avoid x86 IDIV
// overflow traps for `INT_MIN % -1`.
SRem(Bool) // SREM with size: true = 64-bit, false = 32-bit
URem(Bool) // UREM with size: true = 64-bit, false = 32-bit
// Bitwise operations (Bool = true for 64-bit, false for 32-bit)
And(Bool)
AndImm(Int64, Bool) // AND (immediate logical), value is the immediate pattern
Or(Bool)
OrImm(Int64, Bool) // ORR (immediate logical), value is the immediate pattern
Xor(Bool)
XorImm(Int64, Bool) // EOR (immediate logical), value is the immediate pattern
Shl(Bool) // LSL with size
ShlImm(Int, Bool) // LSL with immediate shift amount masked to operand width
AShr(Bool) // Arithmetic shift right with size
AShrImm(Int, Bool) // ASR with immediate shift amount masked to operand width
LShr(Bool) // Logical shift right with size
LShrImm(Int, Bool) // LSR with immediate shift amount masked to operand width
Rotr(Bool) // Rotate right with size
RotrImm(Int, Bool) // ROR with immediate shift amount masked to operand width
// AArch64 EXTR immediate forms:
// extr rd, rn, rm, #shift (64-bit)
// extr wD, wN, wM, #shift (32-bit)
// Encodes: (rn << (bits - shift)) | (rm >> shift)
// Used by Cranelift-style lowering for:
// (x << a) | (y >> b) where a + b == bitwidth.
ExtrImm(Int, Bool) // EXTR immediate shift amount, Bool=true(64)/false(32)
Not(Bool) // Bitwise NOT with size: true = 64-bit, false = 32-bit
AndNot(Bool) // x & ~y
OrNot(Bool) // x | ~y
XorNot(Bool) // x ^ ~y
// Floating-point operations (Bool = true for f32, false for f64)
FAdd(Bool)
FSub(Bool)
FMul(Bool)
FDiv(Bool)
FMin(Bool)
FMax(Bool)
FSqrt(Bool)
FAbs(Bool)
FNeg(Bool)
FCeil(Bool)
FFloor(Bool)
FTrunc(Bool)
FNearest(Bool)
// Memory operations
Load(MemType, Int) // type, offset
Store(MemType, Int) // type, offset
// Narrow load operations (8/16-bit with sign/zero extension)
Load8S(Int) // Load 8-bit signed, sign-extend to 32/64-bit (offset)
Load8U(Int) // Load 8-bit unsigned, zero-extend to 32/64-bit (offset)
Load16S(Int) // Load 16-bit signed, sign-extend to 32/64-bit (offset)
Load16U(Int) // Load 16-bit unsigned, zero-extend to 32/64-bit (offset)
Load32S(Int) // Load 32-bit signed, sign-extend to 64-bit (offset)
Load32U(Int) // Load 32-bit unsigned, zero-extend to 64-bit (offset)
// Moves
Move
LoadConst(Int64) // Load immediate
LoadConstF32(Int) // Load f32 immediate (stored as bits)
LoadConstF64(Int64) // Load f64 immediate (stored as bits)
// Comparisons (result is 0 or 1)
// Cmp(kind, is_64bit): CMP Xn/Wn, Xm/Wm; CSET Wd, cond
Cmp(CmpKind, Bool)
// IntCmp(is_64bit): CMP Xn/Wn, Xm/Wm
// Uses: [lhs, rhs], Defs: [] (only sets flags)
// Used before TrapIf when no boolean compare result is needed.
IntCmp(Bool)
FCmp(FCmpKind)
// Conversions
Extend(ExtendKind)
Truncate
IntToFloat(IntToFloatKind)
FPromote // f32 -> f64
FDemote // f64 -> f32
Bitcast // Reinterpret bits between int/float of same size
// Conditional select
// Select: Xd = cond ? Xn : Xm
// Uses: [cond, true_val, false_val], Defs: [result]
Select
// SelectCmp: Fused compare and select
// SelectCmp(kind, is_64): CMP lhs, rhs; CSEL Xd, true_val, false_val, cond
// Uses: [cmp_lhs, cmp_rhs, true_val, false_val], Defs: [result]
// Saves one instruction compared to separate CMP + CSET + Select
SelectCmp(CmpKind, Bool)
// Bit counting operations (Bool = true for 64-bit, false for 32-bit)
Clz(Bool) // Count leading zeros with size
Popcnt(Bool) // Population count with size
Rbit(Bool) // Reverse bits in register with size
// Special
Nop
// Trap if first operand is unsigned greater than second operand
// TrapIfUgt(trap_code): CMP rn, rm; B.LS skip; BRK #trap_code
// Uses: [lhs, rhs], traps if lhs > rhs (unsigned)
TrapIfUgt(Int)
// Trap if first operand is unsigned greater than or equal to second operand
// TrapIfUge(trap_code): CMP rn, rm; B.LO skip; BRK #trap_code
// Uses: [lhs, rhs], traps if lhs >= rhs (unsigned)
TrapIfUge(Int)
// Floating-point compare (sets NZCV flags, no result value)
// FpuCmp(is_f32): FCMP Sn, Sm or FCMP Dn, Dm
// Uses: [lhs, rhs], Defs: [] (only sets flags)
// Used for NaN check (compare with self) and bound checks
FpuCmp(Bool)
// Conditional trap based on condition flags
// TrapIf(cond, trap_code): B.cond skip; BRK #trap_code
// Uses: [], Defs: [] (uses flags from previous compare)
// trap_code: 1=out_of_bounds, 2=type_mismatch, 3=bad_conversion, 4=integer_overflow
TrapIf(Cond, Int)
// Trap if operand is zero (for division by zero)
// TrapIfZero(is_64, trap_code): CBNZ rn, skip; BRK #trap_code
// Uses: [rn], Defs: [] (traps if rn is zero)
TrapIfZero(Bool, Int)
// Trap if signed division would overflow (INT_MIN / -1)
// TrapIfDivOverflow(is_64, trap_code): checks if lhs == INT_MIN && rhs == -1
// Uses: [lhs, rhs], Defs: [] (traps if overflow would occur)
TrapIfDivOverflow(Bool, Int)
// Raw float-to-int conversion (FCVTZS/FCVTZU without NaN/overflow checks)
// FcvtToInt(is_f32, is_i64, is_signed): FCVTZS/FCVTZU Wd/Xd, Sn/Dn
// Uses: [src_fp], Defs: [dst_int]
// The trapping FloatToInt opcodes are expanded in lower phase to:
// FpuCmp + TrapIf(Vs) + LoadConstF32/F64 + FpuCmp + TrapIf + ... + FcvtToInt
FcvtToInt(Bool, Bool, Bool)
// Floating-point conditional select (for saturating conversions)
// FpuSel(is_f32, cond): FCSEL Sd, Sn, Sm, cond or FCSEL Dd, Dn, Dm, cond
// Uses: [true_val, false_val], Defs: [result]
// Selects true_val if condition is true, false_val otherwise
// Used for NaN handling: select 0.0 if NaN, original value otherwise
FpuSel(Bool, Cond)
// Floating-point maximum (NaN-propagating)
// FpuMaxnm(is_f32): FMAXNM Sd, Sn, Sm or FMAXNM Dd, Dn, Dm
// Uses: [lhs, rhs], Defs: [result]
// Returns the larger value; if one is NaN, returns the other
FpuMaxnm(Bool)
// Floating-point minimum (NaN-propagating)
// FpuMinnm(is_f32): FMINNM Sd, Sn, Sm or FMINNM Dd, Dn, Dm
// Uses: [lhs, rhs], Defs: [result]
// Returns the smaller value; if one is NaN, returns the other
FpuMinnm(Bool)
// AArch64-specific: shifted operand instructions
// These combine an arithmetic/logical op with a shift in one instruction
AddShifted(ShiftType, Int) // ADD Xd, Xn, Xm, shift #amount
AddExtend(IndexExtend, Int) // ADD Xd, Xn, Wm, {uxtw|sxtw} #amount
SubExtend(IndexExtend, Int) // SUB Xd, Xn, Wm, {uxtw|sxtw} #amount
SubShifted(ShiftType, Int) // SUB Xd, Xn, Xm, shift #amount
AndShifted(ShiftType, Int) // AND Xd, Xn, Xm, shift #amount
OrShifted(ShiftType, Int) // ORR Xd, Xn, Xm, shift #amount
XorShifted(ShiftType, Int) // EOR Xd, Xn, Xm, shift #amount
// 32-bit shifted operand forms (Cranelift-aligned for i32 lowering)
AddShifted32(ShiftType, Int) // ADD Wd, Wn, Wm, shift #amount
SubShifted32(ShiftType, Int) // SUB Wd, Wn, Wm, shift #amount
AndShifted32(ShiftType, Int) // AND Wd, Wn, Wm, shift #amount
OrShifted32(ShiftType, Int) // ORR Wd, Wn, Wm, shift #amount
XorShifted32(ShiftType, Int) // EOR Wd, Wn, Wm, shift #amount
// AArch64-specific: multiply-accumulate instructions
Madd // Xd = Xa + Xn * Xm (3 uses: acc, src1, src2)
Msub // Xd = Xa - Xn * Xm (3 uses: acc, src1, src2)
Mneg // Xd = -(Xn * Xm) (2 uses: src1, src2)
// 32-bit multiply-accumulate forms (Cranelift fits_in_64 i32 coverage)
Madd32 // Wd = Wa + Wn * Wm (3 uses: acc, src1, src2)
Msub32 // Wd = Wa - Wn * Wm (3 uses: acc, src1, src2)
Mneg32 // Wd = -(Wn * Wm) (2 uses: src1, src2)
// AArch64-specific: multiply high instructions
Umulh // Xd = (Xn * Xm) >> 64 (unsigned multiply high, 64-bit only)
Smulh // Xd = (Xn * Xm) >> 64 (signed multiply high, 64-bit only)
Umull // Xd = Wn * Wm (unsigned 32x32->64 multiply)
Smull // Xd = Wn * Wm (signed 32x32->64 multiply)
// Function calls
// ReturnCallIndirect: tail call through a function pointer
// Uses: [func_ptr, arg0, arg1, ...]
// Parameters:
// - stack_arg_bytes: bytes of overflow stack args for this tail call
// - num_results: reserved metadata (currently informational)
// This is a terminator - it does not return to the caller
ReturnCallIndirect(Int, Int)
// CallExternalIfI32NeImm: fast-path conditional helper call.
//
// Uses: [actual_i32_vreg]
// Parameters: expected_i32 (immediate), helper (external symbol)
//
// Semantics:
// - If actual_i32 == expected_i32: do nothing.
// - Otherwise: call helper(actual_i32, expected_i32).
//
// TODO(ISS-071): Lower this generic pseudo-op into explicit MachV CFG once
// block-local cold-path helper calls can be represented without emitter-local
// labels.
CallExternalIfI32NeImm(Int, ExternalName)
// Stack operations for spilling
// StackLoad(offset): Load from [SP + offset] into the def register
// StackStore(offset): Store the use register to [SP + offset]
StackLoad(Int)
StackStore(Int)
// Stack parameter load (for params that overflow registers)
// LoadStackParam(offset, class):
// offset: byte offset from entry SP (start of overflow area)
// Stack layout: [int_overflow..., float_overflow...] with V128 aligned to 16.
LoadStackParam(Int, @abi.RegClass)
// NOTE: memory.grow/size/fill/copy and table.grow are lowered to foreign calls.
// NOTE: TableGet, TableSet are desugared at IR level via FuncEnvironment to LoadPtr/StorePtr
// NOTE: GlobalGet, GlobalSet are desugared at IR level via FuncEnvironment to LoadPtr/StorePtr
// Raw pointer operations (for trampolines, no bounds checking)
// LoadPtr(type, offset): Load from [base + offset]
// Uses: [base], Defs: [result]
LoadPtr(MemType, Int)
// LoadPtrRegOffset(type, extend, shift, offset):
// Load from [base + index{, extend}{, scale} + offset]
// Uses: [base, index], Defs: [result]
// - extend: None uses X index; Uxtw/Sxtw use W index extended to X
// - shift: 0 (no scale) or log2(access_size) (scaled register offset)
// - offset: signed displacement in bytes
LoadPtrRegOffset(MemType, IndexExtend, Int, Int)
// LoadPtrNarrowRegOffset(bits, signed, extend, shift, offset): narrow load from
// [base + index{, extend}{, scale} + offset] where bits is 8/16/32.
// Uses: [base, index], Defs: [result]
LoadPtrNarrowRegOffset(Int, Bool, IndexExtend, Int, Int)
// StorePtr(type, offset): Store to [base + offset]
// Uses: [base, value], Defs: []
StorePtr(MemType, Int)
// StorePtrRegOffset(type, extend, shift, offset):
// Store to [base + index{, extend}{, scale} + offset]
// Uses: [base, index, value], Defs: []
StorePtrRegOffset(MemType, IndexExtend, Int, Int)
// StorePtrNarrowRegOffset(bits, extend, shift, offset): narrow store to
// [base + index{, extend}{, scale} + offset] where bits is 8/16/32.
// Uses: [base, index, value], Defs: []
StorePtrNarrowRegOffset(Int, IndexExtend, Int, Int)
// LoadPtrNarrow(bits, signed, offset): Load narrow value from [base + offset]
// Uses: [base], Defs: [result]
LoadPtrNarrow(Int, Bool, Int) // (bits, signed, offset)
// StorePtrNarrow(bits, offset): Store narrow value to [base + offset]
// Uses: [base, value], Defs: []
StorePtrNarrow(Int, Int) // (bits, offset)
// Load external function pointer
// LoadExternalFuncAddr(symbol): Load function pointer for an external helper.
// Uses: [], Defs: [result (function pointer)]
LoadExternalFuncAddr(ExternalName)
// Load code address (resolved by embedding relocation/fixup metadata)
// LoadCodeAddr(symbol): Load function/code pointer for symbol
// Uses: [], Defs: [result (function pointer)]
LoadCodeAddr(CodeSymbol)
// CallPtr: call through a function pointer
// Uses are already constrained by the lowering-selected ABI.
// Defs: [result0, result1, ...] (multiple results supported)
// Parameters: num_args, result classes, clobber class
CallPtr(Int, Array[@abi.RegClass], CallClobberClass)
// CallDirect: direct call to a known code symbol
// Uses are already constrained by the lowering-selected ABI.
// Defs: [result0, result1, ...]
// Parameters: target, num_args, result classes, clobber class
CallDirect(CodeSymbol, Int, Array[@abi.RegClass], CallClobberClass)
// CallExternal: direct call to an external helper symbol.
// Uses are already constrained by the lowering-selected ABI.
// Defs: [result0, result1, ...]
// Parameters: symbol, num_args, result classes, clobber class
CallExternal(ExternalName, Int, Array[@abi.RegClass], CallClobberClass)
// Stack pointer adjustment for outgoing call arguments (Standard)
// AdjustSP(delta): Adjust SP by delta bytes
// delta > 0: deallocate (add to SP)
// delta < 0: allocate (sub from SP)
// Used in call lowering to allocate space for overflow args
AdjustSP(Int)
// Store to outgoing call argument area (Standard)
// StoreToStack(offset): Store use[0] to [SP + offset]
// Used in call lowering to store overflow args before the call
// Uses: [value], Defs: []
StoreToStack(Int)
// Load outgoing-args base pointer into a register
// LoadSP: Defs: [result], Uses: []
// Used for passing stack addresses to C helpers that consume StoreToStack data
LoadSP
// LoadSafepointId(root_count): record a stack-root safepoint at this code
// position and load its generated id into the def register. Root slots are
// the contiguous outgoing-args slots [0, root_count).
LoadSafepointId(Int)
// ============ SIMD Instructions (AArch64 NEON) ============
// V128 constant load - load 128-bit immediate
// LoadConstV128(bytes): MOVI or load from constant pool
// Uses: [], Defs: [result]
LoadConstV128(Bytes)
// Splat - replicate scalar to all vector lanes
// SIMDSplat(lane_size): DUP Vd.T, Wn/Xn
// Uses: [scalar], Defs: [vector]
SIMDSplat(LaneSize)
// Splat from floating-point register
// SIMDSplatF(is_f32): DUP Vd.4S, Vn.S[0] or DUP Vd.2D, Vn.D[0]
// Uses: [fp_scalar], Defs: [vector]
SIMDSplatF(Bool)
// Extract lane to GPR (unsigned)
// SIMDExtractU(lane_size, lane): UMOV Wd/Xd, Vn.T[lane]
// Uses: [vector], Defs: [scalar]
SIMDExtractU(LaneSize, Int)
// Extract lane to GPR (signed)
// SIMDExtractS(lane_size, lane): SMOV Wd/Xd, Vn.T[lane]
// Uses: [vector], Defs: [scalar]
SIMDExtractS(LaneSize, Int)
// Extract lane to FPR
// SIMDExtractF(is_f32, lane): DUP Sd/Dd, Vn.S[lane]/Vn.D[lane]
// Uses: [vector], Defs: [fp_scalar]
SIMDExtractF(Bool, Int)
// Replace lane from GPR
// SIMDInsert(lane_size, lane): INS Vd.T[lane], Wn/Xn
// Uses: [vector, scalar], Defs: [vector]
SIMDInsert(LaneSize, Int)
// Replace lane from FPR
// SIMDInsertF(is_f32, lane): INS Vd.S[lane], Vn.S[0] or Vd.D[lane], Vn.D[0]
// Uses: [vector, fp_scalar], Defs: [vector]
SIMDInsertF(Bool, Int)
// Shuffle - select lanes from two vectors using immediate indices
// Uses two TBL1 calls to avoid consecutive register requirement
// Uses: [v1, v2], Defs: [result, temp1, temp2] (2 temp FPRs for TBL1 intermediates)
SIMDShuffle(FixedArray[Int])
// Swizzle - permute lanes based on indices vector
// SIMDSwizzle: TBL Vd.16B, {Vn.16B}, Vm.16B
// Uses: [values, indices], Defs: [result]
SIMDSwizzle
// Bitwise NOT
// SIMDNot: NOT Vd.16B, Vn.16B (same as MVN)
// Uses: [vector], Defs: [result]
SIMDNot
// Bitwise AND
// SIMDAnd: AND Vd.16B, Vn.16B, Vm.16B
// Uses: [v1, v2], Defs: [result]
SIMDAnd
// Bitwise AND-NOT (a & ~b)
// SIMDBic: BIC Vd.16B, Vn.16B, Vm.16B
// Uses: [v1, v2], Defs: [result]
SIMDBic
// Bitwise OR
// SIMDOr: ORR Vd.16B, Vn.16B, Vm.16B
// Uses: [v1, v2], Defs: [result]
SIMDOr
// Bitwise XOR
// SIMDXor: EOR Vd.16B, Vn.16B, Vm.16B
// Uses: [v1, v2], Defs: [result]
SIMDXor
// Bitselect - select bits from two vectors based on mask
// SIMDBsl: BSL Vd.16B, Vn.16B, Vm.16B (Vd = (Vn & Vd) | (Vm & ~Vd))
// Uses: [v1, v2, mask], Defs: [result]
SIMDBsl
// Any true - check if any lane is non-zero
// SIMDAnyTrue: UMAXV Bd, Vn.16B; UMOV Wd, Vd.B[0]; CMP Wd, #0; CSET Wd, ne
// Uses: [vector], Defs: [result (i32)]
SIMDAnyTrue
// All true - check if all lanes are non-zero
// SIMDAllTrue(lane_size): UMINV + check
// Uses: [vector], Defs: [result (i32)]
SIMDAllTrue(LaneSize)
// Bitmask - extract sign bits to GPR
// SIMDBitmask(lane_size): extract MSB of each lane
// Uses: [vector], Defs: [result (i32)]
// For B8: Defs: [result, temp_weights, temp_shift, temp_work] (3 temp FPRs)
SIMDBitmask(LaneSize)
// Integer add
// SIMDAdd(lane_size): ADD Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDAdd(LaneSize)
// Integer subtract
// SIMDSub(lane_size): SUB Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDSub(LaneSize)
// Integer multiply (no 64-bit variant in NEON)
// SIMDMul(lane_size): MUL Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDMul(LaneSize)
// Saturating add (signed)
// SIMDSqadd(lane_size): SQADD Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDSqadd(LaneSize)
// Saturating add (unsigned)
// SIMDUqadd(lane_size): UQADD Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDUqadd(LaneSize)
// Saturating subtract (signed)
// SIMDSqsub(lane_size): SQSUB Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDSqsub(LaneSize)
// Saturating subtract (unsigned)
// SIMDUqsub(lane_size): UQSUB Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDUqsub(LaneSize)
// Minimum (signed)
// SIMDSmin(lane_size): SMIN Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDSmin(LaneSize)
// Minimum (unsigned)
// SIMDUmin(lane_size): UMIN Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDUmin(LaneSize)
// Maximum (signed)
// SIMDSmax(lane_size): SMAX Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDSmax(LaneSize)
// Maximum (unsigned)
// SIMDUmax(lane_size): UMAX Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDUmax(LaneSize)
// Unsigned rounding halving add (average)
// SIMDUrhadd(lane_size): URHADD Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDUrhadd(LaneSize)
// Absolute value
// SIMDAbs(lane_size): ABS Vd.T, Vn.T
// Uses: [vector], Defs: [result]
SIMDAbs(LaneSize)
// Negate
// SIMDNeg(lane_size): NEG Vd.T, Vn.T
// Uses: [vector], Defs: [result]
SIMDNeg(LaneSize)
// Population count per byte
// SIMDCnt: CNT Vd.16B, Vn.16B
// Uses: [vector], Defs: [result]
SIMDCnt
// Broadcast shift amount to vector (mask and DUP)
// SIMDBroadcastShift(lane_size, negate): mask scalar, optionally negate, DUP to all lanes
// Uses: [shift_scalar], Defs: [temp_gpr, shift_vec]
// temp_gpr: GPR for masked (and optionally negated) shift value
// shift_vec: FPR with shift amount broadcast to all lanes
// negate=true for right shifts (shift amount needs to be negated for SSHL/USHL)
SIMDBroadcastShift(LaneSize, Bool)
// Vector shift (input already broadcast)
// SIMDShiftByVec(lane_size, use_ushl): SSHL or USHL
// Uses: [input_vec, shift_vec], Defs: [result]
// use_ushl=false for SSHL (left shift, signed right shift)
// use_ushl=true for USHL (unsigned right shift)
SIMDShiftByVec(LaneSize, Bool)
// Integer comparison
// SIMDCmp(lane_size, cmp_kind): CMEQ/CMGT/CMGE/CMHI/CMHS Vd.T, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDCmp(LaneSize, SIMDCmpKind)
// Narrow (pack two vectors to narrower lanes)
// SIMDNarrow(dst_lane_size, signed): XTN/SQXTN/SQXTUN/UQXTN Vd.T, Vn.T2
// Uses: [v1, v2], Defs: [result]
SIMDNarrow(LaneSize, Bool)
// Extend low half (widen to larger lanes)
// SIMDExtendLow(dst_lane_size, signed): SXTL/UXTL Vd.T2, Vn.T
// Uses: [vector], Defs: [result]
SIMDExtendLow(LaneSize, Bool)
// Extend high half (widen to larger lanes)
// SIMDExtendHigh(dst_lane_size, signed): SXTL2/UXTL2 Vd.T2, Vn.T
// Uses: [vector], Defs: [result]
SIMDExtendHigh(LaneSize, Bool)
// Extended multiply low
// SIMDExtMulLow(dst_lane_size, signed): SMULL/UMULL Vd.T2, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDExtMulLow(LaneSize, Bool)
// Extended multiply high
// SIMDExtMulHigh(dst_lane_size, signed): SMULL2/UMULL2 Vd.T2, Vn.T, Vm.T
// Uses: [v1, v2], Defs: [result]
SIMDExtMulHigh(LaneSize, Bool)
// Extended add pairwise
// SIMDExtAddPairwise(dst_lane_size, signed): SADDLP/UADDLP Vd.T2, Vn.T
// Uses: [vector], Defs: [result]
SIMDExtAddPairwise(LaneSize, Bool)
// Dot product (i32x4.dot_i16x8_s)
// SIMDDot: sequence of SMULL + SADDLP or SMLAL
// Uses: [v1, v2], Defs: [result]
SIMDDot
// Q15 saturating rounding multiply high
// SIMDQ15MulrSat: SQRDMULH Vd.8H, Vn.8H, Vm.8H
// Uses: [v1, v2], Defs: [result]
SIMDQ15MulrSat
// Floating-point add
// SIMDFAdd(is_f32): FADD Vd.4S/2D, Vn.4S/2D, Vm.4S/2D
// Uses: [v1, v2], Defs: [result]
SIMDFAdd(Bool)
// Floating-point subtract
// SIMDFSub(is_f32): FSUB Vd.4S/2D, Vn.4S/2D, Vm.4S/2D
// Uses: [v1, v2], Defs: [result]
SIMDFSub(Bool)
// Floating-point multiply
// SIMDFMul(is_f32): FMUL Vd.4S/2D, Vn.4S/2D, Vm.4S/2D
// Uses: [v1, v2], Defs: [result]
SIMDFMul(Bool)
// Floating-point divide
// SIMDFDiv(is_f32): FDIV Vd.4S/2D, Vn.4S/2D, Vm.4S/2D
// Uses: [v1, v2], Defs: [result]
SIMDFDiv(Bool)
// Floating-point min (propagating NaN)
// SIMDFMin(is_f32): FMIN Vd.4S/2D, Vn.4S/2D, Vm.4S/2D
// Uses: [v1, v2], Defs: [result]
SIMDFMin(Bool)
// Floating-point max (propagating NaN)
// SIMDFMax(is_f32): FMAX Vd.4S/2D, Vn.4S/2D, Vm.4S/2D
// Uses: [v1, v2], Defs: [result]
SIMDFMax(Bool)
// Floating-point pmin (pseudo-min, returns b if a is NaN)
// SIMDFPMin(is_f32): FCMGT + BSL sequence
// Uses: [v1, v2], Defs: [result]
SIMDFPMin(Bool)
// Floating-point pmax (pseudo-max, returns b if a is NaN)
// SIMDFPMax(is_f32): FCMGT + BSL sequence
// Uses: [v1, v2], Defs: [result]
SIMDFPMax(Bool)
// Floating-point absolute value
// SIMDFAbs(is_f32): FABS Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDFAbs(Bool)
// Floating-point negate
// SIMDFNeg(is_f32): FNEG Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDFNeg(Bool)
// Floating-point square root
// SIMDFSqrt(is_f32): FSQRT Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDFSqrt(Bool)
// Floating-point ceiling
// SIMDFCeil(is_f32): FRINTP Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDFCeil(Bool)
// Floating-point floor
// SIMDFFloor(is_f32): FRINTM Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDFFloor(Bool)
// Floating-point truncate
// SIMDFTrunc(is_f32): FRINTZ Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDFTrunc(Bool)
// Floating-point nearest
// SIMDFNearest(is_f32): FRINTN Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDFNearest(Bool)
// Floating-point comparison
// SIMDFCmp(is_f32, cmp_kind): FCMEQ/FCMGT/FCMGE Vd.4S/2D, Vn.4S/2D, Vm.4S/2D
// Uses: [v1, v2], Defs: [result]
SIMDFCmp(Bool, SIMDFCmpKind)
// Conversions
// SIMDFCvtToIntS(is_f32): FCVTZS Vd.4S/2D, Vn.4S/2D (f32->i32 or f64->i64)
// Uses: [vector], Defs: [result]
SIMDFCvtToIntS(Bool)
// SIMDFCvtToIntU(is_f32): FCVTZU Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDFCvtToIntU(Bool)
// SIMDIntToFloatS(is_f32): SCVTF Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDIntToFloatS(Bool)
// SIMDIntToFloatU(is_f32): UCVTF Vd.4S/2D, Vn.4S/2D
// Uses: [vector], Defs: [result]
SIMDIntToFloatU(Bool)
// Truncate sat f64 to i32 with zero extend (special: produces i32x4 from f64x2)
// SIMDTruncSatF64ToI32SZero: FCVTZS + XTN
// Uses: [vector], Defs: [result]
SIMDTruncSatF64ToI32SZero
// SIMDTruncSatF64ToI32UZero: FCVTZU + XTN
// Uses: [vector], Defs: [result]
SIMDTruncSatF64ToI32UZero
// Convert low i32 lanes to f64 (i32x4 -> f64x2, using low 2 lanes)
// SIMDConvertLowI32ToF64S: SXTL + SCVTF
// Uses: [vector], Defs: [result]
SIMDConvertLowI32ToF64S
// SIMDConvertLowI32ToF64U: UXTL + UCVTF
// Uses: [vector], Defs: [result]
SIMDConvertLowI32ToF64U
// Demote f64 to f32 with zero (f64x2 -> f32x4, zeros in high lanes)
// SIMDDemoteF64ToF32Zero: FCVTN
// Uses: [vector], Defs: [result]
SIMDDemoteF64ToF32Zero
// Promote low f32 to f64 (f32x4 -> f64x2, using low 2 lanes)
// SIMDPromoteLowF32ToF64: FCVTL
// Uses: [vector], Defs: [result]
SIMDPromoteLowF32ToF64
// V128 load (128-bit)
// SIMDLoad(offset): LDR Qd, [Xn, #offset]
// Uses: [base], Defs: [result]
SIMDLoad(Int)
// V128 store (128-bit)
// SIMDStore(offset): STR Qn, [Xd, #offset]
// Uses: [base, value], Defs: []
SIMDStore(Int)
// Load and splat
// SIMDLoadSplat(lane_size, offset): LD1R Vd.T, [Xn]
// Uses: [base], Defs: [result]
SIMDLoadSplat(LaneSize, Int)
// Load and extend (8x8, 16x4, 32x2)
// SIMDLoadExtend(src_bits, signed, offset): load + SXTL/UXTL
// Uses: [base], Defs: [result]
SIMDLoadExtend(Int, Bool, Int)
// Load 32/64 bits and zero extend to v128
// SIMDLoadZero(is_64, offset): LDR S/D, then insert into zero vector
// Uses: [base], Defs: [result]
SIMDLoadZero(Bool, Int)
// Load lane (load single element into existing vector)
// SIMDLoadLane(lane_size, lane, offset): LD1 Vd.T[lane], [Xn]
// Uses: [base, vector], Defs: [result]
SIMDLoadLane(LaneSize, Int, Int)
// Store lane (store single element from vector)
// SIMDStoreLane(lane_size, lane, offset): ST1 Vn.T[lane], [Xd]
// Uses: [base, vector], Defs: []
SIMDStoreLane(LaneSize, Int, Int)
// ============ Relaxed SIMD Instructions ============
// Floating-point fused multiply-add: d = a + (b * c)
// SIMDFMla(is_f32): FMLA Vd.4S/2D, Vn.4S/2D, Vm.4S/2D
// Uses: [accumulator, v1, v2], Defs: [result]
SIMDFMla(Bool)
// Floating-point fused multiply-subtract: d = a - (b * c)
// SIMDFMls(is_f32): FMLS Vd.4S/2D, Vn.4S/2D, Vm.4S/2D
// Uses: [accumulator, v1, v2], Defs: [result]
SIMDFMls(Bool)
// Relaxed dot product i8x16 -> i16x8 (signed/unsigned hybrid)
// SIMDRelaxedDot8to16: sequence to compute dot product
// Uses: [v1, v2], Defs: [result, tmp]
SIMDRelaxedDot8to16
// Relaxed dot product i8x16 -> i32x4 with accumulator
// SIMDRelaxedDot8to32Add: sequence to compute dot product with add
// Uses: [v1, v2, accumulator], Defs: [result, tmp]
SIMDRelaxedDot8to32Add
}
///|
fn Opcode::to_string(self : Opcode) -> String {
match self {
Add(is_64) => if is_64 { "add" } else { "add32" }
AddImm(imm, is_64) => if is_64 { "add #\{imm}" } else { "add32 #\{imm}" }
Sub(is_64) => if is_64 { "sub" } else { "sub32" }
SubImm(imm, is_64) => if is_64 { "sub #\{imm}" } else { "sub32 #\{imm}" }
Mul(is_64) => if is_64 { "mul" } else { "mul32" }
SDiv(is_64) => if is_64 { "sdiv" } else { "sdiv32" }
UDiv(is_64) => if is_64 { "udiv" } else { "udiv32" }
SRem(is_64) => if is_64 { "srem" } else { "srem32" }
URem(is_64) => if is_64 { "urem" } else { "urem32" }
And(is_64) => if is_64 { "and" } else { "and32" }
AndImm(imm, is_64) => if is_64 { "and #\{imm}" } else { "and32 #\{imm}" }
Or(is_64) => if is_64 { "or" } else { "or32" }
OrImm(imm, is_64) => if is_64 { "or #\{imm}" } else { "or32 #\{imm}" }
Xor(is_64) => if is_64 { "xor" } else { "xor32" }
XorImm(imm, is_64) => if is_64 { "xor #\{imm}" } else { "xor32 #\{imm}" }
Shl(is_64) => if is_64 { "shl" } else { "shl32" }
ShlImm(imm, is_64) => if is_64 { "shl #\{imm}" } else { "shl32 #\{imm}" }
AShr(is_64) => if is_64 { "ashr" } else { "ashr32" }
AShrImm(imm, is_64) => if is_64 { "ashr #\{imm}" } else { "ashr32 #\{imm}" }
LShr(is_64) => if is_64 { "lshr" } else { "lshr32" }
LShrImm(imm, is_64) => if is_64 { "lshr #\{imm}" } else { "lshr32 #\{imm}" }
Rotr(is_64) => if is_64 { "rotr" } else { "rotr32" }
RotrImm(imm, is_64) => if is_64 { "rotr #\{imm}" } else { "rotr32 #\{imm}" }
ExtrImm(imm, is_64) => if is_64 { "extr #\{imm}" } else { "extr32 #\{imm}" }
Not(is_64) => if is_64 { "not" } else { "not32" }
AndNot(is_64) => if is_64 { "andnot" } else { "andnot32" }
OrNot(is_64) => if is_64 { "ornot" } else { "ornot32" }
XorNot(is_64) => if is_64 { "xornot" } else { "xornot32" }
FAdd(is_f32) => if is_f32 { "fadd.s" } else { "fadd.d" }
FSub(is_f32) => if is_f32 { "fsub.s" } else { "fsub.d" }
FMul(is_f32) => if is_f32 { "fmul.s" } else { "fmul.d" }
FDiv(is_f32) => if is_f32 { "fdiv.s" } else { "fdiv.d" }
FMin(is_f32) => if is_f32 { "fmin.s" } else { "fmin.d" }
FMax(is_f32) => if is_f32 { "fmax.s" } else { "fmax.d" }
FSqrt(is_f32) => if is_f32 { "fsqrt.s" } else { "fsqrt.d" }
FAbs(is_f32) => if is_f32 { "fabs.s" } else { "fabs.d" }
FNeg(is_f32) => if is_f32 { "fneg.s" } else { "fneg.d" }
FCeil(is_f32) => if is_f32 { "fceil.s" } else { "fceil.d" }
FFloor(is_f32) => if is_f32 { "ffloor.s" } else { "ffloor.d" }
FTrunc(is_f32) => if is_f32 { "ftrunc.s" } else { "ftrunc.d" }
FNearest(is_f32) => if is_f32 { "fnearest.s" } else { "fnearest.d" }
Load(ty, offset) => "load.\{ty} +\{offset}"
Store(ty, offset) => "store.\{ty} +\{offset}"
Load8S(offset) => "load8s +\{offset}"
Load8U(offset) => "load8u +\{offset}"
Load16S(offset) => "load16s +\{offset}"
Load16U(offset) => "load16u +\{offset}"
Load32S(offset) => "load32s +\{offset}"
Load32U(offset) => "load32u +\{offset}"
Move => "mov"
LoadConst(v) => "ldi \{v}"
LoadConstF32(v) => {
// Convert i32 bits to f32 value for display
let f = Float::reinterpret_from_uint(v.reinterpret_as_uint())
"ldf \{f}"
}
LoadConstF64(v) => {
// Convert i64 bits to f64 value for display
let f = v.reinterpret_as_double()
"ldf \{f}"
}
Cmp(kind, is_64) => if is_64 { "cmp.\{kind}" } else { "cmp32.\{kind}" }
IntCmp(is_64) => if is_64 { "icmp" } else { "icmp32" }
FCmp(kind) => "fcmp.\{kind}"
Extend(kind) => "extend.\{kind}"
Truncate => "trunc"
IntToFloat(kind) => "i2f.\{kind}"
FPromote => "fpromote"
FDemote => "fdemote"
Bitcast => "bitcast"
Select => "select"
SelectCmp(kind, is_64) =>
if is_64 {
"select_cmp.\{kind}"
} else {
"select_cmp32.\{kind}"
}
Clz(is_64) => if is_64 { "clz" } else { "clz32" }
Popcnt(is_64) => if is_64 { "popcnt" } else { "popcnt32" }
Rbit(is_64) => if is_64 { "rbit" } else { "rbit32" }
Nop => "nop"
TrapIfUgt(trap_code) => "trap_if_ugt #\{trap_code}"
TrapIfUge(trap_code) => "trap_if_uge #\{trap_code}"
FpuCmp(is_f32) => if is_f32 { "fcmp.s" } else { "fcmp.d" }
TrapIf(cond, trap_code) => "trap_if.\{cond} #\{trap_code}"
TrapIfZero(is_64, trap_code) =>
if is_64 {
"trap_if_zero #\{trap_code}"
} else {
"trap_if_zero32 #\{trap_code}"
}
TrapIfDivOverflow(is_64, trap_code) =>
if is_64 {
"trap_if_div_overflow #\{trap_code}"
} else {
"trap_if_div_overflow32 #\{trap_code}"
}
FcvtToInt(is_f32, is_i64, is_signed) => {
let src = if is_f32 { "f32" } else { "f64" }
let dst = if is_i64 { "i64" } else { "i32" }
let sign = if is_signed { "s" } else { "u" }
"fcvt.\{src}_\{dst}_\{sign}"
}
FpuSel(is_f32, cond) =>
if is_f32 {
"fcsel.s.\{cond}"
} else {
"fcsel.d.\{cond}"
}
FpuMaxnm(is_f32) => if is_f32 { "fmaxnm.s" } else { "fmaxnm.d" }
FpuMinnm(is_f32) => if is_f32 { "fminnm.s" } else { "fminnm.d" }
// AArch64-specific
AddShifted(shift, amount) => "add.\{shift} #\{amount}"
AddExtend(ext, amount) => "add.\{ext} #\{amount}"
SubExtend(ext, amount) => "sub.\{ext} #\{amount}"
SubShifted(shift, amount) => "sub.\{shift} #\{amount}"
AndShifted(shift, amount) => "and.\{shift} #\{amount}"
OrShifted(shift, amount) => "or.\{shift} #\{amount}"
XorShifted(shift, amount) => "xor.\{shift} #\{amount}"
AddShifted32(shift, amount) => "add32.\{shift} #\{amount}"
SubShifted32(shift, amount) => "sub32.\{shift} #\{amount}"
AndShifted32(shift, amount) => "and32.\{shift} #\{amount}"
OrShifted32(shift, amount) => "or32.\{shift} #\{amount}"
XorShifted32(shift, amount) => "xor32.\{shift} #\{amount}"
Madd => "madd"
Msub => "msub"
Mneg => "mneg"
Madd32 => "madd32"
Msub32 => "msub32"
Mneg32 => "mneg32"
Umulh => "umulh"
Smulh => "smulh"
Umull => "umull"
Smull => "smull"
ReturnCallIndirect(stack_arg_bytes, num_results) =>
"return_call_indirect(stack=\{stack_arg_bytes}) -> \{num_results} results"
CallExternalIfI32NeImm(expected_value, helper) =>
"call_external_if_i32_ne_imm \{expected_value} via \{helper}"
StackLoad(offset) => "stack_load [sp+\{offset}]"
StackStore(offset) => "stack_store [sp+\{offset}]"
LoadStackParam(offset, class) => "load_stack_param +\{offset} (\{class})"
// NOTE: memory.grow/size/fill/copy and table.grow are lowered to foreign calls.
// NOTE: TableGet, TableSet, GlobalGet, GlobalSet removed - desugared at IR level
LoadPtr(ty, offset) => "load_ptr.\{ty} +\{offset}"
LoadPtrRegOffset(ty, ext, shift, offset) => {
let offset_str = if offset == 0 { "" } else { " +\{offset}" }
"load_ptr_regoff.\{ty} (\{ext},\{shift})\{offset_str}"
}
LoadPtrNarrowRegOffset(bits, signed, ext, shift, offset) => {
let sign_str = if signed { "s" } else { "u" }
let offset_str = if offset == 0 { "" } else { " +\{offset}" }
"load_ptr\{bits}_\{sign_str}_regoff (\{ext},\{shift})\{offset_str}"
}
StorePtr(ty, offset) => "store_ptr.\{ty} +\{offset}"
StorePtrRegOffset(ty, ext, shift, offset) => {
let offset_str = if offset == 0 { "" } else { " +\{offset}" }
"store_ptr_regoff.\{ty} (\{ext},\{shift})\{offset_str}"
}
StorePtrNarrowRegOffset(bits, ext, shift, offset) => {
let offset_str = if offset == 0 { "" } else { " +\{offset}" }
"store_ptr\{bits}_regoff (\{ext},\{shift})\{offset_str}"
}
LoadPtrNarrow(bits, signed, offset) => {
let sign_str = if signed { "s" } else { "u" }
"load_ptr\{bits}_\{sign_str} +\{offset}"
}
StorePtrNarrow(bits, offset) => "store_ptr\{bits} +\{offset}"
LoadExternalFuncAddr(symbol) => "load_external_func_addr.\{symbol}"
LoadCodeAddr(symbol) => "load_code_addr.\{symbol}"
CallPtr(num_args, result_classes, call_conv) =>
"call_ptr[\{call_conv}](\{num_args}) -> \{result_classes.length()} results"
CallDirect(target, num_args, result_classes, call_conv) =>
"call_direct[\{call_conv}].\{target}(\{num_args}) -> \{result_classes.length()} results"
CallExternal(symbol, num_args, result_classes, call_conv) =>
"call_external[\{call_conv}].\{symbol}(\{num_args}) -> \{result_classes.length()} results"
AdjustSP(delta) =>
if delta >= 0 {
"add sp, sp, #\{delta}"
} else {
"sub sp, sp, #\{-delta}"
}
StoreToStack(offset) => "str [sp+\{offset}]"
LoadSP => "mov rd, outgoing_args_base"
LoadSafepointId(root_count) => "load_safepoint_id roots=\{root_count}"
// SIMD instructions
LoadConstV128(_) => "ldi.v128"
SIMDSplat(lane_size) => "dup.\{lane_size}"
SIMDSplatF(is_f32) => if is_f32 { "dup.4s" } else { "dup.2d" }
SIMDExtractU(lane_size, lane) => "umov.\{lane_size}[\{lane}]"
SIMDExtractS(lane_size, lane) => "smov.\{lane_size}[\{lane}]"
SIMDExtractF(is_f32, lane) =>
if is_f32 {
"dup.s[\{lane}]"
} else {
"dup.d[\{lane}]"
}
SIMDInsert(lane_size, lane) => "ins.\{lane_size}[\{lane}]"
SIMDInsertF(is_f32, lane) =>
if is_f32 {
"ins.s[\{lane}]"
} else {
"ins.d[\{lane}]"
}
SIMDShuffle(_) => "tbl1x2.16b"
SIMDSwizzle => "tbl.16b"
SIMDNot => "not.16b"
SIMDAnd => "and.16b"
SIMDBic => "bic.16b"
SIMDOr => "orr.16b"
SIMDXor => "eor.16b"
SIMDBsl => "bsl.16b"
SIMDAnyTrue => "v128.any_true"
SIMDAllTrue(lane_size) => "all_true.\{lane_size}"
SIMDBitmask(lane_size) => "bitmask.\{lane_size}"
SIMDAdd(lane_size) => "add.\{lane_size}"
SIMDSub(lane_size) => "sub.\{lane_size}"
SIMDMul(lane_size) => "mul.\{lane_size}"
SIMDSqadd(lane_size) => "sqadd.\{lane_size}"
SIMDUqadd(lane_size) => "uqadd.\{lane_size}"
SIMDSqsub(lane_size) => "sqsub.\{lane_size}"
SIMDUqsub(lane_size) => "uqsub.\{lane_size}"
SIMDSmin(lane_size) => "smin.\{lane_size}"
SIMDUmin(lane_size) => "umin.\{lane_size}"
SIMDSmax(lane_size) => "smax.\{lane_size}"
SIMDUmax(lane_size) => "umax.\{lane_size}"
SIMDUrhadd(lane_size) => "urhadd.\{lane_size}"
SIMDAbs(lane_size) => "abs.\{lane_size}"
SIMDNeg(lane_size) => "neg.\{lane_size}"
SIMDCnt => "cnt.16b"
SIMDBroadcastShift(lane_size, negate) => {
let neg = if negate { ".neg" } else { "" }
"dup_shift\{neg}.\{lane_size}"
}
SIMDShiftByVec(lane_size, use_ushl) => {
let op = if use_ushl { "ushl" } else { "sshl" }
"\{op}.\{lane_size}"
}
SIMDCmp(lane_size, kind) => "cmp.\{kind}.\{lane_size}"
SIMDNarrow(lane_size, signed) => {
let sign = if signed { "s" } else { "u" }
"xtn.\{sign}.\{lane_size}"
}
SIMDExtendLow(lane_size, signed) => {
let sign = if signed { "s" } else { "u" }
"xtl.\{sign}.\{lane_size}"
}
SIMDExtendHigh(lane_size, signed) => {
let sign = if signed { "s" } else { "u" }
"xtl2.\{sign}.\{lane_size}"
}
SIMDExtMulLow(lane_size, signed) => {
let sign = if signed { "s" } else { "u" }
"mull.\{sign}.\{lane_size}"
}
SIMDExtMulHigh(lane_size, signed) => {
let sign = if signed { "s" } else { "u" }
"mull2.\{sign}.\{lane_size}"
}
SIMDExtAddPairwise(lane_size, signed) => {
let sign = if signed { "s" } else { "u" }
"addlp.\{sign}.\{lane_size}"
}
SIMDDot => "dot.i16x8_s"
SIMDQ15MulrSat => "sqrdmulh.8h"
SIMDFAdd(is_f32) => if is_f32 { "fadd.4s" } else { "fadd.2d" }
SIMDFSub(is_f32) => if is_f32 { "fsub.4s" } else { "fsub.2d" }
SIMDFMul(is_f32) => if is_f32 { "fmul.4s" } else { "fmul.2d" }
SIMDFDiv(is_f32) => if is_f32 { "fdiv.4s" } else { "fdiv.2d" }
SIMDFMin(is_f32) => if is_f32 { "fmin.4s" } else { "fmin.2d" }
SIMDFMax(is_f32) => if is_f32 { "fmax.4s" } else { "fmax.2d" }
SIMDFPMin(is_f32) => if is_f32 { "fpmin.4s" } else { "fpmin.2d" }
SIMDFPMax(is_f32) => if is_f32 { "fpmax.4s" } else { "fpmax.2d" }
SIMDFAbs(is_f32) => if is_f32 { "fabs.4s" } else { "fabs.2d" }
SIMDFNeg(is_f32) => if is_f32 { "fneg.4s" } else { "fneg.2d" }
SIMDFSqrt(is_f32) => if is_f32 { "fsqrt.4s" } else { "fsqrt.2d" }
SIMDFCeil(is_f32) => if is_f32 { "frintp.4s" } else { "frintp.2d" }
SIMDFFloor(is_f32) => if is_f32 { "frintm.4s" } else { "frintm.2d" }
SIMDFTrunc(is_f32) => if is_f32 { "frintz.4s" } else { "frintz.2d" }
SIMDFNearest(is_f32) => if is_f32 { "frintn.4s" } else { "frintn.2d" }
SIMDFCmp(is_f32, kind) =>
if is_f32 {
"fcmp.\{kind}.4s"
} else {
"fcmp.\{kind}.2d"
}
SIMDFCvtToIntS(is_f32) => if is_f32 { "fcvtzs.4s" } else { "fcvtzs.2d" }
SIMDFCvtToIntU(is_f32) => if is_f32 { "fcvtzu.4s" } else { "fcvtzu.2d" }
SIMDIntToFloatS(is_f32) => if is_f32 { "scvtf.4s" } else { "scvtf.2d" }
SIMDIntToFloatU(is_f32) => if is_f32 { "ucvtf.4s" } else { "ucvtf.2d" }
SIMDTruncSatF64ToI32SZero => "fcvtzs.f64_i32_zero"
SIMDTruncSatF64ToI32UZero => "fcvtzu.f64_i32_zero"
SIMDConvertLowI32ToF64S => "scvtf.i32_f64_low"
SIMDConvertLowI32ToF64U => "ucvtf.i32_f64_low"
SIMDDemoteF64ToF32Zero => "fcvtn.f64_f32_zero"
SIMDPromoteLowF32ToF64 => "fcvtl.f32_f64_low"
SIMDLoad(offset) => "ldr.q +\{offset}"
SIMDStore(offset) => "str.q +\{offset}"
SIMDLoadSplat(lane_size, offset) => "ld1r.\{lane_size} +\{offset}"
SIMDLoadExtend(bits, signed, offset) => {
let sign = if signed { "s" } else { "u" }
"ldext.\{bits}\{sign} +\{offset}"
}
SIMDLoadZero(is_64, offset) =>
if is_64 {
"ldr.d_zero +\{offset}"
} else {
"ldr.s_zero +\{offset}"
}
SIMDLoadLane(lane_size, lane, offset) =>
"ld1.\{lane_size}[\{lane}] +\{offset}"
SIMDStoreLane(lane_size, lane, offset) =>
"st1.\{lane_size}[\{lane}] +\{offset}"
// Relaxed SIMD
SIMDFMla(is_f32) => if is_f32 { "fmla.4s" } else { "fmla.2d" }
SIMDFMls(is_f32) => if is_f32 { "fmls.4s" } else { "fmls.2d" }
SIMDRelaxedDot8to16 => "relaxed_dot.8to16"
SIMDRelaxedDot8to32Add => "relaxed_dot.8to32_add"
}
}
///|
pub impl Show for Opcode with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
pub fn Opcode::call_result_classes(self : Opcode) -> Array[@abi.RegClass]? {
match self {
CallPtr(_, result_classes, _)
| CallDirect(_, _, result_classes, _)
| CallExternal(_, _, result_classes, _) => Some(result_classes)
_ => None
}
}
///|
pub fn Opcode::call_result_count(self : Opcode) -> Int? {
match self.call_result_classes() {
Some(result_classes) => Some(result_classes.length())
None => None
}
}
///|
pub fn result_classes_for_vregs(
results : Array[@abi.VReg],
) -> Array[@abi.RegClass] {
let classes : Array[@abi.RegClass] = []
for result in results {
classes.push(result.class)
}
classes
}
///|
/// Memory type for load/store
pub(all) enum MemType {
I32
I64
F32
F64
V128 // 128-bit SIMD vector
}
///|
fn MemType::to_string(self : MemType) -> String {
match self {
I32 => "i32"
I64 => "i64"
F32 => "f32"
F64 => "f64"
V128 => "v128"
}
}
///|
pub impl Show for MemType with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Comparison kind for integer comparisons
pub(all) enum CmpKind {
Eq
Ne
Slt
Sle
Sgt
Sge
Ult
Ule
Ugt
Uge
}
///|
fn CmpKind::to_string(self : CmpKind) -> String {
match self {
Eq => "eq"
Ne => "ne"
Slt => "slt"
Sle => "sle"
Sgt => "sgt"
Sge => "sge"
Ult => "ult"
Ule => "ule"
Ugt => "ugt"
Uge => "uge"
}
}
///|
pub impl Show for CmpKind with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Comparison kind for float comparisons
pub(all) enum FCmpKind {
Eq
Ne
Lt
Le
Gt
Ge
}
///|
fn FCmpKind::to_string(self : FCmpKind) -> String {
match self {
Eq => "eq"
Ne => "ne"
Lt => "lt"
Le => "le"
Gt => "gt"
Ge => "ge"
}
}
///|
pub impl Show for FCmpKind with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Extend kind - how to extend a value
pub(all) enum ExtendKind {
Signed8To32
Signed8To64
Signed16To32
Signed16To64
Signed32To64
Unsigned8To32
Unsigned8To64
Unsigned16To32
Unsigned16To64
Unsigned32To64
}
///|
fn ExtendKind::to_string(self : ExtendKind) -> String {
match self {
Signed8To32 => "s8_32"
Signed8To64 => "s8_64"
Signed16To32 => "s16_32"
Signed16To64 => "s16_64"
Signed32To64 => "s32_64"
Unsigned8To32 => "u8_32"
Unsigned8To64 => "u8_64"
Unsigned16To32 => "u16_32"
Unsigned16To64 => "u16_64"
Unsigned32To64 => "u32_64"
}
}
///|
pub impl Show for ExtendKind with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Float to Int conversion kind
/// Encodes: source float type, destination int type, signedness
pub(all) enum FloatToIntKind {
F32ToI32S // f32 -> i32 signed (FCVTZS Wd, Sn) - trapping
F32ToI32U // f32 -> i32 unsigned (FCVTZU Wd, Sn) - trapping
F32ToI64S // f32 -> i64 signed (FCVTZS Xd, Sn) - trapping
F32ToI64U // f32 -> i64 unsigned (FCVTZU Xd, Sn) - trapping
F64ToI32S // f64 -> i32 signed (FCVTZS Wd, Dn) - trapping
F64ToI32U // f64 -> i32 unsigned (FCVTZU Wd, Dn) - trapping
F64ToI64S // f64 -> i64 signed (FCVTZS Xd, Dn) - trapping
F64ToI64U // f64 -> i64 unsigned (FCVTZU Xd, Dn) - trapping
// Saturating conversions (NaN->0, overflow->max/min)
F32ToI32SSat // f32 -> i32 signed saturating
F32ToI32USat // f32 -> i32 unsigned saturating
F32ToI64SSat // f32 -> i64 signed saturating
F32ToI64USat // f32 -> i64 unsigned saturating
F64ToI32SSat // f64 -> i32 signed saturating
F64ToI32USat // f64 -> i32 unsigned saturating
F64ToI64SSat // f64 -> i64 signed saturating
F64ToI64USat // f64 -> i64 unsigned saturating
}
///|
fn FloatToIntKind::to_string(self : FloatToIntKind) -> String {
match self {
F32ToI32S => "f32_i32_s"
F32ToI32U => "f32_i32_u"
F32ToI64S => "f32_i64_s"
F32ToI64U => "f32_i64_u"
F64ToI32S => "f64_i32_s"
F64ToI32U => "f64_i32_u"
F64ToI64S => "f64_i64_s"
F64ToI64U => "f64_i64_u"
F32ToI32SSat => "f32_i32_s_sat"
F32ToI32USat => "f32_i32_u_sat"
F32ToI64SSat => "f32_i64_s_sat"
F32ToI64USat => "f32_i64_u_sat"
F64ToI32SSat => "f64_i32_s_sat"
F64ToI32USat => "f64_i32_u_sat"
F64ToI64SSat => "f64_i64_s_sat"
F64ToI64USat => "f64_i64_u_sat"
}
}
///|
pub impl Show for FloatToIntKind with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Int to Float conversion kind
/// Encodes: source int type, destination float type, signedness
pub(all) enum IntToFloatKind {
I32SToF32 // i32 signed -> f32 (SCVTF Sd, Wn)
I32UToF32 // i32 unsigned -> f32 (UCVTF Sd, Wn)
I64SToF32 // i64 signed -> f32 (SCVTF Sd, Xn)
I64UToF32 // i64 unsigned -> f32 (UCVTF Sd, Xn)
I32SToF64 // i32 signed -> f64 (SCVTF Dd, Wn)
I32UToF64 // i32 unsigned -> f64 (UCVTF Dd, Wn)
I64SToF64 // i64 signed -> f64 (SCVTF Dd, Xn)
I64UToF64 // i64 unsigned -> f64 (UCVTF Dd, Xn)
}
///|
fn IntToFloatKind::to_string(self : IntToFloatKind) -> String {
match self {
I32SToF32 => "i32_s_f32"
I32UToF32 => "i32_u_f32"
I64SToF32 => "i64_s_f32"
I64UToF32 => "i64_u_f32"
I32SToF64 => "i32_s_f64"
I32UToF64 => "i32_u_f64"
I64SToF64 => "i64_s_f64"
I64UToF64 => "i64_u_f64"
}
}
///|
pub impl Show for IntToFloatKind with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// AArch64 condition codes (for conditional branches and traps)
/// Condition codes for comparisons
pub(all) enum Cond {
Eq // Equal (Z=1)
Ne // Not equal (Z=0)
Hs // Unsigned higher or same (C=1), also known as CS
Lo // Unsigned lower (C=0), also known as CC
Mi // Minus/negative (N=1)
Pl // Plus/positive or zero (N=0)
Vs // Overflow (V=1) - used for NaN check in float compare
Vc // No overflow (V=0)
// x86_64-only: parity flag conditions (used after UCOMIS* for NaN/unordered checks).
Ps // Parity set (PF=1)
Pc // Parity clear (PF=0)
Hi // Unsigned higher (C=1 && Z=0)
Ls // Unsigned lower or same (C=0 || Z=1)
Ge // Signed greater or equal (N=V)
Lt // Signed less than (N!=V)
Gt // Signed greater than (Z=0 && N=V)
Le // Signed less or equal (Z=1 || N!=V)
Al // Always (unconditional)
}
///|
fn Cond::to_string(self : Cond) -> String {
match self {
Eq => "eq"
Ne => "ne"
Hs => "hs"
Lo => "lo"
Mi => "mi"
Pl => "pl"
Vs => "vs"
Vc => "vc"
Ps => "ps"
Pc => "pc"
Hi => "hi"
Ls => "ls"
Ge => "ge"
Lt => "lt"
Gt => "gt"
Le => "le"
Al => "al"
}
}
///|
pub impl Show for Cond with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Get the condition code value for AArch64 encoding
pub fn Cond::to_bits(self : Cond) -> Int {
match self {
Eq => 0
Ne => 1
Hs => 2
Lo => 3
Mi => 4
Pl => 5
Vs => 6
Vc => 7
Ps | Pc => abort("Cond::to_bits: parity conditions are x86_64-only")
Hi => 8
Ls => 9
Ge => 10
Lt => 11
Gt => 12
Le => 13
Al => 14
}
}
///|
/// Invert condition code (for branch inversion optimization)
/// E.g., Eq -> Ne, Hs -> Lo, Ge -> Lt
pub fn Cond::invert(self : Cond) -> Cond {
match self {
Eq => Ne
Ne => Eq
Hs => Lo
Lo => Hs
Mi => Pl
Pl => Mi
Vs => Vc
Vc => Vs
Ps => Pc
Pc => Ps
Hi => Ls
Ls => Hi
Ge => Lt
Lt => Ge
Gt => Le
Le => Gt
Al => Al // Always is its own inverse (never used in practice)
}
}
///|
/// MachV terminator - how a block ends
pub(all) enum Terminator {
// Jump to target block with SSA block arguments.
// The length of args must match the target block's params.
Jump(Int, Array[@abi.Reg])
Branch(@abi.Reg, Int, Int) // Conditional: condition, then-block, else-block
// BranchCmp: compare two registers and branch on condition
// Emits: CMP Xn, Xm + B.cond (saves one instruction vs Branch)
// Parameters: lhs, rhs, cond, is_64, then-block, else-block
BranchCmp(@abi.Reg, @abi.Reg, Cond, Bool, Int, Int)
// BranchZero: branch if register is zero/nonzero
// Emits: CBZ/CBNZ (saves two instructions vs Branch)
// Parameters: reg, is_nonzero, is_64, then-block, else-block
BranchZero(@abi.Reg, Bool, Bool, Int, Int)
// BranchCmpImm: compare register with immediate and branch on condition
// Emits: CMP Xn, #imm + B.cond (saves one instruction vs loading const to reg)
// Parameters: lhs, imm, cond, is_64, then-block, else-block
BranchCmpImm(@abi.Reg, Int, Cond, Bool, Int, Int)
Return(Array[@abi.Reg]) // Return with values
Trap(Int) // Trap with embedding-defined payload
// BrTable: index register, target block IDs, default block ID
// Uses jump table for O(1) dispatch instead of comparison chain
BrTable(@abi.Reg, Array[Int], Int)
}
///|
fn Terminator::to_string(self : Terminator) -> String {
match self {
Jump(target, args) => {
let mut result = "jump block\{target}"
if args.length() > 0 {
result = result + " ("
for i, a in args {
if i > 0 {
result = result + ", "
}
result = result + a.to_string()
}
result = result + ")"
}
result
}
Branch(cond, then_b, else_b) =>
"branch \{cond}, block\{then_b}, block\{else_b}"
BranchCmp(lhs, rhs, cond, _is_64, then_b, else_b) =>
"br_cmp.\{cond} \{lhs}, \{rhs}, block\{then_b}, block\{else_b}"
BranchZero(reg, is_nonzero, _is_64, then_b, else_b) => {
let op = if is_nonzero { "cbnz" } else { "cbz" }
"\{op} \{reg}, block\{then_b}, block\{else_b}"
}
BranchCmpImm(lhs, imm, cond, _is_64, then_b, else_b) =>
"br_cmp_imm.\{cond} \{lhs}, #\{imm}, block\{then_b}, block\{else_b}"
Return(values) => {
let mut result = "ret"
if values.length() > 0 {
result = result + " "
for i, v in values {
if i > 0 {
result = result + ", "
}
result = result + v.to_string()
}
}
result
}
Trap(payload) => "trap #\{payload}"
BrTable(index, targets, default) => {
let mut result = "br_table \{index}, ["
for i, t in targets {
if i > 0 {
result = result + ", "
}
result = result + "block\{t}"
}
result = result + "], default block\{default}"
result
}
}
}
///|
pub impl Show for Terminator with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Get the call type for this opcode
/// Design: instructions that clobber caller-saved registers
/// are classified as calls for register allocation purposes
pub fn Opcode::call_type(self : Opcode) -> CallType {
match self {
// Direct and indirect function calls (Standard via CallPtr)
CallPtr(_, _, _) | CallDirect(_, _, _, _) | CallExternal(_, _, _, _) =>
Regular
// Conditional helpers can call a C helper on the slow path; treat them as calls.
CallExternalIfI32NeImm(_, _) => Regular
// Tail calls that don't return to caller
ReturnCallIndirect(_, _) => TailCall
// Everything else is not a call
_ => None
}
}
///|
/// Shift type for shifted operand instructions
pub(all) enum ShiftType {
Lsl // Logical shift left
Lsr // Logical shift right
Asr // Arithmetic shift right
}
///|
/// AArch64 addressing-mode extend for register-offset loads/stores.
///
/// Mirrors Cranelift's AMode.{RegExtended,RegScaledExtended} options:
/// - None: use X register offset (LSL option)
/// - Uxtw/Sxtw: extend Wm to X, optionally scaled by access size (S bit)
pub(all) enum IndexExtend {
None
Uxtw
Sxtw
}
///|
fn IndexExtend::to_string(self : IndexExtend) -> String {
match self {
None => "none"
Uxtw => "uxtw"
Sxtw => "sxtw"
}
}
///|
pub impl Show for IndexExtend with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
fn ShiftType::to_string(self : ShiftType) -> String {
match self {
Lsl => "lsl"
Lsr => "lsr"
Asr => "asr"
}
}
///|
pub impl Show for ShiftType with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// SIMD lane size for vector operations
pub(all) enum LaneSize {
B8 // 8-bit lanes (16 lanes in 128-bit vector)
H16 // 16-bit lanes (8 lanes)
S32 // 32-bit lanes (4 lanes)
D64 // 64-bit lanes (2 lanes)
}
///|
fn LaneSize::to_string(self : LaneSize) -> String {
match self {
B8 => "8b"
H16 => "8h"
S32 => "4s"
D64 => "2d"
}
}
///|
pub impl Show for LaneSize with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// SIMD comparison kind for integer vector comparisons
pub(all) enum SIMDCmpKind {
Eq // CMEQ - equal
GtS // CMGT - signed greater than
GeS // CMGE - signed greater than or equal
GtU // CMHI - unsigned greater than (higher)
GeU // CMHS - unsigned greater than or equal (higher or same)
}
///|
fn SIMDCmpKind::to_string(self : SIMDCmpKind) -> String {
match self {
Eq => "eq"
GtS => "gt.s"
GeS => "ge.s"
GtU => "gt.u"
GeU => "ge.u"
}
}
///|
pub impl Show for SIMDCmpKind with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// SIMD floating-point comparison kind
pub(all) enum SIMDFCmpKind {
Eq // FCMEQ - equal
Gt // FCMGT - greater than
Ge // FCMGE - greater than or equal
}
///|
fn SIMDFCmpKind::to_string(self : SIMDFCmpKind) -> String {
match self {
Eq => "eq"
Gt => "gt"
Ge => "ge"
}
}
///|
pub impl Show for SIMDFCmpKind with fn output(self, logger) {
logger.write_string(self.to_string())
}