
FFI with no glue code!
TL;DR: Microvium’s snapshotting paradigm allows a library to generate the FFI glue code, so you don’t have to.
How bad can it be?
Foreign function interfaces (FFIs) are notoriously difficult in JavaScript. If you take a look at the Node-API documentation for Node.js, you’ll see how confusing it can be. Take a brief look at the following “simple” example of a C++ function that adds 2 JavaScript numbers together. But don’t get bogged down in the details here, because the whole point of this post is to say that you don’t need to write code like this!
// addon.cc #include <node.h> namespace demo { using v8::Exception; using v8::FunctionCallbackInfo; using v8::Isolate; using v8::Local; using v8::Number; using v8::Object; using v8::String; using v8::Value; // This is the implementation of the "add" method // Input arguments are passed using the // const FunctionCallbackInfo<Value>& args struct void Add(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); // Check the number of arguments passed. if (args.Length() < 2) { // Throw an Error that is passed back to JavaScript isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "Wrong number of arguments").ToLocalChecked())); return; } // Check the argument types if (!args[0]->IsNumber() || !args[1]->IsNumber()) { isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate, "Wrong arguments").ToLocalChecked())); return; } // Perform the operation double value = args[0].As<Number>()->Value() + args[1].As<Number>()->Value(); Local<Number> num = Number::New(isolate, value); // Set the return value (using the passed in // FunctionCallbackInfo<Value>&) args.GetReturnValue().Set(num); } void Init(Local<Object> exports) { NODE_SET_METHOD(exports, "add", Add); } NODE_MODULE(NODE_GYP_MODULE_NAME, Init) } // namespace demo
Yikes! That’s a long function to add two numbers! And that’s even before we start talking about the complexity of garbage collection, handles, and scopes.
The above code is mostly so-called “glue code“, because most of it exists solely to interface between JavaScript and C++, rather than adding any functionality of its own.
Microvium’s approach is fundamentally different
For those who are new to this blog, Microvium is a JavaScript engine I’ve created for running a subset of JavaScript optimized for microcontrollers. But the concepts I’m going to describe here are much broader than microcontrollers.
I’ll explain the Microvium approach by going through an example.
In Microvium, the first thing you probably want to do is have your C++ host (e.g. firmware) call a JavaScript function, so I’ll cover that example first:
// main.js import { generate, exportToC } from './lib/ffi.js' // Export function to be callable from C exportToC('void', 'myFunctionToCallFromC', [], () => { // ... function code here ... }); generate();
Here I’m using a library called ffi.js
, which I’ll explain later. It exposes an exportToC
function which has the following signature:
function exportToC(returnType: Typename, funcName: string, params: Array<[paramType: Typename, paramName: string]>);
Combined with the function generate
, the function exportToC
automatically generates the required glue code for the exported function.
How is this possible?
Well, remember that in Microvium, the top-level module code runs at compile time, not runtime, and then we deploy a snapshot of the VM state rather than deploying the source or a bundle. And by default, the compile-time code also has access to Node.js modules1 such as fs
, so it can access the file system. The generate
function in the example uses fs
to code-generate C++ files with the glue code for each function that you set up using exportToC
.
So, let’s compile this JavaScript example in a terminal. For simplicity, I’m using --output-bytes
here so I can get the literal snapshot bytes to paste into the C++ code later.
$ microvium main.js --output-bytes Output generated: main.mvm-bc 154 bytes {0x06,0x1c,0x06,0x00,0x9a,0x00,0xf2,0x75,0x03,0x00,0x00,0x00,0x1c,0x00,0x1c,0x00,0x24,0x00,0x24,0x00,0x2a,0x00,0x2c,0x00,0x80,0x00,0x8a,0x00,0xff,0xff,0x49,0x00,0xfe,0xff,0x7d,0x00,0x89,0x00,0x85,0x00,0x01,0x00,0x31,0x00,0x00,0x00,0x05,0x40,0x70,0x75,0x73,0x68,0x00,0x00,0x0d,0x50,0x04,0x31,0x30,0x30,0x88,0x1d,0x00,0x6b,0x12,0x6f,0x67,0x01,0x60,0x00,0x2f,0x50,0x05,0x88,0x19,0x00,0x89,0x00,0x00,0x88,0x1d,0x00,0x6b,0xa0,0x88,0x19,0x00,0x06,0xa0,0x10,0x12,0xe0,0x70,0x04,0x67,0x67,0x01,0x60,0x89,0x00,0x00,0x10,0x12,0x6b,0x11,0x78,0x01,0xa0,0x67,0x10,0x10,0x07,0x6c,0x10,0xa2,0x67,0x67,0x76,0xe2,0x00,0x00,0x00,0x03,0x50,0x01,0x01,0x60,0x00,0x0c,0x00,0x19,0x00,0x02,0x00,0x19,0x00,0x01,0x00,0x08,0xc0,0x05,0x00,0x05,0x00,0x31,0x00,0x39,0x00,0x04,0xd0,0x05,0x00,0x03,0x00}
A side effect of running this command is that it runs the top-level code in main.js
(and transitively all the dependencies), which in turn generates the files App_ffi.hpp
and App_ffi.cpp
for us, which contains the glue code for this example.
So now that we have the generated glue code and the snapshot bytes, we can use this in a minimal C++ project2:
#include "App_ffi.hpp" const uint8_t snapshot[] = {0x06,0x1c,0x06,0x00,0x9a,0x00,0xf2,0x75,0x03,0x00,0x00,0x00,0x1c,0x00,0x1c,0x00,0x24,0x00,0x24,0x00,0x2a,0x00,0x2c,0x00,0x80,0x00,0x8a,0x00,0xff,0xff,0x49,0x00,0xfe,0xff,0x7d,0x00,0x89,0x00,0x85,0x00,0x01,0x00,0x31,0x00,0x00,0x00,0x05,0x40,0x70,0x75,0x73,0x68,0x00,0x00,0x0d,0x50,0x04,0x31,0x30,0x30,0x88,0x1d,0x00,0x6b,0x12,0x6f,0x67,0x01,0x60,0x00,0x2f,0x50,0x05,0x88,0x19,0x00,0x89,0x00,0x00,0x88,0x1d,0x00,0x6b,0xa0,0x88,0x19,0x00,0x06,0xa0,0x10,0x12,0xe0,0x70,0x04,0x67,0x67,0x01,0x60,0x89,0x00,0x00,0x10,0x12,0x6b,0x11,0x78,0x01,0xa0,0x67,0x10,0x10,0x07,0x6c,0x10,0xa2,0x67,0x67,0x76,0xe2,0x00,0x00,0x00,0x03,0x50,0x01,0x01,0x60,0x00,0x0c,0x00,0x19,0x00,0x02,0x00,0x19,0x00,0x01,0x00,0x08,0xc0,0x05,0x00,0x05,0x00,0x31,0x00,0x39,0x00,0x04,0xd0,0x05,0x00,0x03,0x00}; void main() { // Load the JavaScript app from the snapshot App* app = new App(snapshot, sizeof snapshot); // Run the myFunctionToCallFromC function app->myFunctionToCallFromC(); }
How easy is that! Two lines of C++ to spin up the runtime engine and call a JavaScript function!
Let’s extend this example to have it call from JavaScript back to C++. Let’s say that we want to add
two numbers together (like the earlier node.js monstrosity), and print
the result:
import { generate, exportToC, importFromC } from './lib/ffi.js' // Access the C++ function named "add" with parameters (int x, int y) const add = importFromC('int', 'add', [['int', 'x'], ['int', 'y']]); // Access the C++ function name "print" with parameter (std::string msg) const print = importFromC('void', 'print', [['string', 'msg']]); exportToC('void', 'myFunctionToCallFromC', [], () => { const x = add(1, 2); print(`The sum is ${x}`); }); generate();
If we peek inside the generated “App_ffi.hpp” for this one, we’ll see it now has these lines as well:
// ... extern int32_t add(App* app, int32_t x, int32_t y); // Must be implemented elsewhere extern void print(App* app, std::string msg); // Must be implemented elsewhere // ...
So, it’s automatically generated the function signatures of the imported functions, and all the glue code required to give the JavaScript code the ability to call these functions.
Now, let’s provide the implementation of these add
and print
functions in C++:
#include <iostream> #include "App_ffi.hpp" using namespace std; using namespace mvm; const uint8_t snapshot[] = {0x06,0x1c,0x06,0x00,0xd2,0x00,0xb7,0x71,0x03,0x00,0x00,0x00,0x1c,0x00,0x20,0x00,0x28,0x00,0x28,0x00,0x2e,0x00,0x32,0x00,0xb4,0x00,0xc2,0x00,0xff,0xff,0xfe,0xff,0xff,0xff,0x65,0x00,0xfe,0xff,0x99,0x00,0xc1,0x00,0xbd,0x00,0x01,0x00,0x3d,0x00,0x35,0x00,0x05,0x40,0x70,0x75,0x73,0x68,0x00,0x00,0x0c,0x40,0x54,0x68,0x65,0x20,0x73,0x75,0x6d,0x20,0x69,0x73,0x20,0x00,0x00,0x00,0x02,0x60,0x00,0x00,0x02,0x60,0x01,0x00,0x0d,0x50,0x04,0x31,0x30,0x30,0x88,0x1d,0x00,0x6b,0x12,0x6f,0x67,0x01,0x60,0x00,0x2f,0x50,0x05,0x88,0x19,0x00,0x89,0x00,0x00,0x88,0x1d,0x00,0x6b,0xa0,0x88,0x19,0x00,0x06,0xa0,0x10,0x12,0xe0,0x70,0x04,0x67,0x67,0x01,0x60,0x89,0x00,0x00,0x10,0x12,0x6b,0x11,0x78,0x01,0xa0,0x67,0x10,0x10,0x07,0x6c,0x10,0xa2,0x67,0x67,0x76,0xe2,0x00,0x00,0x00,0x1c,0x50,0x05,0x88,0x19,0x00,0x89,0x01,0x00,0x01,0x07,0x08,0x78,0x03,0xa0,0x89,0x02,0x00,0x01,0x88,0x3d,0x00,0x13,0x6c,0x78,0x02,0x67,0x67,0x01,0x60,0x0c,0x00,0x4d,0x00,0x51,0x00,0x19,0x00,0x02,0x00,0x19,0x00,0x01,0x00,0x08,0xc0,0x05,0x00,0x05,0x00,0x35,0x00,0x55,0x00,0x04,0xd0,0x05,0x00,0x03,0x00}; void main() { App* app = new App(snapshot, sizeof snapshot); app->myFunctionToCallFromC(); } int32_t add(App* app, int32_t x, int32_t y) { return x + y; } void print(App* app, string msg) { cout << msg << endl; }
That’s all! The glue code generated in App_ffi
handles the conversions between JavaScript values and C++ values, such as converting the JavaScript string to an std
string
for the print
.
What about dynamic types?
What if we don’t have a specific type we want to pass between JavaScript and C++? The FFI library provides a solution for this as well: Any
.
Let’s say we want to make the add
function polymorphic, so it can add either strings or integers. To do this, we just switch out int
with any
:
const add = importFromC('any', 'add', [['any', 'x'], ['any', 'y']]);
Then on the C++ side, we can write the add
function like this (either adding integers or concatenating strings):
Any add(App* app, Any x, Any y) { if (x.type() == VM_T_NUMBER) { return app.newInt32(x.toInt32() + x.toInt32()); } else { return app.newString(x.toString() + x.toString()); } }
The Any
type is actually a reference type: it’s a garbage-collection-safe reference to a value in the JavaScript VM. It can also be used to safely interact with objects and arrays in JavaScript.
It’s about the concept, not the library
As of this writing, the FFI library used here (ffi.js
) is not included with Microvium. It’s an early-stage concept library, which you can find here. There’s still more thought and functionality that needs to go into it before I’m ready to call it the “standard way” of interacting with Microvium and releasing it alongside the Microvium engine.
But I think the cool part here is not the FFI library itself, but the fact that the snapshotting paradigm facilitates libraries like this. The behavior doesn’t need to be baked into the engine — if you don’t like the way my FFI library does things, you can write your own3! The possibilities are endless. Do you want your library to also generate the makefile? You can! Do you want it to generate main.cpp
? You can! Do you want it to work with C instead of C++? You can! Or rather… given a large enough community of users, you hope that someone else has done it already and shared their solution on npm or somewhere.
The concept runs deeper than just a typical code generator. Of course, anyone can write a code generator for node.js that generates the glue code for you, but it’s not easy in node.js to create a library that allows you to write code like this:
const add = importFromC('int', 'add', [['int', 'x'], ['int', 'y']]); const print = importFromC('void', 'print', [['string', 'msg']]); exportToC('void', 'myFunctionToCallFromC', [], () => { const x = add(1, 2); print(`The sum is ${x}`); });
Why? Because this example combines runtime and compile-time code in the same place. Functions like exportToC
create a bridge between C++ and JavaScript, and encapsulate the details of that bridge. We don’t care how the library works, as long as it adheres to the interface contract — the contract on both sides of the bridge — the contract in both JavaScript and C++.
The entities created when we call importFromC
or exportToC
span both the JavaScript and C++ domain and encapsulate the wiring required to connect the two sides.

It is the snapshotting paradigm of Microvium that enables a library that performs this kind of encapsulation and abstraction of a communication link. And interfacing between JavaScript and C++ is only the beginning of what you can do with this! There are some other things on the horizon that take this to the next level.
P.S. If you’ve made it this far, you might be interested in following me on Twitter…