Image: rustwasm.github. IO /

Author: Liu Jialong

Writing in the front

This article hopes to knock on the door of WebAssembly with Rust. As a primer, this article is expected to help you understand WebAssembly and build a simple WebAssembly application. In addition to Internet Explorer, WebAssembly is currently supported by most major browsers, especially on mobile, including UC, X5 kernel, Safari, etc. After reading this article, I hope to help you put WebAssembly into production.

Introduction to WebAssembly (WASM)

If you really know WebAssembly, you can skip this section.

We can start with two classic wASM demos:

webassembly.org.cn/demo/Tanks/

wasm.continuation-labs.com/d3demo/

Quick summary: WebAssembly (WASM) is a portable, small, fast loading, and Web-compatible format, a new specification from the W3C. The purpose is to replace JS in some scenarios to get closer to the native computing experience, such as games, image/video editing, AR/VR. Speaking is about being smaller and faster.

Wasm is represented in two formats, text and binary. The binary format can be sandboxed in the browser’s JS virtual machine or run in other non-browser environments, such as the common Node environment. Running on the Web is what WASM was designed to do from the beginning, so implementing it in a browser is very simple.

To quickly compile WASM text, run a WASM binary:

Wasm text format code:

(module
    (import "js" "import1" (func $i1)) // Import method 1 from the js environment
    (import "js" "import2" (func $i2)) // Import method 2 from the js environment
    (func $main (call $i1)) // Call method 1
    (start $main)
    (func (export "f") (call $i2)) // Export the internal method f to js. When js calls, method 2 is executed
)
Copy the code

The above content can be briefly, refer to the comments in the code for a general understanding of the main function syntax. The main function is to import two methods import1 and import2 from the JS environment. It also defines a method f itself and exports it to external calls, executing import2 in the body of the method.

The text format itself cannot be executed in the browser and must be compiled into binary format. You can use wABT to compile the text format to binary. Note that the text format itself does not support comment writing, which needs to be removed during compilation. Here use wat2WASM online tool to quickly compile, compile the result is to download the wASM binary file required to run.

With binaries, all that’s left is to execute the call in the browser.

// Define the importObj object assigned to the WASM call
var importObj = {js: { 
    import1: (a)= > console.log("hello,"), // Corresponding to wASM method 1
    import2: (a)= > console.log("world!") // Corresponding to wAMS method 2
}};
// The demo.wasm file is the binary file you just downloaded
fetch('demo.wasm').then(response= >
    response.arrayBuffer() // WASM memory buffer
).then(buffer= >
       /** * instantiate, return an instance of wasm. module and a wasm. instance, * module is a stateless object with ast. module placeholder; * Where instance is a combination of module and ES standards, you can finally call the exported method in the JS environment */
    WebAssembly.instantiate(buffer, importObj) 
).then(({module, instance}) = >
    instance.exports.f() // Execute method f in wASM
);
Copy the code

A brief description of the function execution process:

  • Define one in jsimportObjObject, passed to the WASM environment, that provides methodsimport1 import2Referenced by WASM;
  • Fetch the binary file stream and fetch it to the memory buffer;
  • Instantiated from memory buffer via the browser global object WebAssembly, i.eWebAssembly.instantiate(buffer, importObj), wASM will be executedmainMethod, which will be calledimport1, the console prints Hello;
  • After instantiation return wASM instance, through this instance can call the method in WASM, so as to achieve two-way connection, executioninstance.exports.f()Methods in WASM are calledf.fWill call the js environment againimport2, the console outputs world.

This section of implementation, is it possible to achieve WASM call JS, and indirectly implement the wASM environment browser related operations? We will expand on this later.

Wasm is not implemented by writing text formats directly, so is there a “speaking” implementation way? Currently, the best support mainly includes C, C++, Rust, Lua, etc.

The characteristic Rust

If you know Rust, you can skip this section too.

A language empowering everyone to build reliable and efficient software

Rust was voted the most popular language of 2019.

Screenshots since insights.stackoverflow.com/survey/2019…

Rust was founded 15 years ago, less than five years ago, but it now covers a wide range of companies, including Amazon, Google, Facebook, Dropbox and alibaba, Toutiao, Zhihu, Bilibili and many others in China. So what makes such a young language grow so fast?

  • Rust focuses on security, concurrency, and performance. To achieve this goal, Rust follows three design philosophies: memory security, zero-cost abstraction, and utility
  • Run cross-platform with LLVM.
  • Rust has no runtime GC, and for the most part you don’t have to worry about memory leaks.
  • .

You can’t learn from your heart? Take a quick look at Rust and you may fall under his spell.

Can you answer the following seemingly simple questions correctly? There are three lines of code, the syntax itself is fine, guess what the print result is?

fn main() {
    let s1 = String::from("hello word"); Define a string object
    let s2 = s1; / / assignment
    println!("{}", s1); / / the log output
}
Copy the code
Think about it for a moment and click to see the answer

An error! S1 doesn’t exist anymore.

This is actually one of the more important features of Rust — ownership. When s1 is assigned to S2, ownership of S1 ceases to exist, which means that S1 has been destroyed. Through this feature, the realization of memory management is front-loaded, the realization of memory control in the process of code writing, at the same time, with the help of static check, can ensure that most correctly compiled programs can run normally, improve memory security, but also improve the robustness of the program, improve the control ability of developers.

Ownership is just one of the many features of Rust. It has many good ideas around its three philosophies (security, concurrency, and performance), which also indicates that the cost of getting started is relatively high. For those who are interested, take a closer look. Rust has previously set up four working groups, namely CLI, network, WASM and embedded, indicating the four directions in which Rust hopes to unleash its power. Up to now, it has been implemented in many fields, such as Atex-Web on the server side, Yew on the web front end, wASM on the wASM side, etc. All in all, Rust is a very interesting language that can broaden the boundaries of your abilities. It is recommended to learn about it, even if it is steep to get started, and you may fall in love with it.

Except for wasm, other directions (cli, server, etc.), I still like go, because of the simplicity, ^_^ escape…

All right, so why Rust fits wASM:

  • No runtime GC and no JIT is required to ensure performance
  • There is no garbage collection code, and code optimization can make WASM smaller
  • High level of support (official intervention), compared to other languages, the ecology is perfect so far, ensuring low cost of development

Rust -> wasm

Rust compilation target

Rustc is a cross-platform compiler with multiple targets for wASM compilation. You can check the rustup target list for details. There are three targets for wASM compilation:

  • Wasm32-wasi: Mainly used to implement cross-platform, through the WASM runtime implementation of cross-platform module general, no special Web attributes
  • Wasm32-unknown-emscripten: First you need to know Emscripten and easily support rust compilation with LLVM. The target product provides standard library support through Emscripten to ensure that the target product can be fully run to achieve a standalone cross-platform application.
  • Wasm32-unknown-unknown: A pure build from Rust to WASM without the need for a large C library, resulting in a smaller product size. Heap allocation is implemented through the memory allocator (WEE_ALLOc), so that we can use the various data structures we want, such as Map, List, etc. Use wASM-Bindgen, web-SYS/JS-SYS to interact with JS, ECMAScript, and Web API. The target chain is also currently under official maintenance.

Wasm32-unknown-unknown (wasm32-unknown-unknown) Wasm32 indicates that the address width is 32 bits, and wasM64 may be born later. The first unknown indicates that it can be compiled from any platform, and the second unknown indicates that it can be adapted to any platform.

wasm-pack

Wasm -> NPM -> wASM -> WASM -> NPM -> wASM -> WASM -> NPM -> WASM -> WASM -> NPM -> WASM -> WASM -> NPM -> WASM -> WASM -> NPM -> WASM -> WASM -> NPM To achieve a quick call on the Web, snooping on the “elephant” of wASM-NPM package requires only the following steps:

  1. Install Rust using Rustup
  2. Install the wasm – pack
  3. wasm-pack new hello-wasm.
  4. cd hello-wasm
  5. Run the wasm – pack build.
  6. The output of the PKG directory is the node_module that can be called normally

Take a look at the WASM runtime advantages for a real-world example

Directions are ready! Ready to go! Now you can use Rust to write wASM with pleasure. Here is a comparison between WASM and JS by implementing an MD5 encryption method.

First modify Cargo. Toml to add dependency packages

[dependencies]
wasm-bindgen = "0.2"
md5 = "0.7.0"
Copy the code

Cargo is Rust’s package manager for publishing, downloading, compiling, and more Rust packages on demand. Wasm – Bindgen is to help WASM and JS to interact with the toolkit, smooth out the implementation details, convenient two memory space communication.

Write the implementation (SRC /lib.rs)

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn digest(str: &str) - >String {
    let digest = md5::compute(str);
    let res = format!("{:x}", digest);
    return res;
}
Copy the code

With WASM_bindgen, methods can be quickly exported to JS for invocation without needing to worry about the details of memory communication. Finally, the wASM-pack build builds the package (under the directory PKG), which can be referenced directly on the Web. The product consists of the following parts

├── Package.json ├── ├ me.md ├──.ts ├─ Index_bg. This is the module file eventually referenced by the ECMAScript project, which contains our defined methods and some automatically generated glue functions that use TextEncoder to communicate data between memory.Copy the code

Js calls

import * as wasm from "./pkg";
wasm.digest('xxx');
Copy the code

The resulting WASM PKG package is imported into the Web project, packaged and compiled using webpack@4, and doesn’t even need any other plug-ins to support it.

Speed comparison

Md5 encryption for a string of about 220,000 characters

Encryption 1 time (ms) Encrypt 100 times (ms) Algorithm dependent package
Js version md5 ~ 57 ~ 1300 www.npmjs.com/package/md5
Wasm version md5 ~ 5 ~ 150 crates.io/crates/md5

At the data level, the performance benefits of WASM are obvious. However, it was also found that the difference of performance data was enlarged when 100 times, but the ratio was smaller than that of single encryption. The reason is that in the case of multiple encryption, the proportion of communication cost between JS and WASM increases gradually, resulting in a non-proportional increase in the encryption time, which also indicates that the actual encryption time of WASM is smaller than the result. This illustrates how WASM can be used on the Web: computation-heavy, less interactive, such as audio/video/image processing, gaming, and encryption. In the future, however, this will improve as well, with more efficient value passing enabled by interface-Type, and the future of front-end frameworks may be a real revolution.

Use WASM to implement a complete Web application

With WASM-Bindgen, JS-SYS, and Web-SYS Crates, we can even complete web applications with minimal js dependency. Here is a web-WASM application for converting native color PNG images to black and white.

Effect:

Online experience: Click on me

The general function is to read files through JS, use WASM for black and white image processing, create DOM and render images directly through WASM.

1. Use JS to achieve a simple file reading:

// html
<div>
	<input type="file" id="files" style="display: none" onchange="fileImport();">
	<input type="button" id="fileImport" value="Select a color PNG image">
</div>
Copy the code
// js
$("#fileImport").click(function () {$("#files").click();
})
window.fileImport = function() {
    // Get the File object that reads my File
    var selectedFile = document.getElementById('files').files[0];
    var reader = new FileReader(); // This is the core that does the reading.
    reader.readAsArrayBuffer(selectedFile); // Read the contents of the file, or read the URL of the file
    reader.onload = function () {
        var uint8Array = new Uint8Array(this.result); wasm.grayscale(uint8Array); }}Copy the code

The obtained file is a JS object, and the final file information needs to be passed to WASM through memory, whereas the file object cannot be passed directly to wASM space. We can use FileReader to convert the image file to an 8-bit unsigned array for data transfer. At this point, all you need to do is call the wASM. grayscale method and pass the data to WASM.

2. Wasm retrieves data and reorganizes it

fn load_image_from_array(_array: &[u8]) -> DynamicImage {
    let img = match image::load_from_memory_with_format(_array, ImageFormat::Png) {
        Ok(img) => img,
        Err(error) => {
            panic!("{:? }", error)
        }
    };
    return img;
}

#[wasm_bindgen]
pub fn grayscale(_array: &[u8]) -> Result<(), JsValue> {
    let mut img = load_image_from_array(_array);
    img = img.grayscale();
    let base64_str = get_image_as_base64(img);
    return append_img(base64_str);
}
Copy the code

Wasm takes the array and reassembles it into an image file object. Image Crate quickly converts an unsigned array to an image object (load_image_from_array). And black and white image processing (img.grayscale()). The processed object needs to finally return the content information recognized by the browser tag for the front end to preview, in this case the base64 string.

3. Generate base64 image format in WASM

fn get_image_as_base64(_img: DynamicImage) -> String {
    // Create a memory space
    let mut c = Cursor::new(Vec::new());
    match _img.write_to(&mut c, ImageFormat::Png) {
        Ok(c) => c,
        Err(error) => {
            panic!(
                "There was a problem writing the resulting buffer: {:? }",
                error
            )
        }
    };
    c.seek(SeekFrom::Start(0)).unwrap();
    let mut out = Vec::new();
    // Read data from memory
    c.read_to_end(&mut out).unwrap();
    / / decoding
    let stt = encode(&mut out);
    let together = format!("{} {}"."data:image/png; base64,", stt);
    return together;
}
Copy the code

In the WASM space, the DynamicImage object is converted to a base value, so that it can be passed again. Use Rust Cursor to read and write information of DynamicImage objects. Rust Cursor is similar to front-end Reader/Writer. Information is read and written through a cache area to obtain images stored in memory space. The obtained information can be decoded by Base64 to obtain the original string information, and the obtained string Mosaic format information data:image/ PNG; Base64 composed of a complete picture resource character creation, it can be directly returned to the front end for preview rendering.

The above has completed all the process of image processing, the base64 can be directly returned to JS to create dom preview. But! Is it possible for me to do this in WASM without using JS?

4. Create dom and render images in wASM

Wasm itself cannot manipulate the DOM directly. It has to manipulate the DOM through JS. However, it is still possible to operate dom indirectly by loading JS modules in WASM. Web_sys does this and basically does all the interface implementation. Web_sys can easily implement a pure WASM front-end framework such as Yew.

Picture source: hacks.mozilla.org/2017/02/whe…

pub fn append_img(image_src: String) - >Result<(), JsValue> {
    let window = web_sys::window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let body = document.body().expect("document should have a body");
    let val = document.create_element("img")? ; val.set_attribute("src", &image_src)? ; val.set_attribute("style"."height: 200px")? ; body.append_child(&val)? ;Ok(())}Copy the code

The operation process is basically the same as using JS to operate DOM directly. In fact, it also indirectly calls the JS side method. In practical application, the extra performance loss caused by multiple communication should be avoided as much as possible.

A simple picture black and white processing application completed, complete code: click me. Other functions can be extended in a similar way, such as compression and clipping.

Write in the last

This paper briefly describes the process from Rust to WASM and then to Web based WASM. I hope that after reading this article, I can help you develop ideas to solve problems in actual business development and explore more practical scenarios. Due to the author’s limited level, welcome criticism and correction.

Data reference

rustwasm.github.io/

rustwasm.github.io/wasm-pack/

Github.com/WebAssembly…

Yew. Rs/docs/v/zh_c…

Hacks.mozilla.org/2017/02/whe…

This article is published from netease Cloud Music front end team, can be reproduced freely, please indicate in the title of reprint and reserve the source in a prominent place. We’re always looking for people, so if you’re ready to change jobs and you like cloud music, join us!