Hello Module


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.