C ABI
The FFI support in pony uses the C application binary interface (ABI) to interface with native code. The C ABI is a calling convention, one of many, that allow objects from different programming languages to be used together.
Writing a C library for Pony
Writing your own C library for use by Pony is almost as easy as using existing libraries.
Let's look at a complete example of a C function we may wish to provide to Pony. A Jump Consistent Hash, for example, could be provided in pure Pony as follows:
// Jump consistent hashing in pony, with an inline pseudo random generator
fun jch(key: U64, buckets: I64): I32 =>
var k = key
var b = I64(0)
var j = I64(0)
while j < buckets do
b = j
k = (k * 2862933555777941757) + 1
j = ((b + 1).f64() * (U32(1 << 31).f64() / ((key >> 33) + 1).f64())).i64()
end
b.i32()
Let's say we wish to compare the pure Pony performance to an existing C function with the following header:
#ifndef __JCH_H_
#define __JCH_H_
extern "C"
{
int32_t jch_chash(uint64_t key, uint32_t num_buckets);
}
#endif
Note the use of extern "C". If the library is built as C++ then we need to tell the compiler not to mangle the function name, otherwise Pony won't be able to find it. For libraries built as C this is not needed, of course.
The implemented would be something like:
#include <stdint.h>
#include <limits.h>
#include "math.h"
// A reasonably fast, good period, low memory use, xorshift64* based prng
double lcg_next(uint64_t* x)
{
*x ^= *x >> 12;
*x ^= *x << 25;
*x ^= *x >> 27;
return (double)(*x * 2685821657736338717LL) / ULONG_MAX;
}
// Jump consistent hash
int32_t jch_chash(uint64_t key, uint32_t num_buckets)
{
uint64_t seed = key;
int b = -1;
int32_t j = 0;
do {
b = j;
double r = lcg_next(&seed);
j = floor((b + 1)/r);
} while(j < num_buckets);
return (int32_t)b;
}
We need to compile the native code to a shared library. This example is for OSX. The exact details may vary on other platforms.
clang++ -fPIC -Wall -Wextra -O3 -g -MM jch.c >jch.d
clang++ -fPIC -Wall -Wextra -O3 -g -c -o jch.o jch.c
clang++ -shared -lm -o libjch.dylib jch.o
The Pony code to use this new C library is just like the code we've already seen for using C libraries.
""" This is an example of pony integrating with native code via the builtin FFI
support
"""
use "lib:jch"
use "collections"
use "random"
use @jch_chash[I32](hash: U64, bucket_size: U32)
actor Main
var _env: Env
new create(env: Env) =>
_env = env
let bucket_size: U32 = 1000000
var random = MT
for i in Range[U64](1, 20) do
let r: U64 = random.next()
let hash = @jch_chash(i, bucket_size)
_env.out.print(i.string() + ": " + hash.string())
end
We can now use ponyc to compile a native executable integrating pony and our C library. And that's all we need to do.