I haven’t worked with dynamic memory allocation in wasm much so there might be details I’m missing and some better ways to do this, but for starters you could expose some functions for allocating/freeing sequences of bytes by using std.heap.wasm_allocator
:
const std = @import("std");
// We can't pass/return slices to/from `export`/`extern` functions,
// so we need to pass/return pointers and lengths separately.
export fn allocBytes(len: usize) ?[*]u8 {
return if (std.heap.wasm_allocator.alloc(u8, len)) |slice| slice.ptr else |_| null;
}
export fn freeBytes(ptr: ?[*]const u8, len: usize) void {
if (ptr) |valid_ptr| std.heap.wasm_allocator.free(valid_ptr[0..len]);
}
export fn processValues(ptr: [*]const i32, len: usize) void {
for (ptr[0..len]) |byte| print(byte);
}
extern fn print(value: i32) void;
Then in JS you could use the exposed functions like this:
const instantiateResult = await WebAssembly.instantiateStreaming(fetch("..."), {
env: {
print: value => console.log(value),
},
})
const { memory, allocBytes, freeBytes, processValues } = instantiateResult.instance.exports
const values_len = 10
const values_ptr = allocBytes(values_len * Int32Array.BYTES_PER_ELEMENT)
// Address 0 isn't protected in wasm so don't forget to check for null!
if (values_ptr === 0) throw new Error("OOM")
try {
const values = new Int32Array(memory.buffer, values_ptr, values_len)
for (let i = 0; i < values_len; i++) values[i] = 2 * i - 6
processValues(values_ptr, values_len)
} finally {
freeBytes(values_ptr, values_len * Int32Array.BYTES_PER_ELEMENT)
}