In this step we are going to add the first demo, Hello WebAssembly, our version of the Hello World.
Let's write a simple Hello WebAssembly function in C:
char* helloWebAssembly() {
return "Hello WebAssembly";
}
This function will give us a pointer to a char buffer containing the Hello WebAssembly
string. This buffer will be in the shared linear memory between WASM and JS.
Put it in WebAssembly Explorer, select C89 or C99 formats, and click on Compile.
You will get the WASM and the WebAssembly Text format (WAT) versions of your code.
The WAT is an human-readable version of the WASM code, where you can see the stack-based flow of WebAssembly code:
(module
(table 0 anyfunc)
(memory $0 1)
(data (i32.const 16) "Hello WebAssembly\00")
(export "memory" (memory $0))
(export "helloWebAssembly" (func $helloWebAssembly))
(func $helloWebAssembly (; 0 ;) (result i32)
(i32.const 16)
)
)
In your code you declared a helloWebAssembly()
function, in WASM this function is exported (in the (export "helloWebAssembly" (func $helloWebAssembly))
line) and made available to use in your JS environment.
Download the WASM file (rename it as HelloWebAssembly.wasm
), and put it in your app/HelloWebAssembly
folder.
In order to use the helloWebAssembly()
function, you need to load and instantiate your WASM code. Create a HelloWebAssembly.js
file that will do the job.
The first thing you will need in your JS file is a configuration object that you will use to pass information at the WASM instantiation (shared memory, imported functions...). For this first use case, it will be an empty object:
let configurationObject = {
}
Now we create a loadWASM()
function where we will load and instantiate the .wasm
file. As in the function we are doing lots of asynchronous operations (network fetching, transformations, WASM instantiation...), we will use an async
function to we able to use the async/await
pattern and write more linear code.
async function loadWASM() {
}
Let's begin by using the Fetch API to load the .wasm
file:
let response = await fetch('./HelloWebAssembly.wasm');
Then we take the HTTP response containing the WASM code and we trasnsform it in a byte array (ArrayBuffer in JS):
let arrayBuffer = await response.arrayBuffer();
And now we ask the browser to instantiate this ArrayBuffer as a WASM module, using the WASM API:
let wasmModule = await WebAssembly.instantiate(arrayBuffer, configurationObject);
As now we have an instantiated WASM module, we can look for the exported helloWebAssembly()
function.
let helloWebAssembly = await wasmModule.instance.exports.helloWebAssembly;
The whole JS file should look something like that:
let configurationObject = {
}
async function loadWASM() {
let response = await fetch('./HelloWebAssembly.wasm');
let arrayBuffer = await response.arrayBuffer();
let wasmModule = await WebAssembly.instantiate(arrayBuffer, configurationObject);
let helloWebAssembly = await wasmModule.instance.exports.helloWebAssembly;
}
Calling the helloWebAssembly()
will give us a pointer to the linear memory position where WASM has written the Hello WebAssembly message.
Let's do a function to get this pointer and iterate throught the shared memory to read the characters and build the string.
Let's begin by getting the shared memory buffer from the WASM instace:
let buffer = new Int8Array(wasmInstance.exports.memory.buffer);
And now, let's read the buffer from the position indicated by the pointer, and add the char
to the string:
let str = "";
for (i = pointer; buffer[i]; i++) {
str += String.fromCharCode(buffer[i]);
}
return str;
The function then looks like:
function readWASMbuffer(wasmInstance, pointer) {
let buffer = new Int8Array(wasmInstance.exports.memory.buffer);
let str = "";
for (i = pointer; buffer[i]; i++) {
str += String.fromCharCode(buffer[i]);
}
return str;
}
Now in the loadWASM()
function, we can call readWASMbuffer()
:
async function loadWASM() {
let response = await fetch('./HelloWebAssembly.wasm');
let arrayBuffer = await response.arrayBuffer();
let wasmModule = await WebAssembly.instantiate(arrayBuffer, configurationObject);
let helloWebAssembly = await wasmModule.instance.exports.helloWebAssembly;
let str = readWASMbuffer(wasmModule.instance, helloWebAssembly());
console.log(str);
}
And then, at the end of the file, we simply call loadWASM()
to launch the process:
loadWASM();
We have the C source, the compiled WASM, the JavaScript to instantiate it, let's end by creating a simple HTML to call this JavaScript.
Create a HelloWebAssembly.html
file in the same app/HelloWebAssembly
folder:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JUG SummerCamp 2020 WebAssembly Codelab - Hello WebAssembly</title>
</head>
<body>
<h1>JUG SummerCamp 2020 WebAssembly Codelab</h1>
<h2>Hello WebAssembly</h2>
<p>
We are using a `helloWebAssembly()`
function written in C, compiled into WASM
and loaded and instantiated via JS.
</p>
<p>
Look at the console, it it works you should see
a `Hello WebAssembly` message.
</p>
</body>
</html>
Link it from the index.html
file in the app
folder:
<ul>
<li>
<a href="./HelloWebAssembly/HelloWebAssembly.html">
Hello WebAssembly
</a>
</li>
</ul>
So now, in your HelloWebAssembly.html
file, you can simply load the HelloWebAssembly.js
file:
<script src='./HelloWebAssembly.js'></script>
And if you look at the console, you will see the Hello WebAssembly
message showing that the WASM was correctly loaded and called and that the message was passed in the shared memory.
In the next step we are going to use an online IDE for WASM, WebAssembly Studio. We will begin by redoing the Hello WebAssembly, but this time using the printf()
C function and its POSIX calls...