Skip to content

Instantly share code, notes, and snippets.

@dominictarr
Last active May 5, 2024 19:23
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dominictarr/a4e820d42ef6a0cdd176f19bfac6d9fa to your computer and use it in GitHub Desktop.
Save dominictarr/a4e820d42ef6a0cdd176f19bfac6d9fa to your computer and use it in GitHub Desktop.
wasm zig async example

run

build: zig build-lib -target wasm32-freestanding -dynamic --export-table await.zig

run node await.js

output hello world!!! (but slow like a typwritter effect)

what it does

generate async zig wasm. this requires grappling with several issues:

  • wasm can only expose export/extern which cannot be async
  • frames need to survive until callback happens
  • frames must be passed as pointers

figure out the right approach to all of these at the same time, else cryptic errors.

I did not find any examples of this available, but now there is this one

const fs = require('fs');
const source = fs.readFileSync("./await2.wasm");
const typedArray = new Uint8Array(source);
;(async function () {
var callback, frame
var buffer = Buffer.from("hello world!!!\n")
var result = await WebAssembly.instantiate(typedArray, {
env: {
print: function (ptr, len) {
//if there was memory allocation, result.instance.exports.memory
//might be a different size now and it needs to be bufferized again.
var memory = Buffer.from(result.instance.exports.memory.buffer)
process.stdout.write(memory.slice(ptr, ptr+len))
},
read: function (ptr, len) {
if(len > buffer.length) return 0
var memory = Buffer.from(result.instance.exports.memory.buffer)
var l = Math.min(buffer.length,len)
buffer.copy(memory, ptr, 0, len)
buffer = buffer.slice(len)
return l
},
onReady: function (fn_ptr, frame) {
//callback when something is ready.
//pass back "frame" a pointer to whatever
var table = result.instance.exports.__indirect_function_table
var memory = Buffer.from(result.instance.exports.memory.buffer)
var cb = table.get(fn_ptr)
setTimeout(function () {
cb(frame)
}, Math.random()*1000)
},
}})
result.instance.exports.init()
}())
//onReady takes a callback and a frame pointer,
//and then at some point passes the frame pointer to the callback.
//the frame pointer is typed as anyopaque (same as c void pointer)
//instead of any frame because of compiler errors in get
extern fn onReady(
cb: fn ( frame: *anyopaque ) callconv(.C) void,
frame: *anyopaque
) void;
//copies memory from host into pointer, up to len
extern fn read(ptr: [*]const u8, len:usize) usize;
//writes len bytes from ptr to stdout
extern fn print(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 {
//because we pass a pointer to our own frame to an external function
//that parameter must be typed *anyopaque. if it's typed anyframe
//there are compiler errors that "@Frame(get) not analyzed yet"
onReady(cb, @frame());
}
return read(slice.ptr, slice.len);
}
fn cb (frame: *anyopaque) callconv(.C) void {
//defer allocator.destroy(frame);
//cast frame:*anyopaque to frame:*anyframe so we can resume it.
//this requires also casting the alignment.
resume @ptrCast(anyframe, @alignCast(4, frame));
}
//internal _init function, this can have any sort of async pattern.
fn _init () void {
var array1:[1]u8 = [_]u8{0};
//note: because of zig's uncoloured async, this just looks like a normal loop
while (0 != get(array1[0..array1.len])) {
print(&array1, 1);
}
}
//this is the essential trick, store the _init frame in global variable.
var init_frame :@Frame(_init) = undefined;
//the exported function can't be async
//but we can call another function with async keyword
//but if we did that with a local variable the memory wouldn't be alive
//after init() returns
//so, we make it a global. then, call async _init
//and have any kind of async pattern in there.
//(note, this does mean that init() should only be called exactly once, like main())
export fn init () void {
init_frame = async _init();
}
@resolutionboom
Copy link

This knowledge is quite beneficial. Right word hurdle when I needed it the most

@timothyvaughan
Copy link

Learn more about the technical jargon of how to create a truly tailored advanced language program. The difficulty is definitely challenging but well worth learning geometry dash lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment