Assemblyscript: Passing arrays?

Created on 11 May 2018  ยท  9Comments  ยท  Source: AssemblyScript/assemblyscript

Hi,
I noticed that arrays can only been manipulated within wasm modules when I'm using the playground. Is it possible to pass arrays (either typed or dynamic ones) to and from javascript at the moment? Thanks!

question

Most helpful comment

So all the pointers must be retrieved from the I32/U32 array view (or create an Int32Array/Uint32Array view on your own), and the code will look like this:

WASM side:

export function show_array(): Float64Array {
  let array: Float64Array = new Float64Array(5);
  array[0] = 99.88;
  array[1] = 88.77;
  array[2] = 77.66;
  array[3] = 66.55;
  array[4] = 55.44;
  return array;
}

JS side:

const array_ptr = module.show_array();
const buffer_ptr = module.I32[array_ptr >>> 2];
const byte_offset = module.I32[array_ptr + 4 >>> 2];
const byte_length = module.I32[array_ptr + 8 >>> 2];
const element_byte_size = 8;
const array_start_ptr = 8 + byte_offset;

for (let i = array_start_ptr ; i < array_start_ptr + byte_length; i += element_byte_size)
    console.log(module.F64[buffer_ptr + i >> 3]);

Thanks for all the answers and patience, now it all works :)

All 9 comments

Indirectly, yes. WASM has only four basic types (i32, i64, f32, f64), and what you are passing to/from JS is the offset (i32) of the array structure in memory.

So it has performance overhead like passing strings, right? Would it be faster if I use a loop in JavaScript or AssemblyScript to pass the numbers in the array one by one?

Depends, calling in and out of WASM has some overhead as well. If performance is a concern, I'd suggest to create (and then reuse) a typed array view of the memory region in question on the JS side, and use that to read respectively write the values.

I've managed to use the loader to share my Float64Array between JS and WASM. Another question is, I couldn't get the correct offset through bitwise operators as here mentioned. So I had to manually put a value in the first of the array from WASM to get the offset from JS. For example, "34" for Int32Array and "17" for Float64Array. But these offsets didn't seem to be regular, so I had to do the test again when creating a new array.

Moreover, when I re-declared the same array name with a different length, the offset shifted again. So while writing on the WASM side, I couldn't get the corresponding array from JS anymore. For the normal arrays it got more complex. Is there any simple and safe ways to do this?

When returning a Float64Array from WASM, the JS-side receives the offset of the respective structure in memory. Arrays have one additional level of indirection to their backing ArrayBuffer, as explained here, so that might be where the random offset you described comes from. What you want to do there is to read the buffer offset first, and then use that to access the data of the backing buffer.

I've found out a safer way now. Here are the steps:

  1. On the JS side, use allocate_memory to get every pointer for every items, and write them into an array:
for (let i = 0; i < length; i++)
       ptr_array[i] = module.allocate_memory(1);
  1. Write the item values into the ArrayBuffer using the bitwise operators:
array.forEach((item, index) => {
       const ptr = ptr_array[index];
       module.F64[ptr >>> 3] = item;
});
  1. Send the starting pointer while calling all the WASM function:
module.myFunction( ptr_array[0], array.length, arg3..... );
  1. On the WASM side, get the first pointer, the following items are certain bytes after it depending on which kind of type array you're using. For example, Float64Array uses 8 bytes each. It's described here well. So we use "load" and "store" function to read and write the memory.
for (let i = ptr; i < ptr + len * 8; i += 8) {
       let item = load<f64>(i);
       item = item / 300;
       store<f64>(i, item);
}
  1. Back on the JS side, simply use the bitwise operators again to retrieve those items:
console.log(module.F64[ptr_array[index] >>> 3]);

And all is done! Remember to free/reset memory after that. Use 3 typed arrays in my project without any problem :)

But I still couldn't figure out how to get the typed array offset from the WASM side. The document link provided looks like the HEX code from the WASM file? I still have no luck to find "17" and "27" these 2 offsets as mentioned above...

More precisely, what you get back is a pointer to the Float64Array structure in WASM memory. That structure does not contain the actual data, but another pointer to an ArrayBuffer that holds the data. So, the first 4 bytes at the offset returned are the pointer to the ArrayBuffer, the next 4 bytes is the byteOffset into that ArrayBuffer, the next 4 bytes is the byteLength of that ArrayBuffer. What you'd want to do is:

  1. Retrieve the pointer to the Float64Array by returning it from WASM
  2. Read the first 4 bytes at that pointer to obtain the pointer to the ArrayBuffer, bytes 4-7 to obtain the byteOffset and bytes 8-11 to obtain the byteLength (the last is only necessary if you'd like to do a range check).
  3. Read the value from the address of the ArrayBuffer (as obtained in step 2). The data of an ArrayBuffer starts at offset 8, then add byteOffset + index * elementByteSize (here: 8).
โ•’โ•โ• TypedArray โ•โ••
 7 6 5 4 3 2 1 0  bits
โ”œโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ค
โ”‚    .buffer    โ”‚ points at the backing ArrayBuffer
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  .byteOffset  โ”‚ offset in bytes from the start of .buffer
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  .byteLength  โ”‚ length in bytes from the start of .buffer
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ•’โ• ArrayBuffer โ•โ••
 7 6 5 4 3 2 1 0  bits
โ”œโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ดโ”€โ”ค
โ”‚  .byteLength  โ”‚ number of bytes
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚       0       โ”‚ free space due to alignment
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚      ...      โ”‚ N=byteLength bytes of data
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

(updated the wiki page accordingly)

So all the pointers must be retrieved from the I32/U32 array view (or create an Int32Array/Uint32Array view on your own), and the code will look like this:

WASM side:

export function show_array(): Float64Array {
  let array: Float64Array = new Float64Array(5);
  array[0] = 99.88;
  array[1] = 88.77;
  array[2] = 77.66;
  array[3] = 66.55;
  array[4] = 55.44;
  return array;
}

JS side:

const array_ptr = module.show_array();
const buffer_ptr = module.I32[array_ptr >>> 2];
const byte_offset = module.I32[array_ptr + 4 >>> 2];
const byte_length = module.I32[array_ptr + 8 >>> 2];
const element_byte_size = 8;
const array_start_ptr = 8 + byte_offset;

for (let i = array_start_ptr ; i < array_start_ptr + byte_length; i += element_byte_size)
    console.log(module.F64[buffer_ptr + i >> 3]);

Thanks for all the answers and patience, now it all works :)

@togekk even I have similar issue where I need to pass and return a u8[].

When converted I got,
function decodeRLE(encodedBuffer: u32): u32;

even though I have my AS function signature,
function decodeRLE(encodedBuffer: u8[]): u8[]

How can I retrieve the argument from passed array pointer? Similarly how can I retrieve full u8[] (UInt8Array) from returned array pointer.

I tried the snippet you shared I am getting module.I32 as 'undefined'. Should I do something to get I32 array view?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Iainmon picture Iainmon  ยท  3Comments

evgenykuzyakov picture evgenykuzyakov  ยท  3Comments

kungfooman picture kungfooman  ยท  5Comments

dcodeIO picture dcodeIO  ยท  4Comments

pannous picture pannous  ยท  4Comments