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:

  1. // Jump consistent hashing in Pony, with an inline pseudo random generator
  2. fun jch(key: U64, buckets: I64): I32 =>
  3. var k = key
  4. var b = I64(0)
  5. var j = I64(0)
  6. while j < buckets do
  7. b = j
  8. k = (k * 2862933555777941757) + 1
  9. j = ((b + 1).f64() * (U32(1 << 31).f64() / ((key >> 33) + 1).f64())).i64()
  10. end
  11. b.i32()

Let’s say we wish to compare the pure Pony performance to an existing C function with the following header:

  1. #ifndef __JCH_H_
  2. #define __JCH_H_
  3. extern "C"
  4. {
  5. int32_t jch_chash(uint64_t key, uint32_t num_buckets);
  6. }
  7. #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:

  1. #include <stdint.h>
  2. #include <limits.h>
  3. #include "math.h"
  4. // A reasonably fast, good period, low memory use, xorshift64* based prng
  5. double lcg_next(uint64_t* x)
  6. {
  7. *x ^= *x >> 12;
  8. *x ^= *x << 25;
  9. *x ^= *x >> 27;
  10. return (double)(*x * 2685821657736338717LL) / ULONG_MAX;
  11. }
  12. // Jump consistent hash
  13. int32_t jch_chash(uint64_t key, uint32_t num_buckets)
  14. {
  15. uint64_t seed = key;
  16. int b = -1;
  17. int32_t j = 0;
  18. do {
  19. b = j;
  20. double r = lcg_next(&seed);
  21. j = floor((b + 1)/r);
  22. } while(j < num_buckets);
  23. return (int32_t)b;
  24. }

We need to compile the native code to a shared library. This example is for OSX. The exact details may vary on other platforms.

  1. clang -fPIC -Wall -Wextra -O3 -g -MM jch.c >jch.d
  2. clang -fPIC -Wall -Wextra -O3 -g -c -o jch.o jch.c
  3. 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.

  1. """
  2. This is an example of Pony integrating with native code via the built-in FFI
  3. support
  4. """
  5. use "lib:jch"
  6. use "collections"
  7. use "random"
  8. use @jch_chash[I32](hash: U64, bucket_size: U32)
  9. actor Main
  10. var _env: Env
  11. new create(env: Env) =>
  12. _env = env
  13. let bucket_size: U32 = 1000000
  14. var random = MT
  15. for i in Range[U64](1, 20) do
  16. let r: U64 = random.next()
  17. let hash = @jch_chash(i, bucket_size)
  18. _env.out.print(i.string() + ": " + hash.string())
  19. 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.