0

Inside the excellent WasmFiddle tool, I'm using this WASM function:

int f(char *in, char *out, int len) { 
    for (int i = 0; i < len; i++) {
        out[4*i] = in[i];
        out[4*i+1] = in[i];
        out[4*i+2] = in[i];
        out[4*i+3] = 255;
    }   
    return 0;
}

and this JS code:

var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, wasmImports);
var width = 1000, height = 1000;
var array = new Uint8Array(width*height).fill().map(() => Math.round(Math.random() * 255)); // random values
var rgba = new Uint8ClampedArray(4*width*height);
wasmInstance.exports.f(array, rgba, width*height);

The last line is an error because it fails to pass the reference/pointer of array and rgba.

How to do this, in particular in this WasmFiddle environment?

I've read Pass array to C function with emscripten, How to handle passing/returning array pointers to emscripten compiled code?, Pass a JavaScript array as argument to a WebAssembly function but I don't see how to apply this here.


Attempt #1 inspired from Passing arrays between wasm and JavaScript:

var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, wasmImports);
var width = 10, height = 10;
var array = new Uint8Array(wasmInstance.exports.memory.buffer, 0, width*height).fill().map(() => Math.round(Math.random() * 255)); // random values
var rgba = new Uint8ClampedArray(wasmInstance.exports.memory.buffer, width*height, 4*width*height);
wasmInstance.exports.f(array.byteOffset, rgba.byteOffset, width*height);
log(array);
log(rgba);

but in the result we see [0, 0, 0, 255, 0, 0, 0, 255, ...]: other values haven't been modified as expected. Also when increasing the size to width and height = 1000, it doesn't work anymore.

Basj
  • 41,386
  • 99
  • 383
  • 673
  • Note: The attempt #1 fails with `line 4: Uncaught RangeError: Invalid typed array length: 1000000` when taking height=width=1000. Maybe the problem is that we don't pass a pointer to WASM, here it's the contrary: we are expecting WASM to allocate the memory, and we take it from WASM in JS. We should do the contrary: build the object into JS and pass a pointer to WASM. How to do that? – Basj Aug 08 '22 at 21:54

1 Answers1

0

See MDN's documentation for TypedArray.map():

map() does not mutate the typed array on which it is called (although callbackFn, if invoked, may do so).

So, this makes a new typed array rather than modifying the underlying data.

The correct code would be:

var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, wasmImports);
var width = 10, height = 10;
var array = new Uint8Array(wasmInstance.exports.memory.buffer, 0, width*height).fill();
array.forEach((v, i, a) => { a[i] = Math.round(Math.random() * 255); }); // random values
var rgba = new Uint8ClampedArray(wasmInstance.exports.memory.buffer, width*height, 4*width*height);
wasmInstance.exports.f(array.byteOffset, rgba.byteOffset, width*height);
log(array);
log(rgba);

As for memory size, you have a few options.

Essentially, you can either let the WASM create the memory and export it, or let JS create the memory and import it.

LLVM's LLD WebAssembly docs explain you can use the linker flags --initial-memory=<value> and --max-memory=<value> to set the initial and maximum memory size from the linker (in 64KiB pages).

Otherwise, to set the memory from JS, you would use the flag --import-memory, and construct a WebAssembly.Memory object using initial/maximum memory sizes.

It seems WasmFiddle does not support linker flags, and the linker defaults to exporting the memory, so your only option is to grow the existing memory.

// Grow WASM memory by amount used (in 64KiB pages)
wasmInstance.exports.memory.grow((width*height + width*height*4) >> 16);

This seems to cause strange freezing in my browser - I suspect this is a bug in the editor, so try it in another context to see if it has the same issues.

AlexApps99
  • 3,506
  • 9
  • 21
  • Thanks! One problem solved! But it seems it still doesn't work with width=height=1000, `line 4: Uncaught RangeError: Invalid typed array length: 1000000`. How to do this? Maybe the problem is that we don't pass a pointer to WASM, here it's the contrary: we are expecting WASM to allocate the memory, and we take it from WASM in JS. We should do the contrary: build the object into JS and pass a pointer to WASM. How to do that? – Basj Aug 08 '22 at 21:51
  • Have a look at this example, it shows how to allocate your own memory with an arbitrary size: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory#creating_a_new_memory_object – AlexApps99 Aug 09 '22 at 21:54
  • thanks, feel free to update your answer with this, then I can accept it. – Basj Aug 09 '22 at 22:02
  • I have updated it, there are no great solutions because the tool you are using doesn't seem to offer many advanced features but if you deploy this on your own code you should be able to use these options – AlexApps99 Aug 09 '22 at 22:49
  • Thanks @AlexApps99. I prefer to create variables on JS side, for example `var rgba = new Uint8ClampedArray(4*width*height);`. If I don't use `WasmFiddle` but a real compiler, could you include the compilation command we should use + the code to call the WASM and pass access to `rgba`? – Basj Aug 10 '22 at 07:27
  • I still don't see how to pass a reference to a JS object (created on JS side) to a WASM function @AlexApps99. Even when using `--import-memory` here they don't really create the Array JS-side and pass it to WASM: https://aransentin.github.io/cwasm/. A complete example about this would be interesting it's really the difficult part of the question :) – Basj Aug 10 '22 at 07:39
  • TypedArrays use an underlying ArrayBuffer, so rgba would work the same either approach you take (import or export memory) – AlexApps99 Aug 10 '22 at 19:53
  • Please elaborate on what you're asking regarding JS objects – AlexApps99 Aug 10 '22 at 19:54
  • Example @AlexApps99: I create `var rgba = new Uint8ClampedArray(4*1000*1000);` in my JavaScript code. How to pass a reference to this when calling WASM code? – Basj Aug 10 '22 at 21:32
  • You can't, really. WASM only has one "memory" so to speak, and you need to allocate within that memory when passing pointers back and forth (like you do in your code currently) – AlexApps99 Aug 11 '22 at 00:37