///|
fn require_mapped_value(
function : @semantic_machv.Function,
values : Array[@semantic_machv.Value?],
value : @semantic_machv.Value,
) -> @semantic_machv.Value raise JitPipelineError {
guard function.value_index(value) is Some(index) else {
raise RuntimeAbiContractViolation(
message="\{function.name()}: runtime ABI elaboration used an unmapped value",
)
}
match values[index] {
Some(mapped) => mapped
None =>
raise RuntimeAbiContractViolation(
message="\{function.name()}: runtime ABI elaboration used an unmapped value",
)
}
}
///|
fn mapped_values(
function : @semantic_machv.Function,
values : Array[@semantic_machv.Value?],
source : Array[@semantic_machv.Value],
) -> Array[@semantic_machv.Value] raise JitPipelineError {
source.map(value => require_mapped_value(function, values, value))
}
///|
fn mapped_block(
function : @semantic_machv.Function,
blocks : Array[@semantic_machv.Block],
block : @semantic_machv.Block,
) -> @semantic_machv.Block raise JitPipelineError {
guard function.block_index(block) is Some(index) else {
raise RuntimeAbiContractViolation(
message="\{function.name()}: runtime ABI elaboration used an unmapped block",
)
}
blocks[index]
}
///|
fn allocation_argument_root_count(
function : @semantic_machv.Function,
call : @semantic_machv.SemanticCall,
operands : Array[@semantic_machv.Value],
integer_constants : Array[Int64?],
) -> Int raise JitPipelineError {
match call.signature.params {
[Ptr64, I32, I32, I64] => 1
[Ptr64, I32, Ptr64, I32] => {
guard operands.length() == 4 else {
raise RuntimeAbiContractViolation(
message="\{function.name()}: allocation root count must be a compile-time i32 constant",
)
}
guard function.value_index(operands[3]) is Some(index) else {
raise RuntimeAbiContractViolation(
message="\{function.name()}: allocation root count must be a compile-time i32 constant",
)
}
guard integer_constants[index] is Some(count) &&
count >= 0L &&
count <= @types.I32_MAX.to_int64() else {
raise RuntimeAbiContractViolation(
message="\{function.name()}: allocation root count must be a compile-time i32 constant",
)
}
count.to_int()
}
_ =>
raise RuntimeAbiContractViolation(
message="\{function.name()}: allocation helper has an unsupported ABI",
)
}
}
///|
fn call_context(
function : @semantic_machv.Function,
call : @semantic_machv.SemanticCall,
operands : Array[@semantic_machv.Value],
) -> @semantic_machv.Value raise JitPipelineError {
let index = match call.protocol {
Internal => if call.callee is Indirect { 1 } else { 0 }
Platform => 0
}
guard operands.length() > index &&
function.value_type(operands[index]) == Some(Ptr64) else {
raise RuntimeAbiContractViolation(
message="\{function.name()}: precise roots require a ptr64 execution environment",
)
}
operands[index]
}
///|
fn emit_root_scope_push(
builder : @semantic_machv.FunctionBuilder,
context : @semantic_machv.Value,
roots : Array[@semantic_machv.Value],
object : @semantic_machv.StackObject,
symbol : @semantic_machv.ExternalSymbol,
) -> Unit {
let address = builder.emit(StackAddress(object), [], [Ptr64])[0]
for index, root in roots {
builder.emit(
Store(
@semantic_machv.StoreSpec::new(
W64,
GcRef64,
(index * 8).to_uint64(),
Little,
None,
),
),
[address, root],
[],
)
|> ignore
}
let count = builder.emit(I32Const(roots.length().reinterpret_as_uint()), [], [
I32,
])[0]
builder.emit(
Call(
@semantic_machv.SemanticCall::new(
External(symbol),
@semantic_machv.Signature::new([Ptr64, Ptr64, I32], []),
Platform,
@semantic_machv.CallBehavior::new(ReadWrite, true, false, false, false),
),
),
[context, address, count],
[],
)
|> ignore
}
///|
fn emit_root_scope_pop(
builder : @semantic_machv.FunctionBuilder,
context : @semantic_machv.Value,
symbol : @semantic_machv.ExternalSymbol,
) -> Unit {
builder.emit(
Call(
@semantic_machv.SemanticCall::new(
External(symbol),
@semantic_machv.Signature::new([Ptr64], []),
Platform,
@semantic_machv.CallBehavior::new(ReadWrite, true, false, false, false),
),
),
[context],
[],
)
|> ignore
}
///|
fn copy_terminator(
function : @semantic_machv.Function,
builder : @semantic_machv.FunctionBuilder,
blocks : Array[@semantic_machv.Block],
values : Array[@semantic_machv.Value?],
record : @semantic_machv.TerminatorRecord,
) -> Unit raise JitPipelineError {
match record.kind {
Jump(edge) =>
builder.jump(
mapped_block(function, blocks, edge.target),
mapped_values(function, values, edge.arguments),
)
Branch(condition, true_edge, false_edge) =>
builder.branch(
require_mapped_value(function, values, condition),
mapped_block(function, blocks, true_edge.target),
mapped_values(function, values, true_edge.arguments),
mapped_block(function, blocks, false_edge.target),
mapped_values(function, values, false_edge.arguments),
)
Switch(index, cases, default_edge) => {
let mapped_cases = cases.map(case => {
(
case.bits,
mapped_block(function, blocks, case.edge.target),
mapped_values(function, values, case.edge.arguments),
)
})
builder.switch(
require_mapped_value(function, values, index),
mapped_cases,
mapped_block(function, blocks, default_edge.target),
mapped_values(function, values, default_edge.arguments),
)
}
Return(returned) =>
builder.return_(mapped_values(function, values, returned))
TailCall(call, operands) =>
builder.tail_call_with_metadata(
call,
mapped_values(function, values, operands),
@semantic_machv.TerminatorMetadata::new(
record.metadata.source,
mapped_values(function, values, record.metadata.live_gc_roots),
),
)
NoReturnCall(call, operands) =>
builder.noreturn_call_with_metadata(
call,
mapped_values(function, values, operands),
@semantic_machv.TerminatorMetadata::new(
record.metadata.source,
mapped_values(function, values, record.metadata.live_gc_roots),
),
)
Trap(reason) => builder.trap(reason)
}
if record.metadata.source is Some(source) &&
!(record.kind is TailCall(_, _) || record.kind is NoReturnCall(_, _)) {
builder.set_terminator_source(source)
}
}
///|
fn elaborate_wasmoon_runtime_abi(
function : @semantic_machv.Function,
allocation_symbols : Array[@semantic_machv.ExternalSymbol],
root_scope_symbols : (
@semantic_machv.ExternalSymbol,
@semantic_machv.ExternalSymbol,
),
) -> @semantic_machv.Function raise JitPipelineError {
let mut needs_elaboration = false
let mut root_scope_capacity = 0
for block in function.blocks() {
for instruction in function.block_instructions(block) {
guard function.instruction_operation(instruction) is Some(Call(call)) else {
continue
}
let metadata = function.instruction_metadata(instruction).unwrap()
let hidden_safepoint = match call.callee {
External(symbol) => allocation_symbols.contains(symbol)
_ => false
}
if !metadata.live_gc_roots.is_empty() || hidden_safepoint {
needs_elaboration = true
}
if metadata.live_gc_roots.length() > root_scope_capacity {
root_scope_capacity = metadata.live_gc_roots.length()
}
}
}
if !needs_elaboration {
return function
}
function.verify() catch {
error => raise RuntimeAbiInputInvalid(cause=error)
}
let signature = function.signature()
let builder = @semantic_machv.FunctionBuilder::new(
function.name(),
function.protocol(),
signature.params,
signature.results,
)
let values : Array[@semantic_machv.Value?] = Array::make(
function.value_count(),
None,
)
for index, parameter in function.parameters() {
values[function.value_index(parameter).unwrap()] = Some(
builder.parameters()[index],
)
}
let blocks : Array[@semantic_machv.Block] = [builder.entry_block()]
for index, block in function.blocks() {
if index > 0 {
let types = function
.block_parameters(block)
.map(value => function.value_type(value).unwrap())
blocks.push(builder.create_block(types))
}
for parameter_index, parameter in function.block_parameters(block) {
values[function.value_index(parameter).unwrap()] = Some(
builder.block_parameters(blocks[index])[parameter_index],
)
}
}
let stack_objects : Array[@semantic_machv.StackObject] = []
for object in function.stack_objects() {
stack_objects.push(
builder.create_stack_object(
function.stack_object_size(object).unwrap(),
function.stack_object_alignment(object).unwrap(),
),
)
}
let root_scope_object = if root_scope_capacity > 0 {
Some(builder.create_stack_object(root_scope_capacity * 8, 16))
} else {
None
}
let integer_constants : Array[Int64?] = Array::make(
function.value_count(),
None,
)
let mut next_stack_map_id = 0
for block in function.blocks() {
for instruction in function.block_instructions(block) {
if function.instruction_metadata(instruction).unwrap().stack_map
is Some(_) {
next_stack_map_id += 1
}
}
}
let (push_symbol, pop_symbol) = root_scope_symbols
for block in function.blocks_in_cfg_order() {
let block_index = function.block_index(block).unwrap()
builder.switch_to_block(blocks[block_index])
for instruction in function.block_instructions(block) {
let operation = function.instruction_operation(instruction).unwrap()
let old_operands = function.instruction_operands(instruction)
let operands = mapped_values(function, values, old_operands)
let old_results = function.instruction_results(instruction)
let result_types = old_results.map(value => {
function.value_type(value).unwrap()
})
let old_metadata = function.instruction_metadata(instruction).unwrap()
let roots = mapped_values(function, values, old_metadata.live_gc_roots)
let mut copied_operation = match operation {
StackAddress(object) => {
let index = function.stack_objects().search(object).unwrap()
@semantic_machv.StackAddress(stack_objects[index])
}
_ => operation
}
let copied_operands = operands
let mut stack_map = old_metadata.stack_map
let mut root_scope_context : @semantic_machv.Value? = None
if operation is Call(call) {
if !roots.is_empty() {
if call.behavior.returns_twice {
raise RuntimeAbiContractViolation(
message="\{function.name()}: returns-twice calls cannot use a caller root scope",
)
}
let context = call_context(function, call, old_operands)
let mapped_context = require_mapped_value(function, values, context)
emit_root_scope_push(
builder,
mapped_context,
roots,
root_scope_object.unwrap(),
push_symbol,
)
root_scope_context = Some(mapped_context)
}
let hidden_safepoint = match call.callee {
External(symbol) => allocation_symbols.contains(symbol)
_ => false
}
if hidden_safepoint {
if call.protocol != Platform || !call.behavior.gc_safepoint {
raise RuntimeAbiContractViolation(
message="\{function.name()}: allocation helper must be a platform GC safepoint",
)
}
if stack_map is Some(_) {
raise RuntimeAbiContractViolation(
message="\{function.name()}: allocation helper already has stack-map metadata",
)
}
let argument_root_count = allocation_argument_root_count(
function, call, old_operands, integer_constants,
)
let id_value = builder.emit(
I32Const(next_stack_map_id.reinterpret_as_uint()),
[],
[I32],
)[0]
copied_operands.push(id_value)
let parameters = call.signature.params.copy()
parameters.push(I32)
copied_operation = Call(
@semantic_machv.SemanticCall::new(
call.callee,
@semantic_machv.Signature::new(parameters, call.signature.results),
call.protocol,
call.behavior,
),
)
stack_map = Some(
@semantic_machv.StackMapMetadata::new(
next_stack_map_id,
argument_root_count~,
),
)
next_stack_map_id += 1
}
}
let new_results = builder.emit_with_metadata(
copied_operation,
copied_operands,
result_types,
@semantic_machv.InstructionMetadata::new(
old_metadata.source,
roots,
stack_map?,
),
)
for index, old_result in old_results {
let value_index = function.value_index(old_result).unwrap()
values[value_index] = Some(new_results[index])
integer_constants[value_index] = match operation {
I32Const(bits) =>
Some(bits.reinterpret_as_int().to_int64() & 0xFFFFFFFFL)
I64Const(bits) => Some(bits.reinterpret_as_int64())
Copy if old_operands is [source] =>
integer_constants[function.value_index(source).unwrap()]
_ => None
}
}
if root_scope_context is Some(context) {
emit_root_scope_pop(builder, context, pop_symbol)
}
}
copy_terminator(
function,
builder,
blocks,
values,
function.block_terminator(block).unwrap(),
)
}
builder.finish() catch {
error => raise RuntimeAbiOutputInvalid(cause=error)
}
}