Building a WebAssembly Application

At the beginning of this chapter, you built and ran a simple Rust-based WebAssembly application using WebAssembly studio. In this section, you’ll install some tools on your machine that will allow you to compile and interpret WebAssembly modules.

Installing the WebAssembly Binary Toolkit

The WebAssembly Binary Toolkit (pronounced “wabbit”) is a general-purpose set of command-line tools you’ll use for building, examining, and troubleshooting WebAssembly modules. Whether you’re on Windows, Mac, or Linux, the first thing you’re going to need to install is CMake.[5]

Installing CMake varies widely across operating systems, so you’ll need to check the instructions specific to your platform. Come back and continue with the wabt installation once you’ve verified that CMake is up and running locally.

Next, follow the instructions on the wabt[6] GitHub repository README to complete the installation. You should be able to get the binaries from a release, but if you want to build the toolkit yourself, go ahead and simply run make install and then, after a fairly lengthy compilation process, you’ll see a bunch of executables in the bin directory beneath wherever you checked out the repository, including some or all of the following:

  • wasm2c
  • wasm2wat
  • wasm-interp
  • wasm-objdump
  • wasm-opcodecnt
  • wasm-validate
  • wast2json
  • wat2wasm
  • wat-desugar

The exact list of files may have changed since this book was published, but you’ll at the very least need wat2wasm and wasm-objdump for the next section where you will be writing real WebAssembly code. Make sure that you can execute both of these commands in a terminal or a shell and get the help text before continuing on.

On my machine, make install installed all of the compiled binaries in /usr/local/bin:

 -- Install configuration: "Debug"
 -- Installing: /usr/local/bin/wat2wasm
 -- Installing: /usr/local/bin/wast2json
 -- Installing: /usr/local/bin/wasm2wat
 -- Installing: /usr/local/bin/wasm2c
 -- Installing: /usr/local/bin/wasm-opcodecnt
 -- Installing: /usr/local/bin/wasm-objdump
 -- Installing: /usr/local/bin/wasm-interp
 -- Installing: /usr/local/bin/spectest-interp
 -- Installing: /usr/local/bin/wat-desugar
 -- Installing: /usr/local/bin/wasm-validate
 -- Installing: /usr/local/bin/wabt-unittests

Coding in the WebAssembly Text Format

Before I launch into an arguably dense and detailed section of fairly low-level coding, I want to answer the question of why. Why should you spend the effort learning how to write raw wast when you’ve got modern compilers that can do it for you?

I don’t like magic. I don’t like black boxes that do things I don’t understand. I am put off when I have to use a third-party system when I don’t understand its internal workings, or at least the motivations behind the decisions made in the building of the thing.

You could go forward and build powerful WebAssembly applications and live your life without knowing how it all works inside. However, I contend that this chapter and its contents provide the foundation on which a solid WebAssembly development practice should be built. You could, in theory, skip this chapter. But, as Morpheus told Neo in The Matrix, “You take the red pill—you stay in Wonderland and I show you how deep the rabbit-hole goes.”

When we get into the chapters on building alternative, non-browser hosts for WebAssembly modules, you will absolutely benefit from the knowledge and experience gained in this chapter.

The physical process of writing code in the WebAssembly Text Format (.wat files) is pretty easy—just open up your favorite text editor (VSCode, Atom, and others all have syntax highlighters for WebAssembly) and start typing.

As you now know, WebAssembly doesn’t have a string data type. This makes the canonical “Hello, World” sample a little difficult. In fact, the code required to actually produce that output in a browser is pretty complicated. If you try to go this route as your first exposure to writing wat, you’re likely to get discouraged.

For a simpler example that takes advantage of WebAssembly’s simple data types, let’s try creating a module with a single function that adds two numbers together and returns the result. Open up your text editor and create the add1.wat file with the following contents:

 (module
  (func $add (param $lhs i32) (param $rhs i32) (result i32)
  get_local $lhs
  get_local $rhs
  i32.add)
  (export "add" (func $add))
 )

The first expression at the top of every module is the module declaration. Note that there’s no module name, package name, or namespace. There are a number of other things that can go below this declaration, but in this case, you’re just creating a single function, marked by the func keyword. Each parameter to a function is indicated with an S-expression[7] (a parenthesized syntax for representing data or code as nested trees, first created for Lisp) in the following form:

 (param $parametername datatype)

In this case, there are two 32-bit integer parameters: $lhs and $rhs and the result will be a 32-bit integer.

The call to get_local retrieves a function-scoped value and places it on the Wasm execution stack. In this function, you’re calling get_local twice, putting the two parameters on the stack, and then calling i32.add. This adds the two values on the stack, pops them off, and puts the sum in their place. The value left on the stack at the end of the function is the default return value.

The order these instructions appear in the code can seem awkward. This is pure postfix (operator-last) notation and makes for some difficult reading. You don’t necessarily have to do it this way. There’s an alternative, pure S-expression prefix notation where you put the operation first and operands second, as you see in this code:

 (module
  (func $add (param $lhs i32) (param $rhs i32) (result i32)
  (i32.add
  (get_local $lhs)
  (get_local $rhs)
  )
  )
  (export "add" (func $add))
 )

In the second form, there are more parentheses, but the code is easier to read, especially as you get into more complicated patterns like those in the next chapter. I’ll be using the second form from now on.

To invoke this function in WebAssembly, you use the call keyword, as shown in this code that adds 9 and 5:

 (call $add (i32.const 5) (i32.const 9))

Using the Binary Toolkit

Now that you’ve got some source code, it’s time to compile it and do some exploration with the “wabbit” tools. Go to the directory where you created add1.wat and run the following commands (wat2wasm should be in your path):

 $ ​​wat2wasm​​ ​​add1.wat​​ ​​-o​​ ​​add.wasm
 $ ​​wat2wasm​​ ​​add2.wat​​ ​​-o​​ ​​add_sexpr.wasm

If everything goes according to plan, you’ll receive the “no news is good news” response—nothing. Next, use wasm-objdump to get a look at what actually made it into your compiled add.wasm file. The -x option adds extra detail to the output:

 $ ​​wasm-objdump​​ ​​add.wasm​​ ​​-x
 
 add.wasm: file format wasm 0x1
 
 Section Details:
 
 Type:
  - type[0] (i32, i32) -> i32
 Function:
  - func[0] sig=0 <add>
 Export:
  - func[0] <add> -> "add"

There’s some pretty good information here. You can tell that there’s a function type declared that accepts two integers and returns an integer. There’s a function at index 0 called add. Finally, there’s an export called add.

It is important to keep in mind that you can create functions in a WebAssembly module and not export them. In fact, functions are all private to the module and remain unexported until you explicitly write an export statement. If you run the same object dump command on add_sexpr.wasm, you’ll see the exact same type and exports.

There’s one last thing worth exploring—take a look at what the code looks like after a round trip when you convert the binary add_sexpr.wasm file to its corresponding text format:

 $ ​​wasm2wat​​ ​​add_sexpr.wasm​​ ​​-o​​ ​​roundtrip.wat
 (module
  (type (;0;) (func (param i32 i32) (result i32)))
  (func (;0;) (type 0) (param i32 i32) (result i32)
  get_local 0
  get_local 1
  i32.add)
  (export "add" (func 0)))

Here you can see that there’s a type created, a function of that data type, and then the instructions have reverted to the inverted stack notation rather than the instruction-first version. Finally, you can see that the export now just refers to the function at index 0 rather than a name. The separation of the symbol name and the function index is part of what ensures this kind of round-trip compilation is possible.

The reason this round trip is important is to show what WebAssembly might look like when produced by other languages, and, just in case you had any doubts, to prove that your code is truly portable and can be opened, disassembled, and re-generated at will. This also means anyone with access to these tools can see everything inside your WebAssembly module—a point to take note of when it comes to design and security in the future.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.255.169