DEV Community

xhevo
xhevo

Posted on

Native Code in Browser

Today we are going to talk about a story. A story about a small software development company.

The Story

One day the manager calls on a meeting where she presents the idea for the new project that needs to be developed.
The idea is to create a mobile app that applies filters to your images. Simple as opening the app, selecting an image from your phone's gallery and applying a filter on it.

The development team decides to create a Web API, that will allow developers to upload images and download the images with applied filters.
After they developed the web service and the mobile apps, they needed a super fast algorithm that applies filters on images. All they had available as a development resource at the time was a C++ ninja that had a strong algorithm development background, so they assigned him to develop that feature.

...Fast forward 3 months, everything was working like a Swiss watch.

After the first success on mobile platforms, the company's C-Suite decided to go also on Web, something like an "image converter" web app built with frontend technologies like HTML, CSS, and javascript.
...Say no more, the developers integrated a simple website with the Image Conversion API within 24 hours, and everything worked without a shadow of a doubt perfectly.

...Well until the point when customers scaled so much, that the server could not perform enough calculations to accomplish the needs of every client, so temporary the app was scaled horizontally.

In the meantime, the company was seeking a solution to the problem they had.
They decided to move the conversion logic to the frontend. Which means, the Android and IOS apps would convert images within the app, without sending it to the Web API.

That was awesome, until the point they figured out that they forgot the Web version, where they had to port all that c++ library to JavaScript. Yes, JavaScript.
What does that mean? - It means writing the conversion algorithm from the ground up in JavaScript, writing the unit tests again, writing more unit tests, because of JavaScript's weird data types, testing it across all browsers.

Well, let's be honest. At least once in your lifetime, you imagined running some Non-JavaScirpt programming language in a web browser.
Guess what, they were hoping that such technology exists because they had to come out with the new Web release as soon as possible.

The "unbelievable" that became reality

So, how it all began...?
It all began on one sunny day, when three friends, called Microsoft, Google, and Mozilla, decided to drink a coffee and discuss on topic "browsers".

They talked about the common problems that every javascript developer has, like backward compatibility when using modern ECMAScript versions and cross-browser issues because we develop one JavaScript code but it gets interpreted and executed differently by each JavaScript engine, which means you might have some bugs over there.

At the end of the discussion, they decided to put an end to this circus, by creating a new technology called WebAssembly, and it will run in the same virtual machine as JavaScript, which means it will easily interact with JavaScript and vice versa.

wasm and js, same virtual machine

The standard

Furthermore, they decided that they will create a standard, that will be equal in every browser.

standards

So, what's the new standard about?
It's about the way how the browser will look upon our code. Imagine having a static HTML5 website, and you have to load your JavaScript to add some interaction.
One natural thing that most of the developers do is put a "script" tag with a "href" attribute that points to your JavaScript somewhere on the internet.

That's all ok, but how are we going to load our native code in the browser?
The point is, we won't load our native code in the browser at all, instead, we will load some binary files that hold the ".wasm"(WebAssembly) file extension.

The Binary files

We are going to load binary files instead of text files. Now, you are probably asking your self, about what's the binary content in that file and how are we going to generate it. The answer is simple. We will use magic.
The same magic that we use to convert our source code into binary files called ".dll" and ".exe", and that magic is called Compiler.

If you try to open a generated ".wasm" file, you will see nothing else than a shitload of non-human-readable characters. That's because ".wasm" files are precompiled files, they do not contain any source code.

As for loading these binary files in the browser, there is no straight forward way to use them in our website, such as adding a "script" element, but there is a way to load them using JavaScript. It's called a "glue code". This glue code asynchronously loads the binaries from a given endpoint, and a worth-to-mention thing here is that ".wasm" files cannot be loaded as static files, but instead, they should be served by a server, for some security reasons.

source-code->wasm->browser

Let's see how javascript loading works in simple steps.
JavaScript code gets loaded, then it gets parsed/tokenized/interpreted and optimized bunch of times, and finally some bytecode gets loaded into the VM.
On the other side, WebAssembly is already optimized in compile time and the browser gets it as precompiled. Knowing this, we are now a few steps in advance comparing to JavaScript.

If you are a developer with dotnet background, you probably know what Intermediate Language(IL) stands for. It allows multiple technologies to run in the same runtime and interact with each other. For instance, there could be a desktop app that has some of its plugins developed in VisualBasic, or C++. First, the compiler turns source code into Intermediate Language, and then the Intermediate language gets executed by the runtime. In other words, it's a compilation target.

intermediate-language-image

Knowing this, we can now consider the WebAssembly as a compilation target. Which means that we can compile our native code into wasm and allow JavaScript to interact with it.

Furthermore, WebAssembly is nothing more than a set of instructions packed into a Module. There is also a text representation of the wasm file, the human-readable one, and it's called WebAssembly Text(".wat"). The ".wat" is consisted of AST(Abstract Syntax Tree) and of so-called S-Expressions. You can find more information on AST on this link

cpp-wasm-text-image

Performance

One of the best things that WebAssembly as technology provides, is performance. And it's preferred to use WebAssembly when we have heavy bound CPU computations, like games, video editing, image processing etc.

But wait a second! - If wasm is more performant than JavaScript, why we don't compile the JavaScript into wasm? WebAssembly as a technology works faster when we compile mid-level programming languages like C/C++/Rust.
Why? Because they don't need a special runtime. Unlike C/C++, high-level languages like C# and Java, have special requirements, i.e. you will need to ship their entire runtime to be able to run them.
So the answer will be a big NO, we cannot compile JavaScript into wasm without shipping the entire runtime in the first place.

Optimization

We mentioned some compile-time optimization. There is no normal compiler that would not optimize simple addition operation between 2 or more constants in
compile time.

var result = 1 + 2;
//will be optimized into
var result = 3;




Turning C++ into JavaScript.

In the meantime, while enterprise companies were making inventions with WebAssembly, some fellas had already come up with a "kind of" solution. They had created the Emscripten, an Open Source LLVM to JavaScript compiler.
Its main purpose was to compile C and C++ code into JavaScript, additionally, it could compile any other code that can be translated into LLVM bitcode into JavaScript.
The Emscripten outputs a fast and optimized code. Its default format is "asm.js", a highly optimizable subset of JavaScript that can execute at close to native speed in many cases.

Later on, when the WebAssembly technology had its blueprints released, Binaryen was created to serve as a compiler that turns "asm.js" into wasm.

Back to the Story

All we needed was a way to use our C++ code in the browser. Now with WebAssembly that is more than possible. We can easily use Emscripten and Binaryen to compile our C++ libraries with WebAssembly as a compilation target.
Guess What! That's what exactly happened to a lot of large code-bases like Unity3D Game Engine, Unreal Engine 4 and many more

The Missing stuff

For now, WebAssembly as technology is mature enough for using it, but what about using some of the browser APIs? Like "console", "window", how are we going to access the "DOM elements"?
For now, there are no concrete implementations on these topics, neither for garbage collection, multithreading(best candidate to come next).

Lacking garbage collection means we are the ones that have to care about it.
In cases when we ship our runtime, e.g. with C# we also ship the mono runtime, and the runtime has already implemented the garbage collection. The case is the same with the multithreading. If the runtime has implemented it, it's available.

JavaScript Interops

Having the browser APIs implemented by the framework is awesome, but how are they really implemented, or what if some of them are missing, how can we add to WebAssembly a functionality that is not available yet?

Well, remember when we mentioned earlier the "glue code"? It's the code that gets our wasm file from the server and loads it into the browser. The "glue code" can also do some extra stuff, like adding some information to the module for method implementations.

It's can be confusing at first, but we'll try to get it through an example.
Let's suppose that we have a button on our website that has a simple hit-counter in it.
The hit-counter will behave as follows:
When we click the button we want to increment a value in our code. Next, a paragraph will be populated with the new value.

The implementation will be simple. Binding an "OnClick" event on the button, so when the button is clicked, a method from the WebAssembly module with name "incrementCounter" will be called. We will store our "counterValue" in a global variable in the C++ code.
The implementation of "incrementCounter" method will increment the global variable "counterValue". Next, we need to update the paragraph with the newly incremented value.
Since we have no garbage collection, we cannot track the references of DOM elements.
We need to make another workaround by calling a method that is marked as "extern", which means that the implementation of that method will be provided externally(this varies in every programming language).
When loading the WebAssembly module using "glue code", we can provide to the module implementations for the missing methods(the ones marked as "extern"). In our case, we can pass to the WebAssembly module, an implementation that updates the value of the paragraph.

The source code for the hit-counter example is provided here

I hope that you enjoyed the article.
If you have any comments, please share them :)

Top comments (1)

Collapse
 
igorlazarevski profile image
igor.lazarevski@gmail.com

Very good post. Thanks Xhevo.