Company Logo

Back to all posts

Understanding WebAssembly: The Fundamentals
WebAssembly Legacy Modernisation

Martin Nyaga

WebAssembly

Understanding WebAssembly: The Fundamentals


Welcome to the first entry in our series, Porting Native Apps to the Web with WebAssembly: A Complete Guide!

Introduction


In this series, you’ll discover how WebAssembly (Wasm) can help transform desktop or native apps into fast, capable web applications. We’ll explore the fundamentals of Wasm, discuss architectural considerations, and provide practical strategies for porting native applications to the web with Wasm.

Whether you’re an engineering leader, architect, or developer, this guide will help you develop the right mental models to approach a porting effort and to set you up for success.

To start, let’s look at the history and design of WebAssembly, its role in modern web browsers, and why it’s a game-changer for web applications.

The Birth of WebAssembly


Going Beyond JavaScript

The web’s ubiquity has driven a demand for increasingly advanced applications in the browser. In response, browsers have continuously evolved, adding new capabilities to match the growing complexity of modern web apps.

However, JavaScript — the web’s only built-in programming language — has historically posed challenges to this advancement. Among other limitations, it limits the ability for web apps to leverage existing software and libraries developed in languages other than JavaScript.

This led to the “compile-to-JavaScript” movement that started in the 2010s — an ecosystem of tools and techniques aimed at compiling code written in other programming languages to JavaScript, making it runnable in the browser.

From Asm.JS to Wasm

Among the compile-to-JavaScript technologies that emerged, one of the most prominent was Asm.js — a well defined subset of JavaScript carefully designed to be an efficient compilation target for software written in languages like C/C++.

By using only specific JavaScript features, Asm.js could provide significant performance benefits over hand-written JavaScript. For the first time, C/C++ code could be compiled to Asm.js and run in the browser, and provide better performance than JavaScript code. This unlocked a new world of interesting use cases. The impact of Asm.js, driven by its novel design, was so significant, that it prompted some browsers to develop specific optimizations 1,2 for Asm.js code.

Asm.js demonstrated the potential of compiling native code for the web, but its reliance on JavaScript ultimately limited its capabilities. Despite its clever optimizations to achieve impressive performance, it became clear that a faster, more flexible, more capable and more long-term solution could be realised by a purpose built technology — one that could take inspiration from the design of Asm.js, but was not constrained to JavaScript as its runtime 3, and that could be standardised and widely adopted at the browser level 4.

Wasm is Born

In 2015, WebAssembly (Wasm) was announced as a cross browser initiative to develop and standardise a purpose-built, assembly-like language for web browsers, with the express goal of being able to run code compiled from other languages in the browser.

Today, Wasm is supported by all major browsers, has become an open standard, and has a rapidly growing ecosystem of tools and technologies built around it. Its standardisation ensures longevity, stability and a commitment to backward compatibility, making it an excellent platform on which to base architectural decisions today.

As of January 2025, WebAssembly is used on over 4.5% of websites with adoption growing exponentially. Major companies — including Adobe, Figma, AutoCAD, Google, Microsoft, Shopify and more — are investing heavily in WebAssembly, and using it to power some of their most widely used products.

What is WebAssembly?


Wasm is a low level, assembly-like language optimised for safe and efficient execution in modern web browsers. Its binary instruction format prioritises compact, fast, and portable code that can achieve near native performance5.

On the browser, Wasm is designed to run alongside and inter-operate with JavaScript, extending the possibilities for client-side applications.

We will review its design at a high level here, but you can find full design details at webassembly.org.

Wasm Bytecode

Wasm is a bytecode — a set of instructions in a binary format that runs on a virtual machine (VM). All modern browsers today include a mature Wasm VM by default.

It is conceptually similar to other languages that have a bytecode executed in a virtual machine (e.g. Java or .NET), with the major difference being that Wasm code is not constrained to any single source language; it can be generated from a wide variety of languages.

Wasm code can be disassembled into a text representation (WebAssembly Text Format, or WAT):

;; Module start
(module
  ;; Add function
  (func $add (param i32 i32) (result i32)
    local.get 1
    local.get 0
    i32.add)
  ;; Sub function
  (func $sub (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.sub)
  ;; Exports
  (export "add" (func $add))
  (export "sub" (func $sub))
)

An example of a simple Wasm module in its text format (WAT). This module defines and exports an add and a sub function.

Wasm is a Compilation Target

In contrast to JavaScript, Wasm is not intended to be written directly (although it can be). Wasm is a compilation target; this means that source code from other programming languages is compiled to Wasm code using a compiler. The resulting Wasm code can then be executed on a Wasm runtime, for example, the browser.

This is one of the most powerful aspects of Wasm - it provides a language agnostic way to run code written in almost any programming language in a fast, platform-independent way.

Language Support

A large number of languages today have production ready Wasm tooling.

  • C/C++: Can be compiled to JS/Wasm using Cheerp, Emscripten
  • Go: Supports the GOARCH=wasm target by default
  • Rust: Supports a wasm target by default (—target wasm32-unknown-unknown). Tools like wasm-bindgen and wasm-pack provide better ergonomics for Wasm development
  • C# .NET: Blazor is a full stack or client-only framework that compiles into a combination of Wasm and JavaScript. F# has a similar framework built on top of Blazor called Bolero
  • Kotlin: Has alpha support for Wasm
  • Swift: SwiftWasm is a mature community project to support Wasm compilation
  • Python: Pyodide is a complete python distribution compiled to Wasm
  • And several more.

Running a Wasm Module on the Browser


Wasm code is packaged and distributed as .wasm files called Wasm modules. The following example demonstrates a simple WebAssembly module that imports a console.log function, and exports a single function (exportedFunc). The module is loaded and instantiated through the JavaScript code below, which proceeds to execute the exported function.

(module
  (func $log (import "console" "log") (param i32))
  (func (export "exportedFunc")
    i32.const 42
    call $log))

A sample Wasm module with a single exported function, that logs 42 to the console when called.

const importObject = {
  console: { log: (arg) => console.log(arg) },
};

WebAssembly.instantiateStreaming(
  fetch("example.wasm"),
  importObject
).then(
  (obj) => obj.instance.exports.exportedFunc(),
);

JS code to instantiate and interact with a Wasm module.

The Anatomy of a Wasm Module


The key high level elements of a Wasm module are summarised below.

A diagram of a Wasm Module

High level structure of a Wasm module.

  • Code: Or, functions. These are implementations of logic in the Wasm module as stateless blocks of Wasm instructions. These functions can accept arguments and can return values when called.
  • Imports: Wasm modules can import definitions (memory, functions etc.) from the host (browser) environment. This allows Wasm modules to use functionality provided by the host. For example, a Wasm module might import a function from JS to draw an array of pixels to a canvas, log to the console, or send a notification to the user.
  • Exports: Wasm modules can export definitions (memory, functions, etc.) to the host environment. This allows them to expose useful functionality to JavaScript as callable functions. For example, a Wasm module might export a function to perform a specific statistical calculation, compute the next frame in a game loop, or run an ML inference model.
  • Memory: Wasm modules have access to a linear, growable, untyped memory. Think of this as a large array of bytes that both JavaScript and Wasm can access.
  • Other internal sections (types, tables, globals, element segments, data segments, start function, custom sections), whose details are omitted here for brevity.

Memory, Code, Imports, and Exports form the essential components to understand Wasm’s anatomy from an architectural perspective.

Passing data between Wasm and JavaScript

On the browser, Wasm modules need to communicate with JavaScript code to provide receive useful input or provide output.

Generally speaking, Wasm functions can only accept and return simple numeric types (like integers and floats)6. To handle complex data (like strings or objects), both JavaScript and Wasm can use the shared linear memory to communicate.

For example, to pass a string from JavaScript to Wasm, you can:

  1. Encode the string as bytes
  2. Write those bytes into a specific location in the shared linear memory.
  3. Call a Wasm function, giving it the location of the string and its length.

Wasm code can then interpret these bytes into a readable string.

This mechanism requires that the host environment and the Wasm module both know how to correctly interpret the bytes in memory. Handling this manually can be complex, and so the necessary “glue” code is typically generated by tooling rather than written by hand.

Memory

Wasm virtual machines are 32 bit, which has implications for porting applications as we’ll see later in the series. This also means that Wasm modules can address up to 4GB (or 232 bytes) of linear memory. This is sufficient in most cases, but there are active proposals to support 64 bit and larger memory sizes7 for more demanding use-cases.

We will discuss further details about JS/Wasm interop and the Wasm memory model in a future post in the series.

Design Benefits


Wasm’s design offers a few key benefits, as explicitly discussed in its design goals.

Speed

Wasm can execute at near native speed, enabling otherwise unachievable performance on client-side web applications. Its performance often surpasses JavaScript as a result of its design:

  • Wasm uses a compact binary format, which can be downloaded, decoded, and executed more efficiently than JavaScript’s text format.
  • As a compilation target, it is often optimized ahead of time (AOT), while JavaScript primarily relies on just-in-time (JIT) compilation for optimisation.
  • Supports SIMD (single instruction, multiple data) instructions, enabling vector computations.

Wasm is not only fast, but also predictable:

  • The linear memory model in Wasm, combined with explicit memory management (in source languages like C/C++/Rust) allows strict control over memory layout and allocation.
  • JavaScript, on the other hand, relies on the browser’s garbage collection, which can lead to unpredictable pauses in code execution, with no control over when these occur.

Altogether, Wasm’s design makes it easier to write well performing code by default. For example, Figma saw a 3x improvement in load times of heavy files by switching to WebAssembly.

Portability

A Wasm module makes no assumptions about the platform on which it executes (including the browser). By design, its dependencies are made explicit in the form of imports. This makes Wasm modules executable on any spec-compliant Wasm runtime, provided the required imports can be supplied at runtime.

This design feature of Wasm has led to the development of many use-cases for Wasm outside the browser, despite browser based use-cases being its initial motivation. Wasm can now power not just browser applications, but also server-side runtimes, IoT devices and more.

Safety

Wasm code is executed in a memory-safe sandbox, isolating the running program from all other programs on the host system. By design, Wasm programs cannot interfere with the execution of, or memory from any other running code on the system, and can only access its well defined linear memory.

Wasm code can only escape the sandbox through imports explicitly supplied by the host, providing a clear access control mechanism for Wasm code. For browser use cases, this means that access to the DOM, any web APIs and the underlying system is only through explicitly provided JavaScript functions. In this way, Wasm code is subject to a permissions model that is as strict (or stricter) than JavaScript.

Summary


Wasm has matured into a foundational platform, on top of which a new generation of tooling, frameworks and application architectures is actively being developed, with a multitude of production ready apps being shipped today. It is transforming web development with its speed, portability, and safety.

In this introduction, we’ve explored Wasm’s history, core features, and benefits.In future articles, we will dive deeper into practical applications of Wasm, and specifically, practical ways to use Wasm to bring native applications to the web, with patterns and tips to help you succeed.

Footnotes


  1. MS Edge developed a specialized pipeline for asm.js code.

  2. Optimizations were introduced in V8 specifically with asm.js in mind.

  3. A good explanation about why WebAssembly is faster than asm.js can be found here.

  4. Why create a new standard when there is already asm.js? Some commentary can be found here on the official WebAssembly website.

  5. WebAssembly’s high level goals are listed here.

  6. Recent proposals extend Wasm’s primitive types new opaque “reference types” to allow passing more complex objects across the Wasm <-> host boundary, but the details of this are omitted for brevity.

  7. Memory64 is an active proposal to support 64 bit pointers and larger memory in Wasm. While implemented in some browsers, this is not stable at the time of writing.

Back to all posts

Let's collaborate

Ready to discuss your next project?

Get in touch