I want to use zig to write async wasm32-freestanding
code.
I have already figured out how to pass a callback to an extern function, and receive the callback.
I’m having trouble figuring out how to make it use async/await/suspend/resume.
I think are gonna be multiple steps here but what I want is to call an exported function (from js, the wasm host) which will start an async process - it needs to create a frame, store it somewhere, call an extern function to register a callback, then return. later, calling that callback resumes the frame.
I’ve studied the example in learn zig Introduction | zig.guide but the examples seem to rely on the blocking of std.time.sleep()
I’ve tried a couple of things, and get weird errors.
What I don’t understand is where the @frame() is stored in memory. It seems a little like it’s sitting on the stack, so when I pass around pointers and try to call back it doesn’t exist anymore.
Okay I think I found it:
const frame = allocator.create(@Frame(get)) catch unreachable;
frame.* = async get(array[0..array.len]);
I couldn’t find any documentation that described this but I saw this issue
https://github.com/ziglang/zig/issues/1260#issuecomment-426430429
I don’t understand everything in that issue but I tried it and it worked!
my example code:
const allocator = @import("std").heap.page_allocator;
//onReady takes a callback and a frame pointer,
//and then at some point passes the frame pointer to the callback.
extern fn onReady(
cb: fn ( frame: *@Frame(get) ) callconv(.C) void,
frame: *@Frame(get)
) void;
//copies memory from host into pointer, up to len
extern fn read(ptr: [*]const u8, len:usize) void;
fn get (slice: []u8) usize{
//put a suspend here, otherwise async get()
//will run to the end then return frame at return value.
//we don't want to do that because memory to read isn't ready yet.
suspend {}
read(slice.ptr, slice.len);
return slice.len;
}
fn cb (frame: *@Frame(get)) callconv(.C) void {
defer allocator.destroy(frame);
resume frame.*;
}
export fn init () void {
var array:[11]u8 = [_]u8{0,0,0,0,0,0,0,0,0,0,0};
const frame = allocator.create(@Frame(get)) catch unreachable;
frame.* = async get(array[0..array.len]);
onReady(cb, frame);
}
and then the javascript side:
const fs = require('fs');
const source = fs.readFileSync("./await2.wasm");
const typedArray = new Uint8Array(source);
;(async function () {
var callback, frame
var buffer = Buffer.alloc(0)
var result = await WebAssembly.instantiate(typedArray, {
env: {
print: function (ptr, len) {
var memory = Buffer.from(result.instance.exports.memory.buffer)
console.log(memory.slice(ptr, ptr+len).toString())
},
read: function (ptr, len) {
var memory = Buffer.from(result.instance.exports.memory.buffer)
buffer.copy(memory, ptr, 0, len)
return len
},
onReady: function (fn_ptr, frame) {
var table = result.instance.exports.__indirect_function_table
var memory = Buffer.from(result.instance.exports.memory.buffer)
var cb = table.get(fn_ptr)
callback = () => cb(frame)
},
}})
var r = ~~(Math.random()*1000)
result.instance.exports.init(r)
setTimeout(()=>{
buffer = Buffer.from('hello world\n')
callback()
}, 1000)
}())
this is progress but I want to be able to use code that uses await, but is triggered by callbacks like this