///|
pub using @jit_ffi {type MemoryDescriptor}
/// Linear-memory and indirect-table support for managed native JIT contexts.
///|
pub fn memory_descriptor_length(mem_desc_ptr : MemoryDescriptor) -> Int64 {
c_mem_desc_get_len(mem_desc_ptr)
}
///|
pub fn memory_descriptor_grow(
mem_desc_ptr : MemoryDescriptor,
delta_pages : Int32,
max_pages : Int32,
) -> Int32 {
c_mem_desc_grow(mem_desc_ptr, delta_pages.to_int64(), max_pages).to_int()
}
///|
/// Return 0 on success, -1 for an invalid descriptor, memory range or destination
/// buffer length. Rejected operations leave the destination unchanged.
#borrow(out)
pub fn memory_descriptor_read(
mem_desc_ptr : MemoryDescriptor,
offset : Int64,
out : FixedArray[Byte],
size : Int32,
) -> Int32 {
if size < 0 || size > out.length() {
return -1
}
c_mem_desc_read(mem_desc_ptr, offset, out, size)
}
///|
/// Return 0 on success, -1 for an invalid descriptor, memory range or source
/// buffer length. Rejected operations leave the destination unchanged.
#borrow(data)
pub fn memory_descriptor_write(
mem_desc_ptr : MemoryDescriptor,
offset : Int64,
data : FixedArray[Byte],
size : Int32,
) -> Int32 {
if size < 0 || size > data.length() {
return -1
}
c_mem_desc_write(mem_desc_ptr, offset, data, size)
}
///|
/// Return 0 on success, -1 for an invalid descriptor, memory range or source
/// buffer length. Rejected operations leave the destination unchanged.
/// Return 0 on success, -1 for an invalid descriptor, memory range or source
/// buffer length. Rejected operations leave the destination unchanged.
#borrow(data)
pub fn memory_descriptor_write_bytes(
mem_desc_ptr : MemoryDescriptor,
offset : Int64,
data : Bytes,
size : Int32,
) -> Int32 {
if size < 0 || size > data.length() {
return -1
}
c_mem_desc_write_bytes(mem_desc_ptr, offset, data, size)
}
///|
pub fn memory_descriptor_move(
mem_desc_ptr : MemoryDescriptor,
destination : Int64,
source : Int64,
size : Int32,
) -> Int32 {
c_mem_desc_memmove(mem_desc_ptr, destination, source, size)
}
///|
pub fn memory_descriptor_fill(
mem_desc_ptr : MemoryDescriptor,
destination : Int64,
value : Int32,
size : Int32,
) -> Int32 {
c_mem_desc_memset(mem_desc_ptr, destination, value, size)
}
///|
fn context_func_table_ptr(context : JITContext) -> Int64 {
c_jit_ctx_get_func_table_managed(context)
}
///|
fn context_func_count(context : JITContext) -> Int32 {
c_jit_ctx_get_func_count_managed(context)
}
///|
fn context_table_ptr(
context : JITContext,
table_idx : Int32,
) -> NativeTableHandle {
c_jit_ctx_get_table_ptr_managed(context, table_idx)
}
///|
fn context_table_size(context : JITContext, table_idx : Int32) -> Int32 {
c_jit_ctx_get_table_size_managed(context, table_idx)
}
///|
pub fn NativeJITContext::set_memory(
self : NativeJITContext,
mem0_ptr : MemoryDescriptor,
) -> Unit {
c_jit_ctx_set_memory_managed(self.handle, mem0_ptr)
}
///|
pub fn NativeJITContext::alloc_guarded_memory(
self : NativeJITContext,
initial_pages : Int32,
max_pages : Int32?,
) -> MemoryDescriptor {
c_jit_ctx_alloc_guarded_memory_managed(
self.handle,
initial_pages.to_int64(),
max_pages.unwrap_or(-1).to_int64(),
)
}
///|
pub fn NativeJITContext::func_ptr(
self : NativeJITContext,
func_idx : Int32,
) -> Int64 {
let table_ptr = context_func_table_ptr(self.handle)
let count = context_func_count(self.handle)
if table_ptr != 0L && func_idx >= 0 && func_idx < count {
native_value_slot_read(table_ptr, func_idx)
} else {
0L
}
}
///|
pub fn NativeJITContext::set_table_pointers(
self : NativeJITContext,
jit_tables : Array[JITTable?],
) -> Unit raise NativeSetupError {
let replacement = jit_tables.copy()
let table_ptrs = FixedArray::make(
jit_tables.length(),
NativeTableHandle::null(),
)
let table_sizes = FixedArray::make(jit_tables.length(), 0)
let table_max_sizes = FixedArray::make(jit_tables.length(), -1L)
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(-1L)
}
None => ()
}
}
let status = c_jit_ctx_set_table_pointers_managed(
self.handle,
table_ptrs,
table_sizes,
table_max_sizes,
jit_tables.length(),
)
match status {
1 => ()
0 => raise TableBindingAllocationFailed
_ => raise InvalidBinding("table count exceeds layout or context is closed")
}
self.jit_tables.clear()
for table in replacement {
self.jit_tables.push(table)
}
}
///|
/// Refresh one borrowed table from the context it was registered with.
/// The context supplies both the table identity and its live C-side layout.
pub fn NativeJITContext::refresh_table_layout(
self : NativeJITContext,
table_idx : Int32,
) -> Bool {
if table_idx < 0 || table_idx >= self.jit_tables.length() {
return false
}
guard self.jit_tables[table_idx] is Some(jit_table) else { return false }
let table_ptr = context_table_ptr(self.handle, table_idx)
let size = context_table_size(self.handle, table_idx)
if jit_table.table_ptr.is_null() || table_ptr.is_null() || size < 0 {
return false
}
jit_table.refresh_layout(table_ptr, size)
true
}
///|
pub fn NativeJITContext::set_memory_pointers(
self : NativeJITContext,
memories : Array[MemoryInfo],
) -> Unit raise NativeSetupError {
let ptrs = FixedArray::make(memories.length(), MemoryDescriptor::null())
for i, mem in memories {
ptrs[i] = mem.ptr
}
match
c_jit_ctx_set_memory_pointers_managed(self.handle, ptrs, memories.length()) {
1 => ()
0 => raise MemoryBindingAllocationFailed
_ =>
raise InvalidBinding("memory count exceeds layout or context is closed")
}
}
///|
/// Borrow a raw byte address. Use memory_descriptor for accesses that must
/// retain the allocation independently of this context.
pub fn NativeJITContext::memory_ptr(
self : NativeJITContext,
memidx : Int32,
) -> Int64 {
c_jit_ctx_get_memory_ptr_managed(self.handle, memidx)
}
///|
pub fn NativeJITContext::memory_size(
self : NativeJITContext,
memidx : Int32,
) -> Int64 {
c_jit_ctx_get_memory_size_managed(self.handle, memidx)
}
///|
/// Memory information for JIT execution.
pub struct MemoryInfo {
ptr : MemoryDescriptor // Pointer to `wasmoon_memory_t` (memory descriptor)
size : Int64 // Initial size in bytes (informational)
max_pages : Int32? // Max pages (None = unlimited)
}
///|
/// Create a new MemoryInfo.
pub fn MemoryInfo::MemoryInfo(
ptr : MemoryDescriptor,
size : Int64,
max_pages : Int32?,
) -> MemoryInfo {
{ ptr, size, max_pages, }
}
///|
/// Allocate unmanaged storage for native ABI fixtures (returns 0 on failure).
/// The caller owns its bounds and must free it exactly once. Use descriptors
/// for linear-memory access through checked operations.
pub fn alloc_memory(size : Int64) -> Int64 {
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 : Int64?,
is_memory64~ : Bool = false,
page_size_log2~ : Int32 = 16,
is_shared~ : Bool = false,
) -> MemoryDescriptor {
c_jit_alloc_memory_desc(
size_bytes,
match max_pages {
Some(max) => max
None => -1L
},
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 : Int32,
max_pages : Int32?,
is_shared~ : Bool = false,
) -> MemoryDescriptor {
c_jit_alloc_guarded_memory_desc(
initial_pages.to_int64(),
max_pages.unwrap_or(-1).to_int64(),
if is_shared {
1
} else {
0
},
)
}
///|
/// Release an unmanaged allocation returned by alloc_memory. The caller must
/// ensure no aliases remain and must not pass a descriptor backing address.
pub fn free_memory(mem_ptr : Int64) -> Unit {
c_jit_free_memory(mem_ptr)
}
///|
pub using @jit_ffi {type NativeTableHandle}
///|
#borrow(table)
extern "c" fn table_entry_word(
table : NativeTableHandle,
index : Int32,
word : Int32,
) -> Int64 = "wasmoon_native_table_entry_word"
///|
/// Shared indirect table that can be used by multiple JIT modules.
struct JITTable {
mut table_ptr : NativeTableHandle // Pointer to the shared C-side table
max : Int64?
} derive(Debug)
///|
/// Logical close capability for one shared JIT table view.
struct JITTableOwner {
table : JITTable
} derive(Debug)
///|
/// Create a new owned shared JIT table.
/// Note: size 0 is valid (empty table that can grow later).
pub fn JITTableOwner::try_alloc(size : Int32, max : Int64?) -> JITTableOwner? {
if size < 0 {
return None
}
let table_ptr = c_jit_alloc_shared_indirect_table(size)
if table_ptr.is_null() {
return None
}
Some({ table: { table_ptr, max, }, })
}
///|
/// Borrow the table without transferring its destruction capability.
pub fn JITTableOwner::table(self : JITTableOwner) -> JITTable {
self.table
}
///|
/// Close all aliases of this view and release its reference. Context bindings
/// and independently retained handles remain live. Repeated close is harmless.
pub fn JITTableOwner::close(self : JITTableOwner) -> Unit {
if !self.table.table_ptr.is_null() {
self.table.table_ptr = NativeTableHandle::null()
}
}
///|
/// Set an entry in the shared table.
pub fn JITTable::set(
self : JITTable,
table_idx : Int32,
func_ptr : Int64,
type_hash : Int32,
) -> Unit {
if table_idx < 0 || table_idx >= self.get_size() {
return
}
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 : Int32) -> Int64 {
if table_idx < 0 || table_idx >= self.get_size() {
return 0L
}
table_entry_word(self.table_ptr, table_idx, 0)
}
///|
/// Read raw entry type index from the shared table.
pub fn JITTable::get_entry_type(self : JITTable, table_idx : Int32) -> Int32 {
if table_idx < 0 || table_idx >= self.get_size() {
return -1
}
table_entry_word(self.table_ptr, table_idx, 1).to_int()
}
///|
/// Get the table size.
pub fn JITTable::get_size(self : JITTable) -> Int32 {
self.table_ptr.size()
}
///|
/// Retain the stable managed table descriptor.
pub fn JITTable::raw_ptr(self : JITTable) -> NativeTableHandle {
self.table_ptr
}
///|
/// Refresh table pointer and size from its registered JIT context view.
fn JITTable::refresh_layout(
self : JITTable,
table_ptr : NativeTableHandle,
size : Int32,
) -> Unit {
if self.table_ptr.is_null() || table_ptr.is_null() || size < 0 {
return
}
self.table_ptr = table_ptr
}
///|
/// Get the table max size.
pub fn JITTable::get_max(self : JITTable) -> Int64? {
self.max
}
///|
/// Copy checked byte ranges between live memory descriptors.
pub fn memory_descriptor_copy(
destination_memory : MemoryDescriptor,
destination : Int64,
source_memory : MemoryDescriptor,
source : Int64,
length : Int64,
) -> Int32 {
c_memory_descriptor_copy(
destination_memory, destination, source_memory, source, length,
)
}
///|
#borrow(destination_memory, source_memory)
extern "c" fn c_memory_descriptor_copy(
destination_memory : MemoryDescriptor,
destination : Int64,
source_memory : MemoryDescriptor,
source : Int64,
length : Int64,
) -> Int32 = "wasmoon_mem_desc_copy64"
///|
/// Fill a checked byte range of a live memory descriptor.
pub fn memory_descriptor_fill64(
memory : MemoryDescriptor,
destination : Int64,
value : Int32,
length : Int64,
) -> Int32 {
c_memory_descriptor_fill64(memory, destination, value, length)
}
///|
#borrow(memory)
extern "c" fn c_memory_descriptor_fill64(
memory : MemoryDescriptor,
destination : Int64,
value : Int32,
length : Int64,
) -> Int32 = "wasmoon_mem_desc_memset64"
///|
pub fn memory_descriptor_grow64(
mem_desc_ptr : MemoryDescriptor,
delta_pages : Int64,
) -> Int64 {
c_mem_desc_grow(mem_desc_ptr, delta_pages, -1)
}
///|
/// Retain a bound memory independently of this context. Invalid indices return
/// an empty handle. Passing this handle to descriptor operations keeps the
/// allocation alive throughout the operation.
pub fn NativeJITContext::memory_descriptor(
self : NativeJITContext,
memidx : Int32,
) -> MemoryDescriptor {
c_jit_ctx_get_memory_descriptor_managed(self.handle, memidx)
}