1. What is webassembly?

Historically, virtual machines used to load only JavaScript, and that’s good enough for us, because JavaScript is powerful enough to solve most of the problems people have on the web today. However, when trying to apply JavaScript to areas such as 3D games, virtual reality, augmented reality, computer vision, image/video editing, and a host of other areas that require native performance, we run into performance issues (copied above from mdn).

webassembly, which we analyze semantically as web + assembly, is an assembly language applied on the web, it is not a replacement for javascript, I think they are complementary, using webassembly for the strong computational part. The interaction with the page can take advantage of the simplicity and ease of use of javascript.

webassembly is the fourth w3c standard after css, html, and javascript. webassembly is not expected to be used by developers to write code directly in webassembly, but rather to be able to compile other high-level languages such as C/C++/Rust into webassembly. webassembly.** As of today, all browsers and node.js (even v8) already have a webassembly virtual machine, which means that webassembly is supported in these environments.

2. Why use webassembly

  1. Make “native” modules less complex

    Runtimes (such as Node or Python’s CPython) often allow you to write modules in low-level languages (such as C++). This is because these low-level languages are usually much faster. So you can use native modules in Node, or extended modules in Python. But these modules are often hard to use because they need to be compiled on the user’s device. With WebAssembly’s “native” modules, you can get about the same speed and avoid the complexity.

  2. Easier sandboxing to run native code

    On the other hand, low-level languages like Rust won’t expect WebAssembly to run faster, but they will use WebAssembly for security. But they will use WebAssembly for security, and as we discussed in the WASI announcement, WebAssembly provides you with lightweight sandboxing by default. So, a voice like Rust can sandbox native code modules with WebAssembly.

  3. Share Native Code Across Platforms

    Developers can save development time and reduce maintenance costs if they can share the same code base across different platforms (for example, in Web and desktop applications). This is true for both scripting languages and low-level languages, and WebAssembly gives you a way to do this without degrading the performance of these platforms.

3. webassembly vs javascript

The development of js speed is shown below. In 2008, JIT technology was introduced, which made javascript almost 10 times faster, which allowed us to write server-side content in js. And in 2017, webassembly came along, which will be another turning point.

webassembly

3.1 JIT (just in time) in v8

To talk about why webassembly is fast, we need to talk about how javascript works and JIT technology. It is well known that javascript is a high-level language, and if you want a machine to understand it, you need a “translator” —- interpreter or compiler to translate the high-level language into machine language, simply understand the interpreter is a real-time translation machine, like simultaneous interpretation, while the compiler is to translate all the high-level language in advance, and then handed to the machine, javascript is an interpreted language, meaning that the translation process is a “translation” process. meaning that the translation process is “on the fly”, line by line translation, line by line execution. A language like c++ is translated in advance using a compiler and then run.

Interpreter

  • The advantage is that it starts fast, because you don’t need to compile it in advance.
  • The downside is that let’s say you have a for loop, which means you have to translate the same line of code over and over again, doing the same thing, without being able to do some optimization.

Compiler

  • The advantage is that because it is translated in advance, the code can be optimized before running, which speeds up the runtime.
  • The disadvantage is that it is too slow to start and has to be compiled first.

Compiler optimization

Then JIT technology is the original JS only Interpreter technology added to the Compiler! In the process of real-time translation with interpreter added some features of the compiler, such as a line of code is executed many times, js engine will set it to " Warm" for a series of super-optimization, and then “Hot” for the ultimate optimization when it is executed again.

Type specialization

For example, in js, because types are dynamic, the type of each element in an array is not certain, it could be object, it could be string, it could be number, which means that when you iterate each element of the array, you have to do a series of type checks and so on. When JIT technology is introduced, our js engine may run into the case that the first ten elements are all number, and then boldly predict that the next ones will also be number (of course, they will be checked later).

3.2 Why webassembly is faster

Ok, after the JIT of js, let’s summarize the process of executing js and webassembly.

webassembly

  1. first look at the fetching part, webassembly code is more compact, because js to be more human-readable, so the code mentioned compared to assembly language will be larger. 2. parse/decode process, js need to first into ast, and then through ast to generate IR (intermediate representation), IR and then generate machine code (x86 or arm).

  2. parse/decode process, js needs to be converted into ast, then through ast to IR (intermediate representation), IR then generate machine code (x86 or arm). webassembly does not need this conversion process. See the figure below. You can see that webassembly can generate machine code directly. webassembly

  3. the process of compile + optimize is described in the JIT section, the js engine has to optimize while running, let’s say watch the type of data change or something like that, while webassembly is closer to the underlying machine code, the data type or something is fixed.

  4. In addition, there is also the process of reoptimization in JS. In the type specialization section of the JIT, it is mentioned that the js engine has to predict the type in order to improve performance, and there are definitely times when it fails, so there is a need for reoptimization in the execution of js.

  5. As for the execution part, because webassembly is more low-level (see the following subsection for the two formats of webassembly, wasm and wat formats) than js, even if the final machine code is generated, the degree of optimization that the code written by the webassembly developer must be greater than the optimization that can be done by js.

  6. the GC process is easy to understand. gc in high-level languages that can be converted to webassembly requires manual processing by the developer, while it is automatic in js.

3.3 What does webassembly look like? (wasm format and wat format)

After all this talk, what does webassembly really look like?

Let’s write a simple c++ method as follows:

1
2
3
4
5
6
7
8
// test.c
int addAll(int times) {
    int n = 0;
    for (int i = 0; i < times; ++i) {
        n += i;
    };
    return n;
}
  1. wasm suffix file: the executable file format that is put directly on the webassembly virtual machine, we use the Emscripten tool to generate a wasm file from the above c++ file, the command is: emcc -O3 -s "EXPORTED_FUNCTIONS=['_addAll']" -o test .wasm test.c --no-entry, which exports the addAll method in the wasm file, and the resulting wasm is a binary (actually hexadecimal) file. Open the text.wasm file as follows.

    1
    2
    3
    4
    5
    6
    7
    
    00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60
    01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80
    80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06
    81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65
    6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69
    00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20
    00 41 2A 6A 0B
    
  2. files with wat suffix: files that are easily human-readable and can be coded on this format, using the wasm2wat tool to convert the generated wasm to wat format, wasm2wat test.wasm generates The contents of wat are as follows: it is more human-readable. For a brief introduction, there are only four data types supported in webassembly.

    • i32: 32-bit integer
    • i64: 64-bit integer
    • f32: 32-bit float
    • f64: 64-bit float

    Like i32.sub This is an arithmetic instruction, the first two variables pushed into the stack are the two arguments of the arithmetic instruction, so you can see that this instruction is concise enough.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
        (module
    (type (;0;) (func (result i32)))
    (type (;1;) (func (param i32) (result i32)))
    (type (;2;) (func))
    (type (;3;) (func (param i32)))
    (func (;0;) (type 2)
        nop)
    (func (;1;) (type 1) (param i32) (result i32)
        local.get 0
        i32.const 1
        i32.lt_s
        if  ;; label = @1
        i32.const 0
        return
        end
        local.get 0
        i32.const 1
        i32.sub
        i64.extend_i32_u
        local.get 0
        i32.const 2
        i32.sub
        ...
        // Omitted
    

    Of course wasm and wat formats are interchangeable, depending on whether you want to read it or use it.

3.4 Communication in webassembly and js

As we said in the previous summary, there are only four data types in webassembly, and they are all numeric. Our c++ accumulation function only passes the number n, which represents the number of accumulations, so how do we pass complex data structures like string or object between webassembly and js? The answer is to pass a buffer of memory, which is kind of like passing a pointer of a number type between the two, and then putting the content to be passed into this shared memory. See mdn’s WebAssembly.Memory api for more details here.

WebAssembly

1
2
3
4
5
6
7
8
//创建一个6.4MiB大的内存
const wasmMemory = new WebAssembly.Memory({ initial: 10, maximum: 100 });
WebAssembly.instantiate(wasmBinary, {
  env: {
    // 告诉webassembly这片内存咱俩一起用
    memory: wasmMemory,
  },
});

In js, you can manipulate binary data buffer by typedArray.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 把想要传递的数据转成 ArrayBuffer (假设是 Uint8Array)
const dataBuffer = encodeDataByJS({
  /* my data */
});
// 向 wasm 申请一段内存,由 wasm 代码负责实现并返回内存内存起始地址
const offset = applyMemoryFormWasm(dataBuffer.length);
// 以 unit8 的格式操作 wasm 的内存 (格式应该与 dataBuffer 的格式相同)
const heapUint8 = new Uint8Array(wasmMemory.buffer, offset, dataBuffer.length);
// 把数据写入 wasm 内存
heapUint8.set(dataBuffer);

4. write a webassembly demo

4.1 c++ implementation of an accumulation function

We use the above demo to implement a 1 + 2 + 3 + … + n in c++.

4.2 Generating .wasm files with emscripten

The above section mentions the use of emscripten to generate the test.wasm file.

4.3 How to call the generated .wasm file in js

We try to use the exported addAll method of this wasm file in node as follows.

1
2
3
4
5
6
7
8
9
fs.readFile("./test.wasm", (err, data) => {
  if (err) throw err;
  WebAssembly.instantiate(data).then((module) => {
    console.log("In the native WebAssembly function");
    console.time("performance1");
    console.log(module.instance.exports.addAll(50000));
    console.timeEnd("performance1");
  });
});

Because webassembly is now a web standard, various js engines have the corresponding api (WebAssembly) to call webassembly. the usage is very simple, if you are interested go to mdn and search for additional information.

4.4 Rough comparison of computational performance

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function addAll(n) {
  var result = 0;
  console.log("In the Javascript");
  console.time("performance2");
  for (let i = 0; i < n; i++) {
    result += i;
  }
  console.log(result);
  console.timeEnd("performance2");
}

js and webassembly execute 0 + 1 + 2 + ... + 50000 on my computer is webassembly: 0.07ms; js: 4.886ms; just a simple calculation shows the performance difference.

5. runtime for webassembly

wasmer

In addition to the browser and node, we talk about two webassembly runtimes, the first one is wasmer, the official website says that this runtime can run on any device, it looks more like a docker container and can run wasm. use it to run our cumulative wasm.

wasmer

In addition, wasmr also provides a tool called wapm, which is a bit like npm, and some users on its community will upload their own compiled wasm toolkit, which you can download and run directly in wasmer.

wapm

There will even be a wapm_packages and a .lock file under the wapm project, similar to our package.lock.

wapm project

wasm-micro-runtime

This runtime was developed by the Chinese team at intel and is intended to run on top of iot devices with the following supported platform architectures.

  1. X86-64, X86-32

  2. ARM, THUMB (ARMV7 Cortex-M7 and Cortex-A15 are tested)

  3. AArch64 (Cortex-A57 and Cortex-A53 are tested)

  4. MIPS

  5. XTENSA

    The runtime was compiled locally according to the tutorial and was able to run our cumulative wasm method successfully.

    runtime

    The generated runtime command line file iwasm is only 212k.

6. webassembly now in use scenarios

In the w3c online conference on August 29, 2020, several technology majors introduced their scenarios with webassembly respectively:

  1. Typical scenario 1: bilibili, using webassembly to check video content when users upload videos and generate recommended covers based on the videos, all these operations are implemented in the user’s browser (front-end).

    bilibili

  2. intel on embedded devices, using Webassembly to implement a set of application frameworks.

    intel

  3. Unity game engine also has webassembly implementation.

  4. Emulator (emulator) such as game boy emulator.

  5. some media processing sites, squoosh, ogv.js, Photon, etc.