// ============ Production MachV Function ============
///|
/// Machine-level value kind needed after MilkIR lowering.
///
/// Keep this in MachV instead of carrying `@milkir.Type` through register
/// allocation and emission.
pub(all) enum ValueKind {
I32
I64
F32
F64
V128
Ptr
} derive(Eq, Debug)
///|
/// MachV function - a complete machine-level function in virtual-register form.
pub(all) struct Function {
name : String
params : Array[@abi.VReg] // Function parameters
results : Array[@abi.RegClass] // Result types (for return value allocation)
result_kinds : Array[ValueKind] // Full return kind info for multi-value returns
blocks : Array[@block.MachVBlock]
mut next_vreg_id : Int
mut num_spill_slots : Int // Number of spill slots used by register allocator
// Map from param index to allocated physical register (for params that cross calls)
// This is filled by apply_allocation and used by emit_prologue
param_pregs : Array[@abi.PReg?]
// Stack parameter count for ABI: tracks int overflow count for correct stack layout
mut int_stack_params : Int
// Maximum outgoing argument space needed by any call in this function
// Pre-allocated in prologue so SP doesn't move during call sequences
mut max_outgoing_args_size : Int
// True when lowering materialized the embedding-defined context cache 0 source.
mut uses_context_cache_0_source : Bool
}
///|
pub fn Function::get_max_outgoing_args_size(self : Function) -> Int {
self.max_outgoing_args_size
}
///|
pub fn Function::get_num_spill_slots(self : Function) -> Int {
self.num_spill_slots
}
///|
pub fn Function::get_name(self : Function) -> String {
self.name
}
///|
pub fn Function::get_params(self : Function) -> Array[@abi.VReg] {
self.params
}
///|
pub fn Function::get_blocks(self : Function) -> Array[@block.MachVBlock] {
self.blocks
}
///|
pub fn Function::get_param_pregs(self : Function) -> Array[@abi.PReg?] {
self.param_pregs
}
///|
pub fn Function::get_result_kinds(self : Function) -> Array[ValueKind] {
self.result_kinds
}
///|
pub fn Function::new(name : String) -> Function {
{
name,
params: [],
results: [],
result_kinds: [],
blocks: [],
next_vreg_id: 0,
num_spill_slots: 0,
param_pregs: [],
int_stack_params: 0,
max_outgoing_args_size: 0,
uses_context_cache_0_source: false,
}
}
///|
/// 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
pub fn Function::clone_base(self : Function) -> Function {
{
name: self.name,
params: [],
results: [],
result_kinds: [],
blocks: [],
next_vreg_id: self.next_vreg_id,
num_spill_slots: 0,
param_pregs: [],
int_stack_params: self.int_stack_params,
max_outgoing_args_size: self.max_outgoing_args_size,
uses_context_cache_0_source: self.uses_context_cache_0_source,
}
}
///|
/// Set the number of spill slots (called by register allocator)
pub fn Function::set_num_spill_slots(self : Function, n : Int) -> Unit {
self.num_spill_slots = n
}
///|
/// Add a parameter physical register mapping (called by register allocator)
pub fn Function::add_param_preg(self : Function, preg : @abi.PReg?) -> Unit {
self.param_pregs.push(preg)
}
///|
/// Set the number of integer stack parameters (called during lowering)
pub fn Function::set_int_stack_params(self : Function, n : Int) -> Unit {
self.int_stack_params = n
}
///|
/// Update max outgoing args size if the new size is larger (called during lowering)
pub fn Function::update_max_outgoing_args_size(
self : Function,
size : Int,
) -> Unit {
if size > self.max_outgoing_args_size {
self.max_outgoing_args_size = size
}
}
///|
pub fn Function::mark_uses_context_cache_0_source(self : Function) -> Unit {
self.uses_context_cache_0_source = true
}
///|
pub fn Function::uses_context_cache_0_source(self : Function) -> Bool {
self.uses_context_cache_0_source
}
///|
/// Push a parameter vreg directly (used during regalloc reconstruction)
pub fn Function::push_param(self : Function, vreg : @abi.VReg) -> Unit {
self.params.push(vreg)
}
///|
pub fn Function::new_vreg(self : Function, class : @abi.RegClass) -> @abi.VReg {
let id = self.next_vreg_id
self.next_vreg_id = id + 1
{ id, class }
}
///|
pub fn Function::add_param(self : Function, class : @abi.RegClass) -> @abi.VReg {
let vreg = self.new_vreg(class)
self.params.push(vreg)
vreg
}
///|
pub fn Function::add_result(self : Function, class : @abi.RegClass) -> Unit {
self.results.push(class)
}
///|
/// Add a result kind with full kind information for multi-value returns
pub fn Function::add_result_kind(self : Function, ty : ValueKind) -> Unit {
self.result_kinds.push(ty)
}
///|
/// 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
pub fn Function::needs_extra_results_ptr(self : Function) -> Bool {
let mut int_count = 0
let mut float_count = 0
for ty in self.result_kinds {
if ty is (I32 | I64) {
int_count = int_count + 1
} else if ty is (F32 | F64) {
float_count = float_count + 1
}
}
int_count > 2 || float_count > 2
}
///|
/// 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
pub fn Function::calls_multi_value_function(self : Function) -> Bool {
for block in self.blocks {
for inst in block.insts {
if inst.opcode is CallPtr(_, num_results, _) && num_results > 2 {
return true
}
}
}
false
}
///|
/// Returns true if this function contains any call-like instruction.
pub fn Function::has_calls(self : Function) -> Bool {
for block in self.blocks {
for inst in block.insts {
let call_type = inst.opcode.call_type()
if call_type is Regular || call_type is TailCall {
return true
}
}
}
false
}
///|
/// Returns true if context cache 0 caching should be enabled for this function.
pub fn Function::should_reserve_context_cache_0(self : Function) -> Bool {
ignore(self)
// Cranelift-style policy:
// Keep memory base as a regular SSA value and let RA/CSE/LICM place it.
// Do not reserve a dedicated physical register for context cache 0.
false
}
///|
/// 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.
pub fn Function::uses_context_cache_1(self : Function) -> Bool {
ignore(self)
// Function-table layout is owned by the embedding runtime. MachV no longer
// infers it from a built-in context offset.
false
}
///|
/// Returns true if context cache 1 pointer caching should be enabled for this function.
pub fn Function::should_reserve_context_cache_1(self : Function) -> Bool {
ignore(self)
// Cranelift-style policy:
// Keep context cache 1 pointer as a regular SSA value; avoid a dedicated pinned
// physical register that increases global register pressure.
false
}
///|
pub fn Function::new_block(self : Function) -> @block.MachVBlock {
let id = self.blocks.length()
let block = @block.MachVBlock::new(id)
self.blocks.push(block)
block
}
///|
pub fn Function::print(self : Function) -> String {
let mut result = "machv \{self.name}("
// Parameters
for i, param in self.params {
if i > 0 {
result = result + ", "
}
result = result + param.to_string() + ":" + param.class.to_string()
}
result = result + ")"
// Results
if self.results.length() > 0 {
result = result + " -> "
if self.results.length() == 1 {
result = result + self.results[0].to_string()
} else {
result = result + "("
for i, r in self.results {
if i > 0 {
result = result + ", "
}
result = result + r.to_string()
}
result = result + ")"
}
}
result = result + " {\n"
// Blocks
for block in self.blocks {
result = result + "block\{block.id}"
if block.params.length() > 0 {
result = result + "("
for i, param in block.params {
if i > 0 {
result = result + ", "
}
result = result + param.to_string() + ":" + param.class.to_string()
}
result = result + ")"
}
result = result + ":\n"
// Instructions
for inst in block.insts {
result = result + " \{inst}\n"
}
// Terminator
if block.terminator is Some(term) {
result = result + " \{term}\n"
}
}
result = result + "}\n"
result
}
///|
pub impl Show for Function with fn output(self, logger) {
logger.write_string(self.print())
}