By Christopher J. Wilson

This article discusses assembly language. To learn about an assembly language is to learn about the “mind” of the machine. The things which are easily expressed in assembly reflect, though it may be tenuous, the design of the underlying architecture.

 

Save 37% off The Transparent Web with code fccwilson at manning.com.

 

Assembly

To get a feel for what assembly is all about, let’s look at some x86 code. C makes a stop at assembly before the ‘assembler’ converts that into machine code. If we ask our friendly compiler (I’m using clang) to ‘show its work’ we can peek behind the curtain:

Listing 1. Calculating an important constant

 
  
 int main(int argc, char *argv[]) { 
   return 41 + 1;
 }
  
 

 Compile this with: clang -S add.c

This code generates the following assembly, but some has been omitted for clarity.

Listing 2. The generated assembly code

 
  
 Ltmp2:
         .cfi_def_cfa_register %rbp
         movl $42, %eax ; 
         movl $0, -4(%rbp)
         movl %edi, -8(%rbp)
         movq %rsi, -16(%rbp)
         popq %rbp
         retq
         .cfi_endproc
 
 

 Here we can see the ‘important constant’ that we were calculating. It’s stored in the 32-bit ‘accumulator’ (a register where we put “answers”).

The reason there’s a literal 42 ($42) in the assembly code, rather than something more like add 41 1, is that the compiler has performed ‘constant folding’ for us. This is an optimization — which is on by default — where the compiler computes constant expressions at compile time and then uses that in the code. This is a theme with compiling to low-level languages, where there are opportunities for various optimizations, and compilers have been designed to take advantage of many of them.

Some things to note about this code. It’s as low-level as it reasonably gets. Everything is about pushing around bytes. We store things in registers, load values from memory, and can only perform simple operations like add/substract/shift and so on.

I only show this to demonstrate that WebAssembly is, by comparison, at a higher level.

High-level assembly, WebAssembly

WebAssembly has a few goals:

  1. Be a good compiler target

  2. Be faster than JavaScript with a compact binary representation

As of this writing, WebAssembly looks to be on the cusp of being introduced into multiple browsers. This means that some design aspects are firming up as others undergo changes. One design choice that looks stable for now, but which may change in the future, is the text format. What this means is that the WebAssembly equivalent to the x86 above is provisional.

WebAssembly Text Format

Let’s look at a sample of WebAssembly text representation now:

Listing 3. WebAssembly text format (.WAST) for a short program

 
 
 (module
   (export "at_most_five" (func $at_most_five))
   (export "main" (func $main))
  
   (func $main
     (call $at_most_five (i64.const 5))
     (drop))
  
   (func $at_most_five (param i64) (result i64)
     (if i64 (i64.gt_u (get_local 0) (i64.const 5))
       (i64.const 5)
       (get_local 0))))
  
 (assert_return (invoke "at_most_five" (i64.const 3)) (i64.const 3))
 (assert_return (invoke "at_most_five" (i64.const 9)) (i64.const 5))
 (assert_return (invoke "at_most_five" (i64.const 5)) (i64.const 5))
 
 

This module contains two functions; the first is $main, which calls the next (and primary) function, $at_most_five. $at_most_five accepts a 64-bit integer and returns another 64-bit integer. If the argument is greater than five (using ‘unsigned’ integers) then the constant five is returned, otherwise the argument is returned. The parameter to the function is unnamed. When we refer to it in the body of $at_most_five, we do it using the ‘local variable index’. Because there’s only one local variable, it’s at index zero.

WebAssembly’s text format uses an S-expression syntax (all those parentheses) to unambiguously show the nesting of expressions. In fact, with the S-expression syntax, you’re writing the ‘abstract syntax tree’ (AST) of the code. This can be a little awkward for us humans, but it’s great for computers.

WebAssembly Binary format

The text format is good for reading, but what’s executed in the browser is the ‘binary format’. We can check out what this looks like. We’ll use the ‘WebAssembly Binary Toolkit’ (https://github.com/WebAssembly/wabt) to convert the text format into the binary format.

The toolkit comes with a few programs. The one we’re interested in is wast2wasm. This program converts the text format (extension .wast) to binary format (extension .wasm):

Listing 4. Converting from text to binary (.wast to .wasm)

 
 
 $ ~/code/wabt/out/wast2wasm gt_5.wast --output gt_5.wasm
 > ls -l gt_5.*
 -rw-r--r--@ 1 chris admin 76 Feb 8 21:26 gt_5.wasm
 -rw-r--r--@ 1 chris admin 499 Feb 7 21:30 gt_5.wast
 
 

Neat, now we have a ‘bona fide’ WASM file! Notice that by converting the module to an equivalent binary format, we’ve made it more than six times smaller. The combo of reduced size and binary encoding have a nice effect on load-time. It’s faster to transfer fewer bytes over the network. It’s also quicker to decode a binary format than parse a textual format like JavaScript. Let’s crack open our .wasm file and see what’s inside (using xxd gt_5.wasm):

Listing 5. Binary contents of gt_5.wasm

 
 
 0000000: 0061 736d 0d00 0000 0109 0260 0000 6001  .asm.......`..`.
 0000010: 7e01 7e03 0302 0001 0717 020c 6174 5f6d  ~.~.........at_m
 0000020: 6f73 745f 6669 7665 0001 046d 6169 6e00  ost_five...main.
 0000030: 000a 1902 0700 4205 1001 1a0b 0f00 2000  ......B....... .
 0000040: 4205 5604 7e42 0505 2000 0b0b            B.V.~B.. ...
 
 

We can clearly pick out some of the features from the text format. We can see main and at_most_five are exported as-is, which makes sense because we’ll have to be able call them from JavaScript later.

The file starts with a ‘magic number’ which comprises the bytes 00 61 73 6d, or 0asm in ASCII. WebAssembly stores numbers in little-endian format and that 32-bit number is 0x6d736100. The next number is the version, 0x0000000d (13). The full disassembly of the bytecode gets a little involved after this, but as a peek, let’s look at the code for the main function:

Listing 6. Detailed look at the bytecode for the “main” function

 
 
 07                        first function is 7 bytes long
 00                        zero local variables
 42 05                     (i64.const 5)
 10 01                     (call 01) [i.e. $at_most_five]
 1a                        (drop)
 0b                        (end function)
 
 

We can see the brief (8-byte) function body here. This code follows a few sections of set-up, which defines things like how many functions are in the module and how many and what type of argument each takes. For our main function, we start out by declaring that the next seven bytes that follow are part of the function. Next, there are no local variables. We get on with the code as we saw from the gt_5.wast source. Notice that this is a stack-oriented bytecode. We first put the constant five on the stack, and ‘then’ we call function at index 0x01 ($at_most_five). Because we don’t care about the result, we drop it. Lastly, there’s a byte that marks the end of the block 0x0b (this also marks the end of `loop`s, and `if`s).

This sort of layout is typical of binary formats; there’s a byte that says what section you’re in, how many bytes long that section is, and so on. We didn’t see it here, but even the integers are compressed!

That’s all for this article. For more, download the free first chapter of The Transparent Web and see this Slideshare Presentation.