infix: A JIT-powered FFI library for C

June 1, 2026 · View on GitHub

infix is a modern, lightweight C library that lets you call any C function or create C callbacks at runtime, using simple, human-readable strings to describe the function's signature.

It's designed to be the simplest way to add a dynamic Foreign Function Interface (FFI) to your project, whether you're building a language runtime, a plugin system, or just need to call functions from a dynamically loaded library.

CI

Key Features

  • Human-Readable Signatures: Describe complex C functions with an intuitive string format (e.g., "({double, double}, int) -> *char").
  • Forward & Reverse Calls: Call C functions ("forward") and create C function pointers that call back into your code ("reverse").
  • Direct Marshalling API: Build high-performance language bindings where the JIT compiler calls your object-unboxing functions directly, bypassing intermediate buffers.
  • Simple Integration: Add a single C file and a header directory to your project to get started. No complex dependencies.
  • Type Registry: Define, reuse, and link complex, recursive, and mutually-dependent structs by name.
  • Safe Exception Boundaries: Catch C++ and hardware exceptions (SEH) from native code to prevent host process crashes.
  • Security-First Design: Hardened against vulnerabilities with Write XOR Execute (W^X) memory, guard pages, and fuzz testing.
  • High Performance: A Just-in-Time (JIT) compiler generates optimized machine code trampolines, making calls nearly as fast as a direct C call after the initial setup.

Full Documentation


How It Works: A Quick Example

The heart of infix is its signature string. Here’s how you would call a simple C function:

#include <infix/infix.h>
#include <stdio.h>

// The C function we want to call.
int add(int a, int b) { return a + b; }

int main() {
    // 1. Describe the function's signature as a string.
    const char* signature = "(int, int) -> int";

    // 2. Create a "trampoline"—a JIT-compiled function wrapper.
    infix_forward_t* trampoline = NULL;
    infix_forward_create(&trampoline, signature, (void*)add, NULL);
    infix_cif_func cif = infix_forward_get_code(trampoline);

    // 3. Prepare an array of *pointers* to the arguments.
    int a = 10, b = 32;
    void* args[] = { &a, &b };
    int result;

    // 4. Call the function through the trampoline.
    cif(&result, args);

    printf("Result: %d\n", result); // Output: Result: 42

    // 5. Clean up.
    infix_forward_destroy(trampoline);
    return 0;
}

Creating Callbacks

infix can also generate native C function pointers that call back into your code. This is perfect for interfacing with C libraries that expect callbacks, like qsort.

#include <infix/infix.h>
#include <stdlib.h>

// The C handler function for our callback.
int compare_integers(const void* a, const void* b) {
    return (*(const int*)a - *(const int*)b);
}

void run_qsort_example() {
    // 1. Describe the callback's signature.
    const char* cmp_sig = "(*void, *void) -> int";

    // 2. Create a reverse trampoline.
    infix_reverse_t* context = NULL;
    infix_reverse_create_callback(&context, cmp_sig, (void*)compare_integers, NULL);

    // 3. Get the JIT-compiled C function pointer.
    int (*my_comparator)(const void*, const void*) = infix_reverse_get_code(context);

    // 4. Use the generated callback with the C library function.
    int numbers[] = { 5, 1, 4, 2, 3 };
    qsort(numbers, 5, sizeof(int), my_comparator);
    // `numbers` is now sorted: [1, 2, 3, 4, 5]

    infix_reverse_destroy(context);
}

Safe Exception Boundaries

infix can establish a "safe boundary" around FFI calls to catch C++ or SEH exceptions that occur in native code, preventing them from crashing your process.

#include <infix/infix.h>

void run_safe_example(void* crashing_func) {
    infix_forward_t* trampoline = NULL;
    // Create a "safe" trampoline.
    infix_forward_create_safe(&trampoline, "() -> void", crashing_func, NULL);

    infix_cif_func cif = infix_forward_get_code(trampoline);
    
    // Call the function. If it throws a C++ exception, it will be caught.
    cif(NULL, NULL);

    // Check for exceptions.
    infix_error_details_t err = infix_get_last_error();
    if (err.code == INFIX_CODE_NATIVE_EXCEPTION) {
        printf("Caught an exception from native code: %s\n", err.message);
    }

    infix_forward_destroy(trampoline);
}

High-Performance Language Bindings

If you are writing a binding for a language like Python, Perl, or Lua, infix offers a specialized direct marshalling API. This allows the JIT compiler to call your object unboxing functions ("marshallers") directly, eliminating the need to allocate intermediate C arrays.

// A mock object from a scripting language.
typedef struct { int type; union { int i; double d; } val; } PyObject;

// A "Scalar Marshaller" converts a language object to a raw C value.
infix_direct_value_t marshal_int(void* obj_ptr) {
    PyObject* obj = (PyObject*)obj_ptr;
    return (infix_direct_value_t){ .i64 = obj->val.i };
}

void run_binding_example(void* target_func) {
    // 1. Define handlers for the arguments.
    infix_direct_arg_handler_t handlers[2] = {0};
    handlers[0].scalar_marshaller = marshal_int;
    handlers[1].scalar_marshaller = marshal_int;

    // 2. Create an optimized trampoline.
    infix_forward_t* trampoline;
    infix_forward_create_direct(&trampoline, "(int, int) -> void", target_func, handlers, NULL);

    // 3. Call it directly with an array of language objects.
    PyObject* args[] = { py_obj1, py_obj2 };

    // The JIT code calls `marshal_int` for each arg, then calls the target.
    infix_forward_get_direct_code(trampoline)(NULL, (void**)args);
}

Getting Started

The easiest way to use infix is to add its source directly to your project.

  1. Copy the src/ and include/ directories into your project.
  2. Add src/infix.c to your build system's list of source files.
  3. Add the include/ directory to your include paths.
  4. #include <infix/infix.h> in your code.

Note: If you are linking against infix as a shared library (DLL) on Windows, you must define INFIX_USING_DLL in your project settings. See the Installation Guide for details.

For more advanced build options, including building as a standalone library with CMake or xmake, see the Building and Integration Guide.

Project Philosophy

infix is built on three core principles:

  1. Security First: An FFI library with a JIT is a prime target for vulnerabilities. We defend against these with a multi-layered approach, including strict W^X memory, hardened integer arithmetic, and continuous fuzz testing.
  2. Performance by Design: FFI overhead should be minimal. infix separates the one-time generation cost from the near-zero call-time cost, making it exceptionally fast in high-performance applications when trampolines are cached.
  3. Simplicity and Portability: Platform- and ABI-specific logic is strictly isolated, making the library easy to maintain, simple to integrate, and straightforward to port to new architectures.

Platform Support

infix is rigorously tested on a wide array of operating systems, compilers, and architectures with every commit.

OSVersionArchitectureCompilerStatus
DragonflyBSD6.4.0x86-64GCCdragonflybsd/x64/gcc
FreeBSD15.0x86-64GCCfreebsd/x86/gcc
15.0AArch64GCCfreebsd/a64/gcc
15.0RISC-V64GCCfreebsd/r64/gcc
15.0x86-64Clangfreebsd/x64/clang
15.0AArch64Clangfreebsd/a64/clang
15.0RISC-V64Clangfreebsd/r64/clang
macOSSequoiaAArch64Clangmacos/a64/clang
SequoiaAArch64GCCmacos/a64/gcc
Sequoiax86-64GCCmacos/x64/gcc
Sequoiax86-64Clangmacos/x64/clang
NetBSD10.1AArch64GCCnetbsd/a64/gcc
10.1x86-64GCCnetbsd/x64/gcc
OmniOSr151054x86-64GCComnios/x64/gcc
OpenBSD7.8AArch64Clangopenbsd/a64/clang
7.8AArch64GCCopenbsd/a64/gcc
7.8x86-64Clangopenbsd/x64/clang
7.8x86-64Clangopenbsd/x64/clang
7.8RISC-V64Clangopenbsd/r64/clang
7.8RISC-V64GCCopenbsd/r64/gcc
Solaris11.4x86-64GCCsolaris/x64/gcc
Ubuntu24.04AArch64Clangubuntu/a64/clang
24.04AArch64GCCubuntu/a64/gcc
24.04x86-64Clangubuntu/x64/clang
24.04x86-64GCCubuntu/x64/gcc
24.04RISC-V64Clangubuntu/r64/clang
24.04RISC-V64GCCubuntu/r64/gcc
WindowsServer 2025AArch64Clangwindows/a64/clang
Server 2025AArch64GCCwindows/a64/gcc
Server 2025AArch64MSVCwindows/a64/msvc
Server 2025x86-64Clangwindows/x64/clang
Server 2025x86-64GCCwindows/x64/gcc
Server 2025x86-64MSVCwindows/x64/msvc

In addition to the CI platforms tested here on Github, I can verify infix builds and passes unit tests on Android/Termux.

Licenses

To maximize usability for all, infix is dual-licensed under the Artistic License 2.0 and the MIT License. You may choose to use the code under the terms of either license.

At your discretion, all standalone documentation (.md), explanatory text, Doxygen-style documentation blocks, comments, and code examples contained within this repository may be used, modified, and distributed under the terms of the Creative Commons Attribution 4.0 International License (CC BY 4.0). I encourage you to share and adapt the documentation for any purpose (generating an API reference website, creating tutorials, etc.), as long as you give appropriate credit.