A Complete Introduction to WebAssembly and It’s JavaScript API

A Complete Introduction to WebAssembly and It’s JavaScript API - 图1
Since the introduction of computers, there has been a massive improvement in the performance of native applications. Comparatively, web applications were quite slow due to the fact that JavaScript was not initially built for speed. But with heavy competition amongst the browsers and the rapid development of JavaScript engines such as V8, enabled JavaScript to run very fast on machines. But it was still not able to beat the performance of native applications. This was mainly due to the fact that the JavaScript code had to undergo several processes to result in machine code.
A Complete Introduction to WebAssembly and It’s JavaScript API - 图2
With the introduction of WebAssembly, everything we know as the modern web is expected to be revolutionized. This piece of technology is blazingly fast. Let’s have a look at what WebAssembly is and how we can integrate with JavaScript to build blazingly fast applications.

What is WebAssembly?

Before understanding WebAssembly, let’s have a look at what Assembly is.
Assembly is a low-level programming language that has a very close connection to the architecture’s machine-level instructions. In other words, it is just one process away from being converted to machine understandable code known as machine code. This conversion process is referred to as the assembly.
WebAssembly can simply be referred to as the Assembly for the web. It is a low-level assembly-like language with a compact binary format that enables you to run web applications at native-like speeds. It also provides languages such as C, C++ and Rust with a compilation target and thereby enabling client applications to run on the web with near-native performance.
Furthermore, WebAssembly is designed to run alongside JavaScript, not to replace it. With the WebAssembly JavaScript APIs, you can run code from either language interchangeably, back and forth without any issue. This provides you with applications that utilize the power and performance of WebAssembly and the versatility and adaptability of JavaScript. This opens up a whole new world of web applications that can run code and functionalities that were not intended for the web in the first place.

What Difference Does It Make

Lin Clark predicts that the introduction of WebAssembly in 2017 may trigger a new inflexion point in the life of web development. This event comes after inflexion caused by the introduction of JIT compilation in modern browsers which increased the speed of JavaScript by almost 10 times.
A Complete Introduction to WebAssembly and It’s JavaScript API - 图3
If you compare the compilation process of WebAssembly with that of JavaScript, you will note that several processes have been stripped off and the rest have been trimmed. This is a visual comparison of how these two processes would look like.
A Complete Introduction to WebAssembly and It’s JavaScript API - 图4
If you closely compare the above two visualizations, you will note that the re-optimization part in the WebAssembly has completely been stripped off. This is mainly due to the fact that the compiler does not need to make any assumptions regarding the WebAssembly code because things such as data types are explicitly mentioned in the code.
But this would not be the case with JavaScript as the JIT should make assumptions to run the code and if the assumptions fail, it should reoptimize its code.

How to Get WebAssembly Code

Now comes the important question for web developers. WebAssembly is a great piece of technology. But how do you utilize the power of WebAssembly?
You have several approaches.

  • Write the WebAssembly code from scratch — this is not recommended at all unless you know the basics really well.
  • Compile from C to WebAssembly
  • Compile from C++ to WebAssembly
  • Compile from Rust to WebAssembly
  • Use AssemblyScript to compile a strict variant of Typescript to WebAssembly. This is a great option for web developers who are not familiar with C/C++ or Rust.
  • There are more language options supported. We will mention them below.

Moreover, there are tools such as Emscripten and WebAssembly Studio that help you with the above process.

JavaScript’s WebAssembly API

To fully exploit the features of WebAssembly, we have to integrate it with our JavaScript code. This can be done with the help of the JavaScript WebAssembly API.

Module Compilation and Instantiation

The WebAssembly code resides in a .wasm file. This file should be compiled to machine code that is specific to the machine it is running on. You can use the WebAssembly.compile method to compile your WebAssembly module. After receiving the compiled module, you can use the WebAssembly.instantiate method to instantiate your compiled module. Alternatively, you can pass the array buffer you obtain from fetching .wasm file into the WebAssembly.instantiate method as well. This too works as the instantiate method has two overloads.

  1. let exports;
  2. fetch('sample.wasm').then(response =>
  3. response.arrayBuffer();
  4. ).then(bytes =>
  5. WebAssembly.instantiate(bytes);
  6. ).then(results => {
  7. exports = results.instance.exports;
  8. });

One of the downsides of the above approach is that these methods don’t directly access the byte code, so require an extra step to turn the response into an ArrayBuffer before compiling/instantiating the wasm module.
Instead, we can use the WebAssembly.compileStreaming / WebAssembly.instantiateStreaming methods to achieve the same functionality as above, with an advantage being able to access the byte code directly without the need for turning the response into an ArrayBuffer .

  1. let exports;
  2. WebAssembly.instantiateStreaming(fetch('sample.wasm'))
  3. .then(obj => {
  4. exports = obj.instance.exports;
  5. })

You should note that the WebAssembly.instantiate and WebAssembly.instantiateStreaming return the instance as well as the compiled module as well, which can be used to spin up instances of the module quickly.

  1. let exports;
  2. let compiledModule;
  3. WebAssembly.instantiateStreaming(fetch('sample.wasm'))
  4. .then(obj => {
  5. exports = obj.instance.exports;
  6. //access compiled module
  7. compiledModule = obj.module;
  8. })

Import Object

When we instantiate a WebAssembly module instance, we can optionally pass an import object that would contain the values to be imported into the newly created module instance. These can be of 4 types.

  • global values
  • functions
  • memory
  • tables

The import object can be considered as the tools supplied to your module instance to help it achieve its task. If an import object is not provided, the compiler will assign default values.

Globals

WebAssembly allows you to create global variable instances that can be accessed from your JavaScript and WebAssembly modules. You can import/export these variables and use them across one or more WebAssembly module instances.
You can create a global instance by using the WebAssembly.Global() constructor.

  1. const global = new WebAssembly.Global({
  2. value: 'i64',
  3. mutable: true
  4. }, 20);

The global constructor accepts two parameters.

  • An object containing properties describing the data type and mutability of the global variable. The allowed data types are i32, i64, f32, or f64
  • The initial value of the actual variable. This value should be of the type mentioned in parameter 1. For example, if you mention the type as i32 , your variable should be a 32-bit integer. Likewise, if you mention f64 as the type, then your variable should be a 64-bit float.

    1. const global = new WebAssembly.Global({
    2. value: 'i64',
    3. mutable: true
    4. }, 20);
    5. let importObject = {
    6. js: {
    7. global
    8. }
    9. };
    10. WebAssembly.instantiateStreaming(fetch('global.wasm'), importObject)

    The global instance should be passed onto the importObject in order for it to be accessible in the WebAssembly module instance.

    Memory

    At the point of instantiation, the WebAssembly module would need a memory object allocated. This memory object should be passed with the importObject. If you fail to do so, the JIT compiler would create and attach a memory object to the instance automatically with the default values.
    The memory object attached to the module instance would simply be an ArrayBuffer. This enables for easy memory access by simply using index values. Furthermore, because of being a simple ArrayBuffer , values can simply be passed and shared between JavaScript and WebAssembly.

    Table

    A WebAssembly Table is a resizable array that lives outside of the WebAssembly’s memory. The values of the table are function references. Although this sounds similar to the WebAssembly Memory, the major difference between them is that Memory array is of raw bytes while the Table array is of references.
    The main reason for the introduction of Table is improved security.
    You can use the methods set() , grow() , and get() to manipulate your table.

    Demo

    For my demonstration, I will be using the WebAssembly Studio application to compile my C file into .wasm . You can have a look at the demo over here.
    I have created a function to calculate the power of a number in the wasm file. I am passing the necessary values to the function and receiving the output in JavaScript.
    Similarly, I am doing some string manipulation in wasm . You must note that wasm does not have a type for strings. Hence it will work with the ASCII values. The value returned to the JavaScript would be pointing the memory location where the output is stored. Since the memory object is an ArrayBuffer, I am iterating until I receive all of the characters in the string.
    JavaScript file ``` let exports; let buffer; (async() => { let response = await fetch(‘../out/main.wasm’); let results = await WebAssembly.instantiate(await response.arrayBuffer()); //or // let results = await WebAssembly.instantiateStreaming(fetch(‘../out/main.wasm’)); let instance = results.instance; exports = instance.exports; buffer = new Uint8Array(exports.memory.buffer); findPower(5,3);

    printHelloWorld();

})(); const findPower = (base = 0, power = 0) => { console.log(exports.power(base,power)); } const printHelloWorld = () => { let pointer = exports.helloWorld(); let str = “”; for(let i = pointer;buffer[i];i++){ str += String.fromCharCode(buffer[i]); } console.log(str); }

  1. **C file**

define WASMEXPORT _attribute((visibility(“default”)))

include

WASM_EXPORT double power(double number,double power_value) { return pow(number,power_value); } WASM_EXPORT char* helloWorld(){ return “hello world”; } ```

Use Cases

The introduction of WebAssembly opened up a world of opportunities.

  • Ability to use existing libraries/code written in languages such as C/C++ in the web environment.

For example, if you were unable to find a library for JavaScript that implements a certain functionality, you would have to write the library from scratch and implement it. But if you can find a library that implements the same functionality, but written in a different language, you can use the power of WebAssembly to run it in your web app. This is a major breakthrough as it would save lots of time from the developers perspective.
The Squoosh app uses WebAssembly to fulfil its QR and image detection functionality. This allowed them to support these, even on older browsers, with native-like speeds. Furthermore, eBay was able to implement barcode scanning functionality to their web application by compiling the C++ library used it it’s native applications, into WebAssembly.

  • Run fully native applications written in languages like C, C++, Rust to run on the web, with little modifications to the code. The performance also would be near-native.

Applications like AutoCAD, QT, and even Google Earth were able to run their applications with near-native performance with little modifications to their codebase. This was only possible due to the power of WebAssembly.

  • Use libraries written in languages such as C, C++ or Rust, even if you have similar libraries in JavaScript as the WebAssembly code can run very fast, and can offer better quality.

Team Google was able to compile different encoders from languages such as C and C++ into their Squoosh app and replace regular codecs such as JPEG with MozJPEG. These new replacements offered smaller file sizes without sacrificing the visual quality of the images.

Languages supported

WebAssembly support is not limited only to C, C++ and Rust. Many developers are trying hard to include support for other languages too. Here is a list of languages supported currently.


Criticisms

The introduction of WebAssembly that allowed execution in compiled binary, raised a lot of questions amongst the security aspects. The vulnerabilities can be exploited without even a trace and that can be really hard to detect. Although WebAssembly is equipped with its own security features, I personally believe they need to be further improved. With the newer features, older traditional protection such as antivirus tools and URL filtering are not able to cope at all. This would mean that regular browsers will be even less secure in the future.
You can read more about these issues below.

  • The dark side of WebAssembly
  • Research: More Worries with Wasm - Security Boulevard
  • Security

    Conclusion

    There is speculation that WebAssembly would replace JavaScript. But it quite false I must say. WebAssembly was created to co-exist with JavaScript, not to replace it. Furthermore, debugging JavaScript is easier than WebAssembly and the expressive freedom of JavaScript cannot be employed in Wasm.
    With everyone’s high expectations, it is safe to say that you will be amazed by the genre of applications WebAssembly can pave the way to.

    “No one can say for sure what kinds of applications these performance improvements could enable. But if the past is any indication, we can expect to be surprised“ — Lin Clark

Resources


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏