2

I have an AssemblyScript function that returns any string it is given, as well as the corresponding code to import and run it in NodeJS:

AssemblyScript:

export function parse(x: string): string {
  return x;
}

NodeJS:

import { readFileSync } from 'fs';

export default async function compile(raw) {
  return WebAssembly.instantiate(
    readFileSync('./src/.assembly/engine.wasm') // The WASM file containing compiled AssemblyScript
  ).then(mod => {
    const { parse } = mod.instance.exports;
    return parse(raw);
  });
}

compile('test').then(res => {
  console.log(res);
});

However, when this code is run, it returns an error about the imports argument not being present:

Terminal:

node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[TypeError: WebAssembly.instantiate(): Imports argument must be present and must be an object]

Node.js v18.15.0
error Command failed with exit code 1.

Strangely, the code runs just fine if it uses i32 instead of string:

AssemblyScript:

export function parse(x: i32): i32 {
  return x;
}

NodeJS:

import { readFileSync } from 'fs';

export default async function compile(raw) {
  return WebAssembly.instantiate(
    readFileSync('./src/.assembly/engine.wasm') // The WASM file containing compiled AssemblyScript
  ).then(mod => {
    const { parse } = mod.instance.exports;
    return parse(raw);
  });
}

compile(44).then(res => {
  console.log(res);
});

Terminal:

44

How can I fix the code to work with strings too?

quantasium
  • 64
  • 7

1 Answers1

3

Your punctual error comes from the fact that, when strings are involved, AssemblyScript does a lot more to accommodate them, as they are not in the WebAssembly spec, which makes i32s much easier to work with.

You can see a glimpse of this if you use the strings command to.

i32:

$ strings release.wasm
parse
memory
sourceMappingURL
./release.wasm.map

string:

$ strings release.wasm
abort
__new
__pin
__unpin
__collect
__rtti_base
memory
parse
A|q!
...many more lines later...
A}q6
A|qA
sourceMappingURL
./release.wasm.map

So, what WebAssembly.intantiate complains about is firstly, the lack of the imports object, and secondly, the lack of the abort function that it needs.

Luckily, for a solution, you don't need to worry about any of that, the AssemblyScript loader does things automatically for you. You can use it to instantiate the module, instead of the WebAssembly.instantiate function:

import { readFileSync } from 'fs';
import loader from "@assemblyscript/loader";

export default function compile(raw) {
  let wasmModule = loader.instantiateSync(readFileSync('./build/release.wasm'), {});
  const { __newString, __getString } = wasmModule.exports;
  return __getString(wasmModule.exports.parse(__newString(raw)));
}

console.log(compile('test'));

Apart from that, the way to communicate with strings from javascript is sadly still to use the __newString and __getString that do the work of translating WebAssembly addresses for you, as WebAssembly does not have the concept of stings.

And, for a bit of good news:

$ node index.js 
test

Your code now works as intended.

TachyonicBytes
  • 700
  • 1
  • 8
  • Thank you so much! I was reading the docs and **the loader does seem to be archived**, but there's something else called [static bindings](https://www.assemblyscript.org/compiler.html#host-bindings) that should work. This definitely helped point me in the right direction. – quantasium Mar 15 '23 at 12:44
  • I know they planned to support less glue code, but I forgot about the status of the bindings. It seems the bindings are more advanced than I remember! – TachyonicBytes Mar 15 '23 at 13:04