Are front-end developers already familiar with modern browsers? HTML5, CSS4, JavaScript ES6, these technologies have been gradually popularized in modern browsers to bring great convenience to front-end development.

Thanks to just-in-time (JIT) technology, JavaScript runs 10 times faster, which is one of the reasons why JavaScript is becoming more widely used. But is this the limit?

With the development of browser technology, Web games are poised to make a comeback, but this time they are not flash-based games, but ones that take full advantage of modern HTML5 technology. JavaScript has become the development language of Web games, but for games that need a lot of computation, even with the help of JIT, the performance of JavaScript is not enough to meet the greedy desire of human beings.

How does JavaScript work in a browser?

For today’s computers, they can only read “machine language.” The human brain is limited, so writing machine language directly is a bit difficult. In order to make it easier for people to write programs, humans have invented a large number of “high-level programming languages.

Why is it a special one? Since computers do not understand what high-level programming languages have written, most high-level programming languages have to go through a process called compilation, which translates high-level programming languages into machine languages and then hands them on to computers to run. JavaScript, however, has no “compile” process, so how does the machine recognize this language?

In fact, JavaScript and some other scripting languages operate in an “explain and run” manner, translating code bit by bit to the computer.

So how does “interpreting” JavaScript differ from “compiling” in other languages? Isn’t it all translated into “machine language”? To put it simply, “compilation” is similar to “full-text translation”, that is, once the code is written, all the code is compiled into “machine language”, and then directly handed to the computer. Interpret, on the other hand, is similar to “real-time translation”, where the code is written and not translated.

Both “explain” and “compile” methods have advantages and disadvantages. With the “explain” method, the program is written and ready to run, whereas with the “compile” method, it takes some time to wait for the entire code to compile before it can be executed. This may seem faster, but if a piece of code is to be executed multiple times, the program needs to be “interpreted” again each time it is run, rather than compiled. At this point, the overall efficiency of “compile” seems to be higher because it always translates once, whereas “interpret” runs a translation once. Also, because “compilation” is done on the whole code from the start, it can be tailored to optimize the code.

JavaScript is run using an “interpreted” scheme, which makes it inefficient because the code has to be translated every time it runs. If a function is called looping 10 times, 100 times, you can imagine how efficient it is.

Fortunately, intelligent human beings invented the JIT (Just – in – time) technology, it combines the advantages of “interpretation” and “compilation”, it is actually in the “explanation” the principle of the operation of track at the same time, if a piece of code executed multiple times, would be to compile the code optimization, in this way, if subsequent run again to this code, There is no need to explain.

JIT may seem like a good thing, but it is very difficult to achieve perfect JIT in a dynamic data-typed language like JavaScript. Why is that? Because a lot of things in JavaScript are determined at runtime. Const sum = (a, b, c) => a + b + c; This is a JavaScript arrow function written in ES6 syntax that can be run directly from the browser console, which declares a function called sum. We can then call it directly, such as console.log(sum(1, 2, 3)), and any decent front-end developer can quickly calculate the answer in his head, which will output the number 6. But what if we call it like this: Console. log(sum(‘1’, 2, 3)), the first argument becomes a string, which is completely allowed in JavaScript, but the result is completely different, resulting in a string concatenated with two numbers to get “123”. As a result, optimization for this function becomes very difficult.

Although the “features” of JavaScript make it difficult to implement JIT, it is fair to say that JIT has brought considerable performance improvements to JavaScript.

WebAssembly

To make code run faster, WebAssembly came along (and is now supported by major browsers), which allows you to “compile” code up front and run it directly in the browser, eliminating the need for JIT optimization on the fly. All optimizations can be determined directly at compile time.

What exactly is WebAssembly?

First of all, it is not a direct machine language, because there are so many machines in the world that all speak different languages (with different architectures), so in many cases the machine code is generated specifically for different machine architectures. But it would be too complicated to generate for a variety of machines, and each language would have to write a compiler for each architecture. To simplify this process, we have Intermediate representation (IR), where all code is translated into IR, which is then used to deal with various machine architectures.

In fact, WebAssembly is similar to IR in that it acts as a translator for various machine architectures. WebAssembly is not a direct physical machine language, but a virtual machine language abstracted from it. Going from WebAssembly to machine language requires a “translation” process, but there is not much of a “translation” here, it is machine language to machine language translation, so the speed is very close to pure machine language.

Here’s a Demo on WebAssembly’s website, a small game developed in Unity and published for WebAssembly: webassembly.org/demo/.

Wasm file and.wat file

WebAssembly is stored in *.wasm files, which are compiled binaries that are very small in size.

In the browser, a global Window. WebAssembly object is provided that can be used to instantiate the WASM module.

WebAssembly is a “virtual machine language”, so it also has an “assembly language” version, which is the *.wat file. This is the text representation of the WebAssembly module, described by s-expressions. Wat files can be compiled to *.wasm files directly from the tools. Those of you familiar with LISP are probably familiar with this expression syntax.

A very simple example

Let’s take a look at a very simple example, which has been tested on Chrome 69 Canary and Chrome 70 Canary, and theoretically runs on all browsers that already support WebAssembly. (See browser support below)

First, we write a very simple program using s-expressions:

;; test.wat (module (import "env" "mem" (memory 1)) ;; This specifies importing a memory object (func (export "get") (result i32); Define and export a function called "get" that returns a value of type int32 and takes no argument memory.size); Finally returns the "size" of the memory object (in "pages", currently 1 page = 64 KiB = 65536 Bytes)Copy the code

You can use the WASm2wat tool in WABT to convert a WASM file into a WAT file described using an “S-expression.” You can also convert wat to WASM using the wat2wASM tool.

In the WAT file, double semicolon; Everything at the beginning is a comment.

The wat file above defines a module, imports a memory object, and exports a function called “get” that returns the current “size” of memory.

In WebAssembly, linear memory can be defined directly internally and exported, or imported from outside, but only one memory is allowed. The size of the memory is not fixed. You only need to give it an initial size, which can be extended by calling the grow function as needed. You can also specify the maximum size. (Here all memory sizes are in “pages”, currently 1 page = 64 KiB = 65536 Bytes.)

The wat file above is compiled to wASM using wat2wasm and produces a very small file of only 50 Bytes:

$ wat2wasm test.wat $ xxd test.wasm 00000000: 0061 736d 0100 0000 0105 0160 0001 7f02 .asm....... `... 00000010: 0c01 0365 6e76 036d 656d 0200 0103 0201 ... env.mem...... 00000020: 0007 0701 0367 6574 0000 0a06 0104 003f ..... get....... ? 00000030: 000b ..Copy the code

In order for the program to run in the browser, we also have to write a “glue code” in JavaScript so that the program can be loaded into the browser and executed:

// main.js

const file = await fetch('./test.wasm');
const memory = new window.WebAssembly.Memory({ initial: 1 });
const mod = await window.WebAssembly.instantiateStreaming(file, {
  env: {
    mem: memory,
  },
});
let result;
result = mod.instance.exports.get();  // Call the get function exported by the WebAssembly module
console.log(result);  / / 1
memory.grow(2);
result = mod.instance.exports.get();  // Call the get function exported by the WebAssembly module
console.log(result);  / / 3
Copy the code

Here I use the ES6 syntax that modern browsers already support. First, I load our compiled test.wasm file using the fetch function provided by the browser natively. Note that here, according to the specification, the MIME Type specified in the CONTent-type of the HTTP response must be Application/wASM.

Next, we create a new WebAssembly.Memory object that allows JavaScript to communicate with WebAssembly.

Again next, we use the WebAssembly. InstantiateStreaming to instantiate loaded WebAssembly module, here the first parameter is a Readable Stream, the second parameter is the importObject, Used to specify the structure to import WebAssembly. Since the wat code above specified that we want to import a memory object from env.mem, we need to put our new memory object into env.mem.

WebAssembly also provides a instantiate function whose first argument can provide either an ArrayBuffer or TypedArray. However, it is not recommended to use this function. Students who have done traffic proxy forwarding may be more clear about the specific reason, but there is no specific explanation here.

Finally, we can call the WebAssembly export function get, first printing the value of Memory initial. We then call the memory.grow method to increase the size of memory. The output is 1 + 2 = 3.

An example of WebAssembly and JavaScript data interaction

There is a chunk of memory in WebAssembly that can be defined internally or imported from outside. If it is defined internally, you can export it. JavaScript has full control over this “memory”. After wrapping the Memory object with DataView, JavaScript can use functions under DataView to read or write the Memory object.

Here’s a simple example:

;; example.wat (module (import "env" "mem" (memory 1)) (import "js" "log" (func $log (param i32))) (func (export "example")  i32.const 0 i64.const 8022916924116329800 i64.store (i32.store (i32.const 8) (i32.const 560229490)) (call $log (i32.const 0))))Copy the code

This code starts by importing a memory object from env.mem as the default memory, as in the previous example.

Then import a function from js.log that takes a 32-bit integer argument, returns no value, and is named “$log” inside wat. This name only exists in the wat file, and when compiled to WASM it does not exist, storing only an offset address.

A function is then defined and exported as the “example” function. In WebAssembly, everything in a function is on the stack.

Const i32. Const 0 pushes a 32-bit integer constant 0 on the stack and a 64-bit integer constant 8022916924116329800 on the stack. The i64.store instruction is then called, which stores a 64-bit integer from the first position at the top of the stack into eight consecutive bytes of space starting with the “memory address” specified at the second position at the top of the stack.

In short, you store a 64-bit integer number 8022916924116329800 in eight consecutive bytes starting at the 0th location of memory. This number is expressed in hexadecimal as: 0x 6f 57 20 6f 6C 6C 65 48, but because WebAssembly uses a little-endian Byte Order to store data, 0x48 is stored at the 0th location in memory. The first location stores 0x65… So, the final store is actually 0x 48 65 6C 6C 6F 20 57 6F, corresponding to the ASCII code: “Hello Wo”.

Then, the following instruction (i32.store (i32.const 8) (i32.const 560229490)) is of the “s-expression” form of the above three instructions, Instead, i32.store is used to store a 32-bit integer constant 560229490 up to 4 consecutive bytes of space starting with “memory address” 8.

In fact, the syntax of the above three sentences is exactly equivalent:

i32.const 8
i32.const 560229490
i32.store
Copy the code

Similarly, a 32-bit integer number 560229490 is stored in four consecutive bytes of memory starting at the eighth location. This number is converted into hexadecimal digits: 0x 21 64 6C 72, which is also stored in “little endian” order, so it is actually 0x 72 6C 64 21, corresponding to the ASCII code: “RLD!” .

So, finally, the first 12 bytes of memory are 0x 48 65 6C 6C 6F 20 57 6F 72 6C 64 21, which together correspond to the ASCII code: “Hello World!” .

After compiling this wat to wASM, the file size is 95 Bytes:

$ wat2wasm example.wat $ xxd example.wasm 00000000: 0061 736d 0100 0000 0108 0260 017f 0060 .asm....... `... ` 00000010: 0000 0215 0203 656e 7603 6d65 6d02 0001 ...... env.mem... 00000020: 026a 7303 6c6f 6700 0003 0201 0107 0b01 .js.log......... 00000030: 0765 7861 6d70 6c65 0001 0a23 0121 0041 .example...#.! .A00000040: 0042 c8ca b1e3 f68d c8ab ef00 3703 0041 .B.......... 7.. A 00000050: 0841 f2d8 918b 0236 0200 4100 1000 0b .A..... 6.. A....Copy the code

Next, write “glue code” again in JavaScript:

// example.js

const file = await fetch('./example.wasm');
const memory = new window.WebAssembly.Memory({ initial: 1 });
const dv = new DataView(memory);
const log = offset= > {
  let length = 0;
  let end = offset;
  while(end < dv.byteLength && dv.getUint8(end) > 0) {
    ++length;
    ++end;
  }
  if (length === 0) {
    console.log(' ');
    return;
  }
  const buf = new ArrayBuffer(length);
  const bufDv = new DataView(buf);
  for (let i = 0, p = offset; p < end; ++i, ++p) {
    bufDv.setUint8(i, dv.getUint8(p));
  }
  const result = new TextDecoder('utf-8').decode(buf);
  console.log(result);
};
const mod = await window.WebAssembly.instantiateStreaming(file, {
  env: {
    mem: memory,
  },
  js: { log },
});
mod.instance.exports.example();  // Call the example function exported by the WebAssembly module
Copy the code

Here, memory is wrapped with DataView so that it is easy to read and write to memory objects.

Then, here we implement a log function in JavaScript that takes a parameter (which is specified as an integer in wat above). The following implementation first determines the length of the output string (strings usually end with ‘\0’), then copies the string to an ArrayBuffer of appropriate length, and then decodes it using the TextDecoder class in the browser to get the original string.

Finally, we instantiate the WebAssembly module by putting the log function into js.log of importObject, and finally call the exported example function to see the printed Hello World.

Through WebAssembly, we can directly encapsulate many class libraries written in other languages to run in the browser. For example, Google Developers provides a WebP image encoding library written in C language to load with WebAssembly. To convert a picture of a JPG format for the webp and display case: developers.google.com/web/updates… .

This example uses Emscripten tool to compile C language code. This tool needs to download files from GitHub, Amazon S3 and other servers when it is installed. The speed is extremely slow in the magical network environment in China, and tens of megabytes of files may be suspended for a day. You can try to modify the emSDK file (Python) to add the proxy configuration (but the effect is not obvious), or during the download process will prompt you to download the link and save the path, use another tool to download the file and put it in the specified place, and install again will automatically skip the downloaded file.

The present and future of WebAssembly

The binary version of WebAssembly has been finalized, and future improvements will be made in a compatible form, indicating that WebAssembly has entered the modern standard.

WebAssembly is not perfect yet, and while there are Web games developed using WebAssembly, there are still many imperfections.

For example, WebAssembly now has to be used with “JavaScript Glue code”, that is, JavaScript must be used to fetch WebAssembly files, Then call window. WebAssembly. Instantiate, window. WebAssembly. Instantiate instantiateStreaming etc. Function. In some cases, JavaScript is also required to manage the stack. The official recommended compilation tool Emscripten uses various hacks to reduce the amount of Code generated after compilation, but the resulting JavaScript Glue Code file is still at least 15K.

In the future, it will be possible for WebAssemblies to refer directly to HTML tags such as ; Or it can be referenced as a JavaScript ES6 module, such as: import XXX from ‘./wa.wasm’; ;

Threading support, exception handling, garbage collection, tail-call optimization, etc., have all been added to WebAssembly’s to-do list.

summary

With the advent of WebAssembly, front-end development is no longer limited to JavaScript. C, C++, Go, and so on can contribute code to the browser front-end.

The two examples I wrote using the wat file here are for reference only. In fact, in a production environment it is unlikely that you will develop directly using WAT. Instead, you will write modules in C, C++, Go, etc., and publish them as WebAssembly.

WebAssembly is not meant to replace JavaScript, but to complement it and bring a new option to front-end development. Leave the computation-intensive parts to WebAssembly to get the best out of the browser!


The text/jinliming2

A salty fish full of curiosity about new things

Sound/fluorspar

This article has been authorized by the author, the copyright belongs to chuangyu front. Welcome to indicate the source of this article. Link to this article: knownsec-fed.com/2018-08-08-…

To see more sharing from the front line of KnownsecFED development, please search our wechat official account KnownsecFED. Welcome to leave a comment to discuss, we will reply as far as possible.

Thank you for reading.