Learning WebAssembly #4: Wasm Memory and Working with Strings

Dealing with strings and other complex data types via Wasm memory mechanism.


In the previous parts of this series, we were manipulating scalar numeric data exclusively. More complex data types such as strings do not have their representation in Wasm itself and must be modeled via Wasm memory mechanism.

Wasm Memory

Memory in Wasm is a large array of bytes that can grow over time. One has direct access to the raw bytes of the memory. A memory object can be provided by initialization or created automatically.

The memory object can be shared and parties (like Wasm and JS) can pass values back and forth.

Because Wasm memory is practically just a JavaScript object, it itself is tracked by the garbage collector. This prevents the system from running out of memory.

The memory object is isolated from other memory objects owned by other modules.

Imported Memory from JavaScript

We can create a Memory object in JavaScript and have the WebAssembly module import the memory:

(module
  (import "js" "mem" (memory 1))
  (data (i32.const 0) "Hello")
  
  (func (export "memtest") 
        (result i32)
    i32.const 5  ;; data length
    return))

The module expects a memory object imported under the name js.mem. Data Hello is stored to the memory object on the position 0.

Then, JavaScript code creates a memory object and reads the data of the returned length:

var mem = new WebAssembly.Memory({initial:1});

WebAssembly
  .instantiateStreaming(fetch('memory.wasm'), { 
    js: { mem } 
  })
  .then(({instance}) => {
    var length = instance.exports.memtest();
    var bytes = new Uint8Array(mem.buffer, 0, length);
    var string = new TextDecoder('utf8').decode(bytes);

    console.log(string);  // "Hello"
  });

Raw data is read from the memory object in Uint8Array representation and decoded into UTF8 afterward.

The expected result is printed to the dev console:

» Hello

Exported Memory from Wasm

Albeit importing memory from JavaScript is more typical, we can also have the WebAssembly module create the memory and export it to JavaScript:

(module
  (memory $m 1)
  (export "mem" (memory $m))
  (data (i32.const 0) "Hello")
  
  (func (export "dataLength") 
        (result i32)
    i32.const 5  ;; data length
    return))

Now, the memory object is exported and accessible in JavaScript by the export name:

WebAssembly
  .instantiateStreaming(fetch('memory2.wasm'))
  .then(({instance}) => {
    var memory = instance.exports.mem;

    var length = instance.exports.memtest();
    var bytes = new Uint8Array(memory.buffer, 0, length);
    var string = new TextDecoder('utf8').decode(bytes);

    console.log(string);  // "Hello"
  });

Further Steps

We have learned how to read a string from the memory object shared between Wasm and JavaScript code.

In the next part of this series, we will take a deeper look at the execution of Wasm in a browser and interaction with JavaScript.

Stay tuned!