Build your first WebAssembly component

Original author: Nick Larsen
  • Transfer
When I first heard about WebAssembly technology , it immediately seemed like a cool thing to me and I immediately wanted to try it in practice. From the first desire, to something that worked for me, however, I had to spend a lot of time and sometimes experience some disappointments. In order to save your time and your nerves, if you want to repeat the same path, this article has been written.

image
Warning to reader

This article was written on June 24, 2016. Since WebAssembly is a very young and dynamic technology, over time, many of the things described in this article will become obsolete or completely change - keep this in mind.

Now let's go.

What is WebAssembly?

The official documentation says the following: “WebAssembly or wasm is a new portable, efficient in size and download speed compilation format for the web.” Ummm ... What? The format of what? Text or binary? Yes, this is a frankly bad description. So remove your buzzword bingo cards already and I, based on my experience, will give my definition:

"WebAssembly or wasm is a bytecode specification for writing productive, browser-independent components for the web." This definition, of course, is not the pinnacle of the epistolary genre, but I will try to supplement it. WebAssembly can improve performance by using statically typed variables that cost much less on dynamic runtime than dynamic variables. WebAssembly is being developed by W3C Community Groupand it’s planned to be implemented in all major browsers. And from that moment on, a killer feature is laid out on the table: you can write web component code in any programming language.

Now it sounds better, isn't it?

Let's start

When I learn a new thing, I usually look for the smallest possible example, sufficient to see how everything works. Unfortunately, this approach is not very possible with WebAssembly. In the current state of the specification, wasm is just a bytecode format. Imagine how, in some 1996, Sun Microsystems engineers would imagine JVMs ... but without Java. The conversation would go something like this:

"- Hey, check it out, what a cool machine to execute bytecode we created!
- Cool! But how to write code for it?"
- And so:

image

- Ummm ... cool ... I’ll try it sometime at my leisure.
”“ Fine, let us know if there are any problems or ideas! ”
“ Yes, yes. But I'm a little busy here, I need to look at a few other things ... But as soon as - right away! "

And even this is a bad example, because the JVM is at least based on the Java language, but with WebAssembly we don’t have that either. I hope you get the idea. If you imagine a bytecode without a tool that compiles the code of some programming language into this bytecode, it will be difficult for you to promote it. So how do we get started with WebAssembly?

What came before WebAssembly?

Most technologies are the result of the development of some previous technologies, especially when the planned goal is to get some formal specification. WebAssembly is no exception, it is a continuation of the development of ideas once laid down in asm.js, a specification designed to write javascript code in such a way that it can be compiled with static typing. Wasm developed these ideas by creating a bytecode specification that can be created by a compiler of any programming language, then sent over the Internet as a binary file suitable for execution by any modern browser.

asm.js is just a specification for writing javascript code using a subset of the features of the Javascript language. You can write the code on asm.js manually and, if you are eager to take and write something, it's time to start.

function MyMathModule(global) {
    "use asm";
    var exp = global.Math.exp;
    function doubleExp(value) {
        value = +value;
        return +(+exp(+value) * 2.0);
    }
    return { doubleExp: doubleExp };
}

This is not a very useful feature, but it is written according to the asm.js. specification. If it looks a little silly for you - so know that you are not the only one who thinks so. However, all these “strange” characters (all these unary operators) are necessary. They indicate to the compiler data types in operations. The code is very simple, but if you make a mistake somewhere, the debug console will display a fairly readable error message.

If you want to use this function, you can do it somehow like this:

var myMath = new MyMathModule(window);
for(var i = 0; i < 5; i++) {
    console.log(myMath.doubleExp(i));
}

And, if you did everything correctly, then you should see something similar on the output:

image

And finally, go to WebAssembly

At the moment, we have a working piece of code on asm.js. We can go to the official WebAssembly page on GitHub and find there tools for compiling this code in wasm. The only trouble is that we have to assemble these tools ourselves. This is, frankly, the worst part of the whole quest. These tools are constantly changing and from time to time are in a broken state, especially in terms of their use under Windows.

To build you need make and cmake. If you are running Windows, you will also need Visual Studio 2015. Here are the build instructions for Mac, and here is for Windows.

image

It should be noted that the distribution of the collected binaries of these utilities would be a huge step forward in the promotion of WebAssembly technology.

If you went through all of the above without problems, you got the bin folder in the binaryen folder, where the tools for converting our asm.js code to wasm are located. The first tool is called asm2wasm.exe. It converts the asm.js code to the .s code format, which is a textual representation of the abstract syntax tree (AST) of the wasm format. Running asm2wasm on your asm.js code will give you something like this:

(module
(memory 256 256)
(export "memory" memory)
(type $FUNCSIG$dd (func (param f64) (result f64)))
(import $exp "global.Math" "exp" (param f64) (result f64))
(export "doubleExp" $doubleExp)
(func $doubleExp (param $0 f64) (result f64)
(f64.mul
(call_import $exp
(get_local $0)
)
(f64.const 2)
)
)
)

You can parse this code line by line, but now I just want to emphasize that since wasm is a binary format, just clicking on something in the browser and looking at the code, as you are used to doing it with Javascript, will fail (at least for this moment). What you see will be very similar to the code above.

The next step is to convert this .s format to a wasm-binary, for this we will use the wasm-as.exe utility. Applying it to your .s file, you get the bytecode in the output, for which we started this whole story.

image

image

Now grab the latest version of Firefox or Chrome Canary and include WebAssembly in them.

For Firefox, you need to open about: config and type “wasm” in the search bar. After that, change the value of the javascript.options.wasm option to true and restart the browser. For Chrome Canary, you need to open chrome: // flags, find and enable the Experimental WebAssembly option, and then restart the browser.

Now we need to run our module in the browser. At first, this turned out to be a problem for me, since it is completely not obvious how to do it. I opened the console in Chrome Canary and tried typing “WebAsse” - and nothing, no hints. Then I typed “Was” and got a hint! This object in the inspector looked very poor in terms of documentation. I will omit the whole story of how I rummaged in search of a working example, I will only say that in the end I found it in some JS.md filein the WebAssembly repository. There was something like documentation and an example, here it is:

fetch("my-math-module.wasm")
    .then(function(response) {
        return response.arrayBuffer();
    })
    .then(function(buffer) {
        var dependencies = {
            "global": {},
            "env": {}
        };
        dependencies["global.Math"] = window.Math;
        var moduleBufferView = new Uint8Array(buffer);
        var myMathModule = Wasm.instantiateModule(moduleBufferView, dependencies);
        console.log(myMathModule.exports.doubleExp);
        for(var i = 0; i < 5; i++) {
            console.log(myMathModule.exports.doubleExp(i));
        }
    });

Drop this into your html file, pick up the local web server and open this file in a browser. Here's what it will look like: It's

image

time to go send a bug report. Remember that this is still a very crude and experimental technology, so do not be surprised at the bugs that arise along the way.

image
Congratulations!

You created your first WebAssembly component. What's next? Well, we only slightly cast off the veils of secrecy. Writing asm.js code was the key to this example, and writing some non-trivial functionality would take time and patience. Using emscripten, compiling non-trivial applications in asm.js is much easier. I advise you to read the asm.js specification, especially the section on the memory model, since many concepts have moved directly to asm.js in WebAssembly. Another important point: at the moment, you cannot pass arrays as arguments to a function. There is some agreement that this should change, but so far this is not reflected in the specification. It's time to refresh the logic of working with pointers.

Another caveat: when you start writing non-trivial things in wasm, you may notice that performance can sometimes be slower than the good old Javascript. Just keep in mind that modern Javascript engines in all browsers are very highly optimized and it will take some time for wasm to achieve their effectiveness. The theoretical performance limit of WebAssembly is higher than that of Javascript code in text form, but WebAssembly is not yet ready for industrial use.

Also popular now: