/// Native JIT runtime primitives shared by Wasmoon integration code.
///
/// This module intentionally owns only Wasmoon-independent FFI wrappers and
/// managed native handles. Entry trampoline planning now lives in
/// `wasmoon_jit`; the production `JITContext` wrapper remains in
/// `Milky2018/wasmoon/jit` until its call path switches to that reusable
/// trampoline planner.
///|
pub using @jit_ffi {c_jit_write_i64, c_jit_write_u32, c_jit_read_i64}
///|
pub fn host_target_arch() -> @cwasm.TargetArch {
match @jit_ffi.c_jit_host_target_arch() {
1 => X86_64
2 => AArch64
_ => Unknown
}
}
///|
pub fn target_arch_to_isa(target : @cwasm.TargetArch) -> @isa.ISA {
match target {
X86_64 => AMD64
AArch64 => AArch64
Unknown => AArch64
}
}
///|
/// Low-level trap detail captured by the JIT signal handlers (best-effort).
pub struct TrapDetails {
signal : Int
pc : Int64
lr : Int64
fp : Int64
frame_lr : Int64
fault_addr : Int64
brk_imm : Int
func_idx : Int
x0 : Int64
x1 : Int64
x2 : Int64
x3 : Int64
x6 : Int64
x7 : Int64
x8 : Int64
x9 : Int64
x10 : Int64
x11 : Int64
x15 : Int64
}
///|
pub fn TrapDetails::TrapDetails() -> TrapDetails {
{
signal: 0,
pc: 0L,
lr: 0L,
fp: 0L,
frame_lr: 0L,
fault_addr: 0L,
brk_imm: 0,
func_idx: -1,
x0: 0L,
x1: 0L,
x2: 0L,
x3: 0L,
x6: 0L,
x7: 0L,
x8: 0L,
x9: 0L,
x10: 0L,
x11: 0L,
x15: 0L,
}
}
///|
/// Get the last captured trap details (best-effort).
/// These fields are reset at the start of each JIT call.
pub fn get_last_trap_details() -> TrapDetails {
{
signal: @jit_ffi.c_jit_get_trap_signal(),
pc: @jit_ffi.c_jit_get_trap_pc(),
lr: @jit_ffi.c_jit_get_trap_lr(),
fp: @jit_ffi.c_jit_get_trap_fp(),
frame_lr: @jit_ffi.c_jit_get_trap_frame_lr(),
fault_addr: @jit_ffi.c_jit_get_trap_fault_addr(),
brk_imm: @jit_ffi.c_jit_get_trap_brk_imm(),
func_idx: @jit_ffi.c_jit_get_trap_func_idx(),
x0: @jit_ffi.c_jit_get_trap_xreg(0),
x1: @jit_ffi.c_jit_get_trap_xreg(1),
x2: @jit_ffi.c_jit_get_trap_xreg(2),
x3: @jit_ffi.c_jit_get_trap_xreg(3),
x6: @jit_ffi.c_jit_get_trap_xreg(6),
x7: @jit_ffi.c_jit_get_trap_xreg(7),
x8: @jit_ffi.c_jit_get_trap_xreg(8),
x9: @jit_ffi.c_jit_get_trap_xreg(9),
x10: @jit_ffi.c_jit_get_trap_xreg(10),
x11: @jit_ffi.c_jit_get_trap_xreg(11),
x15: @jit_ffi.c_jit_get_trap_xreg(15),
}
}
///|
pub fn format_signal_name(sig : Int) -> String {
match sig {
5 => "SIGTRAP(5)"
10 => "SIGBUS(10)"
11 => "SIGSEGV(11)"
7 => "SIGBUS(7)"
_ => "SIG(\{sig})"
}
}
///|
pub fn jit_trap_message(code : Int) -> String {
match code {
1 => "out of bounds memory access"
2 => "call stack exhausted"
3 => "unreachable"
4 => "indirect call type mismatch"
5 => "invalid conversion to integer"
6 => "integer divide by zero"
7 => "integer overflow"
8 => "jit backend trap"
9 => "out of memory"
10 => "gc precise roots unavailable"
11 => "invocation cancelled"
_ => "unknown trap"
}
}
///|
pub fn is_cancelled_trap_code(code : Int) -> Bool {
code == 11
}
///|
/// Map Wasm trap reasons to the payload consumed by Wasmoon's native trap
/// handler. MachV only carries the integer payload; this module owns the
/// Wasmoon-specific reason-to-payload policy.
pub fn wasm_trap_payload(reason : String) -> Int {
if reason == "unreachable" {
0
} else if reason.contains("out of bounds") {
1
} else if reason.contains("type mismatch") {
2
} else if reason.contains("invalid conversion") {
3
} else if reason.contains("divide by zero") {
4
} else if reason.contains("overflow") {
5
} else {
6
}
}
///|
pub fn take_pending_trap_message() -> String? {
let code = @jit_ffi.c_jit_get_trap_code()
if code == 0 {
None
} else {
@jit_ffi.c_jit_clear_trap()
Some(jit_trap_message(code))
}
}
///|
pub fn is_wasi_exit_trap_code(code : Int) -> Bool {
code == 100
}
///|
pub fn take_wasi_exit_code(ctx_ptr : Int64) -> Int {
let raw_code = @jit_ffi.c_jit_get_wasi_exit_code(ctx_ptr)
@jit_ffi.c_jit_clear_wasi_exit(ctx_ptr)
if raw_code < 0 {
0
} else {
raw_code
}
}
///|
pub fn make_cstring(s : String) -> FixedArray[Byte] {
let len = s.length()
let mut size = 0
let mut i = 0
while i < len {
let c = s.code_unit_at(i).to_int()
if c < 0x80 {
size += 1
i += 1
} else if c < 0x800 {
size += 2
i += 1
} else if c >= 0xD800 && c <= 0xDBFF && i + 1 < len {
let c2 = s.code_unit_at(i + 1).to_int()
if c2 >= 0xDC00 && c2 <= 0xDFFF {
size += 4
i += 2
} else {
size += 3
i += 1
}
} else {
size += 3
i += 1
}
}
let result = FixedArray::make(size + 1, b'\x00')
let mut pos = 0
i = 0
while i < len {
let c = s.code_unit_at(i).to_int()
if c < 0x80 {
result[pos] = c.to_byte()
pos += 1
i += 1
} else if c < 0x800 {
result[pos] = (0xC0 | (c >> 6)).to_byte()
result[pos + 1] = (0x80 | (c & 0x3F)).to_byte()
pos += 2
i += 1
} else if c >= 0xD800 && c <= 0xDBFF && i + 1 < len {
let c2 = s.code_unit_at(i + 1).to_int()
if c2 >= 0xDC00 && c2 <= 0xDFFF {
let codepoint = 0x10000 + ((c - 0xD800) << 10) + (c2 - 0xDC00)
result[pos] = (0xF0 | (codepoint >> 18)).to_byte()
result[pos + 1] = (0x80 | ((codepoint >> 12) & 0x3F)).to_byte()
result[pos + 2] = (0x80 | ((codepoint >> 6) & 0x3F)).to_byte()
result[pos + 3] = (0x80 | (codepoint & 0x3F)).to_byte()
pos += 4
i += 2
} else {
result[pos] = b'\xEF'
result[pos + 1] = b'\xBF'
result[pos + 2] = b'\xBD'
pos += 3
i += 1
}
} else {
result[pos] = (0xE0 | (c >> 12)).to_byte()
result[pos + 1] = (0x80 | ((c >> 6) & 0x3F)).to_byte()
result[pos + 2] = (0x80 | (c & 0x3F)).to_byte()
pos += 3
i += 1
}
}
result[size] = b'\x00'
result
}
///|
pub fn set_wasi_stdout_capture(ctx_ptr : Int64, enabled : Bool) -> Unit {
@jit_ffi.c_jit_set_wasi_stdout_capture(ctx_ptr, if enabled { 1 } else { 0 })
}
///|
pub fn set_wasi_stderr_capture(ctx_ptr : Int64, enabled : Bool) -> Unit {
@jit_ffi.c_jit_set_wasi_stderr_capture(ctx_ptr, if enabled { 1 } else { 0 })
}
///|
pub fn set_wasi_stdin_buffer(ctx_ptr : Int64, data : Bytes) -> Unit {
@jit_ffi.c_jit_set_wasi_stdin_buffer(ctx_ptr, data, data.length())
}
///|
pub fn set_wasi_stdin_callback(ctx_ptr : Int64, callback : () -> Bytes) -> Unit {
let call_closure : FuncRef[(() -> Bytes) -> Bytes] = fn(f : () -> Bytes) {
f()
}
@jit_ffi.c_jit_set_wasi_stdin_callback(ctx_ptr, call_closure, callback)
}
///|
pub fn clear_wasi_stdin_callback(ctx_ptr : Int64) -> Unit {
@jit_ffi.c_jit_clear_wasi_stdin_callback(ctx_ptr)
}
///|
pub fn clear_wasi_stdin_buffer(ctx_ptr : Int64) -> Unit {
@jit_ffi.c_jit_clear_wasi_stdin_buffer(ctx_ptr)
}
///|
pub fn take_wasi_stdout(ctx_ptr : Int64) -> Bytes {
@jit_ffi.c_jit_take_wasi_stdout(ctx_ptr)
}
///|
pub fn take_wasi_stderr(ctx_ptr : Int64) -> Bytes {
@jit_ffi.c_jit_take_wasi_stderr(ctx_ptr)
}
///|
pub fn init_wasi_context(
ctx_ptr : Int64,
args : Array[String],
env : Array[String],
preopens : Array[(String, String)],
quiet? : Bool = false,
) -> Unit {
if quiet {
@jit_ffi.c_jit_init_wasi_fds_quiet(ctx_ptr, preopens.length())
} else {
@jit_ffi.c_jit_init_wasi_fds(ctx_ptr, preopens.length())
}
set_wasi_stdout_capture(ctx_ptr, false)
set_wasi_stderr_capture(ctx_ptr, false)
clear_wasi_stdin_callback(ctx_ptr)
clear_wasi_stdin_buffer(ctx_ptr)
@jit_ffi.c_jit_set_wasi_args(ctx_ptr, args.length())
for i, arg in args {
@jit_ffi.c_jit_set_wasi_arg(ctx_ptr, i, make_cstring(arg))
}
@jit_ffi.c_jit_set_wasi_envs(ctx_ptr, env.length())
for i, e in env {
@jit_ffi.c_jit_set_wasi_env(ctx_ptr, i, make_cstring(e))
}
for i, entry in preopens {
let (host, guest) = entry
@jit_ffi.c_jit_add_preopen(
ctx_ptr,
i,
make_cstring(host),
make_cstring(guest),
)
}
}
///|
pub fn context_func_table_ptr(ctx_ptr : Int64) -> Int64 {
@jit_ffi.c_jit_ctx_get_func_table(ctx_ptr)
}
///|
pub fn context_func_count(ctx_ptr : Int64) -> Int {
@jit_ffi.c_jit_ctx_get_func_count(ctx_ptr)
}
///|
pub fn context_table_ptr(ctx_ptr : Int64, table_idx : Int) -> Int64 {
@jit_ffi.c_jit_ctx_get_table_ptr(ctx_ptr, table_idx)
}
///|
pub fn context_table_size(ctx_ptr : Int64, table_idx : Int) -> Int {
@jit_ffi.c_jit_ctx_get_table_size(ctx_ptr, table_idx)
}
///|
pub fn throw_exception_tag(ctx_ptr : Int64, tag_addr : Int) -> Unit {
@jit_ffi.c_jit_exception_throw_tag(ctx_ptr, tag_addr)
}
///|
pub fn throw_exception_values(
ctx_ptr : Int64,
tag_addr : Int,
values : Array[Int64],
) -> Unit {
let payload = FixedArray::makei(values.length(), fn(i) { values[i] })
@jit_ffi.c_jit_exception_throw_values(
ctx_ptr,
tag_addr,
payload,
values.length(),
)
}
///|
pub fn setup_segments(
ctx_ptr : Int64,
datas : Array[@types.Data],
elem_segments : Array[Array[Int64]],
data_dropped? : Array[Bool] = [],
elem_dropped? : Array[Bool] = [],
) -> Unit {
@jit_ffi.c_jit_ctx_init_data_segments(ctx_ptr, datas.length())
for i, data in datas {
let bytes = data.init
let size = bytes.length()
let dropped = if i < data_dropped.length() && data_dropped[i] {
1
} else {
0
}
let buf = FixedArray::make(size.max(1), b'\x00')
for j in 0.. Unit {
@jit_ffi.c_jit_ctx_clear_segments(ctx_ptr)
}
///|
struct NativeJITContext(@jit_ffi.JITContext)
///|
pub fn NativeJITContext::try_alloc(total_funcs : Int) -> NativeJITContext? {
let ctx = @jit_ffi.c_jit_alloc_context_managed(total_funcs)
if @jit_ffi.c_jit_context_ptr(ctx) != 0L {
Some(NativeJITContext(ctx))
} else {
None
}
}
///|
pub fn NativeJITContext::ptr(self : NativeJITContext) -> Int64 {
@jit_ffi.c_jit_context_ptr(self.0)
}
///|
pub fn NativeJITContext::set_func(
self : NativeJITContext,
idx : Int,
func_ptr : Int64,
) -> Unit {
@jit_ffi.c_jit_ctx_set_func(self.ptr(), idx, func_ptr)
}
///|
pub fn NativeJITContext::alloc_indirect_table(
self : NativeJITContext,
count : Int,
) -> Bool {
@jit_ffi.c_jit_ctx_alloc_indirect_table(self.ptr(), count) != 0
}
///|
pub fn NativeJITContext::set_indirect(
self : NativeJITContext,
table_idx : Int,
func_idx : Int,
type_idx : Int,
) -> Unit {
@jit_ffi.c_jit_ctx_set_indirect(self.ptr(), table_idx, func_idx, type_idx)
}
///|
pub fn NativeJITContext::set_memory(
self : NativeJITContext,
mem0_ptr : Int64,
) -> Unit {
@jit_ffi.c_jit_ctx_set_memory(self.ptr(), mem0_ptr)
}
///|
pub fn NativeJITContext::set_globals(
self : NativeJITContext,
globals_ptr : Int64,
) -> Unit {
@jit_ffi.c_jit_ctx_set_globals(self.ptr(), globals_ptr)
}
///|
pub fn NativeJITContext::alloc_guarded_memory(
self : NativeJITContext,
initial_pages : Int,
max_pages : Int?,
) -> Int64 {
@jit_ffi.c_jit_ctx_alloc_guarded_memory(
self.ptr(),
initial_pages.to_int64(),
max_pages.unwrap_or(-1).to_int64(),
)
}
///|
pub fn NativeJITContext::func_table_ptr(self : NativeJITContext) -> Int64 {
context_func_table_ptr(self.ptr())
}
///|
pub fn NativeJITContext::set_table_pointers(
self : NativeJITContext,
jit_tables : Array[JITTable?],
) -> Unit {
if jit_tables.is_empty() {
return
}
let table_ptrs = FixedArray::make(jit_tables.length(), 0L)
let table_sizes = FixedArray::make(jit_tables.length(), 0)
let table_max_sizes = FixedArray::make(jit_tables.length(), -1)
for i, jit_table_opt in jit_tables {
match jit_table_opt {
Some(jit_table) => {
table_ptrs[i] = jit_table.raw_ptr()
table_sizes[i] = jit_table.get_size()
table_max_sizes[i] = jit_table.get_max().unwrap_or(-1)
}
None => ()
}
}
@jit_ffi.c_jit_ctx_set_table_pointers(
self.ptr(),
table_ptrs,
table_sizes,
table_max_sizes,
jit_tables.length(),
)
}
///|
pub fn NativeJITContext::set_memory_pointers(
self : NativeJITContext,
memories : Array[MemoryInfo],
) -> Unit {
if memories.is_empty() {
return
}
let ptrs = FixedArray::make(memories.length(), 0L)
for i, mem in memories {
ptrs[i] = mem.ptr
}
@jit_ffi.c_jit_ctx_set_memory_pointers(self.ptr(), ptrs, memories.length())
}
///|
pub fn NativeJITContext::memory_ptr(
self : NativeJITContext,
memidx : Int,
) -> Int64 {
@jit_ffi.c_jit_ctx_get_memory_ptr(self.ptr(), memidx)
}
///|
pub fn NativeJITContext::memory_size(
self : NativeJITContext,
memidx : Int,
) -> Int64 {
@jit_ffi.c_jit_ctx_get_memory_size(self.ptr(), memidx)
}
///|
pub fn NativeJITContext::alloc_wasm_stack(
self : NativeJITContext,
stack_size : Int64,
) -> Bool {
@jit_ffi.c_jit_alloc_wasm_stack(self.ptr(), stack_size) == 0
}
///|
pub fn NativeJITContext::has_wasm_stack(self : NativeJITContext) -> Bool {
@jit_ffi.c_jit_get_wasm_stack_top(self.ptr()) != 0L
}
///|
pub fn NativeJITContext::set_hostcall_callback(
self : NativeJITContext,
callback : () -> Int,
) -> Unit {
let call_closure : FuncRef[(() -> Int) -> Int] = fn(f : () -> Int) { f() }
@jit_ffi.c_jit_set_hostcall_callback(self.ptr(), call_closure, callback)
}
///|
pub fn NativeJITContext::clear_hostcall_callback(
self : NativeJITContext,
) -> Unit {
@jit_ffi.c_jit_clear_hostcall_callback(self.ptr())
}
///|
pub fn NativeJITContext::set_cancellation_callback(
self : NativeJITContext,
callback : () -> Bool,
) -> Unit {
let call_closure : FuncRef[(() -> Bool) -> Int] = fn(f : () -> Bool) {
if f() {
1
} else {
0
}
}
@jit_ffi.c_jit_set_cancellation_callback(self.ptr(), call_closure, callback)
}
///|
pub fn NativeJITContext::clear_cancellation_callback(
self : NativeJITContext,
) -> Unit {
@jit_ffi.c_jit_clear_cancellation_callback(self.ptr())
}
///|
pub fn NativeJITContext::call_trampoline(
self : NativeJITContext,
trampoline_ptr : Int64,
func_ptr : Int64,
values_vec : FixedArray[Int64],
values_len : Int,
use_stack_switch : Bool,
) -> Int {
if use_stack_switch {
@jit_ffi.c_jit_call_with_stack_switch_managed(
self.0,
trampoline_ptr,
func_ptr,
values_vec,
values_len,
)
} else {
@jit_ffi.c_jit_call_trampoline_managed(
self.0,
trampoline_ptr,
func_ptr,
values_vec,
values_len,
)
}
}
///|
/// Get the runtime store function address for the current JIT -> hostcall.
///
/// This value is set by `wasmoon_jit_hostcall` right before invoking the
/// MoonBit hostcall callback.
pub fn get_hostcall_func_addr() -> Int {
@jit_ffi.c_jit_get_hostcall_func_idx()
}
///|
/// Get the pointer to the hostcall scratch `values_vec` buffer.
pub fn get_hostcall_values_ptr() -> Int64 {
@jit_ffi.c_jit_get_hostcall_values_ptr()
}
///|
/// Get the number of argument *slots* in `values_vec`.
///
/// Note: slots are 8 bytes each; V128 occupies 2 slots.
pub fn get_hostcall_num_arg_slots() -> Int {
@jit_ffi.c_jit_get_hostcall_num_args()
}
///|
/// Get the number of result *slots* in `values_vec`.
///
/// Note: slots are 8 bytes each; V128 occupies 2 slots.
pub fn get_hostcall_num_result_slots() -> Int {
@jit_ffi.c_jit_get_hostcall_num_results()
}
///|
/// Memory information for JIT execution.
pub struct MemoryInfo {
ptr : Int64 // Pointer to `wasmoon_memory_t` (memory descriptor)
size : Int64 // Initial size in bytes (informational)
max_pages : Int? // Max pages (None = unlimited)
}
///|
/// Create a new MemoryInfo.
pub fn MemoryInfo::MemoryInfo(
ptr : Int64,
size : Int64,
max_pages : Int?,
) -> MemoryInfo {
{ ptr, size, max_pages }
}
///|
/// Allocate linear memory for WASM (returns 0 on failure).
pub fn alloc_memory(size : Int64) -> Int64 {
@jit_ffi.c_jit_alloc_memory(size)
}
///|
/// Allocate a `wasmoon_memory_t` descriptor with owned backing memory.
pub fn alloc_memory_desc(
size_bytes : Int64,
max_pages : Int?,
is_memory64? : Bool = false,
page_size_log2? : Int = 16,
is_shared? : Bool = false,
) -> Int64 {
@jit_ffi.c_jit_alloc_memory_desc(
size_bytes,
max_pages.unwrap_or(-1),
if is_memory64 {
1
} else {
0
},
page_size_log2,
if is_shared {
1
} else {
0
},
)
}
///|
/// Allocate a guarded (reserved) memory descriptor for memory32 memory0.
/// This is required for bounds-check elimination; out-of-bounds accesses trap via guard pages.
pub fn alloc_guarded_memory_desc(
initial_pages : Int,
max_pages : Int?,
) -> Int64 {
@jit_ffi.c_jit_alloc_guarded_memory_desc(
initial_pages.to_int64(),
max_pages.unwrap_or(-1).to_int64(),
)
}
///|
pub fn free_memory(mem_ptr : Int64) -> Unit {
@jit_ffi.c_jit_free_memory(mem_ptr)
}
///|
pub fn free_memory_desc(mem_desc_ptr : Int64) -> Unit {
@jit_ffi.c_jit_free_memory_desc(mem_desc_ptr)
}
///|
/// Initialize memory with data at offset.
pub fn memory_init(mem_ptr : Int64, offset : Int64, data : Bytes) -> Bool {
let size = data.length()
if size == 0 {
return true
}
@jit_ffi.c_jit_memory_init_bytes(mem_ptr, offset, data, size) == 0
}
///|
/// Read `size` bytes from linear memory at offset.
pub fn memory_read(mem_ptr : Int64, offset : Int64, size : Int) -> Bytes {
if mem_ptr == 0L || size <= 0 {
return b""
}
let out = FixedArray::make(size, b'\x00')
let rc = @jit_ffi.c_jit_memory_read(mem_ptr, offset, out, size)
if rc != 0 {
return b""
}
let arr : Array[Byte] = Array::make(size, b'\x00')
for i in 0.. JITTable? {
if size < 0 {
return None
}
let alloc_size = if size == 0 { 1 } else { size }
let table_ptr = @jit_ffi.c_jit_alloc_shared_indirect_table(alloc_size)
if table_ptr == 0L {
return None
}
Some({ table_ptr, size, max })
}
///|
/// Set an entry in the shared table.
pub fn JITTable::set(
self : JITTable,
table_idx : Int,
func_ptr : Int64,
type_hash : Int,
) -> Unit {
if table_idx < 0 || table_idx >= self.size {
return
}
@jit_ffi.c_jit_shared_table_set(
self.table_ptr,
table_idx,
func_ptr,
type_hash,
)
}
///|
/// Read raw entry value bits from the shared table.
pub fn JITTable::get_entry_value(self : JITTable, table_idx : Int) -> Int64 {
if table_idx < 0 || table_idx >= self.size {
return 0L
}
c_jit_read_i64(self.table_ptr + (table_idx * 16).to_int64())
}
///|
/// Read raw entry type index from the shared table.
pub fn JITTable::get_entry_type(self : JITTable, table_idx : Int) -> Int {
if table_idx < 0 || table_idx >= self.size {
return -1
}
c_jit_read_i64(self.table_ptr + (table_idx * 16 + 8).to_int64()).to_int()
}
///|
/// Get the table size.
pub fn JITTable::get_size(self : JITTable) -> Int {
self.size
}
///|
/// Get the raw shared table pointer.
pub fn JITTable::raw_ptr(self : JITTable) -> Int64 {
self.table_ptr
}
///|
/// Refresh table pointer and size from the active JIT context view.
pub fn JITTable::refresh_layout(
self : JITTable,
table_ptr : Int64,
size : Int,
) -> Unit {
if table_ptr == 0L || size < 0 {
return
}
self.table_ptr = table_ptr
self.size = size
}
///|
/// Get the table max size.
pub fn JITTable::get_max(self : JITTable) -> Int? {
self.max
}
///|
pub fn get_fd_write_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_write_ptr()
}
///|
pub fn get_proc_exit_ptr() -> Int64 {
@jit_ffi.c_jit_get_proc_exit_ptr()
}
///|
pub fn get_fd_read_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_read_ptr()
}
///|
pub fn get_args_sizes_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_args_sizes_get_ptr()
}
///|
pub fn get_args_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_args_get_ptr()
}
///|
pub fn get_environ_sizes_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_environ_sizes_get_ptr()
}
///|
pub fn get_environ_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_environ_get_ptr()
}
///|
pub fn get_clock_time_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_clock_time_get_ptr()
}
///|
pub fn get_random_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_random_get_ptr()
}
///|
pub fn get_fd_close_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_close_ptr()
}
///|
pub fn get_fd_fdstat_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_fdstat_get_ptr()
}
///|
pub fn get_fd_prestat_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_prestat_get_ptr()
}
///|
pub fn get_fd_prestat_dir_name_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_prestat_dir_name_ptr()
}
///|
pub fn get_fd_seek_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_seek_ptr()
}
///|
pub fn get_fd_tell_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_tell_ptr()
}
///|
pub fn get_fd_sync_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_sync_ptr()
}
///|
pub fn get_fd_datasync_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_datasync_ptr()
}
///|
pub fn get_fd_filestat_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_filestat_get_ptr()
}
///|
pub fn get_fd_filestat_set_size_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_filestat_set_size_ptr()
}
///|
pub fn get_path_open_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_open_ptr()
}
///|
pub fn get_path_create_directory_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_create_directory_ptr()
}
///|
pub fn get_path_unlink_file_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_unlink_file_ptr()
}
///|
pub fn get_path_remove_directory_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_remove_directory_ptr()
}
///|
pub fn get_path_rename_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_rename_ptr()
}
///|
pub fn get_clock_res_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_clock_res_get_ptr()
}
///|
pub fn get_sched_yield_ptr() -> Int64 {
@jit_ffi.c_jit_get_sched_yield_ptr()
}
///|
pub fn get_proc_raise_ptr() -> Int64 {
@jit_ffi.c_jit_get_proc_raise_ptr()
}
///|
pub fn get_poll_oneoff_ptr() -> Int64 {
@jit_ffi.c_jit_get_poll_oneoff_ptr()
}
///|
pub fn get_fd_advise_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_advise_ptr()
}
///|
pub fn get_fd_allocate_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_allocate_ptr()
}
///|
pub fn get_fd_pread_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_pread_ptr()
}
///|
pub fn get_fd_pwrite_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_pwrite_ptr()
}
///|
pub fn get_fd_readdir_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_readdir_ptr()
}
///|
pub fn get_fd_renumber_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_renumber_ptr()
}
///|
pub fn get_fd_fdstat_set_flags_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_fdstat_set_flags_ptr()
}
///|
pub fn get_fd_fdstat_set_rights_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_fdstat_set_rights_ptr()
}
///|
pub fn get_fd_filestat_set_times_ptr() -> Int64 {
@jit_ffi.c_jit_get_fd_filestat_set_times_ptr()
}
///|
pub fn get_path_filestat_get_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_filestat_get_ptr()
}
///|
pub fn get_path_filestat_set_times_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_filestat_set_times_ptr()
}
///|
pub fn get_path_link_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_link_ptr()
}
///|
pub fn get_path_readlink_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_readlink_ptr()
}
///|
pub fn get_path_symlink_ptr() -> Int64 {
@jit_ffi.c_jit_get_path_symlink_ptr()
}
///|
pub fn get_sock_accept_ptr() -> Int64 {
@jit_ffi.c_jit_get_sock_accept_ptr()
}
///|
pub fn get_sock_recv_ptr() -> Int64 {
@jit_ffi.c_jit_get_sock_recv_ptr()
}
///|
pub fn get_sock_send_ptr() -> Int64 {
@jit_ffi.c_jit_get_sock_send_ptr()
}
///|
pub fn get_sock_shutdown_ptr() -> Int64 {
@jit_ffi.c_jit_get_sock_shutdown_ptr()
}
///|
pub fn get_spectest_print_ptr() -> Int64 {
@jit_ffi.c_jit_get_spectest_print_ptr()
}
///|
pub fn get_spectest_print_i32_ptr() -> Int64 {
@jit_ffi.c_jit_get_spectest_print_i32_ptr()
}
///|
pub fn get_spectest_print_i64_ptr() -> Int64 {
@jit_ffi.c_jit_get_spectest_print_i64_ptr()
}
///|
pub fn get_spectest_print_f32_ptr() -> Int64 {
@jit_ffi.c_jit_get_spectest_print_f32_ptr()
}
///|
pub fn get_spectest_print_f64_ptr() -> Int64 {
@jit_ffi.c_jit_get_spectest_print_f64_ptr()
}
///|
pub fn get_spectest_print_i32_f32_ptr() -> Int64 {
@jit_ffi.c_jit_get_spectest_print_i32_f32_ptr()
}
///|
pub fn get_spectest_print_f64_f64_ptr() -> Int64 {
@jit_ffi.c_jit_get_spectest_print_f64_f64_ptr()
}
///|
pub fn get_spectest_print_char_ptr() -> Int64 {
@jit_ffi.c_jit_get_spectest_print_char_ptr()
}
///|
fn push_u32_le(bytes : Array[Int], value : Int) -> Unit {
bytes.push(value & 0xff)
bytes.push((value >> 8) & 0xff)
bytes.push((value >> 16) & 0xff)
bytes.push((value >> 24) & 0xff)
}
///|
fn pack_u64_le(bytes : Array[Int], start : Int) -> Int64 {
let mut value : Int64 = 0L
for i in 0..<8 {
let b = bytes[start + i].to_int64() & 0xffL
value = value | (b << (i * 8))
}
value
}
///|
pub fn build_aarch64_load_imm64_fixed_bytes(
reg : Int,
imm : Int64,
) -> Array[Int] {
let bytes : Array[Int] = []
let v0 = (imm & 0xffffL).to_int()
let v1 = ((imm >> 16) & 0xffffL).to_int()
let v2 = ((imm >> 32) & 0xffffL).to_int()
let v3 = ((imm >> 48) & 0xffffL).to_int()
push_u32_le(bytes, 0xd2800000 | ((v0 & 0xffff) << 5) | (reg & 0x1f))
push_u32_le(
bytes,
0xf2800000 | (1 << 21) | ((v1 & 0xffff) << 5) | (reg & 0x1f),
)
push_u32_le(
bytes,
0xf2800000 | (2 << 21) | ((v2 & 0xffff) << 5) | (reg & 0x1f),
)
push_u32_le(
bytes,
0xf2800000 | (3 << 21) | ((v3 & 0xffff) << 5) | (reg & 0x1f),
)
bytes
}
///|
pub fn build_aarch64_direct_call_stub(target_ptr : Int64) -> Array[Int] {
let bytes = build_aarch64_load_imm64_fixed_bytes(16, target_ptr)
push_u32_le(bytes, 0xd61f0000 | ((16 & 0x1f) << 5))
bytes
}
///|
pub fn build_x64_direct_call_stub(target_ptr : Int64) -> Array[Int] {
let bytes : Array[Int] = []
bytes.push(0x49)
bytes.push(0xbb)
let mut value = target_ptr
for _ in 0..<8 {
bytes.push((value & 0xffL).to_int())
value = value >> 8
}
bytes.push(0x41)
bytes.push(0xff)
bytes.push(0xe3)
bytes
}
///|
pub fn direct_call_idx_memory_fill_mem0() -> Int {
-1001
}
///|
pub fn direct_call_idx_memory_copy_mem0() -> Int {
-1002
}
///|
pub struct DirectCallStubSet {
exec_codes : Array[ExecCode]
targets : Map[Int, Int64]
}
///|
pub suberror DirectCallStubInstallError {
UnsupportedDirectCallStubTarget(pseudo_idx~ : Int, code_size~ : Int)
DirectCallStubAllocationFailed(pseudo_idx~ : Int, code_size~ : Int)
} derive(Debug, Eq)
///|
fn direct_call_stub_code_for_target(
target : @cwasm.TargetArch,
target_ptr : Int64,
) -> Array[Int] {
match target {
AArch64 => build_aarch64_direct_call_stub(target_ptr)
X86_64 => build_x64_direct_call_stub(target_ptr)
Unknown => []
}
}
///|
fn DirectCallStubSet::install_one(
self : DirectCallStubSet,
target : @cwasm.TargetArch,
pseudo_idx : Int,
target_ptr : Int64,
) -> Unit raise DirectCallStubInstallError {
let stub_code = direct_call_stub_code_for_target(target, target_ptr)
if stub_code.length() == 0 {
raise UnsupportedDirectCallStubTarget(
pseudo_idx~,
code_size=stub_code.length(),
)
}
match ExecCode::try_alloc(stub_code) {
Some(ec) => {
self.exec_codes.push(ec)
self.targets.set(pseudo_idx, ec.ptr())
}
None =>
raise DirectCallStubAllocationFailed(
pseudo_idx~,
code_size=stub_code.length(),
)
}
}
///|
pub fn install_direct_call_stubs_for_target(
target : @cwasm.TargetArch,
) -> DirectCallStubSet raise DirectCallStubInstallError {
let stubs : DirectCallStubSet = { exec_codes: [], targets: Map([]) }
stubs.install_one(
target,
direct_call_idx_memory_fill_mem0(),
@jit_ffi.c_jit_get_memory_fill_mem0_ptr(),
)
stubs.install_one(
target,
direct_call_idx_memory_copy_mem0(),
@jit_ffi.c_jit_get_memory_copy_mem0_ptr(),
)
stubs
}
///|
pub fn patch_aarch64_load_imm64_fixed(
exec_ptr : Int64,
offset : Int,
reg : Int,
imm : Int64,
) -> Unit {
let bytes = build_aarch64_load_imm64_fixed_bytes(reg, imm)
if bytes.length() != 16 {
abort("unexpected load_imm64_fixed size")
}
let base = exec_ptr + offset.to_int64()
c_jit_write_i64(base, pack_u64_le(bytes, 0))
c_jit_write_i64(base + 8L, pack_u64_le(bytes, 8))
}
///|
pub fn resolve_func_addr_fixup_target(
func_idx : Int,
func_table_ptr : Int64,
) -> Int64? {
if func_idx >= 0 {
if func_table_ptr == 0L {
return None
}
Some(c_jit_read_i64(func_table_ptr + func_idx.to_int64() * 8L))
} else {
resolve_runtime_func_fixup_idx(func_idx)
}
}
///|
pub fn resolve_call_fixup_target(
func_idx : Int,
func_table_ptr : Int64,
direct_call_targets : Map[Int, Int64],
) -> Int64? {
if func_idx >= 0 {
if func_table_ptr == 0L {
return None
}
Some(c_jit_read_i64(func_table_ptr + func_idx.to_int64() * 8L))
} else {
direct_call_targets.get(func_idx)
}
}
///|
pub fn required_context_function_count(
precompiled : @cwasm.PrecompiledModule,
func_signature_count : Int,
) -> Int {
let mut count = precompiled.imports.length()
for entry in precompiled.functions {
let candidate = entry.func_idx + 1
if candidate > count {
count = candidate
}
}
if func_signature_count > count {
func_signature_count
} else {
count
}
}
///|
pub fn patch_aarch64_func_addr_fixup(
exec_ptr : Int64,
fixup : @cwasm.FuncAddrFixup,
target_ptr : Int64,
) -> Unit {
patch_aarch64_func_addr_fixup_parts(
exec_ptr,
fixup.offset,
fixup.reg,
target_ptr,
)
}
///|
pub fn patch_aarch64_func_addr_fixup_parts(
exec_ptr : Int64,
offset : Int,
reg : Int,
target_ptr : Int64,
) -> Unit {
patch_aarch64_load_imm64_fixed(exec_ptr, offset, reg, target_ptr)
}
///|
pub fn patch_aarch64_func_addr_fixup_raw(
exec_ptr : Int64,
offset : Int,
reg : Int,
func_idx : Int,
func_table_ptr : Int64,
) -> Unit {
let target_ptr = match
resolve_func_addr_fixup_target(func_idx, func_table_ptr) {
Some(ptr) => ptr
None => abort("unknown func addr fixup idx \{func_idx}")
}
patch_aarch64_func_addr_fixup_parts(exec_ptr, offset, reg, target_ptr)
}
///|
pub fn patch_x64_func_addr_fixup_parts(
exec_ptr : Int64,
offset : Int,
_reg : Int,
target_ptr : Int64,
) -> Unit {
// x86_emit_mov_imm64 emits: REX.W, B8+rd, then the 8-byte immediate.
c_jit_write_i64(exec_ptr + offset.to_int64() + 2L, target_ptr)
}
///|
pub fn patch_x64_func_addr_fixup_raw(
exec_ptr : Int64,
offset : Int,
reg : Int,
func_idx : Int,
func_table_ptr : Int64,
) -> Unit {
let target_ptr = match
resolve_func_addr_fixup_target(func_idx, func_table_ptr) {
Some(ptr) => ptr
None => abort("unknown func addr fixup idx \{func_idx}")
}
patch_x64_func_addr_fixup_parts(exec_ptr, offset, reg, target_ptr)
}
///|
pub fn patch_func_addr_fixup_raw_for_target(
target : @cwasm.TargetArch,
exec_ptr : Int64,
offset : Int,
reg : Int,
func_idx : Int,
func_table_ptr : Int64,
) -> Unit {
match target {
AArch64 =>
patch_aarch64_func_addr_fixup_raw(
exec_ptr, offset, reg, func_idx, func_table_ptr,
)
X86_64 =>
patch_x64_func_addr_fixup_raw(
exec_ptr, offset, reg, func_idx, func_table_ptr,
)
Unknown =>
abort("cannot patch function-address fixup for unknown cwasm target")
}
}
///|
pub fn patch_aarch64_func_addr_fixups(
exec_ptr : Int64,
fixups : Array[@cwasm.FuncAddrFixup],
func_table_ptr : Int64,
) -> Unit {
for fixup in fixups {
let target_ptr = match
resolve_func_addr_fixup_target(fixup.func_idx, func_table_ptr) {
Some(ptr) => ptr
None => abort("unknown func addr fixup idx \{fixup.func_idx}")
}
patch_aarch64_func_addr_fixup(exec_ptr, fixup, target_ptr)
}
}
///|
pub fn patch_x64_func_addr_fixups(
exec_ptr : Int64,
fixups : Array[@cwasm.FuncAddrFixup],
func_table_ptr : Int64,
) -> Unit {
for fixup in fixups {
let target_ptr = match
resolve_func_addr_fixup_target(fixup.func_idx, func_table_ptr) {
Some(ptr) => ptr
None => abort("unknown func addr fixup idx \{fixup.func_idx}")
}
patch_x64_func_addr_fixup_parts(
exec_ptr,
fixup.offset,
fixup.reg,
target_ptr,
)
}
}
///|
pub fn patch_func_addr_fixups_for_target(
target : @cwasm.TargetArch,
exec_ptr : Int64,
fixups : Array[@cwasm.FuncAddrFixup],
func_table_ptr : Int64,
) -> Unit {
match target {
AArch64 => patch_aarch64_func_addr_fixups(exec_ptr, fixups, func_table_ptr)
X86_64 => patch_x64_func_addr_fixups(exec_ptr, fixups, func_table_ptr)
Unknown =>
if fixups.length() > 0 {
abort("cannot patch function-address fixups for unknown cwasm target")
}
}
}
///|
pub fn encode_aarch64_bl_imm26(pc : Int64, target_ptr : Int64) -> Int? {
let diff = target_ptr - pc
if diff % 4L != 0L {
return None
}
let imm26 = diff / 4L
if imm26 < -0x2000000L || imm26 > 0x1FFFFFFL {
return None
}
Some((0x94000000L | (imm26 & 0x3FFFFFFL)).to_int())
}
///|
pub fn encode_x64_call_rel32_disp(pc : Int64, target_ptr : Int64) -> Int? {
let disp = target_ptr - (pc + 5L)
if disp < -0x80000000L || disp > 0x7fffffffL {
return None
}
Some((disp & 0xffffffffL).to_int())
}
///|
pub fn patch_aarch64_call_veneer_target(
exec_ptr : Int64,
fixup : @cwasm.CallFixup,
target_ptr : Int64,
) -> Unit {
// Veneer layout (16 bytes):
// +0: ldr x16, #8
// +4: br x16
// +8: target_ptr literal (u64)
c_jit_write_i64(exec_ptr + fixup.veneer_offset.to_int64() + 8L, target_ptr)
}
///|
pub fn patch_aarch64_direct_call(
exec_ptr : Int64,
fixup : @cwasm.CallFixup,
target_ptr : Int64,
) -> Unit {
let pc = exec_ptr + fixup.offset.to_int64()
if encode_aarch64_bl_imm26(pc, target_ptr) is Some(encoded) {
c_jit_write_u32(pc, encoded)
return
}
if fixup.veneer_offset >= 0 {
let veneer_pc = exec_ptr + fixup.veneer_offset.to_int64()
if encode_aarch64_bl_imm26(pc, veneer_pc) is Some(encoded) {
patch_aarch64_call_veneer_target(exec_ptr, fixup, target_ptr)
c_jit_write_u32(pc, encoded)
return
}
}
abort(
"direct call target out of range (func_idx=\{fixup.func_idx}, veneer_offset=\{fixup.veneer_offset})",
)
}
///|
pub fn patch_x64_direct_call(
exec_ptr : Int64,
fixup : @cwasm.CallFixup,
target_ptr : Int64,
) -> Unit {
let pc = exec_ptr + fixup.offset.to_int64()
match encode_x64_call_rel32_disp(pc, target_ptr) {
Some(encoded) => c_jit_write_u32(pc + 1L, encoded)
None =>
abort(
"direct call target out of rel32 range (func_idx=\{fixup.func_idx})",
)
}
}
///|
pub fn patch_aarch64_call_fixups(
exec_ptr : Int64,
fixups : Array[@cwasm.CallFixup],
func_table_ptr : Int64,
direct_call_targets : Map[Int, Int64],
) -> Unit {
for fixup in fixups {
let target_ptr = match
resolve_call_fixup_target(
fixup.func_idx,
func_table_ptr,
direct_call_targets,
) {
Some(ptr) => ptr
None => abort("missing direct-call target for idx \{fixup.func_idx}")
}
patch_aarch64_direct_call(exec_ptr, fixup, target_ptr)
}
}
///|
pub fn patch_x64_call_fixups(
exec_ptr : Int64,
fixups : Array[@cwasm.CallFixup],
func_table_ptr : Int64,
direct_call_targets : Map[Int, Int64],
) -> Unit {
for fixup in fixups {
let target_ptr = match
resolve_call_fixup_target(
fixup.func_idx,
func_table_ptr,
direct_call_targets,
) {
Some(ptr) => ptr
None => abort("missing direct-call target for idx \{fixup.func_idx}")
}
patch_x64_direct_call(exec_ptr, fixup, target_ptr)
}
}
///|
pub fn patch_call_fixups_for_target(
target : @cwasm.TargetArch,
exec_ptr : Int64,
fixups : Array[@cwasm.CallFixup],
func_table_ptr : Int64,
direct_call_targets : Map[Int, Int64],
) -> Unit {
match target {
AArch64 =>
patch_aarch64_call_fixups(
exec_ptr, fixups, func_table_ptr, direct_call_targets,
)
X86_64 =>
patch_x64_call_fixups(
exec_ptr, fixups, func_table_ptr, direct_call_targets,
)
Unknown =>
if fixups.length() > 0 {
abort("cannot patch call fixups for unknown cwasm target")
}
}
}
///|
/// GC-managed executable code block wrapper.
struct ExecCode(@jit_ffi.ExecCode)
///|
/// Allocate and copy code to executable memory (GC-managed).
pub fn ExecCode::try_alloc(code : Array[Int]) -> ExecCode? {
let size = code.length()
if size == 0 {
return None
}
let bytes = FixedArray::make(size, b'\x00')
for i, b in code {
bytes[i] = b.to_byte()
}
let ec = @jit_ffi.c_jit_alloc_exec_managed(bytes, size)
if @jit_ffi.c_jit_exec_code_ptr(ec) != 0L {
Some(ExecCode(ec))
} else {
None
}
}
///|
/// Get the function pointer from this executable code.
pub fn ExecCode::ptr(self : ExecCode) -> Int64 {
@jit_ffi.c_jit_exec_code_ptr(self.0)
}