Skip to content

Latest commit

 

History

History
300 lines (223 loc) · 12.5 KB

README.md

File metadata and controls

300 lines (223 loc) · 12.5 KB

By Stefan Verhoeven, Faruk Diblen, Jurriaan H. Spaaks, Adam Belloum, and Christiaan Meijer.

Using C++ in a web app with WebAssembly

Let's say you have some C++ code laying around that you would like to make available to a wider audience, by putting it on the web. Until recently, this used to be pretty difficult, and may even have required reimplementation of the software in JavaScript, the programming language that browsers use.

Wouldn't it be great if you could run your existing C++ code on the web with only minor effort?

That way, loads more people would be able to see your results, interact with your algorithm, and apply it for their own purposes.

In this blog, we'll show you how to take a simple algorithm written in C++ and make it available as a web application. Subsequent blogs in this series will expand on the current one by laying out more advanced topics, specifically how to make the app interactive, how to visualize the results, and how to deal with long running tasks.

Root finding

So today's aim is to have a simple web app that determines the root of a mathematical function 2x^3 - 4x^2 + 6, i.e. the value of x where y = 0.

equation.png

Function 2x^3 - 4x^2 + 6.

For this, we'll use an iterative method known as the Newton-Raphson root finding method. Remember Newton? Quiet fellow, fabulous hair? Yes, that Newton. The way Newton-Raphson works is, you give it the equation whose root you want to find, along with the derivative of that equation. Then you take an initial_guess of what you think the value of the root could be, then let the method iterate towards the solution. The solution is approximate within a tolerance, which you can also set. Anyway, the algorithm is written C++, but with some trickery, we'll be able to use that C++ code from the browser, without the need to port it first!

newton.jpg

Newton (and his hair). Image courtesy of Wikimedia Commons.

Now before you say "That'll be so much slower than running it native!" or "C++ from the browser? Impossible!", just hold your horses for a sec. With the right tools, it is possible to run C++ code in the browser, with an acceptable performance penalty. For example, Gabriel Cuvillier was able to run the video game Doom 3 in the browser. He was able to do this by compiling the game's source code into WebAssembly, a low-level language that browsers can run. And if it works for video games, it will likely work for your research software, too.

hold-your-horses.jpeg

Hold your horses. Image courtesy of Luciano Marelli via pixabay.

What we'll need

OK, now that you're fully on board with this, let's get to it. Here's a list of what we need:

  1. We are going to write a small HTML page, so you will need basic knowledge of HTML and JavaScript.
  2. Some C++ code to illustrate the process. We'll use our Newton-Raphson C++ code.
  3. A program that can take our existing C++ code and compile it into a WebAssembly module. For this, we'll use Emscripten's emcc compiler, the most popular C++ to WebAssembly compiler of the bunch.
  4. To use the WebAssembly functionality from JavaScript, a binding is required. The binding will map C++ constructs to their JavaScript equivalent and back. For this, we'll use embind.
  5. A web server to serve our files. We'll use Python 3's http.server, but other web servers work equally well.

Tying it all together

The C++ code

Here is the equation whose root we want to find, along with its derivative, since that's what Newton-Raphson requires:

#ifndef H_PROBLEM_HPP
#define H_PROBLEM_HPP

float equation(float x);
float derivative(float x);

#endif

File: problem.hpp

// An example equation
float equation(float x) {
  return 2 * x * x * x - 4 * x * x + 6;
}

// Derivative of the above equation
float derivative(float x) {
  return 6 * x * x - 8 * x;
}

File: problem.cpp

The snippet below shows the contents of the file newtonraphson.hpp. It is the header file for the Newton-Raphson iterative root finding algorithm. It defines a class named NewtonRaphson. Besides the constructor method NewtonRaphson(float tolerance_in), NewtonRaphson has one other public method, solve, which takes a float, and returns another float. Furthermore, NewtonRaphson also has a private member, tolerance of type float, which is used to store the class instance's private data.

#ifndef H_NEWTONRAPHSON_HPP
#define H_NEWTONRAPHSON_HPP

class NewtonRaphson {
  public:
    NewtonRaphson(float tolerance_in);
    float solve(float initial_guess);
  private:
    float tolerance;
};
#endif

File: newtonraphson.hpp.

File newtonraphson.cpp contains the corresponding implementation:

#include "newtonraphson.hpp"
#include "problem.hpp"
#include <cmath>

// Define the constructor method of NewtonRaphson instances
NewtonRaphson::NewtonRaphson(float tolerance_in) : tolerance(tolerance_in) {}

// Define the 'solve' method of NewtonRaphson instances
float NewtonRaphson::solve(float initial_guess) {
  float x = initial_guess;
  float delta_x = 0;
  do {
    delta_x = equation(x) / derivative(x);
    x = x - delta_x;
  } while (std::abs(delta_x) >= tolerance);
  return x;
};

File: newtonraphson.cpp.

From this definition, NewtonRaphson instances need to be initialized with a value for tolerance_in, which is then stored as the private member tolerance. Once the object instance has been constructed, users can call its solve method to iteratively find equation's root, with equation and its derivative being imported from problem.hpp via the include line near the top.

Check on command line

The following code is a minimal command line program that we can use to check if everything is working correctly:

#include <iostream>
#include <iomanip>

#include "newtonraphson.hpp"

int main() {
  float initial_guess = -4;
  float tolerance = 0.001;
  NewtonRaphson newtonraphson(tolerance);
  float root = newtonraphson.solve(initial_guess);

  std::cout << "Function root is approximately at x = ";
  std::cout << std::fixed << std::setprecision(2) << root << std::endl;

  return 0;
}

File: cli.cpp.

Our command line program can be compiled with:

g++ -o cli.exe problem.cpp newtonraphson.cpp cli.cpp

Subsequently running it should give the following output:

./cli.exe
The value of the root is : -1.00

Now we're ready to move on to the WebAssembly part.

Binding

To use the Newton-Raphson code from JavaScript, we'll need to define the bindings file. The binding allows compiled code to be called from JavaScript. For our Newton-Raphson code, the binding file looks like this:

#include <emscripten/bind.h>
#include "newtonraphson.hpp"

using namespace emscripten;

EMSCRIPTEN_BINDINGS(newtonraphson) {
   class_<NewtonRaphson>("NewtonRaphson")
      .constructor<float>()
      .function("solve", &NewtonRaphson::solve)
      ;
}

File: bindings.cpp.

The binding file uses embind binding statements to expose the NewtonRaphson class, its constructor method, as well as its public method solve.

Compiling to WebAssembly

First we need to download and install Emscripten to get the compiler. The Newton-Raphson source and its binding can be compiled into a WebAssembly module with Emscripten's emcc compiler, as follows:

emcc -I. -o newtonraphson.js -Oz -s MODULARIZE=1 \
  -s EXPORT_NAME=createModule --bind problem.cpp newtonraphson.cpp bindings.cpp

This will generate a WebAssembly module newtonraphson.wasm, along with a JavaScript file newtonraphson.js. We also export the createModule JavaScript function in the compile command so it can be used to load and initialize the WebAssembly module. Using this JavaScript library, we can find the root of the mathematical function, and subsequently display its value with the following HTML:

<html>
   <head>
      <!-- Load WebAssembly module -->
      <script type="text/javascript" src="newtonraphson.js"></script>
   </head>
   <body>
      <div>
         Function root is approximately at x =
         <span id="answer"/>
      </div>
      <script>
         // Wait for module to initialize,
         createModule().then(({NewtonRaphson}) => {
            // Hardcoded input values
            const initial_guess = -4;
            const tolerance = 0.001;
            // Perform computation
            const newtonraphson = new NewtonRaphson(tolerance);
            const root = newtonraphson.solve(initial_guess);
            // Write the value of 'root' to the tag whose 'id' is equal to "answer"
            document.getElementById("answer").innerHTML = root.toFixed(2);
         });
      </script>
   </body>
</html>

File: index.html.

Hosting the app with a web server

We'll need a web server to display the HTML page in a web browser. For this, we'll use the http.server module from Python 3 to host all files in the current directory on port 8000, like so:

# change to directory with index.html, newtonraphson.* files
python3 -m http.server 8000

From the figure at the top of the article, the root of the equation should be at x = -1.00. Visit http://localhost:8000/ to see if your browser shows the correct result.

result.png

The resulting page if everything works. See the live version of the app on GitHub pages.

Recap

  1. We wrote a simple algorithm in C++
  2. We defined the JavaScript interface by writing Emscripten bindings
  3. We compiled the algorithm and bindings to a WebAssembly module with Emscripten compiler
  4. We ran the algorithm in a web browser using some JavaScript to talk to the WebAssembly module.

The nice thing about this solution is that we don't need expensive infrastructure to perform computation as the computation is done in the user's web browser--we just need somewhere to host the files.

Get in touch with us

This blog was written by the Generalization Team of the Netherlands eScience Center. The team consists of Stefan Verhoeven, Faruk Diblen, Jurriaan H. Spaaks, Adam Belloum and Christiaan Meijer. Feel free to get in touch with the generalization team at [email protected].

Where to go from here?

In upcoming blogs we will cover:

We'll wrap up the series in a final blog that combines the topics of the whole series in a full-featured web application. If you're curious what that's going to look like, make sure to check out the live demo on GitHub Pages.

If you enjoyed this article, leave a comment and give us a clap!

These blogs were written as part of the "Passing XSAMS" project. To learn more about the project, check out its project page.

Thank you to our proof readers Jan van Dijk, Daan Boer, Lourens Veen and Patrick Bos.