#WebAssembly, how to define extern console.log in zig?

1 messages Β· Page 1 of 1 (latest)

forest warren
#

Hi,

I understood I can define external javascript objects that can be used in the zig program. I found that the browser console can be defined in the WASM loader as:

var importObject = {
    env: {
        consoleLog: (arg) => console.log(arg), // Useful for debugging on zig's side
        memory: memory,
    },
};
WebAssembly.instantiateStreaming(fetch("bootloader.wasm"), importObject)

Then in the zig code I was able to define:

extern fn consoleLog(arg: u32) void;

But writing only a integer is not that useful... How can I define consoleLog argument to accept a string so I guess an array of u8 of undefined size?
I tried multiple things but nothing the compiler accepted πŸ™‚

Thanks!

summer dew
#

You can define an extern fn consoleLog(ptr: [*]u8, len: usize) but it requires some extra glue code on the JS side

#

You can then write a wrapper for it in zig using std.fmt.allocPrint or similar so you can format stuff :)

forest warren
#

let's start with the first one πŸ™‚ Looks at little easier.
On the zig side, how do you use it?

extern fn consoleLogWp(ptr: [*]u8, len: usize) void;

consoleLogWp("hello", 5);

error

bootloader.zig:36:18: error: expected type '[*]u8', found '*const [5:0]u8'
    consoleLogWp("hello", 5);
                 ^~~~~~~
bootloader.zig:36:18: note: cast discards const qualifier
bootloader.zig:10:30: note: parameter type declared here
extern fn consoleLogWp(ptr: [*]u8, len: usize) void;
#

Does it need an explicit cast ?

unkempt lodge
#

string literals are constant, [*]u8 is mutable

#

change it to [*]const u8

summer dew
#

oops yeah that's my bad

#

You'd probably want to wrap it like this: ```rs
extern fn consoleLogJS(ptr: [*]const u8, len: usize) void;
fn consoleLog(s: []const u8) void {
consoleLogJS(s.ptr, s.len);
}

#

Then you can call consoleLog("Hello, world!") etc

forest warren
#

hum, not so easy on the javascript side... not sure what happens,

Uncaught (in promise) LinkError: WebAssembly.instantiate(): Import #2 module="env" function="consoleLogJS" error: function import requires a callable
cunning carbon
#

Change the Zig function to extern "env" fn

#

?

summer dew
#

What's your js side module instantiation code?

#

Like what are you passing to WebAssembly.instantiate?

forest warren
#

for the moment, nothing smart, just that to see what is received:

var importObject = {
    env: {
        // Useful for debugging on zig's side
        consoleLogJS: (arg, len) => console.log(arg),
        consoleLog: (arg) => console.log(arg), 
        memory: memory,
    },
};
summer dew
#

hm, that should work afaict

forest warren
#

ah different behavior (I guess some browser cache issues sometimes):consoleLog("hello") => 6560

#

hum whatever the string passed, arg = 6560 and len is the string length as expected:

Zig: 6560 of len: 10

var importObject = {
    env: {
        // Useful for debugging on zig's side
        consoleLogJS: (arg, len) => console.log("Zig: "+ arg + " of len: " + len),
        consoleLog: (arg) => console.log(arg), 
        memory: memory,
    },
};
consoleLogWp("1234567890");
summer dew
#

yeah, so 6560 is the address

#

You need to look that up in the module's memory, then extract len bytes and parse that as utf-8

forest warren
#

ok but memory.buffer is known outside of env. Hum... scratching my head

summer dew
#

You need to store it somewhere the function can access

#

eg. in a closure variable at the same scope as importObject

forest warren
#

the buffer is referred in the WASM instantiation:

WebAssembly.instantiateStreaming(fetch("bootloader.wasm"), importObject).then((result) => {
    const wasmMemoryArray = new Uint8Array(memory.buffer);

    const drawframebuffer = (canvas_id) => {
        const fb_width = 320;
        const fb_height = 200;

        const canvas = document.getElementById(canvas_id);
        const context = canvas.getContext("2d");
        const imageData = context.createImageData(canvas.width, canvas.height);
        context.clearRect(0, 0, canvas.width, canvas.height);

        result.instance.exports.renderPhysicalFrameBuffer(parseInt(canvas_id));

        const bufferOffset = result.instance.exports.getPhysicalFrameBufferPointer();
        const imageDataArray = wasmMemoryArray.slice(
            bufferOffset,
            bufferOffset + fb_width * fb_height * 4
        );
        imageData.data.set(imageDataArray);

        context.clearRect(0, 0, canvas.width, canvas.height);
        context.putImageData(imageData, 0, 0);
    };

    // boot the Zig Machine
    result.instance.exports.boot();

    // draw the first FB
    drawframebuffer("0");

    // Check memory
    // console.log(memory.buffer);

    // Start the VBL loop
    setInterval(() => {
        drawframebuffer("0");
        drawframebuffer("1");
    }, 20);
});
summer dew
#

yep so then you store it in a variable that you've already declared in a higher scope

#

so that the function can reference it

forest warren
#

ok, so it looks like yes memory.buffer is known:

consoleLogJS: (arg, len) => console.log("Zig: "+ arg + " of len: " + len + " => " + memory.buffer),

Zig: 6560 of len: 10 => [object ArrayBuffer]
#

so now need to find out how to navigate in this ArrayBuffer

#
        consoleLogJS: (arg, len) => {
            let arr8 = new Uint8Array(memory.buffer);
            console.log("Zig: "+ arg + " of len: " + len + " => " + arr8.slice(arg, arg+len));
        },
cunning carbon
forest warren
#

youhou!

        consoleLogJS: (arg, len) => {
            let arr8 = new Uint8Array(memory.buffer);
            console.log("Zig: "+ arg + " of len: " + len + " => " + new TextDecoder().decode(arr8.slice(arg, arg+len)));
        },
#

Thanks @summer dew πŸ™‚

#

even better :

        consoleLogJS: (arg, len) => {
            let arr8 = new Uint8Array(memory.buffer.slice(arg, arg+len));
            console.log(new TextDecoder().decode(arr8));
        },
#

thanks @cunning carbon , I'll take a look!