cmake-js/fastcall

Name: fastcall

Owner: CMake.js

Description: fastcall - Fast, dyncall based foreign function interface library for Node.js

Created: 2016-08-11 20:53:57.0

Updated: 2018-05-10 05:36:15.0

Pushed: 2018-04-12 14:10:22.0

Homepage: https://www.npmjs.com/package/fastcall

Size: 1378

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Build Status

TOC

About

fastcall is a foreign function interface library which aim is to provide an easy to use, 100% JavaScript based method for developers to use native shared libraries in Node.js, without needing to touch anything in C++ and without sacrificing too much performance. It's designed with performance and simplicity in mind, an it has comparable function call overhead with hand made C++ native modules. See the benchmarks.

Why?

There is a a popular dynamic binding library for Node.js: node-ffi. Then why we need another one could ask? For performance! There is a good 20x-40x function call performance overhead when using node-ffi compared to hand made C++ native module, which is unacceptable in most cases (see the benchmarks).

Features
Requirements
Install
install --save fastcall
Clone and Build
clone --recursive https://github.com/cmake-js/fastcall.git
astcall
install

For subsequent builds, type:

e-js compile

But don't forget to install CMake.js as a global module first:

install -g cmake-js

Benchmarks

To run benchmarks, type:

 benchmarks

Results:

Results

There are 3 tests with synchronous and asynchronous versions:

All tests implemented in:

Yeah, there is a room for improvement at fastcall side. Improving marshaling and callback performance is the main target of the future development.

Documentation and Tutorials

fastcall.Library

Central part of fastcall is the Library class. With that you can load shared libraries to Node.js' address spaces, and could access its functions and declared types.

s Library {
constructor(libPath, options);

isSymbolExists(name);

release();

declare(); declareSync(); declareAsync();

function(); syncFunction(); asyncFunction();

struct();

union();

array();

callback();

get functions();

get structs();

get unions();

get arrays();

get callbacks();

get interface();

Constructor:

Methods:

Properties:

ref

Let's take a look at ref before going into the details (credits for TooTallNate). ref is a native type system with pointers and other types those are required to address C based interfaces of native shared libraries. It also has a native interface compatible types for structs, unions and arrays.

In fastcall there are a bundled versions of ref, ref-array, ref-struct and ref-union. Those are 100% compatible with the originals, they are there because I didn't wanted to have a CMake.js based module to depend on anything node-gyp based stuff. Bundled versions are built with CMake.js. The only exception is ref-array, fastcall's version contains some interface breaking changes for the sake of a way much better performance.

t fastcall = require('fastcall');

00% ref@latest built with CMake.js:
t ref = fastcall.ref;

00% ref-struct@latest
t StructType = fastcall.StructType;

00% ref-union@latest
t UnionType = fastcall.UnionType;

odified ref-struct@latest
t ArrayType = fastcall.ArrayType;

ref-array changes:

See the original FAQ there: https://github.com/TooTallNate/ref-array

There are two huge performance bottleneck exists in this module. The first is the price of array indexer syntax:

t IntArray = new ArrayType('int');
t arr = new IntArray(5);
1] = 1;
0] = arr[1] + 1;

Those [0] and [1] are implemented by defining Object properties named “0” and “1” respectively. For supporting a length of 100, there will be 100 properties created on the fly. On the other hand those indexing numbers gets converted to string on each access.

Because of that, fastcalls version uses get and set method for indexing:

t IntArray = new ArrayType('int');
t arr = new IntArray(5);
set(1, 1);
set(0, arr.get(1) + 1);

Not that nice, but way much faster than the original.

The other is the continous reinterpretation of a dynamically sized array.

t IntArray = new ArrayType('int');
t outArr = ref.refType(IntArray);
t outLen = ref.refType('uint');

b.getIntegers(outArr, outLen);
t arr = outArr.unref();
t len = outLen.unref();
rr is an IntArray with length of 0
ut we know that after the pointer
here are a len number of element elements,
o we can do:

(let i = 0; i < len; i++) {
console.log(arr[i]);

So far so good, but in each iteration arr will gets reinterpreted to a new Buffer with a size that would provide access to an item of index i. That's slow.

In fastcall's version array's length is writable, so you can do:

length = len;
(let i = 0; i < len; i++) {
console.log(arr.get(i));

Which means only one reinterpret, and this is a lot of faster than the original.

Unfortunately those are huge interface changes, that's why it might not make it in a PR.

declaring functions

The interface uses ref and co. for its ABI.

For declaring functions, you can go with a node-ffi like syntax, or with a C like syntax.

Examples:

t fastcall = require('fastcall');
t Library = fastcall.Library;
t ref = fastcall.ref;

t lib = new Library('bimbo.dll')
ction('int mul(int, int)') // arg. names could be omitted
ction('int add(int value1, int)')
ction('int sub(int value1, int value2)')
ction({ div: ['int', ['int', 'int']] })
ction('void foo(void* ptr, int** outIntPtr)')
ction({ poo: ['pointer', [ref.types.int, ref.refType(ref.types.float)]] });

Declared functions are accessible on the lib's interface:

t result = lib.interface.mul(42, 42);

Sync and async:

About sync and async modes please refer for fastcall.Library's documentation.

If a function is async, it runs in a separate thread, and the result is a Bluebird Promise.

t lib = new Library(...)
ncFunction('int mul(int, int)');

interface.mul(42, 42)
n(result => console.log(result));

You can always switch between a function's sync and async modes:

t lib = new Library(...)
ction('int mul(int, int)');

t mulAsync = library.interface.mul.async;
t mulSync = mulAsync.sync;
rt.strictEqual(mulSync, library.interface.mul);
rt.strictEqual(mulAsync, mulSync.async.async.sync.async);

You get the idea.

Concurrency and thread safety:

By default, a library's asynchronous functions are running in parallel distributed in libuv's thread pool. So they are not thread safe.

For thread safety there are two options could be passed to fastcall.Library's constructor: syncMode.lock and syncMode.queue.

With lock, a simple mutex will be used for synchronization. With queue, all of library's asynchronous calls are enqueued, and only one could execute at once. The former is a bit slower, but that allows synchronization between synchronous calls of the given library. However the latter will throw an exception if a synchronous function gets called while there is an asynchronous invocation is in progress.

Metadata:

In case of:

t lib = new Library(...)
ction('int mul(int value1, int value2)');

lib.functions.mul gives the same metadata object's instance as lib.interface.mul.function.

- properties:

- methods

declaring structs and unions

Easy as goblin pie. (Note: struct interface is ref-struct, and union is ref-array based.)

You can go with the traditional (node-ffi) way:

t fastcall = require('fastcall');
t StructType = fastcall.StructType;

et's say you have a library with a function of:
loat dist(Point* p1, Pint* p2);

t Point = new StructType({
x: 'int',
y: 'int'


t lib = new Library(...)
ction({ dist: ['float', [ref.refType(Point), ref.refType(Point)]] });

t point1 = new Point({ x: 42, y: 42 });
t point2 = new Point({ x: 43, y: 43 });
t dist = lib.interface.dist(point1.ref(), point2.ref());

But there is a better option. You can declare struct on library interfaces, and this way, fastcall will understand JavaScript structures on function interfaces.

Declaring structures:

bject syntax
struct({
Point: {
    x: 'int',
    y: 'int'
}


 like syntax
struct('struct Point { int x; int y; }');

After that the structure is accessible on library's structs property:

t pointMetadata = lib.structs.Point;

metadata properties:

The real benefit is that, by this way - because the library knows about your structure - you can do the following:

function({ dist: ['float', ['Point*', 'Point*']] });

-- OR ---

function('float dist(Point* point1, Point* point2)');

-- THEN ---

t result =
lib.interface.mul({ x: 42, y: 42 }, { x: 43, y: 43 });

Way much nicer and simpler syntax like the original, ain't it?

About unions:

Declaring unions is exactly the same, except 'union' used instead of 'struct' on appropriate places. (Note: struct interface is ref-union based.)


on('union U { int x, float y }')
ction('float pickFloatPart(U* u)');

t f = lib.interface.pickFloatPart({ y: 42.2 });

 is 42.2
declaring arrays

Declaring arrays is not much different than declaring structures and unions. There is one difference: arrays with fixed length are supported.

 code:

 struct using fixed length array
ct S5 {
int fiveInts[5];


 astruct using an arbitrary length array
ct SA {
int ints[]; // or: int* ints;


xample functions:

rints five values
 printS5(S5* s);

rint length number of values
ength is a parameter, because SA knows nothing about
he length of SA.ints
 printSA(SA* s, int length);

Access those in fastcall:


ay('int[] IntArray') // arbitrary length
uct('struct S5 { IntArray[5] ints; }')
uct('struct SA { IntArray[] ints; }') // or: IntArray ints
ction('void printS5(S5* s)')
ction('void printSA(SA* s, int len)');

interface.printS5({ ints: [1, 2, 3, 4, 5] });
interface.printSA({ ints: [1, 2, 3] }, 3);

Of course object syntax is available too:


ay({ IntArray: 'int' }) // arbitrary length
uct({ S5: { ints: 'IntArray[5]' } })
uct({ SA: { ints: 'IntArray' } })
ction({ printS5: ['void', ['IntArray[5]']] })
ction({ printSA: ['void', ['IntArray', 'int']] });

After that the array is accessible on library's arrays property:

t intArrayMetadata = lib.arrays.IntArray;

metadata properties:

Of course you can declare fixed length arrays directly:

array('int[4] IntArray4');

And any fixed or arbitrary length array type could get resized on usage:


uct('struct S5 { IntArray4[5] ints; }')
uct('struct SA { IntArray4[] ints; }')
declaring everything :)

fastcall.Library's declare, declareSync and declareAsync functions could declare every type of stuff at once.

So the array example could be written like:

declare('int[] IntArray;' +
'struct S5 { IntArray[5] ints; };' +
'struct SA { IntArray[] ints; };' +
'void printS5(S5* s);' +
'void printSA(SA* s, int len);');
callbacks

You can create native pointers for arbitrary JavaScript functions, and by this way you can use JavaScript functions for native callbacks.

 code

def int (*TMakeIntFunc)(float, double);

bambino(float fv, double dv, TMakeIntFunc func)

return (int)func(fv, dv) * 2;

To drive this, you need a callback and a function on your fastcall based library defined. It could get declared by object syntax:


lback({ TMakeIntFunc: ['int', ['float', 'double']] })
ction({ bambino: ['int', ['float', 'double', 'TMakeIntFunc']] });

t result = lib.interface.bambino(19.9, 1.1, (a, b) => a + b);
esult is 42

Or with a familiar, C like syntax:


lare('int (*TMakeIntFunc)(float, double);' +
'int bambino(float fv, double dv, TMakeIntFunc func)');

t result = lib.interface.bambino(19.9, 1.1, (a, b) => a + b);
esult is 42

Callback's metadata are accessible on library's callbacks property:

t TMakeIntFuncMetadata = lib.callbacks.TMakeIntFunc;

- metadata properties:

- methods

pointer factories

If you wanna use you callbacks, structs, unions or arrays more than once (in a loop, for example), without being changed, you can create a (ref) pointer from them, and with those, function call performance will be significantly faster. Callback, struct, union and array factories are just functions on library's property: interface.

For example:

ef-struct way

t StructType = require('fastcall').StructType; // or require('ref-struct')
t MyStruct = new StructType({ a: 'int', b: 'double' });

t struct = new MyStruct({ a: 1, b: 1.1 });
t ptr = struct.ref(); // ptr instanceof Buffer

interface.printStruct(ptr);

astcall way

declare('struct StructType { int a; double b; }');

t ptr = lib.interface.StructType({ a: 1, b: 1.1 });
tr instanceof Buffer

ou can create the modifyable struct instance of course
y using the metadata property: type

t structMetadata = lib.structs.StructType;
t struct = structMetadata.type({ a: 1, b: 1.1 });
..
ct.a = 2;
ct.b = 0.1;
..
t ptr = struct.ref();
r
t ptr = lib.interface.StructType(struct);

Or with callbacks:

declare('void (*CB)(int)');

t callbackPtr = lib.interface.CB(x => x * x);
allbackPtr instanceof Buffer

interface.someNativeFunctionWithCallback(42, callbackPtr);
hat's a slightly faster than:
interface.someNativeFunctionWithCallback(42, x => x * x);

string pointers:

For converting JavaScript string to (ref) pointers back and forth there are ref.allocCString() and ref.readCString() methods available.

However for converting JavaScript strings to native-side read-only strings there is a much faster alternative available in fastcall:

fastcall.makeStringBuffer([string] str)

Example:

t fastcall = require('fastcall');
..

declare('void print(char* str)');

t ptr = fastcall.makeStringBuffer(
'Sárgarigó, madárfészek, ' +
'az a legszebb, aki részeg');

interface.print(ptr);
hat's faster than writing:
interface.print(
'Sárgarigó, madárfészek, ' +
'az a legszebb, aki részeg');
RAII

Native resources must get freed somehow. We can rely on Node.js' garbage collector for this task, but that would only work if our native code's held resources are memory blocks. For other resources it is more appropriate to free them manually, for example in try … finally blocks. However, there are more complex cases.

Let's say we have a math library that works on the GPU with vectors and matrices. A complex formula will create a bunch of temporary vectors and matrices, that will hold a lot memory on the GPU. In this case, we cannot rely on garbage collector of course, because that knows nothing about VRAM. Decorating our formula with try … finally blocks would be a horrible idea in this complex case, just think about it:

t vec1 = lib.vec([1, 2, 3]);
t vec2 = lib.vec([4, 5, 6]);
t result = lib.pow(lib.mul(vec1, vec2), lib.abs(lib.add(vec1, vec2)));

ould be something like:

t vec1 = lib.vec([1, 2, 3]);
t vec2 = lib.vec([4, 5, 6]);
tmpAdd = null, tmpAbs = null, tmpMul = null;
result;
{
tmpAdd = lib.add(vec1, vec2);
tmpAbs = lib.abs(tmpAdd);
tmpMul = lib.mul(vec1, vec2);
result = lib.pow(tmpMul, tmpAbs);

lly {
tmpAdd && tmpAdd.free();
tmpMul && tmpMul.free();
tmpAbs && tmpAbs.free();

So we need an automatic, deterministic scoping mechanism for JavaScript for supporting this case. Like C++ RAII or Python's __del__ method.

The good news is all three mentioned techniques are supported in fastcall.

Disposable

For accessing fastcall's RAII features you need a class that inherits from Disposable. It's a very simple class:

s Disposable {
constructor(disposeFunction, aproxAllocatedMemory);

dispose();

resetDisposable(disposeFunction, aproxAllocatedMemory);

Example:

t lib = require('lib');
t fastcall = require('fastcall');
t ref = fastcall.ref;
t Disposable = fastcall.Disposable;

s Stuff {
constructor() {
    const out = ref.alloc('void*');
    lib.createStuff(out);
    const handle = out.defer();
    super(
        () => {
            // you should NEVER mention "this" there
            // that would prevent garbage collection
            lib.releaseStuff(handle);
        },
        42 /*Bytes*/);
    this.handle = handle;
}


ater

stuff = null;
{
stuff = new Stuff();
// do stuff :)

lly {
stuff && stuff.dispose();


eplace the underlying handle:

t stuff = new Stuff();

et some native handle from anywhere
t out = ref.alloc('void*');
createStuff(out);
t someOtherHandle = out.defer();

ow, stuff should wrap that

ou should implement this in a private method:
f.dispose(); // old handle gets released
f.resetDisposable(() => lib.releaseStuff(someOtherHandle), 42);
f.handle = someOtherHandle;

For prototype based inheritance please use Disposable.Legacy as the base class:

tion Stuff() {
Disposable.Legacy.call(this, ...);


.inherits(Stuff, Disposable.Legacy);
automatic cleanup (GC)

When there is no alive references exist for your objects, they gets disposed automatically once Node.js' GC cycle kicks in (lib.releaseStuff(handle) would get called from the above example). There is nothing else to do there. :) (Reporting approximate memory usage would help in this case, though.)

scopes

For deterministic destruction without that try … catch mess, fastcall offers scopes. Let's take a look at an example:

t scope = require('fastcall').scope;

et's say, we have Stuff class
rom the previous example.

e(() => {
const stuff1 = new Stuff();
// do stuff :)
return scope(() => {
    const stuff2 = new Stuff();
    const stuff3 = new Stuff();
    const stuff4 = fuseStuffs(/*tmp*/fuseStuff(stuff1, stuff2), stuff3);
    return stuff4;
});

Kinda C++ braces.

Rules:

Escaping and compound disposables:

A disposable could escape from a nest of scopes at anytime. This will be handy for creating classes those are a compound of other disposables. Escaped disposable would become a free object that won't get affected further by the above rules. (They would get disposed manually or by the garbage collector eventually.)

s MyClass extends Disposable {
constructor() {
    // MyClass is disposable but has no
    // direct native references,
    // so its disposeFunction is null
    super(null);

    this.stuff1 = null;
    this.stuff2 = null;
    scope(() => {
        const stuff1 = new Stuff();
        // do stuff :)
        this.stuff1 = scope.escape(stuff1);
        const tmp = new Stuff();
        this.stuff2 = scope.escape(fuseStuffs(tmp, stuff1));
    });
    // at this point this.stuff1 and
    // this.stuff2 will be alive
    // despite their creator scope got ended
}

dispose() {
    // we should override dispose() to
    // free our custom objects
    this.stuff1.dispose();
    this.stuff2.dispose();
}


ater

e(() => {
const poo = new MyClass();
// so this will open a child scope in
// MyClass' constructor that frees its
// temporaries automatically.
// MyClass.stuff1 and MyClass.stuff2 escaped,
// so they has nothing to do with the current scope.

owever poo got created in the above scope,
o it gets disposed at the end,
nd MyClass.stuff1 and MyClass.stuff2 gets disposed at that point
ecause of the overridden dispose().

Result:

scope's result is the result of its function.

t result = scope(() => {
return new Stuff();

esult is the new Stuff

Rules of propagation is described above. Propagation also happens when the function returns with:

This lookup is recursive, and apply for scope.escape()'s argument, so you can escape an array of Map of disposables for example with a single scope.escape() call.

Async:

If a scope's function returns a promise (any then-able object will do) then it turns to an asynchronous scope, it also returns a promise. Same rules apply like with synchronous scopes.

t result = scope(() =>
lib.loadAsync()
.then(loaded => {
    // do something with loaded ...
    return new Stuff();
}));

lt.then(result => {
// result is the new Stuff

Coroutines:

Coroutines supported by calling scope.async. So the above example turns to:

t result = yield scope.async(function* () {
const loaded = yield lib.loadAsync();
return new Stuff();

Way much nicer, eh? It does exactly the same thing.

node-ffi compatible interface

If you happen to have a node-ffi based module, you can switch to fastcall with a minimal effort, because there is a node-ffi compatible interface available:

t fastcall = require('fastcall');

n
t ffi = fastcall.ffi;

ou'll have
Library
Function
Callback
StructType // == fastcall.StructType (ref-struct)
UnionType // == fastcall.UnionType (ref-union)
ArrayType // == fastcall.ArrayType (ref-array)

Works exactly like node-ffi and ref modules. However there are some minor exceptions:

Showcase

Credits

License

right 2016 Gábor Mez? (gabor.mezo@outlook.com)

nsed under the Apache License, Version 2.0 (the "License");
may not use this file except in compliance with the License.
may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

ss required by applicable law or agreed to in writing, software
ributed under the License is distributed on an "AS IS" BASIS,
OUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
the License for the specific language governing permissions and
tations under the License.

This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.