Author: Liu Guanyu, senior front end engineer and technical manager of 360 Qi Dance Group, has participated in many large front end projects such as 360 Navigation, 360 Film, 360 finance and 360 games. Focus on the latest developments in W3C standards, IOT, machine learning and is currently a member of the W3C CSS working Group.

background

With the rapid development of Web applications, JavaScript, Web front-end and even the whole Internet have undergone profound changes. As the front end begins to take on more responsibility, the need for efficiency becomes more urgent. In addition to the evolution of the language itself, Web practitioners, as well as major browser vendors, continue to explore. In 2012, Mozillia engineers introduced ASM.js and Emscripten, making it possible for efficient programs written in C/C++ and multiple programming languages to be translated into JavaScript and run in the browser.

Further, WebAssembly(REFERRED to as WASM) technology was proposed, and a variety of RESEARCH and development organizations were quickly established. Various peripheral tool chains were constantly improved, and relevant experimental data also strongly proved the feasibility of this optimization and acceleration route.

In 2018, in particular, the W3C’s WebAssembly Working Group released its first working draft containing core standards, JavaScript apis, and Web apis. In addition to C/C++ and Rust, the Golang language also officially supports wASM compilation. It’s rare to see a consensus of support from major browsers for this new technology, and perhaps a new era of the Web is at hand.

A brief introduction to WASM

Open the WASM website and you can see its ambitious technical goals. In addition to defining a portable, compact, and fast loading binary format, there are plans for support for mobile, non-browser, and even IoT devices, and a series of toolchains will be built over time. For those interested, you can see the official wASM explanation here.

In short, WASM is not a programming language, but a new bytecode format that is currently supported by major browsers. Unlike JavaScript, which requires interpreted execution, WASM bytecode is similar to the underlying machine code and can be loaded and run quickly, thus providing a significant performance improvement over JavaScript interpreted execution.

The chart below shows wASM support in the major browsers as of July 2018.

In addition to running in a browser, wASM can already run in command-line environments, including NodeJS.

Wasm tool chain structure

As originally envisioned, each high-level language compiles its source code into an intermediate language representation (LLVM IR) that is recognized by the underlying VIRTUAL machine (LLVM) through its own front-end compilation tool. At this point, the underlying LLVM can generate different machine codes for LLVM IR according to different CPU architectures, and optimize the space and performance of these machine codes at compile time. Most high-level languages support WASM in this way. The two steps mentioned above are also referred to as the compiler front end and the compiler back end.

The code that compiles into WASM is the program that ultimately does the actual work. For this, there is a text format called s-expression with the extension.wast to make it easier for programs to read. Wasm and WAST can be interrolled with wABT toolchain. An S-expression looks like this:

(module
 (type $iii (func (param i32 i32) (result i32)))
 (memory $0 0)
 (export "memory" (memory $0))
 (export "add" (func $assembly/module/add))
 (func $assembly/module/add (; 0;) (type $iii) (param $0 i32) (param $1 i32) (result i32) ;; @ assembly/module.ts:2:13(i32.add ;; @ assembly/module.ts:2:9(get_local $0) ;; @ assembly/module.ts:2:13
   (get_local $1)
  )
 )
)
Copy the code

There are several high-level languages that support wASM compilation, especially AssemblyScript, a typescript-based language that uses AssemblyScript’s tool chain support to make the final transition to WASM.

According to the above architecture, browsers and various runtime environment providers each provide different runtime support to smooth out the differences between CPU architectures, resulting in the need to support WASM high-level language and only support compilation to the intermediate language presentation layer. It is expected that as the comfort level of the development environment increases, more and more high-level languages will join the wASM support camp.

Use AssemblyScript to write WASM

AssemblyScript provides a subset of TypeScript to help TS students compile into WASM using standard JavaScript apis to bridge language differences. Let the programmer monkey can have fun coding.

AssemblyScript is divided into three main sub-projects:

  • AssemblyScript: The main program that converts TypeScript to WASM
  • Binaryen. js: AssemblyScript is a low-level implementation of wASM that converts the main program into a TypeScript wrapper for Binaryen, based on binaryen libraries.
  • Wast.js: AssemblyScript is a low-level implementation of WASM that relies on the WAST library and is a TypeScript wrapper to WAST.

It should be noted here that the tool chain is still under development and individual steps may not be stable yet. We try our best to ensure that the installation and configuration process is rigorous. If there is any change, please refer to the official description.

To support compilation, we first need to install AssemblyScript support. In order to compile properly, you first need to ensure that your Node version is 8.0 or above. You also need to install the TypeScript runtime.

Let’s get started:

Step 1: Install dependencies

To avoid future dependency issues, let’s first install AssemblyScript support

git clone https://github.com/AssemblyScript/assemblyscript.git
cd assemblyscript
npm install
npm link
Copy the code

After executing the above commands, you can use the asc command to determine if the installation is correct. If the installation is normal, the asC command instructions are displayed on the CLI.

Step 2: Create a new project

Next, we will create a new NPM project, such as wasmExample. If necessary, add ts-Node and typescript devDependencies and install the dependencies. Then, under the project root, we create a new directory: Assembly. Let’s go to the Assembly directory, and we’ll add tsconfig.json here as follows:

{
  "extends": ".. /node_modules/assemblyscript/std/assembly.json"."include": [
    "./**/*.ts"]}Copy the code

Step 3: Write code

Let’s add simple TS code to this directory as follows:

export function add(a: i32, b: i32) :i32 {
  return a + b;
}
Copy the code

We store the above TypeScript code as: module.ts. So, now from the project root, our file structure looks like this:

Step 4: Configure NPM Scripts

For ease of operation, we add the build step to NPM scripts by opening package.json in the root directory of the project and updating the scripts field to:

 "scripts": {
    "build": "npm run build:untouched && npm run build:optimized"."build:untouched": "asc assembly/module.ts -t dist/module.untouched.wat -b dist/module.untouched.wasm --validate --sourceMap --measure"."build:optimized": "asc assembly/module.ts -t dist/module.optimized.wat -b dist/module.optimized.wasm --validate --sourceMap --measure --optimize"
   }
Copy the code

To keep the project clean, we put the compilation target in the dist folder in the project root directory. At this point, we need to create a new dist directory under the project root directory.

Step 5: Compile

Now, in the project root directory, we’ll run: NPM run build. If no errors are reported, you’ll see six files generated in the dist directory.

Let’s not get into the details of the document yet. At this point, we are ready to compile. Careful readers may have noticed that different arguments are used in the compile command above. For these parameters, we can type ASC directly on the command line to query the details of the command and its use.

Step 6: Import the compilation results

Now we have the result of the compilation. Currently, since WASM can only be introduced by JavaScript, we need to introduce compiled WASM into JavaScript programs.

We add a module to the project root directory and import code: module.js as follows:

const fs = require("fs");
const wasm = new WebAssembly.Module(
    fs.readFileSync(__dirname + "/dist/module.optimized.wasm"), {});module.exports = new WebAssembly.Instance(wasm).exports;
Copy the code

In the meantime, we need a code that uses Module. For example, index.js, as follows:

var myModule = require("./module.js");
console.log(myModule.add(1.2));
Copy the code

The exciting moment comes when we run Node index.js in the project root directory to see if the results are what we expect. The reader can modify the call data in index.js to test the correctness of the module. Note that because WASM has the concept of data types, data types are more precise than TypeScript. So, in the example above, if the input is not an integer (the above example is the WASM definition of i32), it will not be consistent with the traditional JavaScript result, such as your call myModule.add(2.5, 2), the result may be 4. Therefore, we need to pay close attention to data types when calling wASM programs.

Using in a browser

In the previous section, we showed how NodeJS can be integrated, but the most significant productivity boost is in the browser. So how do we use our compiled code in the browser?

JavaScript call wasm

For JavaScript calls to WASM, the following steps are generally used:

  1. Load the bytecode for WASM.
  2. The obtained bytecode is converted into an ArrayBuffer, which is the only structure that can compile correctly. The above ArrayBuffer is validated at compile time. Verify to compile. After compilation, resolve a Webassembly.module through Promise.
  3. Instantiation of a module that needs to be synchronized through the Webassembly. Instance API after it is acquired.
  4. Steps 2 and 3 above can be equivalently replaced with the instaniate asynchronous API.
  5. It can then be called as if using a JavaScript module.

For complete steps, you can also see the following flow chart:

Here’s an example of asynchronous code we’ll call async_module.js:

// asynchronously introduce examples
const fs = require("fs");
const readFile = require("util").promisify(fs.readFile);

const getInstance = async (wasm, importObject={}) => {
	let buffer = new Uint8Array(wasm)
	return await WebAssembly.instantiate(wasm, importObject)
}

let ins;

const noop = (a)= > {};

const exportFun = (obj, funName) = > {
	return (typeof obj[funName] === "function")? obj[funName] : noop; }async function getModuleFun(filePath, funName ,importObject={}) {
	if (ins){
		return exportFun(ins, funName)
	}

	const wasmText = await readFile(filePath);
	const mod = await getInstance(wasmText, importObject);

	return exportFun(mod.instance.exports, funName)
}

module.exports = getModuleFun;
Copy the code

When called, all we need to do is code happily with the WASM object:

var myModule = require("./async_module.js");

// Call code
(async() = > {const fun = await myModule(__dirname + "/dist/module.optimized.wasm"."add")
	console.log(fun(1.2))
	console.log(fun(4.10000))
})()
Copy the code

Here are all the apis currently available in JavaScript that work with WASM

Use WebPack integration to load workflows

Starting with webpack4, the default wasm loading scheme is officially provided. If your WebPack is a previous version of WebPack 4, you may need to install development packages such as AssemblyScript-typescript-Loader.

The version of Webpack I’m currently using is 4.16.2, and native support for WASM is fairly complete. According to official information, there will be more robust support for wasm in webpack5.

NPX webpack will automatically compile the wASM module:

import("./module.optimized.wasm").then(module= > {
    const container = document.createElement("div");
    container.innerText = "Hello, WebAssembly.";
    container.innerText += " add(1, 2) is " + module.add(1.2);
    document.body.appendChild(container);
});
Copy the code

Working with JavaScript in WASM

Since WASM cannot currently manipulate the Dom directly, it may be necessary to leverage the power of JavaScript to do so, in which case we need to invoke JavaScript in WASM.

The second argument importObject is supported by both the webassembly.instance and webassembly.instantiate functions, The importObject argument is used by JavaScript to pass in the JavaScript module that wASM needs to call.

As a demonstration, we’ll modify the module.js code above to add the result as a number of *. We’ll still use synchronous code here for the sake of the demonstration, but in fact asynchronous code is more common.

const fs = require("fs");
const wasm = new WebAssembly.Module(
    fs.readFileSync(__dirname + "/dist/module.optimized.wasm"), {});module.exports = new WebAssembly.Instance(wasm, {
  window: {show: function (num){
        console.log(Array(num).fill("*").join(""))
    }
  }
}).exports;
Copy the code

The index.js caller is changed to:

var myModule = require("./module.js");
myModule.add(1.2);
Copy the code

In the meantime, we need to modify the TypeScript source code:

// Declare the module type imported from outside
declare namespace window {
    export function show(v: number) :void;
}

export function add(a: i32, b: i32) :void {
  window.show(a + b);
}
Copy the code

Let’s go back to the project root and rerun NPM run Build.

After running node index.js, we can see that the original result is represented by the number of *. The WebAssembly successfully invokes the JavaScript code.

summary

For THE WASM technology, we summarize as follows:

  • The standard is still at the working draft stage and is not recommended for use in actual stabilization projects.
  • Big goal, major browser manufacturers, major mainstream language follow up enthusiasm is very high, suitable for a new technology to follow up for a long time.
  • The latest versions of the major browsers are basically supported. If you need to be compatible with past browsers, especially IE series, there is no particularly good solution, individual interfaces are incompatible.
  • Toolchain development is very active at present, but it also brings unstable interface, and the way of use may change. Each tool chain is still in its infancy without overwhelming efficiency and maturity advantages.
  • Learning materials, especially Chinese materials, are scarce. It takes a certain amount of effort, and if necessary, follow up on the source code.

Nevertheless, I am very optimistic about the future of WASM in performance demanding areas such as gaming, video and audio applications.

The resources

  • webassembly.org/
  • www.npmjs.com/package/ass…
  • Github.com/AssemblyScr…
  • www.w3.org/TR/2018/WD-…
  • www.ibm.com/developerwo…
  • Segmentfault.com/a/119000000…
  • Developer.mozilla.org/en-US/docs/…
  • www.npmjs.com/package/ass…
  • Blog.csdn.net/a986597353/…
  • webpack.js.org/

Thank you

In this paper, the topic selection process, reference an Jia, Li Songfeng, Liu Yuchen and other colleagues. After written, Mr. Li songfeng and Mr. Liu Yuchen gave a lot of pertinent revision suggestions, and hereby express our sincere thanks.

Related articles

  • WebAssembly contrast JavaScript and its usage scenarios mp.weixin.qq.com/s/AiKvAnTWq…
  • Rust, WebAssembly with Webpack introduction strategy mp.weixin.qq.com/s/kVr04twJ4…
  • A few picture to let you understand WebAssembly mp.weixin.qq.com/s/8KBiHp2rK…

About Weird Dance Weekly

Qiwu Weekly is a front-end technology community operated by qiwu Group, a professional front-end team of 360 Company. After paying attention to the public number, directly send the link to the background can contribute to us.