Where are you using WebAssembly?
Wasm promises to let developers build once and run anywhere. Are you using it yet?
At work, for production apps
At work, but not for production apps
I don’t use WebAssembly but expect to when the technology matures
I have no plans to use WebAssembly
No plans and I get mad whenever I see the buzzword
Security / Software Development / WebAssembly

Demystifying WebAssembly: What Beginners Need to Know

Explore the basics of WebAssembly, including how it works with web browsers, how to compile code to Wasm, and the best practices for writing secure code. 
Jun 2nd, 2023 5:35am by
Featued image for: Demystifying WebAssembly: What Beginners Need to Know

WebAssembly (Wasm) is a binary format that was designed to enhance the performance of web applications. It was created to address the limitations of JavaScript, an interpreted language that can lead to slower performance and longer page load times.

With WebAssembly, developers can compile code to a low-level binary format that can be executed by modern web browsers at near-native speeds. This can be particularly useful for applications that require intensive computation or need to process large amounts of data.

Compiling code to Wasm requires some knowledge of the programming language and tools being used, as well as an understanding of the WebAssembly format and how it interacts with the browser environment. However, the benefits of improved performance and security make it a worthwhile endeavor for many developers.

In this article, we will explore the basics of WebAssembly, including how it works with web browsers, how to compile code to Wasm, and best practices for writing secure WebAssembly code.

We will also discuss benchmarks and examples that illustrate the performance benefits of using WebAssembly compared to traditional web technologies. You will learn how WebAssembly can be used to create faster, more efficient and more secure web applications.

The Benefits of Using WebAssembly

As mentioned previously, WebAssembly offers faster execution times and improved performance compared to JavaScript, due to its efficient binary format and simpler instruction set. It enables developers to use other languages to create web applications, such as C++, Rust, and others.

Wasm also provides a more secure environment for running code on the web. In addition to performance, there are several other benefits to using it in web development:

Portability. Wasm is designed to be language-agnostic and can be used with multiple programming languages, enabling developers to write code in their preferred language and compile it to WebAssembly for use on the web.

Security. It provides a sandboxed environment for executing code, making it more secure than executing untrusted code directly in the browser.

Interoperability. Wasm modules can be easily integrated with JavaScript, allowing developers to use existing libraries and frameworks alongside new WebAssembly modules.

Accessibility. It can be used to bring applications written in native languages to the web, making them more accessible to users without requiring them to install additional software.

WebAssembly can be represented in two forms: binary format and textual format.

The binary format is Wasm’s native format, consisting of a sequence of bytes that represent the program’s instructions and data. This binary format is designed to be compact, efficient and easily parsed by machines. The binary format is also the form that is typically transmitted over the network when a Wasm program is loaded into a web page.

The textual representation of WebAssembly, on the other hand, is a more human-readable form that is similar to assembly language. The textual format is designed to be more readable, and easier to write and debug, than the binary format. The textual format consists of a series of instructions, each represented using a mnemonic and its operands, and it can be translated to the binary format using a WebAssembly compiler.

The textual format can be useful for writing and debugging Wasm programs, as it allows developers to more easily read and understand the program’s instructions. Additionally, the textual format can be used to write programs in high-level programming languages that can then be compiled to WebAssembly, which can help to simplify the process of writing and optimizing Wasm programs.

What Is the WebAssembly Instruction Set?

WebAssembly has a simple, stack-based instruction set that is designed to be easy to optimize for performance. It supports basic types such as integers and floating-point numbers, as well as more complex data structures such as vectors and tables.

The Wasm instruction set consists of a small number of low-level instructions that can be used to build more complex programs. These instructions can be used to manipulate data types such as integers, floats and memory addresses, and to perform control flow operations such as branching and looping.

Some examples of WebAssembly instructions include

  • i32.add: adds two 32-bit integers together.
  • f64.mul: multiplies two 64-bit floating-point numbers together.
  • i32.load: loads a 32-bit integer from memory.
  • stores a 32-bit integer into memory.
  • br_if: branches to a given label if a condition is true.

WebAssembly instructions operate on a stack-based virtual machine, where values are pushed onto and popped off of a stack as instructions are executed. For example, the i32.add instruction pops two 32-bit integers off the stack, adds them together, and then pushes the result back onto the stack.

This is significant because it improves the efficiency and simplicity of execution.

A stack-based architecture allows for the efficient execution of instructions. Since values are pushed onto the stack, instructions can easily access and operate on the topmost values without the need for explicit addressing or complex memory operations. This reduces the number of instructions needed to perform computations, resulting in faster execution.

Also, the stack-based model simplifies the design and implementation of the virtual machine. Instructions can be designed to work directly with values on the stack, eliminating the need for complex register management or memory addressing modes. This simplicity leads to a more compact and easier-to-understand instruction set.

The small number of instructions in the WebAssembly instruction set makes it easy to optimize and secure. Because the instructions are low-level, they can be easily translated into machine code, making Wasm programs fast and efficient.

Additionally, the fixed instruction set means that those programs are not prone to the same types of security vulnerabilities that can occur in more complex instruction sets.

How Does Wasm Work with the Browser?

WebAssembly code is loaded and executed within the browser’s sandboxed environment. It is typically loaded asynchronously using the fetch() API and then compiled and executed using the WebAssembly API.

Wasm can work with web browsers to provide efficient and secure execution of code in the client-side environment. Its code can be loaded and executed within a web page using JavaScript, and can interact with the Document Object Model (DOM) and other web APIs.

When a web page loads a WebAssembly module, the browser downloads the module’s binary file and compiles it to machine code using a virtual machine called the WebAssembly Runtime. The WebAssembly Runtime is integrated into the browser’s JavaScript engine and translates the Wasm code into machine code that can be executed by the browser’s processor.

Once the WebAssembly module is loaded and compiled, the browser can execute its functions and interact with its data. Wasm code can also call JavaScript functions and access browser APIs using JavaScript interop, which allows seamless communication between WebAssembly and JavaScript.

WebAssembly’s efficient execution can provide significant performance benefits for web applications, especially for computationally intensive tasks such as data processing or scientific calculations. Additionally, Wasm’s security model, which enforces strict memory isolation and control flow integrity, can improve the security of web applications and reduce the risk of security vulnerabilities.

How to Compile Code to WebAssembly

To compile code to WebAssembly, developers can use compilers that target the Wasm binary format, such as Clang or Emscripten.

Developers can also use languages that have built-in support for WebAssembly, such as Rust or AssemblyScript.

To compile code to WebAssembly, you will need a compiler that supports generating Wasm output. Here are some general steps:

  1. Choose a programming language that has a compiler capable of generating WebAssembly output. Some popular languages that support WebAssembly include C/C++, Rust and Go.
  2. Install the necessary tools for compiling code to WebAssembly. This can vary depending on the programming language and the specific compiler being used. For example, to compile C/C++ code to WebAssembly, you may need to install Emscripten, which is a toolchain for compiling C/C++ to WebAssembly.
  3. Write your code in the chosen programming language, making sure to follow any specific guidelines for WebAssembly output. For example, in C/C++, you may need to use special Emscripten-specific functions to interact with the browser environment.
  4. Use the compiler to generate WebAssembly output from your code. This will typically involve passing in command-line options or setting environment variables to specify that the output should be in Wasm format.

Optionally, optimize the WebAssembly output for performance or size. This can be done using tools such as Wasm-opt or Wasm-pack. Load the generated WebAssembly code in your application or website using JavaScript or another compatible language.

Wasm modules are typically loaded asynchronously using the fetch() API.

Once the module is loaded, it can be compiled and instantiated using the WebAssembly API.

To load and run a WebAssembly module, you first need to create an instance of the module using the WebAssembly.instantiateStreaming or WebAssembly.instantiate method in JavaScript. These methods take the URL of the WebAssembly binary file as an argument and return a Promise that resolves to a WebAssembly.Module object and a set of exported functions.

Once you have the WebAssembly.Module object and exported functions, you can call the exported functions to interact with the Wasm module. These functions can be called just like any other JavaScript function, but they execute WebAssembly code instead of JavaScript code.

Here’s an example of how to load and run a simple WebAssembly module in JavaScript:

In this example, we use the fetch API to load the WebAssembly binary file as an ArrayBuffer, and then pass it to the WebAssembly.instantiate method to create an instance of the WebAssembly module.

We then get the exported function add from the instance, call it with arguments 1 and 2, and print the result to the console.

It’s important to note that WebAssembly modules run in a sandboxed environment and cannot access JavaScript variables or APIs directly.

To communicate with JavaScript, WebAssembly modules must use the WebAssembly.Memory and WebAssembly.Table objects to interact with data and function pointers that are passed back and forth between the WebAssembly and JavaScript environments.

Performance Advantages of WebAssembly

WebAssembly can improve performance compared to other web technologies in a number of ways.

First, Wasm code can be compiled ahead-of-time (AOT) or just-in-time (JIT) to improve performance. AOT compilation allows WebAssembly code to be compiled to machine code that can be executed directly by the CPU, bypassing the need for an interpreter.

JIT compilation, on the other hand, allows WebAssembly code to be compiled to machine code on the fly, at runtime, which can provide faster startup times and better performance for code that is executed frequently.

Additionally, WebAssembly can take advantage of hardware acceleration, such as SIMD (single instruction, multiple data) instructions, to further improve performance. SIMD instructions allow multiple operations to be performed simultaneously on a single processor core, which can significantly speed up mathematical and other data-intensive operations.

Here are some benchmarks and examples that illustrate the performance benefits of using WebAssembly.

Game of Life. A cellular automaton that involves updating a grid of cells based on a set of rules. The algorithm is simple, but it can be computationally intensive. The WebAssembly version of the algorithm runs about 10 times faster than the JavaScript version.

Image processing. Image processing algorithms can be highly optimized using SIMD instructions, which are available in WebAssembly. The Wasm version of an image processing algorithm can run about three times faster than the JavaScript version.

AI/machine learning. Machine learning algorithms can be highly compute-intensive, making them a good candidate for WebAssembly. TensorFlow.js is a popular JavaScript library for machine learning, but its performance can be improved by using the WebAssembly version of TensorFlow. In some benchmarks, the Wasm version runs about two times faster than the JavaScript version.

Audio processing. WebAssembly can be used to implement real-time audio processing algorithms. The Web Audio API provides a way to process audio data in the browser, and the WebAssembly version of an audio processing algorithm can run about two times faster than the JavaScript version.

Wasm Security Considerations

WebAssembly supports various security policies that allow web developers to control how their code interacts with the browser’s resources. For example, Wasm modules can be restricted from accessing certain APIs or executing certain types of instructions.

WebAssembly code runs within the browser’s sandboxed environment, which limits its access to the user’s system.

Wasm code is subject to the same-origin policy, which restricts access to resources from a different origin (i.e., domain, protocol and port). This prevents Wasm code from accessing sensitive resources or data on a website that it shouldn’t have access to.

WebAssembly also supports sandboxing through the use of a memory-safe execution environment. This means that Wasm code cannot access memory outside of its own allocated memory space, preventing buffer overflow attacks and other memory-related vulnerabilities.

Additionally, WebAssembly supports features such as trap handlers, which can intercept and handle potential security issues, and permissions, which allow a module to specify which resources it needs access to.

Furthermore, Wasm can be signed and verified using digital signatures, ensuring that the code has not been tampered with or modified during transmission or storage. WebAssembly code can also be executed in a secure execution environment, such as within a secure enclave, to further enhance its security.

Best Practices for Writing Secure Wasm Code

When writing WebAssembly code, there are several best practices that developers can follow to ensure the security of their code.

Validate inputs. As with any code, it is important to validate inputs to ensure that they are in the expected format and range. This can help prevent security vulnerabilities such as buffer overflows and integer overflows.

Use memory safely. WebAssembly provides low-level access to memory, which can be a source of vulnerabilities such as buffer overflows and use-after-free bugs. It is important to use memory safely by checking bounds, initializing variables and releasing memory when it is no longer needed.

Avoid branching on secret data. Branching on secret data can leak information through side channels such as timing attacks. To avoid this, it is best to use constant-time algorithms or to ensure that all branches take the same amount of time.

Use typed arrays. WebAssembly provides typed arrays that can be used to store and manipulate data in a type-safe manner. Using typed arrays can help prevent vulnerabilities such as buffer overflows and type confusion.

Limit access to imported functions. Imported functions can introduce vulnerabilities if they are not properly validated or if they have unintended side effects. To limit the risk, it is best to restrict access to imported functions and to validate their inputs and outputs.

Use sandboxes. To further isolate WebAssembly code from the rest of the application, it can be run in a sandboxed environment with restricted access to resources such as the file system and network. This can help prevent attackers from using WebAssembly code as a vector for attacks

Keep code minimal. Write minimal code with clear boundaries that separate untrusted and trusted code, thus reducing the attack surface area.

Avoid using system calls as much as possible. Instead, use web APIs to perform operations that require input/output or other system-related tasks.

Use cryptographic libraries. Well-known cryptographic libraries like libsodium, Bcrypt, or scrypt can help secure your data.

Group Created with Sketch.
TNS owner Insight Partners is an investor in: Pragma, fermyon.
THE NEW STACK UPDATE A newsletter digest of the week’s most important stories & analyses.