///|
pub using @callable {type NativeCallableRegistry}
///|
#borrow(context, output)
extern "c" fn write_bound_entry_code(
context : JITContext,
target : Int64,
output : FixedArray[Byte],
) -> Int32 = "wasmoon_jit_write_bound_entry_code"
///|
/// A native entry bound to the instance environment that owns its code.
/// The leaf trampoline preserves all guest arguments and tail-call stack usage.
pub struct NativeCallable {
context : NativeJITContext
entry : InstalledTrampoline
}
///|
pub fn NativeJITContext::bind_callable(
self : NativeJITContext,
target : Int64,
) -> NativeCallable raise JitInstallError {
let bytes = FixedArray::make(40, b'\x00')
let size = write_bound_entry_code(self.handle, target, bytes)
if size <= 0 {
raise TrampolineAllocationFailed(code_size=0)
}
let code : Array[Int32] = []
for i in 0.. Int64 {
self.context |> ignore
self.entry.entry_address()
}
///|
pub fn NativeCallable::release(self : NativeCallable) -> Unit {
self.entry.release()
}
///|
/// One immutable installation of leaf context bindings. Entry addresses are
/// valid only while this owner is retained and unreleased. The caller must
/// finish every invocation before release, as with a standalone NativeCallable.
pub struct NativeCallableBatch {
priv context : NativeJITContext
priv entry : InstalledTrampoline?
priv offsets : FixedArray[Int32]
}
///|
/// Preserve the standalone binding ABI while publishing all entries together.
/// No executable page is shared with another batch or modified after publication.
/// Target code is borrowed and must outlive these bindings, as with bind_callable.
pub fn NativeJITContext::bind_callables(
self : NativeJITContext,
targets : Array[Int64],
) -> NativeCallableBatch raise JitInstallError {
let offsets = FixedArray::make(targets.length(), -1)
let bytes = FixedArray::make(40, b'\x00')
let code : Array[Int32] = []
for index, target in targets {
if target == 0L {
continue
}
let size = write_bound_entry_code(self.handle, target, bytes)
if size <= 0 {
raise TrampolineAllocationFailed(code_size=0)
}
let aligned = (code.length().to_int64() + 15L) / 16L * 16L
if aligned + size.to_int64() > 2147483647L {
raise TrampolineAllocationFailed(code_size=0)
}
while code.length().to_int64() < aligned {
code.push(0)
}
offsets[index] = code.length()
for i in 0.. Int64 {
self.context |> ignore
guard index >= 0 &&
index < self.offsets.length() &&
self.offsets[index] >= 0 &&
self.entry is Some(entry) else {
return 0L
}
let base = entry.entry_address()
if base == 0L {
0L
} else {
base + self.offsets[index].to_int64()
}
}
///|
/// Release the complete installation once all of its callers are finished.
pub fn NativeCallableBatch::release(self : NativeCallableBatch) -> Unit {
if self.entry is Some(entry) {
entry.release()
}
}
///|
/// Bind shared Store identities with this instance's local type and tag maps.
pub fn NativeJITContext::bind_callable_registry(
self : NativeJITContext,
registry : NativeCallableRegistry,
local_types : Array[Int32],
tags : Array[Int32],
functions~ : Array[Int32] = [],
) -> Unit {
let tag_addresses = FixedArray::makei(tags.length(), i => tags[i])
self.handle.bind_callable_registry_snapshot(
registry,
local_types[:],
tag_addresses,
FixedArray::makei(functions.length(), i => functions[i]),
)
}
///|
/// Install an independent callable view for a standalone context.
pub fn NativeJITContext::set_callable_types(
self : NativeJITContext,
local_types : Array[Int32],
parents : Array[Int32],
entries : Array[(Int64, Int32)],
tags : Array[Int32],
) -> Unit {
let registry = NativeCallableRegistry::new()
registry.replace(parents, entries)
self.bind_callable_registry(registry, local_types, tags)
}