Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access WebAssembly linear memory from C/C++

I'm writing a small C program intended to be compiled to wasm w/ emcc and run in a web browser. Because wasm exported functions can only accept simple number values as parameter inputs and return values, I need to share memory between the JavaScript API and the compiled WebAssembly code in order to access more complex data types like strings or char arrays. The problem is that I can't for the life of me figure out how to access WebAssembly linear memory from inside of my C program.

My ultimate goal is to be able to read strings initialized in JavaScript inside of my C program, and then also read strings that are modified/initialized in my C program back in the web browser's JavaScript code.

Here is a basic example of what I'm trying to do:

main.js

const importObject = {
  'env': {
    'memoryBase': 0,
    'tableBase': 0,
    'memory': new WebAssembly.Memory({initial: 256}),
    'table': new WebAssembly.Table({initial: 0, element: 'anyfunc'})
  }
}

// using the fetchAndInstantiate util function from
// https://github.com/mdn/webassembly-examples/blob/master/wasm-utils.js
fetchAndInstantiate('example.wasm', importObject).then(instance => {

      // call the compiled webassembly main function
      instance.exports._main()
      console.log(importObject.env.memory)
})

example.c

int main() {
    // somehow access importObject.env.memory 
    // so that I can write a string to it
    return 0;
}

This question gets me part of the way there, however, I still don't understand how to read/write from the WebAssembly memory buffer in my C code.

like image 852
Brannon Avatar asked Oct 14 '17 19:10

Brannon


2 Answers

What you need to do is communicate a location within the WebAssembly module that both the C and JavaScript code read / write to.

Here's a simple example that adds a number to each element in array. This is the C code:

const int SIZE = 10;
int data[SIZE];

void add(int value) { 
  for (int i=0; i<SIZE; i++) {
    data[i] = data[i] + value;
  }
}

int* getData() {
  return &data[0];
}

The important thing in the above code is the int* getData() function, which returns a reference to the start of the data array. When compiled to WebAssembly, this will return an integer which is the location of the data array within the modules linear memory.

Here's an example of how to use it:

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

// obtain the offset to the array
var offset = wasmInstance.exports.getData();

// create a view on the memory that points to this array
var linearMemory = new Uint32Array(wasmInstance.exports.memory.buffer, offset, 10);

// populate with some data
for (var i = 0; i < linearMemory.length; i++) {
  linearMemory[i] = i;
}

// mutate the array within the WebAssembly module
wasmInstance.exports.add(10);

// log the results
for (var i = 0; i < linearMemory.length; i++) {
  log(linearMemory[i]);
}

You can see the complete example in this WASM fiddle.

like image 183
ColinE Avatar answered Nov 20 '22 16:11

ColinE


There are 2 opposite approaches:

  1. Declare all your data elements as globals and add helper functions to return begining address for each.
  2. Don't use globals, allocate desired Memory in JS, calculate offsets and pass those offsets to called functions. In this case available memory will start from 0 (zero).

(1) is ok for simple things. (2) is suitable when your data size is unknown.

like image 2
Vitaly Avatar answered Nov 20 '22 18:11

Vitaly