A_P
A_P

Reputation: 336

What's the correct way to share memory between my AssemblyScript module and my JS?

I'm following this code here, trying to share memory between my AssemblyScript code and my JS:

  let aryPtr = instance.exports.allocateF32Array(3);
  let ary = new Float32Array(instance.exports.memory.buffer, aryPtr, 3);

  ary[0] = 1.0;
  ary[1] = 2.0;
  instance.exports.addArray(aryPtr);

And my index.ts:

export function allocateF32Array(length: i32): Float32Array {
  return new Float32Array(length);
}

export function addArray(data: Float32Array): i32 {
  data[2] = data[0] + data[1];
  return data.length;
}

But this results in RuntimeError: memory access out of bounds in addArray. Have I misunderstood how this is supposed to work?

Upvotes: 2

Views: 1801

Answers (2)

Redu
Redu

Reputation: 26161

A little late but this might be useful for people ending up here.

In my opinion AssemblyScript is a fantastic language to easily adopt WASM ability into your JS/TS code. The recent release 0.25.1 comes with bindings ready on many types. Yet still you can easily implement the bindings yourself.

Remember that you can only interchange i32, i64, f32 and f64 data directly inbetween your host and WASM module. Besides you can access the WASM memory from JS/TS (host) side.

Let us investigate the initial part of the code

let aryPtr = instance.exports.allocateF32Array(3);
let ary = new Float32Array(instance.exports.memory.buffer, aryPtr, 3);

ary[0] = 1.0;
ary[1] = 2.0;
instance.exports.addArray(aryPtr);
  • allocateF32Array(n) is an exported function from WASM. When you invoke it with an integer argument n it creates a Float32Array of length n at WASM side and passes it's pointer back to host side. The pointer is it's location in the module memory and gets assigned to aryPtr.
  • Then on the second line it creates a seperate Float32Array just to use as a view to access the WASM memory. So it passes the buffer to use, offset (pointer), and length) to the Float32Array constructor. Now you can access the WASM memory's related part through ary. Any modification you do on ary will actually happen on the WASM side Float32Array.
  • Make some assignments to index 0 and 1.
  • Call another exported function addArray(ptr) with the pointer of it.

Now you should implement the allocateF32Array and addArray) functions to export.

allocateF32Array(n):

export function allocateF32Array(length: i32): i32 {
  const arr : Float32Array = new Float32Array(length);
  return changetype<i32>(arr); // return the pointer
}
  • And the other function can be implemented as follows

addArray(ptr):

export function addArray(ptr: usize): f32 {
  const arr : Float32Array = changetype<Float32Array>(ptr); // obtain Float32Array from the pointer
  return arr[0] + arr[1];
}

Finally, this is in fact the raw method and you don't really need to get this low. Nowadays the bindings are readily implemented in the .build/release.js file from where you import the exported function just like ESM imports.

Upvotes: 1

MaxGraey
MaxGraey

Reputation: 361

I recommend to use the official loader for such purposes.

On the JavaScript side: (node.js for example)

const fs = require("fs");
const loader = require("@assemblyscript/loader");
const module = loader.instantiateSync(
  fs.readFileSync("optimized.wasm"),
  {}
);
var ptrArr = module.__retain(module.__allocArray(module.FLOAT32ARRAY, [1, 2, 0]));
console.log('length:', module.addArray(ptrArr));

const arr = module.__getFloat32Array(ptrArr);
console.log('result:', arr[2]);

// free array
module.__release(ptrArr);

On the AssemblyScript side:

export const FLOAT32ARRAY = idof<Float32Array>();

export function addArray(data: Float32Array): i32 {
  data[2] = data[0] + data[1];
  return data.length;
}

Upvotes: 1

Related Questions