infix
A JIT-Powered FFI Library for C
|
This guide outlines the steps required to add support for a new CPU architecture or Application Binary Interface (ABI) to the infix
FFI library. The library is designed to be highly portable, with a clean separation between platform-agnostic logic and ABI-specific implementations.
We will use RISC-V 64-bit (RV64GC) with the standard LP64D ABI as a practical example throughout this guide.
This is the most critical step. Before writing any code, you must have a solid understanding of the target ABI specification. For RISC-V, this means studying the official calling convention document.
You need to answer these key questions:
a0
through a7
(also known as x10
through x17
).float
and double
?fa0
through fa7
(also known as f10
through f17
).a0
and a1
for integers/pointers up to 128 bits. fa0
and fa1
for floats/doubles.a...
) or FPRs (fa...
).a0
.s0
/fp
), the return address (ra
), and the saved registers s1
through s11
.The first code change is to teach the library how to recognize the new platform at compile time. Open src/common/infix_config.h
.
Add Architecture Macro: Locate the processor architecture detection block and add a new #define INFIX_ARCH_*
macro. The standard compiler macro for RISC-V is __riscv
, and the bit width is checked with __riscv_xlen
.
c // In src/common/infix_config.h, after the other architecture checks... #elif defined(__riscv) && __riscv_xlen == 64 #define INFIX_ARCH_RISCV64 #else #error "Unsupported architecture." #endif
Add ABI Macro: In the "Target ABI Logic Selection" section, define a new INFIX_ABI_*
macro based on the combination of OS and architecture.
c // In src/common/infix_config.h, inside the #ifndef INFIX_ABI_FORCED block... #if defined(INFIX_ARCH_AARCH64) // ... #elif defined(INFIX_ARCH_RISCV64) #define INFIX_ABI_LP64D // LP64D is the standard RISC-V ABI #elif defined(INFIX_ARCH_X64) // ... #endif
This is the core of the porting effort. You must provide a concrete implementation of the two ABI "specification" v-tables: infix_forward_abi_spec
and infix_reverse_abi_spec
.
src/arch/riscv64/
, and add the necessary files. It's best to copy and adapt from an existing architecture like aarch64
.abi_riscv64.c
abi_riscv64_common.h
abi_riscv64_emitters.c
abi_riscv64_emitters.h
**Define Register Enums (abi_riscv64_common.h
)**: Create enums for the general-purpose registers (GPRs) and floating-point registers (FPRs). The enum values should correspond to their 5-bit encoding in machine code.
```c // In src/arch/riscv64/abi_riscv64_common.h typedef enum { ZERO_REG = 0, RA_REG = 1, SP_REG = 2, /* ... */ A0_REG = 10, A1_REG = 11, /* ... */ } riscv_gpr;
typedef enum { FT0_REG = 0, /* ... */ FA0_REG = 10, FA1_REG = 11, /* ... */ } riscv_fpr; ```
**Implement ABI Logic (abi_riscv64.c
)**: This file will contain the implementations for the ten functions required by the ABI specs. The prepare_forward_call_frame
function is the most complex, as it must correctly apply all the ABI rules you researched in Step 0.
```c // In src/arch/riscv64/abi_riscv64.c #include "common/infix_internals.h" #include "abi_riscv64_common.h" #include "abi_riscv64_emitters.h"
// Forward Declarations for all 10 required static functions... static infix_status prepare_forward_call_frame_riscv64(...); static infix_status generate_forward_prologue_riscv64(...); // ... etc. for all 10 functions.
// Define the ABI Specification V-Table Instances const infix_forward_abi_spec g_riscv_forward_spec = { .prepare_forward_call_frame = prepare_forward_call_frame_riscv64, .generate_forward_prologue = generate_forward_prologue_riscv64, // ... fill in all 5 function pointers }; const infix_reverse_abi_spec g_riscv_reverse_spec = { .prepare_reverse_call_frame = prepare_reverse_call_frame_riscv64, // ... fill in all 5 function pointers };
// Implementation of all 10 functions... static infix_status prepare_forward_call_frame_riscv64(/*...*/) { // Core classification logic: // 1. Allocate the layout struct from the provided arena. // 2. Check for return-by-reference (consumes a0). // 3. Loop through arguments, classifying each one. // 4. Assign arguments to a0-a7, fa0-fa7, or the stack based on classification. // 5. Calculate total stack space needed and return the completed layout. } // ... implement the other 9 functions ... ```
In src/arch/riscv64/abi_riscv64_emitters.c
and its header, you will write small, focused functions to generate the 32-bit RISC-V machine code instructions. Each function will assemble the bitfields specified in the ISA manual.
Example Emitter for LD
(Load Doubleword):
The final step is to hook your new implementation into the main trampoline engine.
infix_internals.h
: Add an include for your new emitters header inside the architecture-specific block at the bottom of the file. c // In src/common/infix_internals.h #if defined(INFIX_ABI_SYSV_X64) || defined(INFIX_ABI_WINDOWS_X64) #include "arch/x64/abi_x64_emitters.h" #elif defined(INFIX_ABI_AAPCS64) #include "arch/aarch64/abi_arm64_emitters.h" #elif defined(INFIX_ABI_LP64D) #include "arch/riscv64/abi_riscv64_emitters.h" // Add this line #endif
Update trampoline.c
:
extern
declaration for your new spec v-tables inside a new #if defined(INFIX_ABI_LP64D)
block.get_current_forward_abi_spec()
and get_current_reverse_abi_spec()
functions..c
files to the unity build section at the very end of the file.```c // In src/core/trampoline.c
// ... near the top ... #elif defined(INFIX_ABI_AAPCS64) extern const infix_forward_abi_spec g_arm64_forward_spec; extern const infix_reverse_abi_spec g_arm64_reverse_spec; #elif defined(INFIX_ABI_LP64D) extern const infix_forward_abi_spec g_riscv_forward_spec; extern const infix_reverse_abi_spec g_riscv_reverse_spec; #endif
// ... in get_current_forward_abi_spec() ... #elif defined(INFIX_ABI_AAPCS64) return &g_arm64_forward_spec; #elif defined(INFIX_ABI_LP64D) return &g_riscv_forward_spec; #else
// ... (repeat for get_current_reverse_abi_spec) ...
// ... at the very bottom (unity build section) ... #elif defined(INFIX_ABI_AAPCS64) #include "../arch/aarch64/abi_arm64.c" #include "../arch/aarch64/abi_arm64_emitters.c" #elif defined(INFIX_ABI_LP64D) #include "../arch/riscv64/abi_riscv64.c" #include "../arch/riscv64/abi_riscv64_emitters.c" #else #error "No supported ABI was selected for the unity build in trampoline.c." #endif ```
Once the library compiles for your new target, the final and most important step is to run the entire test suite. This is best done on real hardware, but a high-fidelity emulator like QEMU can also be used.
101_by_value.c
, 102_by_reference.c
, and 402_variadic_functions.c
, as these are the most likely to reveal subtle ABI implementation errors.