onyx-lang / onyx

✨ The compiler and developer toolchain for Onyx

Home Page:https://onyxlang.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to export an Onyx wasm module to Javascript

alexspurling opened this issue · comments

I am trying to translate this AssemblyScript example to Onyx (https://wasmbyexample.dev/examples/reading-and-writing-graphics/reading-and-writing-graphics.assemblyscript.en-us.html). I have compiled the Onyx module to .wasm using the -r js target:

onyx build checker.onyx -o checker.wasm -r js

After a bit of experimentation, I was able to import this module into javascript with the following code:

const importWasmModule = async (wasmModuleUrl) => {
    let importObject = {
        host: {
            print_str: (obj) => console.log(obj),
            time: () => console.log("time"),
        }
    };

    return await WebAssembly.instantiateStreaming(
        fetch(wasmModuleUrl),
        importObject
    );
}

Note that the importObject has a host property with a print_str and time function because without these, I would get errors like this: "Uncaught (in promise) TypeError: WebAssembly.instantiate(): Import #0 module="host": module is not an object or function".

In my Onyx code, I have three values that I want to export from the module:

use core {println}

CHECKERBOARD_SIZE :: 20;

#export "CHECKERBOARD_BUFFER_SIZE" CHECKERBOARD_BUFFER_SIZE
CHECKERBOARD_BUFFER_SIZE: i32 = CHECKERBOARD_SIZE * CHECKERBOARD_SIZE * 4;

#export "CHECKERBOARD_BUFFER_POINTER" CHECKERBOARD_BUFFER_POINTER
CHECKERBOARD_BUFFER_POINTER: [CHECKERBOARD_BUFFER_SIZE] i32;

#export "generateCheckerBoard" generateCheckerBoard
generateCheckerBoard :: (
    darkValueRed: i32,
    darkValueGreen: i32,
    darkValueBlue: i32,
    lightValueRed: i32,
    lightValueGreen: i32,
    lightValueBlue: i32) -> i32 {
   // ...
}

I'm not sure if this is the correct way to export constants and functions - I had to dig this syntax out of the onyx codebase. I can't find any documentation for the #export directive.

These exports work for the generateCheckerBoard function, but not the two global variables. In fact, when I view the wasm module in the JS console I see that the two checkboard variables point to the print_str function defined above.

image

If you decompile the .wasm file you can see why:

  (export "CHECKERBOARD_BUFFER_SIZE" (func $import0))
  (export "CHECKERBOARD_BUFFER_POINTER" (func $import0))
  (export "generateCheckerBoard" (func $func21))

It looks like the instead of exporting the two variables, it is exporting the first value to be imported ($import0).

You can find all my code here: https://github.com/alexspurling/checker

How can I fix this so that I can export a reference to the i32 array from the Onyx module to Javascript?

Good work so far! I'm sorry but I have not written the documentation on #export yes. You are using it correctly, at least for generateCheckerBoard. In theory you shouldn't be able to export constants like CHECKERBOARD_BUFFER_SIZE, but apparently I never explicit check for that. You should only be able to export functions.

For your case, I would recommend making a dummy function that simply returns the address of CHECKERBOARD_BUFFER_POINTER. Then export this function so you can call it from JS to know where the buffer is.

For example,

#export "getCheckboardBuffer" getCheckerboardBuffer
getCheckerboardBuffer :: () -> rawptr {
    return &CHECKERBOARD_BUFFER_POINTER
}

I think you would have to do the same thing for CHECKERBOARD_BUFFER_SIZE.

I'm going to file this as a bug because you shouldn't be able to #export constants.

Thanks again for the quick response. Perhaps you should consider having the ability to export constants as that is something that is possible in AssemblyScript? If I decompile the .wasm file I generated by following the tutorial I linked above I see this:

  (global $global0 i32 (i32.const 20))
  (global $global1 i32 (i32.const 0))
  (global $global2 i32 (i32.const 1600))
  (global $global3 i32 (i32.const 8))
  (global $global4 (mut i32) (i32.const 32776))
  (global $global5 i32 (i32.const 32776))
  (export "CHECKERBOARD_BUFFER_POINTER" (global $global1))
  (export "CHECKERBOARD_BUFFER_SIZE" (global $global2))
  (export "generateCheckerBoard" (func $func1))

As you can see the constants are stored as globals and those globals are exported directly. I'm not sure if it would ever be necessary to do this if you can use a getter function as a workaround. I suppose it only comes into play if you need to consider compatibility with other programs that compile to .wasm.

WASM globals were a feature in Onyx that I have mostly removed. I did so because most other languages targeting WASM do not have a way to directly use WASM globals, so I figured interoperability would not be too affected.

It is something worth considering bringing back, if not just for a use case like this.

I was able to partially get the application to work with your suggestion. However, there is some memory corruption happening.

The checkerboard pattern has 3 blank squares:

image

This is because the array being returned has some values overwritten:

image

You can see the value 20 here which represents the value of CHECKERBOARD_SIZE so clearly somehow the memory for this value is overlapping with the memory allocated for the array.

The full code is available here: https://github.com/alexspurling/checker

I'm not sure if this is the right place to ask - it would be good if there was a gitter or discord channel we could ask simple questions in. I'd also like to know how to call the "print_str" function from Onyx.

If you can push your most recent code to that GitHub, I can take a look at what is going wrong.

I am in the process of creating a Discord for Onyx, and I will update the website and README to include a link to join it, so keep an eye out for that. It should be released soon.

Sorry, I thought I had already pushed the latest version. It's now up to date.

No problem. I did update website to include a link to the Discord. You can go and join the Discord and create a question in the forum, and I will follow up.

I did get it to work, but it actually relates another bug someone else found (#52).

This issue I mentioned above is now fixed so I will close this issue.

I compiled the latest source and can confirm that the new compiler fixes the memory bug. However, I had to work around a new compilation error which was not happening before:

(/mnt/c/Users/alexs/dev/onyx/advent/checker.onyx:6,30) Array type size expression must be 'i32', got 'i32'.
 6 | CHECKERBOARD_BUFFER_POINTER: [CHECKERBOARD_BUFFER_SIZE] u8;

I had to hardcode the size of the buffer like so in order to get the code to compile:

CHECKERBOARD_BUFFER_POINTER: [1600] u8;