It's time to talk about WebAssembly text format. The file extension is “.wat” so I'll call it Wat below.
Every wasm is a module. A simple Wat can be:
(module)
It's a legal module which can be loaded and executed in browser after compiling. Of course, it does nothing. The compiled “.wasm“ contains the content shown below. It's generated by the wat2wasm -v
command of WABT.
0000000: 0061 736d ; WASM_BINARY_MAGIC
0000004: 0100 0000 ; WASM_BINARY_VERSION
The beginning 0061736D
is a magic number. It's \0asm
(in 4 bytes) which identifies the binary as a wasm binary. 01000000
is the version number (little-endian). The current version number is 1.
A module contains the following sections:
import
export
start
global
memory
data
table
elements
function 與 code
I'll talk about these sections in later documents. For now, just grab a little knowledge of several fundamental sections. Let's add a main
function first.
(module
(func $main)
)
The $main
function doesn't define any parameter and the result type. After compiling, the wasm file contains the following content.
0000000: 0061 736d ; WASM_BINARY_MAGIC
0000004: 0100 0000 ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01 ; section code
0000009: 00 ; section size (guess)
000000a: 01 ; num types
; type 0
000000b: 60 ; func
000000c: 00 ; num params
000000d: 00 ; num results
0000009: 04 ; FIXUP section size
; section "Function" (3)
000000e: 03 ; section code
000000f: 00 ; section size (guess)
0000010: 01 ; num functions
0000011: 00 ; function 0 signature index
000000f: 02 ; FIXUP section size
; section "Code" (10)
0000012: 0a ; section code
0000013: 00 ; section size (guess)
0000014: 01 ; num functions
; function body 0
0000015: 00 ; func body size (guess)
0000016: 00 ; local decl count
0000017: 0b ; end
0000015: 02 ; FIXUP func body size
0000013: 04 ; FIXUP section size
It contains Type, Function and Code sections. Each section has their own fields.
Wat can use type
to define the Type section; however, the Type section is often generated automatically according to the content of Wat. For example, you can use wasm2wat
to convert wasm to wat.
(module
(type (;0;) (func))
(func (;0;) (type 0)))
As you can see, (;0;)
is a comment. The text between (;
and ;)
will be ignored. In the above, those two (;0;)
mean the index 0 respectively.
(type (;0;) (func))
defines the function type which has no parameter and returns nothing. Every type has an index which starts with 0. The type information shown by wat2wasm -v
is:
; type 0
000000b: 60 ; func
000000c: 00 ; num params
000000d: 00 ; num results
0000009: 04 ; FIXUP section size
(func (;0;) (type 0))
defines a function. Every function has an index which starts with 0. The type of the function is the index 0. That is (type (;0;) (func))
.
The text format allows you to name functions because using numeric indices to refer to items can be confusing and annoying. After compiling, the binary will contain only the integer.
You have only one function so the indices of type and function are both 0s. Let's add an import
.
(module
(import "env" "helloworld" (func $helloworld))
(func $main)
)
Now the wasm contains:
0000000: 0061 736d ; WASM_BINARY_MAGIC
0000004: 0100 0000 ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01 ; section code
0000009: 00 ; section size (guess)
000000a: 01 ; num types
; type 0
000000b: 60 ; func
000000c: 00 ; num params
000000d: 00 ; num results
0000009: 04 ; FIXUP section size
; section "Import" (2)
000000e: 02 ; section code
000000f: 00 ; section size (guess)
0000010: 01 ; num imports
; import header 0
0000011: 03 ; string length
0000012: 656e 76 env ; import module name
0000015: 0a ; string length
0000016: 6865 6c6c 6f77 6f72 6c64 helloworld ; import field name
0000020: 00 ; import kind
0000021: 00 ; import signature index
000000f: 12 ; FIXUP section size
; section "Function" (3)
0000022: 03 ; section code
0000023: 00 ; section size (guess)
0000024: 01 ; num functions
0000025: 00 ; function 0 signature index
0000023: 02 ; FIXUP section size
; section "Code" (10)
0000026: 0a ; section code
0000027: 00 ; section size (guess)
0000028: 01 ; num functions
; function body 0
0000029: 00 ; func body size (guess)
000002a: 00 ; local decl count
000002b: 0b ; end
0000029: 02 ; FIXUP func body size
0000027: 04 ; FIXUP section size
As shown above, it has type 0 and type 1 now. The same type shares a type x. type 0 is the type of $helloworld
and type 1 is the type of $main
.
The above shows Import sections and relative fields. The imported function has an index 0 so $main
has an index 1. Let's convert “.wasm“ back to “.wat“.
(module
(type (;0;) (func (param i32)))
(type (;1;) (func))
(import "env" "log" (func (;0;) (type 0)))
(func (;1;) (type 1)))
Through this way, you can understand the binary encoding of wasm. If you want to know detailedly, Binary Encoding describes the binary encoding of the WebAssembly modules.
Let's define the function body and the start
section.
(module
(import "env" "helloworld" (func $helloworld))
(func $main
call $helloworld
)
(start $main)
)
call
calls function directly. You can specify the callee by an index or a name. If the module has a start node defined, the function it refers should be called by the loader after the instance is initialized. That is, after loading and instantiating the module, $main
will be called. Then, the $main
function will call $helloworld
.
After compiling the above Wat, the wasm conatins:
(module
(type (;0;) (func))
(import "env" "helloworld" (func (;0;) (type 0)))
(func (;1;) (type 0)
call 0)
(start 1))
You've known the basic of a module. I'll explain more in later documents. Now, let's use the above module to say “Hello World” through the following HTML and JavaScript.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="console"></div>
<script>
const console = document.getElementById('console');
const importObj = {
env: {
helloworld() {
console.innerHTML = 'Hello World';
}
}
};
WebAssembly.instantiateStreaming(fetch('helloworld.wasm'), importObj);
</script>
</body>
</html>
Because you have (import "env" "helloworld" (func $helloworld))
in the Wat, the imported object must have an env
property. The env
object must own a helloworld
function.
In the example, $helloworld
will set the innerHTML
property of the DOM of <div id="console"></div>
to 'Hello World'
so you can see the 'Hello World'
shown in the browser.