How to Return a JavaScript String from a Webassembly Function

How can I return a JavaScript string from a WebAssembly function

WebAssembly doesn't natively support a string type, it rather supports i32 / i64 / f32 / f64 value types as well as i8 / i16 for storage.

You can interact with a WebAssembly instance using:

  • exports, where from JavaScript you call into WebAssembly, and WebAssembly returns a single value type.
  • imports where WebAssembly calls into JavaScript, with as many value types as you want (note: the count must be known at Module compilation time, this isn't an array and isn't variadic).
  • Memory.buffer, which is an ArrayBuffer that can be indexed using (among others) Uint8Array.

It depends on what you want to do, but it seems like accessing the buffer directly is the easiest:

const bin = ...; // WebAssembly binary, I assume below that it imports a memory from module "imports", field "memory".
const module = new WebAssembly.Module(bin);
const memory = new WebAssembly.Memory({ initial: 2 }); // Size is in pages.
const instance = new WebAssembly.Instance(module, { imports: { memory: memory } });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);

If your module had a start function then it got executed at instantiation time. Otherwise you'll likely have an export which you call, e.g. instance.exports.doIt().

Once that's done, you need to get string size + index in memory, which you would also expose through an export:

const size = instance.exports.myStringSize();
const index = instance.exports.myStringIndex();

You'd then read it out of the buffer:

let s = "";
for (let i = index; i < index + size; ++i)
s += String.fromCharCode(buffer[i]);

Note that I'm reading 8-bit values from the buffer, I'm therefore assuming the strings were ASCII. That's what std::string would give you (index in memory would be what .c_str() returns), but to expose something else such as UTF-8 you'd need to use a C++ library supporting UTF-8, and then read UTF-8 yourself from JavaScript, obtain the codepoints, and use String.fromCodePoint.

You could also rely on the string being null-terminated, which I didn't do here.

You could also use the TextDecoder API once it's available more widely in browsers by creating an ArrayBufferView into the WebAssembly.Memory's buffer (which is an ArrayBuffer).


If, instead, you're doing something like logging from WebAssembly to JavaScript, then you can expose the Memory as above, and then from WebAssembly declare an import which calls JavaScript with size + position. You could instantiate your module as:

const memory = new WebAssembly.Memory({ initial: 2 });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);
const instance = new WebAssembly.Instance(module, {
imports: {
memory: memory,
logString: (size, index) => {
let s = "";
for (let i = index; i < index + size; ++i)
s += String.fromCharCode(buffer[i]);
console.log(s);
}
}
});

This has the caveat that if you ever grow the memory (either through JavaScript using Memory.prototype.grow, or using the grow_memory opcode) then the ArrayBuffer gets neutered and you need to create it anew.


On garbage collection: WebAssembly.Module / WebAssembly.Instance / WebAssembly.Memory are all garbage collected by the JavaScript engine, but that's a pretty big hammer. You likely want to GC strings, and that's currently not possible for objects which live inside a WebAssembly.Memory. We've discussed adding GC support in the future.

How to return a string (or similar) from Rust in WebAssembly?

WebAssembly only supports a few numeric types, which is all that can be returned via an exported function.

When you compile to WebAssembly, your string will be held in the module's linear memory. In order to read this string from the hosting JavaScript, you need to return a reference to its location in memory, and the length of the string, i.e. two integers. This allows you to read the string from memory.

You use this same technique regardless of whichever language you are compiling to WebAssembly. How can I return a JavaScript string from a WebAssembly function provides a detailed background to the problem.

With Rust specifically, you need to make use of the Foreign Function Interface (FFI), using the CString type as follows:

use std::ffi::CString;
use std::os::raw::c_char;

static HELLO: &'static str = "hello from rust";

#[no_mangle]
pub fn get_hello() -> *mut c_char {
let s = CString::new(HELLO).unwrap();
s.into_raw()
}

#[no_mangle]
pub fn get_hello_len() -> usize {
HELLO.len()
}

The above code exports two functions, get_hello which returns a reference to the string, and get_hello_len which returns its length.

With the above code compiled to a wasm module, the string can be accessed as follows:

const res = await fetch('chip8.wasm');
const buffer = await res.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(module);

// obtain the module memory
const linearMemory = instance.exports.memory;

// create a buffer starting at the reference to the exported string
const offset = instance.exports.get_hello();
const stringBuffer = new Uint8Array(linearMemory.buffer, offset,
instance.exports.get_hello_len());

// create a string from this buffer
let str = '';
for (let i=0; i<stringBuffer.length; i++) {
str += String.fromCharCode(stringBuffer[i]);
}

console.log(str);

The C equivalent can be seen in action in a WasmFiddle.

Pass string from C++ to JS using emscripten

When calling a raw WebAssembly function like that you only basic types are supported. In this case you are returned a pointer which is just a number (pointer are number in JS just like they are in C/C++). You can use that pointer to read bytes out of WebAssembly memory and produce a JS string from them using, for example, UTF8ToString(number), or you can use one of the higher level binding systems such as embind to take care of it for you.

See https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html for more information.

Calling a function from JavaScript

I found the solution, that I need something to detect and confirm that wasm had been loaded and ready for processing, same the one used in JS to check if the document is ready:

if (document.readyState === 'complete') {
// The page is fully loaded
}

// or

document.onreadystatechange = () => {
if (document.readyState === 'complete') {
// document ready
}
};

So, as wasm initiation function in my code is async I used the below in JS:

<!DOCTYPE html>
<html lang="en">
<head>
<title>WASM</title>
<!-- WASM -->
<script src="http://localhost:8080/www/wasm_exec.js"></script>
<script src="http://localhost:8080/www/loadWasm.js"></script>
</head>
<body>
</body>
<script>
(async () => {
try {
await init();
alert("Wasm had been loaded")
console.log(multiply(5, 3));
} catch (e) {
console.log(e);
}
})();

/***** OR ****/
(async () => {
await init();
alert("Wasm had been loaded")
console.log(multiply(5, 3));
})().catch(e => {
console.log(e);
});
/*************/
</script>
</html>

This helped me been sure that the document is ready to process and call the wasm function.

The wasm loading function simply became:

async function init(){
const go = new Go();
const result = await WebAssembly.instantiateStreaming(
fetch("http://localhost:8080/www/main.wasm"),
go.importObject
);
go.run(result.instance);
}


Related Topics



Leave a reply



Submit