Categories
Software | May 18, 2023

WebAssembly and the Future of Web Applications Development

Solomon Hykes, co-founder of Docker, published a tweet claiming that “If WASM-WASI existed in 2008, we wouldn’t have needed to create Docker. That’s how important it is.” 

Now and then, IT experts become enthusiastic about a new feature in web development. Currently, there’s a lot of buzz surrounding WebAssembly (WASM), a new method of delivering code to browsers and Internet of Things (IoT) devices. This is possible through the WebAssembly System Interface (WASI) and the WebAssembly Gateway Interface (WAGI), which enable access to browser devices and networking capabilities.

WebAssembly‘s initial stable version was released in 2016, but its origin can be traced back to 2011 when Mozilla initiated the asm.js project to execute highly optimized JavaScript code in the browser. Google soon joined the effort and by 2017, all major browsers supported the WebAssembly standard.

The impact of this technology is just starting to be revealed, and in this article, we aim to shed light on its key features, advantages, and disadvantages, and inspire you to try compiling your own applications to WebAssembly.

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust/C#, enabling deployment on the web for client and server applications.

Source

In simple terms, WebAssembly is another method of running code on the browser or any device that supports it. Think of it as a new type of application runtime that is available in the browser (and beyond, through the WebAssembly System Interface or WASI) and is a subset of normal JavaScript that can be optimized to run faster (smaller file size and no parsing required by the browser). The host can also quickly verify that the code is safe due to its low-level binary format.

The basic concept here is that you can compile code from other languages into WebAssembly format and run it on any browser or WASM-compatible device (IoT) without worrying about the underlying infrastructure.

Ah! It is a target for compilation… But what is it? 

A compilation target is a platform or architecture for which code is compiled, including the operating system, processor architecture, and other specific details of the target platform. It’s possible to write WebAssembly code and also to compile it from other compatible languages. The result is a unique bytecode WebAssembly format that can run on any browser or WebAssembly-enabled environment for any architecture or device.

In practical terms, when code is compiled to WebAssembly, it is converted to the instruction set of a stack-based virtual machine and stored in a binary format in a .wasm file. The machine code is designed to be easily compiled to real processors, allowing the .wasm file to be ingested by a runtime, such as a browser or Node.js, and converted into actual machine code that can be executed safely.

But, wait a minute! Writing code in a language to be compiled into a browser executable application? It sounds familiar, right? Java applets, Flash, Silverlight? Despite the similarities, there are many good reasons to explore WebAssembly, but the main difference is that it runs natively on modern browsers with near-native performance, without the need for additional software or extensions. This is a major improvement compared to the experiences with Java, Flash, or Silverlight, where installations were often required to access websites or play games.

Another awesome WebAssembly feature is that we can use our already known programming language to build artifacts to be compiled into WebAssembly and share them among any other WebAssembly targeted application, despite the programming language on which they build on. With this approach, we blur the line between front-end and back-end development by writing web-enabled controls with Rust, C# or Python and even JavaScript.

How does WebAssembly break into web platform

In an abstract way, we can represent the web platform with two major parts: a virtual machine (VM) that runs the web app’s code and a set of web APIs that web app’s can call in order to control the web browser (or any other device) functionality and “make the things happen”.

Before WebAssembly, the VM could load only JavaScript, and this worked well for us to solve most problems people have today on the web. But with modern computational needs, we run into performance problems when we try to use JavaScript for more intensive use cases, like 3D games, Virtual and Augmented Reality, computer vision, image/video editing, and a number of other fields that demand native performance. Mobile and other resource-constrained platforms can further amplify these performance bottlenecks. Additionally, the cost of downloading, parsing, and compiling very large JavaScript applications can be prohibitive.

With the advent of WebAssembly in browsers, the virtual machine that we talked about earlier will now load and run two types of code (JavaScript AND WebAssembly, which can call one another as required). WebAssembly JavaScript API wraps exported WebAssembly code with JavaScript functions that can be called normally, and WebAssembly code can import and synchronously call normal JavaScript functions. In fact, the basic unit of WebAssembly code is called a module and WebAssembly modules are symmetric in many ways to ES modules.

It’s a standard runtime for multiple languages, offering good isolation and fast start-up. There’s no need to run an entire operating system to host WebAssembly, all you need is to run a browser and host a JavaScript runtime.

Where’s Docker stand? 

Docker is a technology for wrapping up a program and its dependencies into a single package and then executing the application. Practically speaking, a Docker image is a set of snapshots of a file system. These snapshots are layered together to form a single environment that appears (to the application) to be a complete operating system. Docker images are lighter than virtual machine images. And when they are executed, Docker containers tend to require fewer system resources than virtual machines.

WebAssembly modules are compiled applications. Unlike Docker, a WebAssembly module does not come with a pre-packaged file system or other low-level operating system primitives. Instead, files, directories, environment variables, clocks, and other system resources can be attached directly to the WebAssembly module during startup on the browser. 

Because the WebAssembly format is platform-neutral, one single binary can be compiled and then executed on a variety of operating systems and architectures. Unlike Docker images, it is not limited to just Windows and Linux. WebAssembly binaries are runnable on all major operating systems. They can run on x86, x64, ARM, and other processor architectures as well.

Containers have some real strengths. Existing programs can run unaltered in containers. Programs that rely on heavy IO with the file system and require high degrees of control over underlay architecture may find containers more friendly than the more stringent WebAssembly security sandbox. And most importantly, servers that require access to the sockets layer will thrive in the container world.

And JavaScript?

WebAssembly is a different language from JavaScript, but it is not intended as a replacement. Instead, it is designed to complement and work alongside JavaScript, allowing web developers to take advantage of both languages’ strong points:

JavaScript is a high-level language, flexible and expressive enough to write web applications. It has many advantages, it is dynamically typed, requires no compile step, and has a huge ecosystem that provides powerful frameworks, libraries, and other tools.

WebAssembly, on the other hand, is a low-level assembly-like language with a compact binary format that runs with near-native performance and provides languages with low-level memory models, such as C++ and Rust, and high-level languages with garbage-collected memory models, like C# or GO. WebAssembly bytecode is not meant to be written by humans; we need a Wasm compiler to translate it. Luckily, most popular programming languages have their own Wasm compiler. You’ll find an updated list on github.com/awesome-wasm-langs 

The JavaScript ecosystem is huge. Just take a look at NPM; It’s massive. But it is also not for every topic. JavaScript is the first choice, while other languages come second.

Sometimes, you are faced with a problem, and while looking for libraries to solve it, you find them in C/C++ or in Rust even in C#, but not in JavaScript. So, you can either sit down and write your own JavaScript library, or your new option is to tap into other language’s ecosystem, using WebAssembly. 

To be fair, both JavaScript and WebAssembly have the same peak performance. They are equally fast, but it is much easier to stay on the fast path with WebAssembly than it is with JavaScript. It is actually way too easy sometimes to unknowingly and unintentionally end up in a slower path in your JavaScript engine than it is in the WebAssembly engine.

Why does everyone think WebAssembly is faster than JavaScript, then? 

We need to get a little “technical” in order to understand how this whole “falling off the fast path” happens. Let’s start talking a bit about the JavaScript engine using V8, Chrome’s JavaScript and WebAssembly engine artifacts naming, to illustrate the concept. It will be the same with Spider Monkey from Firefox.

JavaScript files and WebAssembly files have two different entry points to the engine or browser VM. JavaScript files get passed to Ignition, which is V8’s interpreter, and it reads the JavaScript file as text, interprets it and runs it. While it’s running it, it collects analytics data about how the code is behaving, and that is then used by TurboFan, the V8’s optimizing compiler, to generate machine codes.

WebAssembly, on the other hand, gets passed to Liftoff, V8’s WebAssembly compiler.

Once that compiler is done, the same TurboFan kicks in and generates optimizing codes. 

Now, there are some differences here: The obvious one is that the first stage has a different name and a different logo, but there’s also a conceptual difference, Ignition is the interpreter, and Liftoff is a compiler that generates machine codes. It would be an overgeneralization to say that machine code is always faster than interpreted code, but on average, it’s probably true. 

For JavaScript, the optimizing compiler only kicks in eventually, and the source code has to run and be observed before it can be optimized, because certain assumptions are made from the observations. Machine code is generated, and then the machine code is running. Once these assumptions don’t hold anymore, you have to fall back to the interpreter, because the compiler can’t guarantee that the machine code does the right thing anymore. That’s called a de-opt or de-optimization. 

With WebAssembly, TurboFan always kicks in right after the Liftoff compiler, and you always stay on the TurboFan output, you always stay on the fast path, and you can never get de-opted. That is where the misconception that WebAssembly is faster comes from, but in fact your code can easily get de-opted in JavaScript, and not in WebAssembly.

The Rust WebAssembly team did a really nice benchmark in both JavaScript and WebAssembly and ran it in different browsers. 

And yes! WebAssembly (green) is faster, but the main takeaway here is that JavaScript (red) tends to spread. It is kind of unpredictable in how long it takes, while WebAssembly is spot on, always the same time, even across browsers. WebAssembly delivers more predictable performance than JavaScript. 

Let’s see WebAssembly in action

We talked about how some big companies are using WebAssembly to bring their existing products, that they probably wrote in C++ or C, to the web. For example, that was the case with AutoCAD, a well-known product who had been working for years. Now they’ve put in the effort of compiling it to WebAssembly, and suddenly, with the same C++ code base, it was running in the browser. It’s mind-blowing to think about what we were capable of 5 or 6 years ago. 

Another example would be the Unity game engine or the Unreal game engine, which now support WebAssembly. These game engines often already have a kind of abstraction built in because you build your game, and then you compile it to PlayStation, XBox, or other system. Now, WebAssembly is just another target, and what is impressive is that the browser and WebAssembly are able to deliver the performance necessary to run these kinds of games. 

Browser-based Crypto Mining is now possible using WebAssembly binaries. Unethical for sure, but possible.

Security? 

As a binary format for executing code in a web environment, WebAssembly poses some security challenges and risks, especially with regard to running untrusted code in the browser. However, the security of WebAssembly is also improved by several key features and practices:

  • Sandboxing. It runs in a secure sandbox environment, isolated from the main web page, which helps to prevent attacks such as cross-site scripting (XSS) and cross-site request forgery (CSRF).
  • Memory Safety. It has built-in memory safety features that prevent buffer overflows, which are a common source of security vulnerabilities.
  • Validation. WebAssembly code is validated before it is executed, which helps to prevent malicious code from being executed.
  • Limited API Access. It has limited access to the web browser APIs, which reduces the attack surface and helps to prevent malicious code from accessing sensitive information.

Despite these security features, developers still need to be cautious and follow best practices when developing and deploying WebAssembly code. This includes verifying the origin of the code, avoiding unsafe or untrusted APIs, and keeping the code and libraries up-to-date to address any potential security vulnerabilities.

WebAssembly provides improved security compared to traditional web development, but it is not immune to security risks. Developers must be aware of these risks and take appropriate measures to protect their users and applications.

Nice, but where’s the catch?

WebAssembly has many advantages for web development, but there are also some disadvantages and limitations to consider:

  • Complexity. The source code is a binary format, considered low-level, which may pose difficulties for developers who are not familiar with Assembly language concepts.
  • Compatibility. It is supported by modern web browsers, but not all browsers support it, which can limit its adoption and usability.
  • Debugging. Debugging WebAssembly code can be more difficult compared to JavaScript, making it challenging to find and resolve issues in the code.
  • Size. The code can be larger than equivalent JavaScript code, which can increase the download and execution time, particularly on slow connections.
  • Maturity. WebAssembly is a relatively new technology, and some of its features and capabilities may still be evolving, leading to potential compatibility issues.

Developers should consider both the advantages and limitations of WebAssembly when deciding whether to use it for a project. While WebAssembly provides many benefits for web development, it is important to be aware of its challenges and limitations.

Conclusion

WebAssembly has the potential to greatly impact web development and developers. With its binary executable format and desirable features for various applications, WebAssembly provides a way to address gaps in the web platform that have not been filled by JavaScript. It offers increased performance and new opportunities for developers to leverage existing code from other languages and bring it to the web. As a result, developers can build more powerful and efficient web applications with a wider range of capabilities. 

With WebAssembly becoming more widely adopted, it is crucial for front-end developers to understand and incorporate this technology into their skill set. It’s time to broaden your skills by learning a new language other than JavaScript.

By Leonardo Buret

Software Engineer with over 20 years of experience in the industry, specializing in Microsoft .NET technology, DevOps and FullStack Development. I've been working at Patagonian since 2016, and I'm passionate about electronics and technology since birth. I'm also a father of two and Head coach of M8-9 Roca Rugby Club team!

Leave a Reply

Your email address will not be published. Required fields are marked *