The WebAssembly text toolkit is a toolkit to peek into a Wasm module in a textual format. Although the Wasm module is binary, this toolkit allows you to convert Wasm into a human-readable text format.
Create a text file in WebAssembly text format (wat) and generate a Wasm file from it
Generate a wat file from a Wasm file
Generate a dump of the Wasm file
The wat2wasm Utility
This tool handcrafts Wasm files in a text format and then generates the actual Wasm file. Since this is a human-readable format, it allows for a deeper understanding of the Wasm layout under the hood.
Figure 3-1 is the build screenshot for the WABT make process.
It starts with the func keyword followed by parameters it accepts or the return type.
i32: a 32-bit integer
i64: a 64-bit integer
f32: a 32-bit float
f64: a 64-bit float
In this example, you see a definition of the function with a i32 (32-bit integer) return type.
The next part of the function definition is the function body. Here the expression is i32.const 100. This expression pushes the value 100 onto the stack. (Recall from Chapter 1 that WebAssembly is a stack-based architecture.)
The last step is the export of the defined function. The export step is crucial because it makes the function visible to the host. The guest module exposes the function with index 0 (this module only defines one function) to the host runtime (Node.js in this case).
Name the hellowat2wasm function, which the host uses when invoking the function within the Wasm module.
Time for loading and execution of the Wasm file.
Let’s use a Node.js-based runtime to load the Wasm file. Please make sure you have Node.js installed on the machine.
Create an index.js file in the wat directory.
In this code, you load the example.wasm file into a memory buffer and then instantiate the module. Once you have instantiated the module, invoke the hellowat2wasm function, which was exported by the Wasm module.
You should see 100 as the output.
Let’s proceed to a little more advanced wat program, where you create a wat file to add two integers.
Next, use the wat2wasm tool to generate the add.wasm file.
Now it is time to consume this Wasm module from the Node.js runtime.
Create an add.js file.
The code loaded the add.wasm file into the memory buffer, created an instance, and invoked the add method on the exported Wasm module.
If all goes well, you should see 110 being printed on the console.
Now let’s build a small calculator using the wat file, which defines three functions and exposes all three functions to the host.
Now use the wat2wasm tool to generate the Wasm file from the calc.wat file.
This program loads the calc.wasm file and invokes the exported functions one by one.
So far, you have seen how to create a wat file that exported three functions, which the host runtime can then consume. Next, let’s dig a bit more deeply into the WebAssembly text format, where you see how one function in a module can invoke another function in the same module.
You already know this function. Next, let’s create a wrapper around this function.
Create a file called wrap.wat.
The preceding code defines an add function, which gets two integers from the stack, adds them, and pushes the result onto the stack. Next, wrap the function with another function, where you put 56 and 44 onto the stack and then call the wrapped add function. Finally, expose this wrapper as add1 to the host.
You can see the add1 function is invoked, which prints the result as 100.
So far, you have seen how to consume an exported function from a Wasm module and how the Node.js runtime consumes it. Next, let’s import a function from the Node.js host and invoke it within the Wasm module.
The preceding code creates a Wasm module with an exported add1 function. This function puts two values (56 and 44) on the stack and makes a call to an add function, which, in turn, invokes the add function defined on the host under the namespace example. The import statement here is telling to import add function from an example namespace. Instead of calling $add, you can also invoke the add function by call 0 instruction as 0 is the index of the add function.
Let’s now explore WebAssembly memory. It is important to know how to isolate Wasm modules and deal with types other than integer and floats. As you know, Wasm only allows four basic types (i32, i64, f32, and f64) to be passed between the host and guest.
Knowledge of the Wasm memory model allows you to encode complex types between host and guest. You see some examples of this in later chapters.
In WebAssembly, memory is a linear array of bytes that can grow over time. WebAssembly provides instructions like i32.load and i32.store to read and write from the specific memory area. The host creates this linear memory array and provides it to the Wasm module. The Wasm module code only has visibility within that memory area, and it remains isolated from other modules running on the same host. This is how Wasm achieves memory sandboxing.
If the host is a JavaScript-based Node.js, think of this linear memory as an ArrayBuffer. To encode complex types like the string, you need to represent/encode this string into a byte array within the memory allocated for the Wasm module.
Import one function and one variable from the runtime host. The function is the example namespaced log function, and the variable is the JavaScript namespaced mem variable. The idea here is that you create a string in Wasm memory (a linear memory provisioned by the host) from the guest and then pass the offset and length to the host. The host can then read from that offset to the length and then decode the string and print it.
Define a data segment in the wat file, and its content is “Hello Wat”. The data segment allows you to write a string into the Wasm memory at a given offset. A logme function exports two constants (offset and length) onto the stack and invokes the log function internally. This log function passes the offset and length to the host, which reads from the Wasm module memory at the offset until the length and prints the content.
Convert the string.wat file to a string.wasm file using the wat2wasm tool.
The example.log function reads the memory array at a specific offset and to a specific length. Offset and length are provided by the guest, which has put the “Hello Wat” string into that array.
The memory array itself.
The host’s job is to pass this memory to the guest. This is done via importObject, which is imported into the Wasm module. The Wasm module puts the string into the memory and passes the offset and length of the memory the host has allocated it. The guest invokes the example.log function on the host. This function reads the memory at offset and length and prints the contents.
This is how you can achieve communication between the host and the guest.
Tables
Let’s look at the tables section in the wat file. You know that functions within a module can only be invoked by passing the function’s index to the call instruction. An instruction called call_indirect in Wasm provides a layer of indirection to the function calls. With the call instruction, the index passed is the index of the exact location of the function in memory, where call_indirect allows to invoke the function by an indirection by a structure called a table. The table holds the actual function location and maps this index to a virtual index used by the host code. This allows you to do late binding to the function call; for example, hosts that are compiled. For example, a Rust binary can decide at runtime which function to call by passing the index of the function to be invoked. This is a powerful mechanism provided by WebAssembly for the dynamic runtime invocation of a function.
Let’s look at a wat file.
In the wat file, you added (table 3 funcref) just below the module section. This tells the Wasm host to create a module of table size of 3. Here, funcref specifies that elements of this table are references to the functions.
The add, subtract, and multiply functions are defined as usual. The next section in the wat file is the elem, which defines the functions referenced by the table. In our case, the add, subtract, and multiply functions are part of the elem section.
The (type $return_i32 (func (param i32 i32)(result i32))) section specifies the type of the function to be invoked. In this case, since all three functions have similar signatures, you keep one type.
This function takes three integer parameters. The first is the index of the function in the table. The other two are the parameters to be operated upon by the function to be invoked, like add, multiply, or subtract.
local.get 1, local.get 2, and local.get 0 push the index of the function and two other values on the stack. By default, the call_indirect instruction pops the top value from the stack. Because you push the function’s index on the stack, it picks up, and the actual function is invoked. The callee then pops the other two values from the stack and invokes the needed operation.
Create a Wasm file from the wat file using the wat2wasm utility.
You can see how the individual functions are invoked using a specific index in a Wasm module table. You can take this index as input or based on some logic and dynamically call a function.
These tables can also be dynamically created by the host and can also be shared between different modules. However, this kind of dynamic linking is beyond the scope of this book.
The wasm2wat Utility
Now that you’ve seen the wat2wasm tool, let’s look at a tool that reverses the process, which means taking a Wasm file and generating a wat file. This can help debug a Wasm file.
Take the calc.wasm file generated in the previous section and use the wasm2wat executable against it. The wasm2wat executable is located in the same location as the wat2wasm binary (under the wabt/bin directory).
This tool can help one to debug and look at some possible issues in the Wasm file.
Object Dump Using wasm-objdump
wasm-objdump is a utility that allows you to do a dump of the WebAssembly file. It prints information about the Wasm binary file. You can pass flags to print different details, like headers, full content, and function bodies.
Apart from the utilities, there are more utilities like wasm-interp, wasm-decompile, and wasm-strip. These are left for you to experiment with.
Summary
This chapter discussed utilities like wat2wasm, where you wrote the wat files by hand and learned their segments and structure. You also saw how functions could be exported and imported using the wat format. The chapter also covered other utilities like wasm2wat and utilities for generating the dump of the Wasm file.