This is an explanation and expansion of discussion #26 now that I've started actually implemented and designed my idea.

Overview

The Direct Marshalling API (also known as the "Bundle" API) is an advanced feature designed specifically for high-performance language bindings (e.g., Perl, Python, Ruby, Lua). I've found arbitrary benchmarks of Affix.pm shrinks runtime by ~15% on SysV and nearly 25% on Windows over infix's standard trampolines.

The Problem with Standard FFI

In a standard infix_forward_create call, the workflow at runtime looks like this:

  1. Host Language: Unboxes values from native objects (e.g., PyObject*) into temporary C variables.
  2. Host Language: Constructs an array of pointers (void* args[]) pointing to those temporaries.
  3. Infix JIT: Reads from args[], moves data into registers/stack.
  4. Target: Function executes.

Steps 1 and 2 often require malloc/free or complex stack management in the host language loop, creating overhead.

The Direct Solution

The Direct Marshalling API moves the unboxing logic inside the JIT-compiled trampoline.

  1. Host Language: Passes an array of raw object pointers (e.g., PyObject**) directly to the trampoline.
  2. Infix JIT: Calls specific, user-provided "Marshaller" functions to unbox data just-in-time into the correct registers.
  3. Target: Function executes.

This eliminates the intermediate void* array and temporary C variables managed by the caller, significantly reducing cache pressure and instructions per call.

API Reference

Data Structures

infix_direct_value_t

A union returned by scalar marshallers. Since a C function can only return one value, this union allows the marshaller to return any primitive type, which the JIT then moves to the correct register class (Integer vs XMM).

typedef union {
    uint64_t u64;  // For all unsigned integers <= 64 bits
    int64_t i64;   // For all signed integers <= 64 bits
    double f64;    // For float and double
    void* ptr;     // For pointers
} infix_direct_value_t;

infix_direct_arg_handler_t

A struct containing function pointers that define how to handle a specific argument. You must provide one of these for every argument in the signature.

typedef struct {
    // 1. Scalar Marshaller: Called for primitives and simple pointers.
    //    Input:  Pointer to your language object (e.g., PyObject*).
    //    Output: The raw C value in the union.
    infix_direct_value_t (*scalar_marshaller)(void* source_object);

    // 2. Aggregate Marshaller: Called for structs/unions passed by value.
    //    Input:  Pointer to language object.
    //    Input:  Pointer to destination buffer (stack-allocated by JIT).
    //    Input:  Type info for introspection.
    void (*aggregate_marshaller)(void* source_object, void* dest_buffer, const infix_type* type);

    // 3. Writeback Handler: Called after the function returns (for out-params).
    //    Input:  Pointer to language object.
    //    Input:  Pointer to the (potentially modified) C data.
    //    Input:  Type info.
    void (*writeback_handler)(void* source_object, void* c_data_ptr, const infix_type* type);
} infix_direct_arg_handler_t;

Functions

infix_forward_create_direct

Generates the specialized trampoline.

infix_status infix_forward_create_direct(
    infix_forward_t** out_trampoline,
    const char* signature,
    void* target_function,
    infix_direct_arg_handler_t* handlers, // Array of handlers, one per argument
    infix_registry_t* registry
);

infix_forward_get_direct_code

Retrieves the callable function pointer. Note the signature differs from standard trampolines.

// lang_objects is an array of pointers to your language objects (e.g. PyObject* args[])
typedef void (*infix_direct_cif_func)(void* return_buffer, void** lang_objects);

infix_direct_cif_func cif = infix_forward_get_direct_code(trampoline);

Usage Example

Imagine creating a binding for a scripting language where every value is a Value struct. We want to call void move_point(Point p, int dx).

1. Define the Source Object

typedef enum { VAL_INT, VAL_OBJECT } ValType;
typedef struct {
    ValType type;
    union { int i; void* fields[]; } data;
} Value;

2. Implement Marshallers

// Marshaller for 'int' arguments
infix_direct_value_t marshal_int(void* obj_ptr) {
    Value* v = (Value*)obj_ptr;
    return (infix_direct_value_t){ .i64 = (v->type == VAL_INT ? v->data.i : 0) };
}

// Marshaller for 'Point' struct arguments
void marshal_point(void* obj_ptr, void* dest, const infix_type* type) {
    Value* v = (Value*)obj_ptr;
    Point* p = (Point*)dest;
    // Assume we know the layout of the Value's fields for this example
    Value* val_x = (Value*)v->data.fields[0];
    Value* val_y = (Value*)v->data.fields[1];
    p->x = (double)val_x->data.i;
    p->y = (double)val_y->data.i;
}

3. Create the Trampoline

// Signature: Point move_point(Point p, int dx);
// Arg 0: Point (Aggregate)
// Arg 1: int (Scalar)

infix_direct_arg_handler_t handlers[2] = {0};

// Setup Arg 0 (Point)
handlers[0].aggregate_marshaller = marshal_point;

// Setup Arg 1 (int)
handlers[1].scalar_marshaller = marshal_int;

infix_forward_t* trampoline;
infix_forward_create_direct(&trampoline,
    "({double,double}, int) -> void",
    (void*)move_point,
    handlers,
    NULL);

4. Call It

// Prepare array of pointers to Value objects
Value* arg0 = ...; // Point object
Value* arg1 = ...; // Int object
void* script_args[] = { arg0, arg1 };

// Call
infix_direct_cif_func cif = infix_forward_get_direct_code(trampoline);
cif(NULL, script_args); // No return buffer needed for void

Performance Considerations

  1. Register Pressure: The generated JIT code must shuffle data between the lang_objects array, the marshaller return registers, and the argument registers. infix optimizes this, but complex signatures with many arguments may spill to the stack.
  2. Inlining: The marshaller functions you provide are called via function pointers. For maximum performance, keep them small and fast.
  3. Writebacks: Using writeback_handler adds overhead (allocating stack space, preserving return registers, making the call). Use only when necessary (pointers/references).