WebAssembly LERP C++

The following example demonstrates how to use a WebAssembly binary in a PeerMR job. It is based off of the Emscripten LERP C++ example. The binary is a simple linear interpolation function written in C++ and compiled to WebAssembly using Emscripten. PeerMR can be used to run this in a distributed manner across multiple browsers.

First, create the C++ source lerp.cpp:

#include <emscripten/bind.h>

using namespace emscripten;

float lerp(float a, float b, float t) {
    return (1 - t) * a + t * b;
}

EMSCRIPTEN_BINDINGS(my_module) {
    function("lerp", &lerp);
}

Next, compile it to WebAssembly using Emscripten:

emcc -lembind -O0 -s ENVIRONMENT=worker -s EXPORT_NAME="LerpModule" -s MODULARIZE=1 -o lerp.js lerp.cpp

-O0 optimizes the binary for size. -s ENVIRONMENT=worker sets the environment to a web worker which is the execution environment for PeerMR jobs. -s EXPORT_NAME="LerpModule" sets the name of the exported module. -s MODULARIZE=1 wraps the output in a module to not pollute the global namespace. -o lerp.js specifies the output file.

This will generate two files: lerp.js and lerp.wasm. The lerp.js file is a JavaScript wrapper around the WebAssembly binary. Both files are needed to run the WebAssembly binary in a browser. Make these files available as public URLs, for example by uploading them to a cloud storage service. In this example, we will use GCS and assume the bucket name is lerpbucket.

Next, create a PeerMR job that uses the WebAssembly binary:

const execution = new JobExecution(storageType = 'pmrfs');
execution.workerCount = 2;

// lerp.js will fetch the WASM binary
execution.scripts = ['https://storage.googleapis.com/lerpbucket/lerp.js'];

// register the URL of each WASM binary that the job needs
execution.wasmBinaries = ['https://storage.googleapis.com/lerpbucket/lerp.wasm'];

execution.setInputFn(async function input() {
  return [
    [0.0, 1.0, 0.5],
    [0.0, 1.0, 0.25],
    [0.0, 1.0, 0.75],
    [0.0, 1.0, 0.1],
    [0.0, 1.0, 0.9],
    [0.0, 1.0, 0.0],
    [0.0, 1.0, 1.0]
  ];
});
  
execution.addStage(new MapStage(async function map(x) {
  // import the LerpModule
  // use `context.getWebAssemblyURL` to specify the URL of the WASM binary
  const lerpModule = await LerpModule({
    locateFile: function (path) {
      if (path.endsWith('.wasm')) {
        return context.getWebAssemblyURL('lerp.wasm');
      }
      return path;
    }
  });

  // lerp each value
  for await (const kv of x) {
    let [k, v] = kv;
    context.emit(k, lerpModule.lerp(v[0], v[1], v[2]));
  }
  
  await context.onComplete();
}));

execution.start(jobRunner);