FFIs: a new hope

Linguae Francae
A common language used by people of diverse backgrounds to communicate with one another, often a basic form of speech with simplified grammar, particularly, one that is not the first language of any of its speakers.

This is one of those latin words that is suprisingly useful in English - which I think of as a language that has less precision than is maybe ideal.

This blog post is about language - but not human languages.

In the world of computers, C has solidified itself as the “default” language. My guess is this it’s a byproduct of having manual memory management - which is kinda cool when you don’t have much memory to mess with. I also think it’s probably an effect of having things like Linux or the BSDs written in C.

(PL historians please correct me if I’m wrong.)

As a result, we often need to use it to load code written in C or talk to other non-C code.

“Why would we do this?!??” you ask, questioning why anyone would willingly go back to the language you begrudgingly learned in university.

Unfortunately, C is pretty fast. It’s fast in the same way that a falling knife is fast. Falling knives also famously don’t have handles.

In addition, basically every language has decided to have an FFI! This means everyone speaks C! This is how you get the context where a German and an Argentine end up talking to each other in English. The conversation is probably partially harder because neither of them learned English as their first language. This is entirely the same circumstance in computer languages. The constructs in Swift might not map amazingly to those in Rust, but they can technically communicate over a Foreign Function Interface.

Okay, hopefully you now see why it is useful.

We’ll show a quick applied example of why someone might do something as horrifying as this.

This code is written in C. We will turn it into a “shared object” to load it into a Python script and then call into that “shared object” over the FFI.

unsigned long long fibonacci(unsigned long long n) {
    unsigned long long i = 0, a = 0, b = 1, tmp;

    for (; i < n; i++) {
        tmp = a;
        a = b;
        b = tmp + b;
    }

    return a;
}

In order to do this, I run:

clang -shared -o libfibonacci.so -fPIC fib.c

Now let’s do a Python file:

# We need this `ctypes` thing in order to import
# the shared object and to define the types used.
import ctypes
import time

def fibonacci(n):
    i, a, b = 0, 0, 1

    while i < n:
        a, b = b, a + b
        i += 1

    return a

def main():
    n = 93

    lib = ctypes.CDLL('./libfibonacci.so')
    lib.fibonacci.argtypes = [ctypes.c_ulonglong]
    lib.fibonacci.restype = ctypes.c_ulonglong

    python_start = time.time()
    python_result = fibonacci(n)
    python_duration = time.time() - python_start

    c_start = time.time()
    c_result = lib.fibonacci(n)
    c_duration = time.time() - c_start

    print("In C: ", c_result, c_duration)
    print("In Python: ", python_result, python_duration)

main()

Wooooooo!!

In C:  12200160415121876738 3.0994415283203125e-06
In Python:  12200160415121876738 5.9604644775390625e-06

We are now calling into C from Python!!!! Wow amazing!!! It’s also a little bit faster in C.

In part two we will talk about how this can go really wrong.