In the beginning, God created the Bit and the Byte. And from those he created the Word.
And there were two Bytes in the Word, and nothing else existed. And God separated the One from the Zero, and he saw it was good.
Assembly language
A long, long time ago, developers were using machine codes to program computers. It was hard to keep all of them in mind, easy to make a mistake, and almost impossible to read. After struggling with machine codes, they created mnemonic codes to refer to machine code instructions aka Assembly language. The assembler was responsible for translating assembly language to machine codes.
Higher-level abstractions
The complexity of the systems kept growing and so higher-level programming languages were created to hide this complexity behind abstractions. The first widely used high-level general-purpose programming language was FORTRAN, invented in IBM in 1954. Then we got BASIC, C, C++, and many more. We are still using compilers to translate high-level languages into machine codes. The problem is, that compiler produces architecture-specific code, so if you have compiled your C program to x86 architecture, you cannot run it on AMR processor (due to a different set of available CPU instructions).
To solve this problem, developers have created another set of abstractions - bytecode (aka intermediate language) and virtual machines. Bytecode is the instruction set of the virtual machine, that is architecture-independent. Translating bytecode to a CPU specific instructions is a responsibility of a virtual machine. In this way, we can write once, run anywhere! C# and Java are good examples of this concept.
The Internet Era
In the 90s the Internet was born, first web pages, first browsers, first dynamic pages. In May 1995, Brendan Eich wrote the prototype of JavaScript language in 10 days (we all know the consequences of that) and it was shipped in Netscape Navigator. Since then, we are living in the Internet Era.
We know that JavaScript is not the most efficient nor fastest programming language, despite the effort engineers in Google, Mozilla, Apple, Microsoft, and other companies put into JS engines we still see a lot to improve there. Another problem we have - there is only one language to develop client-side web applications. Yeah, I know about TypeScript, but is still traspilies in JavaScript code before it can be executed in the browser.
We need to go deeper - Web Assembly
To solve these problems, W3C together with guys from Mozilla, Google, Microsoft, and Apple created a specification for WebAssembly - an open standard for binary code and textual assembly language to enable high-performance applications on web pages. Now you can compile your C code (as well as C++ and Rust) into WebAssembly bytecode, and execute it inside the virtual machine running in the browser. The VM is designed to be faster to parse than JavaScript, as well as faster to execute and to enable very compact code representation.
How does it work?
For now, all interactions with WebAssembly code is done via JavaScript code, so when you write WebAssembly code, you should define which functions you want to export and import so later you can call them.
Here we import function i
and export function e
:
;; simple.wasm
(module
(func $i (import "imports" "i") (param i32))
(func (export "e")
i32.const 42
call $i))
After the page is loaded in the browser, you can load the .wasm file as a regular resource and do the following to call the wasm function:
- Get the
.wasm
bytes into a typed array orArrayBuffer
- Compile the bytes into a
WebAssembly.Module
- Instantiate the
WebAssembly.Module
with imports to get the callable exports - Call function from the module
Another important concept is Linear memory - a low-overhead "shared memory" between JS and WebAssembly. You can pass the data to do some calculation on a WebAssebmly side and pass the result back to display it on the web page.
Benchmarks
PSPDFKit has created a real-world benchmark based on PSPDFKit for Web. In this benchmark, we will compare the real-world code performance in WASM and JavaScript in three major browsers: Chrome 75, Firefox 68 and Edge 44. Lower result is better:
As you can see, WebAssembly is showing better score in all browsers, the biggest difference between JS and WASM is in Firefox, Edge is the slowest of all.
Browser support, limitations, and future plans
At the moment, the MVP version of WebAssembly is supported in all major browsers (including iOS and Android). You can compile C, C++ and Rust code to Web Assembly.
Microfost has compiled mono runtime into Web Assembly, so you can run .NET code in the browser (but it's a great story for another post, which will bring us even deeper since you are running IL code in VM (CLR) which runs in another VM (WebAssebly) in the browser). On top of that, Microsoft is developing Blazor - a component-based client-side platform to write C# web applications that run in the browser (bye-bye JavaScript).
As well, right now you cannot interact with DOM from WebAssembly code, so all DOM interactions should be done via proxy JS function.
There are future plans to allow WebAssembly modules to be loaded just like ES6 modules (using <script type='module'>
) as well as adding multithreading support, garbage collection, and DOM interaction.
Conclusion
Since most of the modern applications are running in the browsers, I think it's a great step forward to bring other languages in the game. I can't wait to see the next versions of WebAssembly standard and the variety of features it will bring us.