• Wasm
  • Javascript

Introduction to Web Assembly

After the downfall of browser plugins such as Flash Player, JavaScript became the dominant option for in-browser computation. With the introduction of Web Assembly (Wasm), it is now possible to compile code written in other languages to run in the browser.

Written by Jacob Gelman

Assembly languages

Before exploring Web Assembly, it is important to understand what an assembly language is more generally. In simple terms, an assembly language is a low-level text-based programming language in which programs are a series of instructions understood by a particular type of CPU.

There is no single language called "assembly"; since different CPU architectures support different instruction sets, each CPU architecture needs its own assembly language. For example, modern Intel chips use the x86 architecture whereas Apple's M-series chips use ARM64, each of which has its own assembly language. An assembly program is fed into an assembler to be converted to machine code, a binary representation of the same program ready to be executed by the CPU.

In modern times, it is neither practical nor advisable to write programs in assembly for systems that do not require bare-metal performance; high-level languages allow programs to be written in a manner that is easier, safer, and more portable.

Enter Web Assembly (Wasm)

Despite its name, WebAssembly (Wasm) is not an assembly language in the traditional sense since it is not specific to a CPU architecture. According to webassembly.org:

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.

In other words, Wasm serves as a "common ground" between various programming languages and native machine code, allowing the browser to perform the final translation.

Wasm in the browser has two primary benefits: portability and performance. Deploying programs across different platforms still remains a challenge. JavaScript is a highly portable language; the same JavaScript program can be executed in the same way in any standard web browser. WebAssembly opens up the same portability of JavaScript to other languages.

In terms of performance, JavaScript arrives at the browser in source form and lacks static typing, necessitating an initial parsing step and ongoing optimizations during runtime. In contrast, WebAssembly arrives at the browser in a binary format that can be translated into machine code and executed with minimal runtime overhead—allowing WebAssembly to vastly outperform JavaScript for certain types of tasks.

Writing Wasm

In practical applications, Wasm serves as a compilation target for other languages (e.g. C/C++, Rust, etc.) and thus isn't typically written or examined directly. However, for gaining an understanding of Wasm at the fundamental level, we will do so in this article.

WebAssembly is used to refer to both to the WebAssembly binary format (.wasm) and the WebAssembly text format (.wat). The text format is used by humans to write or examine Wasm, and the binary format is what is executed by the browser. A Wasm module can be written by hand in a .wat file and then converted into a binary .wasm file using the wat2wasm tool from the WebAssembly Binary Toolkit.

Example: Fahrenheit to Celsius

JavaScript

Let's examine a simple JavaScript module exporting a single function, f2c, which converts temperatures in Fahrenheit to Celsius:

convert.jsmain.js
export function f2c(f) {
return (f - 32) * (5 / 9);
}
convert.jsmain.js
export function f2c(f) {
return (f - 32) * (5 / 9);
}

Wasm

Now, let's implement a functionally-equivalent module in WebAssembly and perform the same conversion.

convert.watmain.jsconvert.wasm
(module
(func (export "f2c") (param $f f32) (result f32)
local.get $f
f32.const 32.0
f32.sub
f32.const 5.0
f32.mul
f32.const 9.0
f32.div
)
)
convert.watmain.jsconvert.wasm
(module
(func (export "f2c") (param $f f32) (result f32)
local.get $f
f32.const 32.0
f32.sub
f32.const 5.0
f32.mul
f32.const 9.0
f32.div
)
)
Instruction breakdown

Examine the f2c function in convert.wat. Since Wasm is a "stack-based virtual machine," each instruction operates on the stack. Let's break down what occurs during the course of execution when the function is called (assume 212 is passed as the argument):

InstructionStack (bottom to top)Description
local.get $f[212.0]Get value of argument $f (212.0), push onto stack
f32.const 32.0[212.0 32.0]Push constant 32.0 onto stack
f32.sub[180.0]Pop top two values from stack, take their difference, and push result back onto stack
f32.const 5.0[180.0 5.0]Push constant 5.0 onto stack
f32.mul[900.0]Pop top two values from stack, take their product, and push result back onto stack
f32.const 9.0[900.0 9.0]Push constant 9.0 onto stack
f32.div[100.0]Pop top two values from stack, take their quotient, and push result back onto stack

The value on the top of the stack is taken as the return value, so the result is 100 as expected (212°F = 100°C).