///|
fn hot_builder_results_type(
func : HotFunc,
result_types : Array[ValType],
) -> TypeId {
match result_types.length() {
0 => hot_type_void(func)
1 => hot_type_intern_value(func, result_types[0])
_ => hot_type_intern_results(func, result_types)
}
}
///|
fn hot_builder_local_value_type(func : HotFunc, local_id : LocalId) -> ValType {
match hot_local_type(func, local_id) {
Some(ty) => ty
None => abort("hot builder local id must reference an existing param/local")
}
}
///|
fn hot_builder_tag_idx_raw(tag_idx : TagIdx) -> Int32 {
let TagIdx(raw) = tag_idx
raw.reinterpret_as_int()
}
///|
/// Build a control's region holder without introducing an encoded block.
/// Attach the holder to a compatible control slot; result_arity is its
/// fallthrough stack arity. Body nodes must belong to the same function.
pub fn hot_build_region(
func : HotFunc,
body : Array[NodeId],
result_arity : Int32,
) -> NodeId {
let region_id = hot_build_node(
func,
HotOp::Block,
hot_type_void(func),
children=body,
)
let label_id = hot_label_alloc(
func,
HotLabelKind::RegionLabel,
hot_type_void(func),
region_id,
result_arity,
)
hot_node_set(func, region_id, {
..hot_node_get(func, region_id),
imm0: label_id,
})
region_id
}
///|
pub fn hot_build_node(
func : HotFunc,
op : HotOp,
ty : TypeId,
imm0~ : Int32 = 0,
imm1~ : Int32 = 0,
children~ : Array[NodeId] = [],
) -> NodeId {
let node = {
op,
ty,
flags: hot_flags_for_exact_payload(func, op, imm0),
order: -1,
imm0,
imm1,
child_base: 0,
child_count: 0,
}
match children.length() {
0 => hot_alloc_node(func, node)
1 => hot_alloc_node_with_child1(func, node, children[0])
2 => hot_alloc_node_with_child2(func, node, children[0], children[1])
3 =>
hot_alloc_node_with_child3(
func,
node,
children[0],
children[1],
children[2],
)
_ => hot_alloc_node_with_children(func, node, children)
}
}
///|
// Match hot_build_node's empty-child branch without allocating its default row.
// Keep allocation, type/span checks, append order and revision in hot_alloc_node.
fn hot_build_node0(
func : HotFunc,
op : HotOp,
ty : TypeId,
imm0~ : Int32 = 0,
imm1~ : Int32 = 0,
) -> NodeId {
hot_alloc_node(func, {
op,
ty,
flags: hot_flags_for_exact_payload(func, op, imm0),
order: -1,
imm0,
imm1,
child_base: 0,
child_count: 0,
})
}
///|
fn hot_build_node1(
func : HotFunc,
op : HotOp,
ty : TypeId,
child0 : NodeId,
imm0~ : Int32 = 0,
imm1~ : Int32 = 0,
) -> NodeId {
hot_alloc_node_with_child1(
func,
{
op,
ty,
flags: hot_flags_for_exact_payload(func, op, imm0),
order: -1,
imm0,
imm1,
child_base: 0,
child_count: 0,
},
child0,
)
}
///|
fn hot_build_node2(
func : HotFunc,
op : HotOp,
ty : TypeId,
child0 : NodeId,
child1 : NodeId,
imm0~ : Int32 = 0,
imm1~ : Int32 = 0,
) -> NodeId {
hot_alloc_node_with_child2(
func,
{
op,
ty,
flags: hot_flags_for_exact_payload(func, op, imm0),
order: -1,
imm0,
imm1,
child_base: 0,
child_count: 0,
},
child0,
child1,
)
}
///|
fn hot_build_node3(
func : HotFunc,
op : HotOp,
ty : TypeId,
child0 : NodeId,
child1 : NodeId,
child2 : NodeId,
imm0~ : Int32 = 0,
imm1~ : Int32 = 0,
) -> NodeId {
hot_alloc_node_with_child3(
func,
{
op,
ty,
flags: hot_flags_for_exact_payload(func, op, imm0),
order: -1,
imm0,
imm1,
child_base: 0,
child_count: 0,
},
child0,
child1,
child2,
)
}
///|
pub fn hot_build_nop(func : HotFunc) -> NodeId {
hot_build_node(func, HotOp::Nop, hot_type_void(func))
}
///|
pub fn hot_build_unreachable(func : HotFunc) -> NodeId {
hot_build_node(func, HotOp::Unreachable, hot_type_void(func))
}
///|
pub fn hot_build_drop(func : HotFunc, value : NodeId) -> NodeId {
hot_build_node1(func, HotOp::Drop, hot_type_void(func), value)
}
///|
pub fn hot_build_return(func : HotFunc, values : Array[NodeId]) -> NodeId {
let expected = hot_type_result_arity(func, hot_body_result_type(func))
if values.length() != expected {
abort("hot return builder values must match the function body result arity")
}
hot_build_node(func, HotOp::Return, hot_type_void(func), children=values)
}
///|
pub fn hot_build_const_i32(func : HotFunc, value : I32) -> NodeId {
let const_id = hot_alloc_const(func, HotConstPayload::i32_const(value))
hot_build_node(
func,
HotOp::Const,
hot_type_intern_value(func, @lib.ValType::i32()),
imm0=const_id,
)
}
///|
pub fn hot_build_const_i64(func : HotFunc, value : I64) -> NodeId {
let const_id = hot_alloc_const(func, HotConstPayload::i64_const(value))
hot_build_node(
func,
HotOp::Const,
hot_type_intern_value(func, @lib.ValType::i64()),
imm0=const_id,
)
}
///|
pub fn hot_build_const_f32(func : HotFunc, value : F32) -> NodeId {
let const_id = hot_alloc_const(func, HotConstPayload::f32_const(value))
hot_build_node(
func,
HotOp::Const,
hot_type_intern_value(func, @lib.ValType::f32()),
imm0=const_id,
)
}
///|
pub fn hot_build_const_f64(func : HotFunc, value : F64) -> NodeId {
let const_id = hot_alloc_const(func, HotConstPayload::f64_const(value))
hot_build_node(
func,
HotOp::Const,
hot_type_intern_value(func, @lib.ValType::f64()),
imm0=const_id,
)
}
///|
pub fn hot_build_const_v128_splat(func : HotFunc, byte : Byte) -> NodeId {
let payload_id = hot_alloc_instr_payload(
func,
HotInstrPayload::new(
@lib.Instruction::v128_const(
byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
byte, byte, byte,
),
),
)
hot_build_node(
func,
HotOp::Simd,
hot_type_intern_value(func, @lib.ValType::v128()),
imm0=payload_id,
)
}
///|
pub fn hot_build_const_v128_zero(func : HotFunc) -> NodeId {
hot_build_const_v128_splat(func, b'\x00')
}
///|
pub fn hot_build_ref_null(func : HotFunc, value : RefType) -> NodeId {
let const_id = hot_alloc_const(func, HotConstPayload::ref_null_const(value))
hot_build_node(
func,
HotOp::RefNull,
hot_type_intern_value(func, @lib.ValType::ref_null_type(value)),
imm0=const_id,
)
}
///|
pub fn hot_build_ref_func(
func : HotFunc,
func_idx : @lib.FuncIdx,
ref_type : RefType,
) -> NodeId {
let @lib.FuncIdx(raw) = func_idx
hot_build_node(
func,
HotOp::RefFunc,
hot_type_intern_value(func, @lib.ValType::ref_type(ref_type)),
imm0=raw.reinterpret_as_int(),
)
}
///|
pub fn hot_build_select(
func : HotFunc,
result_type : ValType,
if_true : NodeId,
if_false : NodeId,
condition : NodeId,
) -> NodeId {
let instr = @lib.Instruction::select(
types=if result_type.is_ref_type() { Some([result_type]) } else { None },
)
let payload_id = hot_alloc_instr_payload(func, HotInstrPayload::new(instr))
hot_build_node(
func,
HotOp::Select,
hot_type_intern_value(func, result_type),
imm0=payload_id,
children=[if_true, if_false, condition],
)
}
///|
pub fn hot_build_string_const(func : HotFunc, value : Bytes) -> NodeId {
let const_id = hot_alloc_const(func, HotConstPayload::string_const(value))
hot_build_node(
func,
HotOp::Const,
hot_type_intern_value(func, @lib.ValType::stringref()),
imm0=const_id,
)
}
///|
pub fn hot_build_local_get(func : HotFunc, local_id : LocalId) -> NodeId {
hot_build_node(
func,
HotOp::LocalGet,
hot_type_intern_value(func, hot_builder_local_value_type(func, local_id)),
imm0=local_id,
)
}
///|
pub fn hot_build_local_set(
func : HotFunc,
local_id : LocalId,
value : NodeId,
) -> NodeId {
let _ = hot_builder_local_value_type(func, local_id)
hot_build_node1(
func,
HotOp::LocalSet,
hot_type_void(func),
value,
imm0=local_id,
)
}
///|
/// Capture an existing value at its source execution position.
/// Use this when extracting an operand into a preceding local-set root.
pub fn hot_build_local_set_from_value(
func : HotFunc,
local_id : LocalId,
value : NodeId,
) -> NodeId {
let order = hot_node_get(func, value).order
let set_id = hot_build_local_set(func, local_id, value)
func.nodes[set_id] = { ..func.nodes[set_id], order, }
set_id
}
///|
/// Copy a tee's write into a statement without changing its source order.
/// The caller must replace each moved tee use with a read at that use site.
pub fn hot_build_local_set_from_tee(func : HotFunc, tee_id : NodeId) -> NodeId {
let tee = hot_node_get(func, tee_id)
if tee.op != HotOp::LocalTee || tee.child_count != 1 {
abort("hot_build_local_set_from_tee requires a unary local.tee")
}
let set_id = hot_build_local_set(
func,
tee.imm0,
hot_child_get(func, tee_id, 0),
)
func.nodes[set_id] = { ..func.nodes[set_id], order: tee.order, }
set_id
}
///|
pub fn hot_build_local_tee(
func : HotFunc,
local_id : LocalId,
value : NodeId,
) -> NodeId {
hot_build_node1(
func,
HotOp::LocalTee,
hot_type_intern_value(func, hot_builder_local_value_type(func, local_id)),
value,
imm0=local_id,
)
}
///|
/// Turn a set into a value-producing write without changing its source order.
/// The caller must remove the old set and preserve this order when replacing
/// the read that receives the tee.
pub fn hot_build_local_tee_from_set(func : HotFunc, set_id : NodeId) -> NodeId {
let set = hot_node_get(func, set_id)
if set.op != HotOp::LocalSet || set.child_count != 1 {
abort("hot_build_local_tee_from_set requires a unary local.set")
}
let tee_id = hot_build_local_tee(
func,
set.imm0,
hot_child_get(func, set_id, 0),
)
func.nodes[tee_id] = { ..func.nodes[tee_id], order: set.order, }
tee_id
}
///|
pub fn hot_build_global_get(
func : HotFunc,
global_idx : Int32,
result_type : ValType,
) -> NodeId {
hot_build_node(
func,
HotOp::GlobalGet,
hot_type_intern_value(func, result_type),
imm0=global_idx,
)
}
///|
pub fn hot_build_global_set(
func : HotFunc,
global_idx : Int32,
value : NodeId,
) -> NodeId {
hot_build_node1(
func,
HotOp::GlobalSet,
hot_type_void(func),
value,
imm0=global_idx,
)
}
///|
pub fn hot_build_br(
func : HotFunc,
target : LabelId,
values~ : Array[NodeId] = [],
) -> NodeId {
hot_build_node(
func,
HotOp::Br,
hot_type_void(func),
imm0=target,
children=values,
)
}
///|
pub fn hot_build_br_if(
func : HotFunc,
target : LabelId,
condition : NodeId,
values~ : Array[NodeId] = [],
) -> NodeId {
let children = values.copy()
children.push(condition)
hot_build_node(func, HotOp::BrIf, hot_type_void(func), imm0=target, children~)
}
///|
pub fn hot_build_br_table(
func : HotFunc,
targets : Array[LabelId],
default_target : LabelId,
selector : NodeId,
values~ : Array[NodeId] = [],
) -> NodeId {
let table_id = hot_alloc_branch_table(
func,
HotBranchTable::new(targets, default_target),
)
let children = values.copy()
children.push(selector)
hot_build_node(
func,
HotOp::BrTable,
hot_type_void(func),
imm0=table_id,
children~,
)
}
///|
pub fn hot_build_call(
func : HotFunc,
func_idx : Int32,
result_types : Array[ValType],
args : Array[NodeId],
) -> NodeId {
hot_build_node(
func,
HotOp::Call,
hot_builder_results_type(func, result_types),
imm0=func_idx,
children=args,
)
}
///|
pub fn hot_build_call_indirect(
func : HotFunc,
type_idx : TypeIdx,
table_idx : TableIdx,
result_types : Array[ValType],
args : Array[NodeId],
callee : NodeId,
) -> NodeId {
let sig_id = hot_alloc_call_sig(
func,
HotCallSig::new(type_idx, table_idx=Some(table_idx)),
)
let children = args.copy()
children.push(callee)
hot_build_node(
func,
HotOp::CallIndirect,
hot_builder_results_type(func, result_types),
imm0=sig_id,
children~,
)
}
///|
pub fn hot_build_call_ref(
func : HotFunc,
type_idx : TypeIdx,
result_types : Array[ValType],
args : Array[NodeId],
callee_ref : NodeId,
) -> NodeId {
let sig_id = hot_alloc_call_sig(func, HotCallSig::new(type_idx))
let children = args.copy()
children.push(callee_ref)
hot_build_node(
func,
HotOp::CallRef,
hot_builder_results_type(func, result_types),
imm0=sig_id,
children~,
)
}
///|
pub fn hot_build_return_call(
func : HotFunc,
func_idx : Int32,
args : Array[NodeId],
) -> NodeId {
hot_build_node(
func,
HotOp::ReturnCall,
hot_type_void(func),
imm0=func_idx,
children=args,
)
}
///|
pub fn hot_build_return_call_indirect(
func : HotFunc,
type_idx : TypeIdx,
table_idx : TableIdx,
args : Array[NodeId],
callee : NodeId,
) -> NodeId {
let sig_id = hot_alloc_call_sig(
func,
HotCallSig::new(type_idx, table_idx=Some(table_idx)),
)
let children = args.copy()
children.push(callee)
hot_build_node(
func,
HotOp::ReturnCallIndirect,
hot_type_void(func),
imm0=sig_id,
children~,
)
}
///|
pub fn hot_build_return_call_ref(
func : HotFunc,
type_idx : TypeIdx,
args : Array[NodeId],
callee_ref : NodeId,
) -> NodeId {
let sig_id = hot_alloc_call_sig(func, HotCallSig::new(type_idx))
let children = args.copy()
children.push(callee_ref)
hot_build_node(
func,
HotOp::ReturnCallRef,
hot_type_void(func),
imm0=sig_id,
children~,
)
}
///|
pub fn hot_build_load(
func : HotFunc,
result_type : ValType,
memarg : MemArg,
address : NodeId,
) -> NodeId {
let memarg_id = hot_alloc_memarg(func, HotMemArg::new(memarg))
hot_build_node(
func,
HotOp::Load,
hot_type_intern_value(func, result_type),
imm0=memarg_id,
children=[address],
)
}
///|
pub fn hot_build_store(
func : HotFunc,
memarg : MemArg,
address : NodeId,
value : NodeId,
) -> NodeId {
let memarg_id = hot_alloc_memarg(func, HotMemArg::new(memarg))
hot_build_node(func, HotOp::Store, hot_type_void(func), imm0=memarg_id, children=[
address, value,
])
}
///|
pub fn hot_build_table_get(
func : HotFunc,
table_idx : Int32,
result_type : ValType,
index : NodeId,
) -> NodeId {
hot_build_node(
func,
HotOp::TableGet,
hot_type_intern_value(func, result_type),
imm0=table_idx,
children=[index],
)
}
///|
pub fn hot_build_table_set(
func : HotFunc,
table_idx : Int32,
index : NodeId,
value : NodeId,
) -> NodeId {
hot_build_node(func, HotOp::TableSet, hot_type_void(func), imm0=table_idx, children=[
index, value,
])
}
///|
pub fn hot_build_simd(
func : HotFunc,
result_types : Array[ValType],
instr : Instruction,
children : Array[NodeId],
) -> NodeId {
let payload_id = hot_alloc_instr_payload(func, HotInstrPayload::new(instr))
hot_build_node(
func,
HotOp::Simd,
hot_builder_results_type(func, result_types),
imm0=payload_id,
children~,
)
}
///|
pub fn hot_build_heap(
func : HotFunc,
result_types : Array[ValType],
instr : Instruction,
children : Array[NodeId],
) -> NodeId {
let payload_id = hot_alloc_instr_payload(func, HotInstrPayload::new(instr))
hot_build_node(
func,
HotOp::Heap,
hot_builder_results_type(func, result_types),
imm0=payload_id,
children~,
)
}
///|
pub fn hot_build_atomic(
func : HotFunc,
result_types : Array[ValType],
instr : Instruction,
children : Array[NodeId],
) -> NodeId {
let payload_id = hot_alloc_instr_payload(func, HotInstrPayload::new(instr))
hot_build_node(
func,
HotOp::Atomic,
hot_builder_results_type(func, result_types),
imm1=payload_id,
children~,
)
}
///|
pub fn hot_build_unary(
func : HotFunc,
result_type : ValType,
operand : NodeId,
) -> NodeId {
hot_build_node1(
func,
HotOp::Unary,
hot_type_intern_value(func, result_type),
operand,
)
}
///|
pub fn hot_build_binary(
func : HotFunc,
result_type : ValType,
lhs : NodeId,
rhs : NodeId,
) -> NodeId {
hot_build_node2(
func,
HotOp::Binary,
hot_type_intern_value(func, result_type),
lhs,
rhs,
)
}
///|
pub fn hot_build_compare(func : HotFunc, lhs : NodeId, rhs : NodeId) -> NodeId {
hot_build_node2(
func,
HotOp::Compare,
hot_type_intern_value(func, @lib.ValType::i32()),
lhs,
rhs,
)
}
///|
pub fn hot_build_convert(
func : HotFunc,
result_type : ValType,
operand : NodeId,
) -> NodeId {
hot_build_node1(
func,
HotOp::Convert,
hot_type_intern_value(func, result_type),
operand,
)
}
///|
pub fn hot_build_tuple_make(
func : HotFunc,
result_types : Array[ValType],
values : Array[NodeId],
) -> NodeId {
hot_build_node(
func,
HotOp::TupleMake,
hot_builder_results_type(func, result_types),
children=values,
)
}
///|
pub fn hot_build_tuple_extract(
func : HotFunc,
result_type : ValType,
tuple : NodeId,
index : Int32,
) -> NodeId {
hot_build_node1(
func,
HotOp::TupleExtract,
hot_type_intern_value(func, result_type),
tuple,
imm0=index,
)
}
///|
pub fn hot_build_throw(
func : HotFunc,
tag_idx : TagIdx,
args : Array[NodeId],
) -> NodeId {
hot_build_node(
func,
HotOp::Throw,
hot_type_void(func),
imm0=hot_builder_tag_idx_raw(tag_idx),
children=args,
)
}
///|
/// Represent one legacy catch payload lane at catch-region entry. Ordered
/// payload lanes must appear as the catch region's leading roots.
pub fn hot_build_catch_payload(
func : HotFunc,
tag_idx : TagIdx,
val_type : ValType,
) -> NodeId {
hot_build_node(
func,
HotOp::Catch,
hot_type_intern_value(func, val_type),
imm0=hot_builder_tag_idx_raw(tag_idx),
)
}
///|
pub fn hot_build_throw_ref(func : HotFunc, exnref : NodeId) -> NodeId {
hot_build_node1(func, HotOp::ThrowRef, hot_type_void(func), exnref)
}
///|
pub fn hot_build_delegate(func : HotFunc, target : LabelId) -> NodeId {
hot_build_node(func, HotOp::Delegate, hot_type_void(func), imm0=target)
}
///|
pub fn hot_build_block(
func : HotFunc,
block_type : BlockType,
body : Array[NodeId],
) -> NodeId {
let ty = hot_type_intern_block_result(func, block_type)
let id = hot_build_node(func, HotOp::Block, ty, children=body)
let label_id = hot_label_alloc(
func,
HotLabelKind::BlockLabel,
ty,
id,
hot_type_result_arity(func, ty),
)
hot_node_set(func, id, { ..hot_node_get(func, id), imm0: label_id, })
id
}
///|
pub fn hot_build_resolved_block(
func : HotFunc,
block_type : BlockType,
params : Array[ValType],
results : Array[ValType],
body : Array[NodeId],
) -> NodeId {
let ty = hot_type_intern_resolved_block_result(
func, block_type, params, results,
)
let id = hot_build_node(func, HotOp::Block, ty, children=body)
let label_id = hot_label_alloc(
func,
HotLabelKind::BlockLabel,
ty,
id,
results.length(),
)
hot_node_set(func, id, { ..hot_node_get(func, id), imm0: label_id, })
id
}
///|
pub fn hot_build_loop(
func : HotFunc,
block_type : BlockType,
body : Array[NodeId],
) -> NodeId {
let ty = hot_type_intern_block_result(func, block_type)
let id = hot_build_node(func, HotOp::Loop, ty, children=body)
let label_id = hot_label_alloc(
func,
HotLabelKind::LoopLabel,
ty,
id,
hot_type_result_arity(func, ty),
)
hot_node_set(func, id, { ..hot_node_get(func, id), imm0: label_id, })
id
}
///|
pub fn hot_build_if(
func : HotFunc,
block_type : BlockType,
condition : NodeId,
then_body : Array[NodeId],
else_body~ : Array[NodeId]? = None,
) -> NodeId {
let ty = hot_type_intern_block_result(func, block_type)
let result_arity = hot_type_result_arity(func, ty)
let then_region = hot_build_region(func, then_body, result_arity)
let else_region = match else_body {
Some(body) => hot_build_region(func, body, result_arity)
None => HOT_NONE_NODE
}
let id = hot_build_node3(
func,
HotOp::If,
ty,
condition,
then_region,
else_region,
)
let label_id = hot_label_alloc(
func,
HotLabelKind::IfLabel,
ty,
id,
result_arity,
)
hot_node_set(func, id, { ..hot_node_get(func, id), imm0: label_id, })
id
}
///|
pub fn hot_build_try(
func : HotFunc,
block_type : BlockType,
body : Array[NodeId],
catch_body~ : Array[NodeId]? = None,
) -> NodeId {
let ty = hot_type_intern_block_result(func, block_type)
let result_arity = hot_type_result_arity(func, ty)
let body_region = hot_build_region(func, body, result_arity)
let catch_region = match catch_body {
Some(items) => hot_build_region(func, items, result_arity)
None => HOT_NONE_NODE
}
let id = hot_build_node2(func, HotOp::Try, ty, body_region, catch_region)
let label_id = hot_label_alloc(
func,
HotLabelKind::TryLabel,
ty,
id,
result_arity,
)
hot_node_set(func, id, { ..hot_node_get(func, id), imm0: label_id, })
id
}
///|
pub fn hot_build_try_table(
func : HotFunc,
block_type : BlockType,
catches : Array[HotCatchArm],
body : Array[NodeId],
catch_list_body~ : Array[NodeId] = [],
) -> NodeId {
let ty = hot_type_intern_block_result(func, block_type)
let result_arity = hot_type_result_arity(func, ty)
let body_region = hot_build_region(func, body, result_arity)
let catch_list_region = hot_build_region(func, catch_list_body, result_arity)
let catch_info_id = hot_alloc_catch_info(func, HotCatchInfo::new(catches))
let id = hot_build_node2(
func,
HotOp::TryTable,
ty,
body_region,
catch_list_region,
imm1=catch_info_id,
)
let label_id = hot_label_alloc(
func,
HotLabelKind::TryTableLabel,
ty,
id,
result_arity,
)
hot_node_set(func, id, { ..hot_node_get(func, id), imm0: label_id, })
id
}