Gareth Andrew

BlogAbout

Fun with Futhark and Node-FFI

I recently came across the Futhark Programming Language, for "High-performance purely functional data-parallel array programming on the GPU". It's designed to be a very small language used for the compute intensive workloads of an application written in some other language. So why not try it with NodeJS?

Install Futhark

There are installation instructions in the Futhark manual. Thankfully there is an AUR Package for Arch Linux users like me. If you want acceleration you need either or both OpenCL and CUDA set up and installed.

A Futhark Program

For test purposes I just copied the fact_iterative.fut sample from the Futhark distribution. It simply computes the factorial of a number.

let fact(n: i32): i32 =
  loop out = 1 for i < n do
    out * (i+1)

let main(n: i32): i32 =
  fact(n)

Then we can compile the program:

> futhark c fact.fut

This will create an executable fact (or whatever you've named the file), which we can execute by passing the input on STDIN

> echo 10 | ./fact
3628800i32

A C Program

Now we want to test compiling the futhark program into a shared library which we can access from another programming language. We'll with C first, since there is some documentation. First, we compile the Futhark program into a library:

> futhark c --library fact.fut
> gcc fact.c -o libfact.so -shared -fPIC`

This will produce a shared library file and a fact.h header file which defines the interface of our library:

/*
 * Initialisation
*/

struct futhark_context_config ;
struct futhark_context_config *futhark_context_config_new();
void futhark_context_config_free(struct futhark_context_config *cfg);
void futhark_context_config_set_debugging(struct futhark_context_config *cfg,
                                          int flag);
void futhark_context_config_set_logging(struct futhark_context_config *cfg,
                                        int flag);
struct futhark_context ;
struct futhark_context *futhark_context_new(struct futhark_context_config *cfg);
void futhark_context_free(struct futhark_context *ctx);
int futhark_context_sync(struct futhark_context *ctx);
char *futhark_context_get_error(struct futhark_context *ctx);
void futhark_context_pause_profiling(struct futhark_context *ctx);
void futhark_context_unpause_profiling(struct futhark_context *ctx);

/*
 * Entry points
*/

int futhark_entry_main(struct futhark_context *ctx, int32_t *out0, const
                       int32_t in0);

/*
 * Miscellaneous
*/

void futhark_debugging_report(struct futhark_context *ctx);

We have some functions for creating and freeing the futhark config and context, and a method to sync the context, and there is a function futhark_entry_main which corresponds to the main function in our futhark library. We can write a simple C program that puts those functions together:

#include "fact.h"
#include <stdio.h>

int main() {
  struct futhark_context_config *cfg = futhark_context_config_new();
  struct futhark_context *ctx = futhark_context_new(cfg);

  int input = 10;
  int output;
  futhark_entry_main(ctx, &output, input);
  futhark_context_sync(ctx);

  printf("Result: %d\n", output);

  futhark_context_free(ctx);
  futhark_context_config_free(cfg);
}

Compile gcc main.c -lfact and then run ./main.

A JS Program

In NodeJS the main way to interact with C/C++ is via the Addons API, but there's a much simpler way with the node-ffi and ref packages. These will allow us to replicate the C program above quite directly:

var ffi = require('ffi');
var ref = require('ref');

// Define Types
var ctxStruct = ref.types.void;
var ctxStructPtr = ref.refType(ctxStruct);
var ctxConfig = ref.types.void;
var ctxConfigPtr = ref.refType(ctxConfig);
var intPtr = ref.refType('int');

// Define Library structure
var libfact = ffi.Library('libfact', {
  'futhark_entry_main': [ 'int', [ ctxStructPtr, intPtr, 'int' ] ],
  'futhark_context_config_new': [ ctxConfigPtr, [  ] ],
  'futhark_context_config_free': [ 'int', [ ctxConfigPtr ] ],
  'futhark_context_new': [ ctxStructPtr, [ ctxConfigPtr ] ],
  'futhark_context_sync': [ 'int', [ ctxStructPtr ] ],
  'futhark_context_free': [ 'int', [ ctxStructPtr ] ],
});


var config = libfact.futhark_context_config_new();
var ctx = libfact.futhark_context_new(config);

if (ctx.isNull()) {
  console.log("Couldn't get context");
}else {
  var result = ref.alloc('int');
  libfact.futhark_entry_main(ctx, result, 10);
  libfact.futhark_context_sync(ctx);

  console.log("Result: ", result.deref());

  libfact.futhark_context_free(ctx);
  libfact.futhark_context_config_free(config);
}

Acceleration

In order to get acceleration we just need to recompile the library with either the CUDA or OpenCL backends. For my CUDA installation I just needed to run:

> futhark cuda --library fact.fut 
> gcc fact.c -o libfact.so -fPIC -shared -lcuda -lnvrtc -I/opt/cuda/include -L/opt/cuda/lib64"

And there it is GPU Accelerated code in NodeJS :) The code is on Github.