javascriptemscriptenwebassemblytyped-arrays

How does WebAssembly <-> JavaScript memory interaction work with multiple Typed Arrays?


I've got a simple c function.

void fill(float *a, float *b)
{
  a[0] = 1;
  b[0] = 2;
}

int main()
{
    float a[1];
    float b[1];

    fill(a, b);

    printf("%f\n", a[0]);
    printf("%f\n", b[0]);

    return 0;
}

That gives me

1.000000
2.000000

Now I'm trying to do the same but from JavaScript via WebAssembly.

var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, wasmImports);

const a = new Float32Array(wasmInstance.exports.memory.buffer, 0, 1)
const b = new Float32Array(wasmInstance.exports.memory.buffer, 4, 1)

wasmInstance.exports.fill(a, b)

log(a)
log(b)

Here is the wasm fiddle https://wasdk.github.io/WasmFiddle/?19x523

This time a is [2] and b is [0]. I think I'm doing something wrong with the memory. I assume both a and b point to the beginning of the memory. That's why a is first [1] and immediately afterwards [2]. I though the offsets from new Float32Array(wasmInstance.exports.memory.buffer, 4, 1) where the offset is 4 are somehow translated to WebAssembly.

How can I achieve that a and b actually use different memory? Thank you. I'm really stuck.


Solution

  • There is a problem with this exported function call:

    wasmInstance.exports.fill(a, b)

    a and b are JS Float32Array objects. Never assume any JS objects will be translated to any C data types automagically. Although a JS TypedArray behaves similar to C array, TypedArray is still a JS object that basically is a key-value storage, then how C can access JS object's field? C has no idea how to deal with a JS object.

    WebAssembly Types

    Okay, let's look at it more closely in a lower level in WebAssembly. Here's the compiled result of void fill(float *a, float *b):

     (func $fill (; 0 ;) (param $0 i32) (param $1 i32)
      (i32.store
       (get_local $0)
       (i32.const 1065353216)
      )
      (i32.store
       (get_local $1)
       (i32.const 1073741824)
      )
     )
    

    I won't go over details, but at least it is easy to figure out this function $fill needs two parameters of i32 type: (param $0 i32) (param $1 i32). So fill() expects numbers, not TypedArrays, as parameters. WebAssembly defines the following types as function parameter types and return types: i32, i64, f32, f64, basically 32/64 bit intergers/floats. There is no other types like JS key-value store, no even array types.

    Therefore, whatever languages you use in Wasm side, you are not supposed to pass any JS types other than numbers to functions under wasmInstance.exports directly. Many languages like Golang, Rust, and Emscripten C++ (not C) provides interfaces for seamless type translation by wrapping around exported functions in JS side and by hacking around those number types and Wasm memory addresses (so they need a well-defined ABI). However, you still must pass only number types if you access the exported functions directly through WebAssembly.Instance.exports.

    Accessing Arrays

    So then what integer value you need to pass to fill()? Well I think you are already close to the answer in the question, as you correctly set offsets for the arrays. You need to pass value of C pointers as integers. In the Wasm linear memory, a C pointer is an offset address to the Wasm memory. So you need to slightly change the code like this:

    var wasmModule = new WebAssembly.Module(wasmCode);
    var wasmInstance = new WebAssembly.Instance(wasmModule, wasmImports);
    
    const ptrA = 0; // Yeah it's the same value as NULL, I'd avoid using zero...
    const ptrB = 4;
    
    //# fill the WASM memory (any pointer gives automatic access to memory)
    wasmInstance.exports.fill(ptrA, ptrB); //# pointer is a position INT for memory
    
    //# after fill updates, extract the new filled values back from WASM memory
    const a = new Float32Array(wasmInstance.exports.memory.buffer, ptrA, 1);
    const b = new Float32Array(wasmInstance.exports.memory.buffer, ptrB, 1);
    
    console.log("float32 a from memory[0] : " + a);
    console.log("float32 b from memory[4] : " + b);
    

    Now you will get the value you want ;)

    Related: Using emscripten how to get C++ uint8_t array to JS Blob or UInt8Array