Affix

 view release on metacpan or  search on metacpan

Changes.md  view on Meta::CPAN


### Fixed

- Anonymous wrapper functions created via wrap() were leaking because of a redundant SvREFCNT_inc call and the use of newRV_inc instead of newRV_noinc. This prevented the underlying CV and its associated Affix struct (including its memory arenas) fro...
- Implicitly loaded libraries (by path) were not having their reference counts decremented because the handle was not stored in the Affix struct. Additionally, using Affix::Lib objects did not increment the registry reference count, potentially leadi...
- Passing Pointer[SV] arguments to C functions caused a reference count leak of 1 per call because SvREFCNT_inc was called without a corresponding decrement.
- The library registry cleanup logic was missing from the main CV destructor and had a potential crash-inducing bug in the bundled destructor.
- [infix] Corrected an ARM64 bug in `emit_arm64_ldr_vpr` and `emit_arm64_str_vpr` where boolean conditions were being passed instead of actual byte sizes, causing data truncation for floating-point values in the direct marshalling path.
- [infix] Fixed MSVC ARM: SEH XDATA layout to follow the architecture's specification exactly, enabling reliable exception handling on Windows on ARM.
- [infix] Hardened instruction cache invalidation on ARM64 Linux/BSD with a robust manual fallback using assembly (`dc cvau`, `ic ivau`, etc.), ensuring generated code is immediately visible to the CPU.
- [infix] Fixed the DWARF `.eh_frame` generation for ARM64 Linux `FORWARD` trampolines, correcting the instruction sequence and offsets to enable reliable C++ exception propagation.
- [infix] Corrected a performance issue on x64 by adding `vzeroupper` calls in epilogues when AVX instructions are potentially used, avoiding transition penalties.
- [infix] Fixed bitfield parsing logic to correctly handle colons in namespaces vs bitfield widths.
- [infix] Fixed missing support for 256-bit and 512-bit vectors in SysV reverse trampolines.
- [infix] Rewrote `_layout_struct` in `src/core/types.c` to correctly handle bitfields larger than 8 bits and ensures `bit_offset` is always within the correct byte, matching standard C (well, GNU) compiler packing behavior.
- [infix] Fixed a bug in the SysV recursive classifier that was incorrectly applying strict natural alignment checks to bitfield members. This was causing structs containing bitfields to be unnecessarily passed on the stack instead of in registers.
- [infix] Trampolines allocated in a user-managed "shared arena" were being added to the internal global cache. When the user destroyed the arena, the cache retained dangling pointers to the trampoline signatures.

### Added

- Float16 support

Changes.md  view on Meta::CPAN

 - Added optimized opcodes (OP_PUSH_FLOAT16, OP_RET_FLOAT16) to the internal VM dispatcher for high-performance marshalling.

- Bitfield Support:
 - Enhanced Struct [...] syntax in Affix.pm to support bitfield widths (e.g., a => UInt32, 3).
 - Implemented bitmask-based marshalling in Affix.c to correctly pack and unpack C-style bitfields within structs.

- SIMD Vector Improvements:
 - Added M512, M512d, and M512i type helpers.
 - Ensured compatibility with infix's refined vector alignment and passing rules.
- [infix] Added support for half-precision floating-point (`float16`).
- [infix] Implemented C++ exception propagation through JIT frames on Linux (x86-64 and ARM64) using manual DWARF `.eh_frame` generation and `__register_frame`.
- [infix] Implemented Structured Exception Handling (SEH) for Windows x64 and ARM64 for C++ exception propagation through trampolines.
- [infix] Added `infix_forward_create_safe` API to establish an exception boundary that catches native exceptions and returns a dedicated error code (`INFIX_CODE_NATIVE_EXCEPTION`).
- [infix] Added support for 256-bit (AVX) and 512-bit (AVX-512) vectors in the System V ABI.
- [infix] Added support for receiving bitfield structs in reverse call trampolines.
- [infix] Added trampoline caching. Identical signatures and targets now share the same JIT-compiled code and metadata via internal reference counting, significantly reducing memory overhead and initialization time.
- [infix] Added a new opt-in build mode (`--sanity`) that emits extra JIT instructions to verify stack pointer consistency around user-provided marshaller calls, making it easier to debug corrupting language bindings.

### Changed

- Pull infix v0.1.6.

builder/Affix/Builder.pm  view on Meta::CPAN

                $debug .
                ' -g3 -gdwarf-4 ' .
                ' -Wno-deprecated -pipe ' .
                ' -Wall -Wextra -Wpedantic -Wvla -Wnull-dereference ' .
                ' -Wswitch-enum  -Wduplicated-cond ' .
                ' -Wduplicated-branches';
            $cflags .= ' -fvar-tracking-assignments' unless $Config{osname} eq 'darwin';
        }
        elsif ( !$is_win ) {
            $cflags
                .= ' -DNDEBUG -DBOOST_DISABLE_ASSERTS -Ofast -ftree-vectorize -ffast-math -fno-align-functions -fno-align-loops -fno-omit-frame-pointer -flto=auto';
        }

        # Threading support (Critical for shm_open/librt on Linux)
        if ( !$is_win ) {
            $cflags  .= ' -pthread';
            $ldflags .= ' -pthread';
        }
    }
    method write_file( $filename, $content ) { path($filename)->spew_raw($content) or die "Could not open $filename: $!\n" }
    method read_file ($filename)             { path($filename)->slurp_utf8         or die "Could not open $filename: $!\n" }

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

#define NUM_GPR_ARGS 8
/** @internal The total number of VPRs available for argument passing. */
#define NUM_VPR_ARGS 8
/** @internal A safe limit on the number of fields to classify to prevent DoS from exponential complexity. */
#define MAX_AGGREGATE_FIELDS_TO_CLASSIFY 32

//
static bool is_hfa(const infix_type * type, const infix_type ** base_type);

/** @internal The v-table of AArch64 functions for generating forward trampolines. */
static infix_status prepare_forward_call_frame_arm64(infix_arena_t * arena,
                                                     infix_call_frame_layout ** out_layout,
                                                     infix_type * ret_type,
                                                     infix_type ** arg_types,
                                                     size_t num_args,
                                                     size_t num_fixed_args,
                                                     void * target_fn);
static infix_status generate_forward_prologue_arm64(code_buffer * buf, infix_call_frame_layout * layout);
static infix_status generate_forward_argument_moves_arm64(code_buffer * buf,
                                                          infix_call_frame_layout * layout,
                                                          infix_type ** arg_types,
                                                          size_t num_args,
                                                          c23_maybe_unused size_t num_fixed_args);
static infix_status generate_forward_call_instruction_arm64(code_buffer *, infix_call_frame_layout *);
static infix_status generate_forward_epilogue_arm64(code_buffer * buf,
                                                    infix_call_frame_layout * layout,
                                                    infix_type * ret_type);
const infix_forward_abi_spec g_arm64_forward_spec = {
    .prepare_forward_call_frame = prepare_forward_call_frame_arm64,
    .generate_forward_prologue = generate_forward_prologue_arm64,
    .generate_forward_argument_moves = generate_forward_argument_moves_arm64,
    .generate_forward_call_instruction = generate_forward_call_instruction_arm64,
    .generate_forward_epilogue = generate_forward_epilogue_arm64};

/** @internal The v-table of AArch64 functions for generating reverse trampolines. */
static infix_status prepare_reverse_call_frame_arm64(infix_arena_t * arena,
                                                     infix_reverse_call_frame_layout ** out_layout,
                                                     infix_reverse_t * context);
static infix_status generate_reverse_prologue_arm64(code_buffer * buf, infix_reverse_call_frame_layout * layout);
static infix_status generate_reverse_argument_marshalling_arm64(code_buffer * buf,
                                                                infix_reverse_call_frame_layout * layout,
                                                                infix_reverse_t * context);
static infix_status generate_reverse_dispatcher_call_arm64(code_buffer * buf,
                                                           infix_reverse_call_frame_layout * layout,
                                                           infix_reverse_t * context);
static infix_status generate_reverse_epilogue_arm64(code_buffer * buf,
                                                    infix_reverse_call_frame_layout * layout,
                                                    infix_reverse_t * context);
const infix_reverse_abi_spec g_arm64_reverse_spec = {
    .prepare_reverse_call_frame = prepare_reverse_call_frame_arm64,
    .generate_reverse_prologue = generate_reverse_prologue_arm64,
    .generate_reverse_argument_marshalling = generate_reverse_argument_marshalling_arm64,
    .generate_reverse_dispatcher_call = generate_reverse_dispatcher_call_arm64,
    .generate_reverse_epilogue = generate_reverse_epilogue_arm64};

/** @internal The v-table for the new Direct Marshalling ABI. */
static infix_status prepare_direct_forward_call_frame_arm64(infix_arena_t * arena,
                                                            infix_direct_call_frame_layout ** out_layout,
                                                            infix_type * ret_type,
                                                            infix_type ** arg_types,
                                                            size_t num_args,
                                                            infix_direct_arg_handler_t * handlers,
                                                            void * target_fn);
static infix_status generate_direct_forward_prologue_arm64(code_buffer * buf, infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_argument_moves_arm64(code_buffer * buf,
                                                                 infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_call_instruction_arm64(code_buffer * buf,
                                                                   infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_epilogue_arm64(code_buffer * buf,
                                                           infix_direct_call_frame_layout * layout,
                                                           infix_type * ret_type);
const infix_direct_forward_abi_spec g_arm64_direct_forward_spec = {
    .prepare_direct_forward_call_frame = prepare_direct_forward_call_frame_arm64,
    .generate_direct_forward_prologue = generate_direct_forward_prologue_arm64,
    .generate_direct_forward_argument_moves = generate_direct_forward_argument_moves_arm64,
    .generate_direct_forward_call_instruction = generate_direct_forward_call_instruction_arm64,
    .generate_direct_forward_epilogue = generate_direct_forward_epilogue_arm64};

/**
 * @internal
 * @brief Recursively finds the first primitive floating-point type in a potential HFA.
 * @details This function performs a depth-first search to find the very first `float`
 *          or `double` primitive within an aggregate. This becomes the candidate

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

    // Verify that ALL members recursively conform to this single base type.
    size_t field_count = 0;
    if (!is_hfa_recursive_check(type, base, &field_count))
        return false;
    if (out_base_type)
        *out_base_type = base;
    return true;
}
/**
 * @internal
 * @brief Stage 1 (Forward): Analyzes a signature and creates a call frame layout for AAPCS64.
 * @details This function assigns each argument to a location (GPR, VPR, or Stack) according
 *          to the AAPCS64 rules. It contains extensive conditional logic to handle ABI
 *          deviations on Apple and Windows platforms, especially for variadic arguments
 *          and 16-byte aggregate alignment.
 *
 * @param arena The temporary arena for allocations.
 * @param out_layout Receives the created layout blueprint.
 * @param ret_type The function's return type.
 * @param arg_types Array of argument types.
 * @param num_args Total number of arguments.
 * @param num_fixed_args Number of non-variadic arguments.
 * @param target_fn The target function address.
 * @return `INFIX_SUCCESS` on success, or an error code on failure.
 */
static infix_status prepare_forward_call_frame_arm64(infix_arena_t * arena,
                                                     infix_call_frame_layout ** out_layout,
                                                     infix_type * ret_type,
                                                     infix_type ** arg_types,
                                                     size_t num_args,
                                                     size_t num_fixed_args,
                                                     void * target_fn) {
    if (out_layout == nullptr)
        return INFIX_ERROR_INVALID_ARGUMENT;
    infix_call_frame_layout * layout =
        infix_arena_calloc(arena, 1, sizeof(infix_call_frame_layout), _Alignof(infix_call_frame_layout));
    if (layout == nullptr) {
        *out_layout = nullptr;
        return INFIX_ERROR_ALLOCATION_FAILED;
    }
    layout->arg_locations =
        infix_arena_calloc(arena, num_args, sizeof(infix_arg_location), _Alignof(infix_arg_location));
    if (layout->arg_locations == nullptr && num_args > 0) {
        *out_layout = nullptr;
        return INFIX_ERROR_ALLOCATION_FAILED;
    }

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

    if (layout->total_stack_alloc > INFIX_MAX_STACK_ALLOC) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    *out_layout = layout;
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 2 (Forward): Generates the function prologue for the AArch64 trampoline.
 * @details Sets up the stack frame by saving the frame pointer (X29) and link register (X30),
 *          saves callee-saved registers (X19-X22) that will be used to hold the trampoline's
 *          context, moves the trampoline's arguments into those preserved registers, and
 *          allocates the necessary stack space for stack-passed arguments.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_forward_prologue_arm64(code_buffer * buf, infix_call_frame_layout * layout) {
    // `stp x29, x30, [sp, #-16]!` : Push Frame Pointer and Link Register to the stack, pre-decrementing SP.
    emit_arm64_stp_pre_index(buf, true, X29_FP_REG, X30_LR_REG, SP_REG, -16);
    // `stp x19, x20, [sp, #-16]!` : Save callee-saved registers that we will use for our context.
    emit_arm64_stp_pre_index(buf, true, X19_REG, X20_REG, SP_REG, -16);
    // `stp x21, x22, [sp, #-16]!`
    emit_arm64_stp_pre_index(buf, true, X21_REG, X22_REG, SP_REG, -16);
    // `mov x29, sp` : Establish the new Frame Pointer after all registers are pushed.
    emit_arm64_mov_reg(buf, true, X29_FP_REG, SP_REG);

    layout->prologue_size = (uint32_t)buf->size;

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

 *          into the correct GPRs, VPRs, or stack slots, respecting HFA rules and platform-specific
 *          variadic conventions like Apple's stack-only approach.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param arg_types The array of argument types.
 * @param num_args The total number of arguments.
 * @param num_fixed_args The number of fixed (non-variadic) arguments.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_forward_argument_moves_arm64(code_buffer * buf,
                                                          infix_call_frame_layout * layout,
                                                          infix_type ** arg_types,
                                                          size_t num_args,
                                                          c23_maybe_unused size_t num_fixed_args) {
    // If returning a large struct, the ABI requires the hidden pointer (our return buffer, in X20)
    // to be passed in the indirect result location register, x8.
    if (layout->return_value_in_memory)
        emit_arm64_mov_reg(buf, true, X8_REG, X20_REG);  // mov x8, x20
    // Standard AAPCS64 Quirk: For variadic calls, a GPR must contain the number of VPRs used.
    // This rule does NOT apply to Apple's ABI, so we exclude it for macOS.
#if !defined(INFIX_OS_MACOS)

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

    }
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 3.5 (Forward): Generates the call instruction.
 * @details Emits a null-check on the target function pointer followed by a
 *          `BLR` (Branch with Link to Register) instruction. If the pointer
 *          is null, a `BRK` instruction is executed to crash safely.
 * @param buf The code buffer.
 * @param layout The call frame layout.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_forward_call_instruction_arm64(code_buffer * buf,
                                                            c23_maybe_unused infix_call_frame_layout * layout) {
    if (layout->target_fn)
        // For a bound trampoline, the target is hardcoded. Load it into X19.
        emit_arm64_load_u64_immediate(buf, X19_REG, (uint64_t)layout->target_fn);
    // For an unbound trampoline, X19 was already loaded from the first argument in the prologue.
    // `cbnz x19, #8` : If the target function pointer in x19 is not zero, branch 8 bytes forward.
    emit_arm64_cbnz(buf, true, X19_REG, 8);
    // `brk #0` : If the pointer was null, execute a breakpoint instruction to cause a deliberate crash.
    emit_arm64_brk(buf, 0);
    // `blr x19` : Branch with link to the target function address in x19.
    emit_arm64_blr_reg(buf, X19_REG);
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 4 (Forward): Generates the function epilogue.
 * @details Emits code to handle the return value (from X0/X1 or V0-V3), deallocates
 *          the stack frame, restores callee-saved registers, and returns to the caller.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param ret_type The function's return type.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_forward_epilogue_arm64(code_buffer * buf,
                                                    infix_call_frame_layout * layout,
                                                    infix_type * ret_type) {
    layout->epilogue_offset = (uint32_t)buf->size;
    // If the function returns a value and it wasn't returned via hidden pointer...
    if (ret_type->category != INFIX_TYPE_VOID && !layout->return_value_in_memory) {
        // ...copy the result from the appropriate return register(s) into the user's return buffer (pointer in X20).
        const infix_type * hfa_base = nullptr;

        // The order of these checks is critical. Handle the most specific cases first.
        // On Apple Silicon, long double is 8 bytes. Only emit 128-bit store if size is actually 16.
        if ((is_long_double(ret_type) && ret_type->size == 16) ||

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

 * @internal
 * @brief Stage 1 (Reverse): Calculates the stack layout for a reverse trampoline stub.
 * @details This function determines the total stack space the JIT-compiled stub will need
 *          for its local variables. This space includes:
 *          1. A buffer to store the return value before it's placed in registers.
 *          2. An array of `void*` pointers (`args_array`) to pass to the C dispatcher.
 *          3. A contiguous data area where the contents of all incoming arguments
 *             (from registers or the caller's stack) will be saved.
 *
 * @param arena The temporary arena for allocations.
 * @param[out] out_layout The resulting reverse call frame layout blueprint, populated with offsets.
 * @param context The reverse trampoline context with full signature information.
 * @return `INFIX_SUCCESS` on success, or an error code on failure.
 */
static infix_status prepare_reverse_call_frame_arm64(infix_arena_t * arena,
                                                     infix_reverse_call_frame_layout ** out_layout,
                                                     infix_reverse_t * context) {
    infix_reverse_call_frame_layout * layout = infix_arena_calloc(
        arena, 1, sizeof(infix_reverse_call_frame_layout), _Alignof(infix_reverse_call_frame_layout));
    if (!layout)
        return INFIX_ERROR_ALLOCATION_FAILED;
    // The return buffer must be large enough and aligned for any type.
    size_t return_size = (context->return_type->size + 15) & ~15;
    // The array of pointers that will be passed to the C dispatcher.
    size_t args_array_size = (context->num_args * sizeof(void *) + 15) & ~15;
    // The contiguous block where we will save the actual argument data.
    size_t saved_args_data_size = 0;
    for (size_t i = 0; i < context->num_args; ++i) {
        if (context->arg_types[i]->size > INFIX_MAX_ARG_SIZE) {

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

        }
        // Ensure each saved argument slot is 16-byte aligned for simplicity and correctness.
        saved_args_data_size += (context->arg_types[i]->size + 15) & ~15;
    }
    // Security check against excessively large aggregate argument data size.
    if (saved_args_data_size > INFIX_MAX_ARG_SIZE) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    size_t total_local_space = return_size + args_array_size + saved_args_data_size;
    // The total stack allocation for the frame must be 16-byte aligned.
    if (total_local_space > INFIX_MAX_STACK_ALLOC) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    layout->total_stack_alloc = (total_local_space + 15) & ~15;
    // Local variables are accessed via positive offsets from the stack pointer (SP)
    // after the initial `sub sp, sp, #alloc` in the prologue.
    // The layout on our local stack will be: [ return_buffer | args_array | saved_args_data ]
    layout->return_buffer_offset = 0;
    layout->args_array_offset = layout->return_buffer_offset + (int32_t)return_size;
    layout->saved_args_offset = layout->args_array_offset + (int32_t)args_array_size;
    *out_layout = layout;
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 2 (Reverse): Generates the prologue for the reverse trampoline stub.
 * @details This function emits the standard AArch64 function entry code. It saves the
 *          caller's frame pointer (X29) and the link register (X30, the return address)
 *          to the stack, establishes a new frame by pointing X29 to the current stack
 *          pointer, and allocates the pre-calculated stack space for local variables.
 *
 * @param buf The code buffer to write to.
 * @param layout The blueprint containing the total stack space to allocate.
 * @return `INFIX_SUCCESS` on success.
 */
static infix_status generate_reverse_prologue_arm64(code_buffer * buf, infix_reverse_call_frame_layout * layout) {
    // `stp x29, x30, [sp, #-16]!` : Save Frame Pointer and Link Register, pre-decrementing SP.
    emit_arm64_stp_pre_index(buf, true, X29_FP_REG, X30_LR_REG, SP_REG, -16);
    // `mov x29, sp` : Establish the new frame pointer.
    emit_arm64_mov_reg(buf, true, X29_FP_REG, SP_REG);
    // `sub sp, sp, #total_stack_alloc` : Allocate space for our local variables.
    if (layout->total_stack_alloc > 0)
        emit_arm64_sub_imm(buf, true, false, SP_REG, SP_REG, (uint32_t)layout->total_stack_alloc);
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 3 (Reverse): Generates code to marshal arguments into the `void**` array.
 * @details This generates `STR` instructions to copy argument data from their native
 *          locations (GPRs, VPRs, or the caller's stack) into a contiguous "saved args"
 *          area on the stub's local stack. It then populates the `args_array` with
 *          pointers to this saved data, respecting all platform-specific ABI deviations.
 *
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param context The reverse context.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_argument_marshalling_arm64(code_buffer * buf,
                                                                infix_reverse_call_frame_layout * layout,
                                                                infix_reverse_t * context) {
    // Handle Return Value Pointer (Indirect Result Location)
    // If the return type is a large struct (> 16 bytes), the caller passes a hidden pointer in X8.
    // X8 is volatile, so we must save this pointer into our stack frame immediately.
    bool ret_is_aggregate =
        (context->return_type->category == INFIX_TYPE_STRUCT || context->return_type->category == INFIX_TYPE_UNION ||
         context->return_type->category == INFIX_TYPE_ARRAY || context->return_type->category == INFIX_TYPE_COMPLEX);
    bool return_in_memory = ret_is_aggregate && context->return_type->size > 16;

    if (return_in_memory) {
        // str x8, [sp, #return_buffer_offset]
        emit_arm64_str_imm(buf, true, X8_REG, SP_REG, layout->return_buffer_offset);
    }

    // Iterate over arguments
    size_t gpr_idx = 0;
    size_t vpr_idx = 0;
    size_t current_saved_data_offset = 0;

    // Arguments passed on the caller's stack start at offset 16 from our new frame pointer (X29).
    // X29 was established after pushing X29, X30, X19, X20, X21, X22.
    // [X29]    -> saved X29
    // [X29+8]  -> saved X30 (LR)
    // [X29+16] -> caller's first stack argument
    size_t caller_stack_offset = 16;

    for (size_t i = 0; i < context->num_args; ++i) {
        infix_type * type = context->arg_types[i];
        bool is_variadic_arg = i >= context->num_fixed_args;

        // Calculate where to save this argument's data in our local stack frame.
        int32_t arg_save_loc = (int32_t)(layout->saved_args_offset + current_saved_data_offset);

#if defined(INFIX_OS_MACOS)
        // macOS ABI deviation:
        // On macOS ARM64, ALL variadic arguments are passed on the stack.
        // They are also promoted: types < 8 bytes occupy a full 8-byte stack slot.
        if (is_variadic_arg) {
            size_t size_on_stack = (type->size < 8) ? 8 : type->size;
            size_on_stack = (size_on_stack + 7) & ~7;  // Align to 8 bytes

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

 * @details This emits the instructions to load the three arguments for the dispatcher
 *          (`context`, `return_buffer_ptr`, `args_array_ptr`) into the correct registers
 *          (X0, X1, X2) and then calls the dispatcher via `blr` (branch with link to register).
 *
 * @param buf The code buffer.
 * @param layout The blueprint containing stack offsets.
 * @param context The context, containing the dispatcher's address.
 * @return `INFIX_SUCCESS` on success.
 */
static infix_status generate_reverse_dispatcher_call_arm64(code_buffer * buf,
                                                           infix_reverse_call_frame_layout * layout,
                                                           infix_reverse_t * context) {
    // Arg 1: Load context pointer into X0.
    emit_arm64_load_u64_immediate(buf, X0_REG, (uint64_t)context);
    bool ret_is_aggregate =
        (context->return_type->category == INFIX_TYPE_STRUCT || context->return_type->category == INFIX_TYPE_UNION ||
         context->return_type->category == INFIX_TYPE_ARRAY || context->return_type->category == INFIX_TYPE_COMPLEX);
    bool return_in_memory = ret_is_aggregate && context->return_type->size > 16;
    // Arg 2: Load pointer to return buffer into X1.
    if (return_in_memory)
        // We saved the pointer from X8 earlier, now we load it back.

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

    emit_arm64_load_u64_immediate(buf, X9_REG, (uint64_t)context->internal_dispatcher);
    emit_arm64_blr_reg(buf, X9_REG);  // blr x9
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 5 (Reverse): Generates the epilogue for the reverse trampoline stub.
 * @details After the C dispatcher returns, this code retrieves the return value from the
 *          return buffer on the stub's local stack and places it into the correct native return
 *          registers (X0, X1, V0, etc.) as required by the AAPCS64. It then tears down the
 *          stack frame and returns control to the native caller.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param context The reverse context.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_epilogue_arm64(code_buffer * buf,
                                                    infix_reverse_call_frame_layout * layout,
                                                    infix_reverse_t * context) {
    bool ret_is_aggregate =
        (context->return_type->category == INFIX_TYPE_STRUCT || context->return_type->category == INFIX_TYPE_UNION ||
         context->return_type->category == INFIX_TYPE_ARRAY || context->return_type->category == INFIX_TYPE_COMPLEX);
    bool return_in_memory = ret_is_aggregate && context->return_type->size > 16;
    if (context->return_type->category != INFIX_TYPE_VOID && !return_in_memory) {
        const infix_type * base = nullptr;

        // Explicitly check for 128-bit types.
        // Note: On macOS ARM64, long double is 8 bytes, so is_long_double() is true but size is 8.

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

        else if (is_float(context->return_type) || is_double(context->return_type) ||
                 (is_long_double(context->return_type) && context->return_type->size == 8))
            emit_arm64_ldr_vpr(buf, context->return_type->size, V0_REG, SP_REG, layout->return_buffer_offset);
        else {
            // Integer, pointer, or small struct returned in GPRs.
            emit_arm64_ldr_imm(buf, true, X0_REG, SP_REG, layout->return_buffer_offset);
            if (context->return_type->size > 8)
                emit_arm64_ldr_imm(buf, true, X1_REG, SP_REG, layout->return_buffer_offset + 8);
        }
    }
    // Deallocate stack and restore frame.
    if (layout->total_stack_alloc > 0)
        // add sp, sp, #total_stack_alloc
        emit_arm64_add_imm(buf, true, false, SP_REG, SP_REG, (uint32_t)layout->total_stack_alloc);  // Cast size_t
    // Restore Frame Pointer and Link Register, then return.
    emit_arm64_ldp_post_index(
        buf, true, X29_FP_REG, X30_LR_REG, SP_REG, 16);  // ldp x29, x30, [sp], #16 (Load pair, post-indexed)
    emit_arm64_ret(buf, X30_LR_REG);                     // ret
    return INFIX_SUCCESS;
}

/**
 * @internal
 * @brief Stage 1 (Direct): Analyzes a signature and creates a call frame layout for AAPCS64.
 */
static infix_status prepare_direct_forward_call_frame_arm64(infix_arena_t * arena,
                                                            infix_direct_call_frame_layout ** out_layout,
                                                            infix_type * ret_type,
                                                            infix_type ** arg_types,
                                                            size_t num_args,
                                                            infix_direct_arg_handler_t * handlers,
                                                            void * target_fn) {
    // Reuse the standard classification logic.
    infix_call_frame_layout * standard_layout = nullptr;
    infix_status status =
        prepare_forward_call_frame_arm64(arena, &standard_layout, ret_type, arg_types, num_args, num_args, target_fn);
    if (status != INFIX_SUCCESS)
        return status;

    // Create the new direct layout and copy basic info.
    infix_direct_call_frame_layout * layout =
        infix_arena_calloc(arena, 1, sizeof(infix_direct_call_frame_layout), _Alignof(infix_direct_call_frame_layout));
    if (!layout)
        return INFIX_ERROR_ALLOCATION_FAILED;

    layout->args =
        infix_arena_calloc(arena, num_args, sizeof(infix_direct_arg_layout), _Alignof(infix_direct_arg_layout));
    if (!layout->args && num_args > 0)
        return INFIX_ERROR_ALLOCATION_FAILED;

    layout->num_args = num_args;
    layout->target_fn = target_fn;

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

    layout->total_stack_alloc = (total_needed + 15) & ~15;

    *out_layout = layout;
    return INFIX_SUCCESS;
}

/**
 * @internal
 * @brief Stage 2 (Direct): Generates the function prologue.
 */
static infix_status generate_direct_forward_prologue_arm64(code_buffer * buf, infix_direct_call_frame_layout * layout) {
    // Standard prologue: save FP/LR, set up new FP.
    emit_arm64_stp_pre_index(buf, true, X29_FP_REG, X30_LR_REG, SP_REG, -16);
    // Save callee-saved registers for our context.
    // X19: target_fn, X20: ret_ptr, X21: lang_args array
    emit_arm64_stp_pre_index(buf, true, X19_REG, X20_REG, SP_REG, -16);
    emit_arm64_stp_pre_index(buf, true, X21_REG, X22_REG, SP_REG, -16);  // X22 as scratch
    emit_arm64_mov_reg(buf, true, X29_FP_REG, SP_REG);

    // The direct CIF is called with (ret_ptr, lang_args) in X0, X1.
    emit_arm64_mov_reg(buf, true, X20_REG, X0_REG);  // x20 = ret_ptr

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

    if (layout->total_stack_alloc > 0)
        emit_arm64_sub_imm(buf, true, false, SP_REG, SP_REG, (uint32_t)layout->total_stack_alloc);
    return INFIX_SUCCESS;
}

/**
 * @internal
 * @brief Stage 3 (Direct): Generates code to call marshallers and move arguments.
 */
static infix_status generate_direct_forward_argument_moves_arm64(code_buffer * buf,
                                                                 infix_direct_call_frame_layout * layout) {
    if (layout->return_value_in_memory)
        emit_arm64_mov_reg(buf, true, X8_REG, X20_REG);

    // Re-calculate standard stack size to find where scratch space begins
    size_t standard_alloc_size = 0;
    {
        size_t stack_offset = 0;
        for (size_t i = 0; i < layout->num_args; ++i) {
            if (layout->args[i].location.type == ARG_LOCATION_STACK) {
                size_t s = layout->args[i].type->size;

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

        }
    }
    return INFIX_SUCCESS;
}

/**
 * @internal
 * @brief Stage 3.5 (Direct): Generates the call instruction.
 */
static infix_status generate_direct_forward_call_instruction_arm64(code_buffer * buf,
                                                                   infix_direct_call_frame_layout * layout) {
    emit_arm64_load_u64_immediate(buf, X19_REG, (uint64_t)layout->target_fn);
    emit_arm64_cbnz(buf, true, X19_REG, 8);
    emit_arm64_brk(buf, 0);
    emit_arm64_blr_reg(buf, X19_REG);
    return INFIX_SUCCESS;
}

/**
 * @internal
 * @brief Stage 4 (Direct): Generates the epilogue, including write-back calls.
 */
static infix_status generate_direct_forward_epilogue_arm64(code_buffer * buf,
                                                           infix_direct_call_frame_layout * layout,
                                                           infix_type * ret_type) {
    layout->epilogue_offset = (uint32_t)buf->size;
    // Handle C function's return value.
    if (ret_type->category != INFIX_TYPE_VOID && !layout->return_value_in_memory) {
        const infix_type * hfa_base = nullptr;
        if ((is_long_double(ret_type) && ret_type->size == 16) ||
            (ret_type->category == INFIX_TYPE_VECTOR && ret_type->size == 16))
            emit_arm64_str_q_imm(buf, V0_REG, X20_REG, 0);
        else if (is_hfa(ret_type, &hfa_base)) {
            size_t num_elements = ret_type->size / hfa_base->size;

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

 * @brief The System V classification for an "eightbyte" (a 64-bit chunk of a type).
 */
typedef enum {
    NO_CLASS,  ///< This eightbyte has not been classified yet. It's the initial state.
    INTEGER,   ///< This eightbyte should be passed in a general-purpose register (GPR).
    SSE,       ///< This eightbyte should be passed in an SSE register (XMM).
    MEMORY     ///< The argument is too complex or large and must be passed on the stack.
} arg_class_t;

/** The v-table of System V x64 functions for generating forward trampolines. */
static infix_status prepare_forward_call_frame_sysv_x64(infix_arena_t * arena,
                                                        infix_call_frame_layout ** out_layout,
                                                        infix_type * ret_type,
                                                        infix_type ** arg_types,
                                                        size_t num_args,
                                                        size_t num_fixed_args,
                                                        void * target_fn);
static infix_status generate_forward_prologue_sysv_x64(code_buffer * buf, infix_call_frame_layout * layout);
static infix_status generate_forward_argument_moves_sysv_x64(code_buffer * buf,
                                                             infix_call_frame_layout * layout,
                                                             infix_type ** arg_types,
                                                             size_t num_args,
                                                             size_t num_fixed_args);
static infix_status generate_forward_call_instruction_sysv_x64(code_buffer *, infix_call_frame_layout *);
static infix_status generate_forward_epilogue_sysv_x64(code_buffer * buf,
                                                       infix_call_frame_layout * layout,
                                                       infix_type * ret_type);
const infix_forward_abi_spec g_sysv_x64_forward_spec = {
    .prepare_forward_call_frame = prepare_forward_call_frame_sysv_x64,
    .generate_forward_prologue = generate_forward_prologue_sysv_x64,
    .generate_forward_argument_moves = generate_forward_argument_moves_sysv_x64,
    .generate_forward_call_instruction = generate_forward_call_instruction_sysv_x64,
    .generate_forward_epilogue = generate_forward_epilogue_sysv_x64};

/** The v-table of System V x64 functions for generating reverse trampolines. */
static infix_status prepare_reverse_call_frame_sysv_x64(infix_arena_t * arena,
                                                        infix_reverse_call_frame_layout ** out_layout,
                                                        infix_reverse_t * context);
static infix_status generate_reverse_prologue_sysv_x64(code_buffer * buf, infix_reverse_call_frame_layout * layout);
static infix_status generate_reverse_argument_marshalling_sysv_x64(code_buffer * buf,
                                                                   infix_reverse_call_frame_layout * layout,
                                                                   infix_reverse_t * context);
static infix_status generate_reverse_dispatcher_call_sysv_x64(code_buffer * buf,
                                                              infix_reverse_call_frame_layout * layout,
                                                              infix_reverse_t * context);
static infix_status generate_reverse_epilogue_sysv_x64(code_buffer * buf,
                                                       infix_reverse_call_frame_layout * layout,
                                                       infix_reverse_t * context);
const infix_reverse_abi_spec g_sysv_x64_reverse_spec = {
    .prepare_reverse_call_frame = prepare_reverse_call_frame_sysv_x64,
    .generate_reverse_prologue = generate_reverse_prologue_sysv_x64,
    .generate_reverse_argument_marshalling = generate_reverse_argument_marshalling_sysv_x64,
    .generate_reverse_dispatcher_call = generate_reverse_dispatcher_call_sysv_x64,
    .generate_reverse_epilogue = generate_reverse_epilogue_sysv_x64};

/** The v-table for the new Direct Marshalling ABI. */
static infix_status prepare_direct_forward_call_frame_sysv_x64(infix_arena_t * arena,
                                                               infix_direct_call_frame_layout ** out_layout,
                                                               infix_type * ret_type,
                                                               infix_type ** arg_types,
                                                               size_t num_args,
                                                               infix_direct_arg_handler_t * handlers,
                                                               void * target_fn);
static infix_status generate_direct_forward_prologue_sysv_x64(code_buffer * buf,
                                                              infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_argument_moves_sysv_x64(code_buffer * buf,
                                                                    infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_call_instruction_sysv_x64(code_buffer * buf,
                                                                      infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_epilogue_sysv_x64(code_buffer * buf,
                                                              infix_direct_call_frame_layout * layout,
                                                              infix_type * ret_type);
const infix_direct_forward_abi_spec g_sysv_x64_direct_forward_spec = {
    .prepare_direct_forward_call_frame = prepare_direct_forward_call_frame_sysv_x64,
    .generate_direct_forward_prologue = generate_direct_forward_prologue_sysv_x64,
    .generate_direct_forward_argument_moves = generate_direct_forward_argument_moves_sysv_x64,
    .generate_direct_forward_call_instruction = generate_direct_forward_call_instruction_sysv_x64,
    .generate_direct_forward_epilogue = generate_direct_forward_epilogue_sysv_x64};

/**
 * @internal
 * @brief Recursively classifies the eightbytes of an aggregate type.
 * @details This is the core of the complex System V classification algorithm. It traverses
 * the fields of a struct/array, examining each 8-byte chunk ("eightbyte") and assigning it a

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

    if (type->size > 8 && classes[1] == NO_CLASS)
        classes[1] = INTEGER;
    // Count the number of valid, classified eightbytes.
    if (classes[0] != NO_CLASS)
        (*num_classes)++;
    if (classes[1] != NO_CLASS)
        (*num_classes)++;
}
/**
 * @internal
 * @brief Stage 1 (Forward): Analyzes a signature and creates a call frame layout for System V.
 * @details This function iterates through a function's arguments, classifying each one
 *          to determine its location (GPR, XMM, or stack) according to the SysV ABI rules.
 * @param arena The temporary arena for allocations.
 * @param out_layout Receives the created layout blueprint.
 * @param ret_type The function's return type.
 * @param arg_types Array of argument types.
 * @param num_args Total number of arguments.
 * @param num_fixed_args Number of non-variadic arguments.
 * @param target_fn The target function address.
 * @return `INFIX_SUCCESS` on success, or an error code on failure.
 */
static infix_status prepare_forward_call_frame_sysv_x64(infix_arena_t * arena,
                                                        infix_call_frame_layout ** out_layout,
                                                        infix_type * ret_type,
                                                        infix_type ** arg_types,
                                                        size_t num_args,
                                                        size_t num_fixed_args,
                                                        void * target_fn) {
    if (out_layout == nullptr)
        return INFIX_ERROR_INVALID_ARGUMENT;
    // Allocate the layout struct that will hold our results.
    infix_call_frame_layout * layout =
        infix_arena_calloc(arena, 1, sizeof(infix_call_frame_layout), _Alignof(infix_call_frame_layout));
    if (layout == nullptr) {
        *out_layout = nullptr;
        return INFIX_ERROR_ALLOCATION_FAILED;
    }
    layout->is_variadic = num_args > num_fixed_args;
    layout->target_fn = target_fn;
    layout->arg_locations =
        infix_arena_calloc(arena, num_args, sizeof(infix_arg_location), _Alignof(infix_arg_location));
    if (layout->arg_locations == nullptr && num_args > 0) {
        *out_layout = nullptr;

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

    if (layout->total_stack_alloc > INFIX_MAX_STACK_ALLOC) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    *out_layout = layout;
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 2 (Forward): Generates the function prologue for the System V trampoline.
 * @details Sets up a standard stack frame, saves registers for the trampoline's context,
 *          and allocates stack space for arguments.
 * @param buf The code buffer.
 * @param layout The call frame layout.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_forward_prologue_sysv_x64(code_buffer * buf, infix_call_frame_layout * layout) {
    // Standard Function Prologue
    emit_push_reg(buf, RBP_REG);              // push rbp
    emit_mov_reg_reg(buf, RBP_REG, RSP_REG);  // mov rbp, rsp
    // Save Callee-Saved Registers
    // We will use these registers to store our context (target_fn, ret_ptr, args_ptr)
    // across the native function call, so we must save their original values first.
    emit_push_reg(buf, R12_REG);  // push r12
    emit_push_reg(buf, R13_REG);  // push r13
    emit_push_reg(buf, R14_REG);  // push r14
    emit_push_reg(buf, R15_REG);  // push r15

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

 * @brief Stage 3 (Forward): Generates code to move arguments from the `void**` array
 *          into their correct native locations (registers or stack).
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param arg_types The array of argument types.
 * @param num_args Total number of arguments.
 * @param num_fixed_args Number of fixed arguments.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_forward_argument_moves_sysv_x64(code_buffer * buf,
                                                             infix_call_frame_layout * layout,
                                                             infix_type ** arg_types,
                                                             size_t num_args,
                                                             c23_maybe_unused size_t num_fixed_args) {
    // If returning a large struct, the hidden pointer (stored in r13) must be moved to RDI.
    if (layout->return_value_in_memory)
        emit_mov_reg_reg(buf, GPR_ARGS[0], R13_REG);  // mov rdi, r13
    // Marshall Register Arguments
    // Loop over all arguments that are passed in registers.
    for (size_t i = 0; i < num_args; ++i) {
        infix_arg_location * loc = &layout->arg_locations[i];

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

    // The ABI requires that AL contains the number of XMM registers used for arguments.
    if (layout->is_variadic)
        // mov al, num_xmm_args (or mov eax, num_xmm_args)
        emit_mov_reg_imm32(buf, RAX_REG, (int32_t)layout->num_xmm_args);
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 3.5 (Forward): Generates the null-check and call instruction.
 * @param buf The code buffer.
 * @param layout The call frame layout.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_forward_call_instruction_sysv_x64(code_buffer * buf,
                                                               c23_maybe_unused infix_call_frame_layout * layout) {
    // For a bound trampoline, load the hardcoded address into R12.
    // For an unbound trampoline, R12 was already loaded from RDI in the prologue.
    if (layout->target_fn)
        emit_mov_reg_imm64(buf, R12_REG, (uint64_t)layout->target_fn);
    // On SysV x64, the target function pointer is stored in R12.
    emit_test_reg_reg(buf, R12_REG, R12_REG);  // test r12, r12 ; check if function pointer is null
    emit_jnz_short(buf, 2);                    // jnz +2       ; if not null, skip the crash instruction
    emit_ud2(buf);                             // ud2          ; crash safely if null
    emit_call_reg(buf, R12_REG);               // call r12     ; call the function
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 4 (Forward): Generates the function epilogue for the System V trampoline.
 * @details Emits code to handle the function's return value (from RAX/RDX, XMM0/XMM1, or
 *          the x87 FPU stack for `long double`) and properly tear down the stack frame.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param ret_type The function's return type.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_forward_epilogue_sysv_x64(code_buffer * buf,
                                                       infix_call_frame_layout * layout,
                                                       infix_type * ret_type) {
    layout->epilogue_offset = (uint32_t)buf->size;
    // Handle Return Value
    // If the function returns something and it wasn't via a hidden pointer...
    if (ret_type->category != INFIX_TYPE_VOID && !layout->return_value_in_memory) {
        if (is_long_double(ret_type))
            // `long double` is returned on the x87 FPU stack (st0).
            // We store it into the user's return buffer (pointer held in r13).
            // fstpt [r13] (Store Floating Point value and Pop)
            emit_fstpt_mem(buf, R13_REG, 0);

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

    emit_pop_reg(buf, RBP_REG);
    emit_ret(buf);
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 1 (Reverse): Calculates the stack layout for a reverse trampoline stub.
 * @details This function determines the total stack space needed for the stub's local variables,
 *          including the return buffer, the `void**` args_array, and the saved argument data.
 * @param arena The temporary arena for allocations.
 * @param[out] out_layout The resulting reverse call frame layout blueprint.
 * @param context The reverse trampoline context.
 * @return `INFIX_SUCCESS` on success.
 */
static infix_status prepare_reverse_call_frame_sysv_x64(infix_arena_t * arena,
                                                        infix_reverse_call_frame_layout ** out_layout,
                                                        infix_reverse_t * context) {
    infix_reverse_call_frame_layout * layout = infix_arena_calloc(
        arena, 1, sizeof(infix_reverse_call_frame_layout), _Alignof(infix_reverse_call_frame_layout));
    if (!layout)
        return INFIX_ERROR_ALLOCATION_FAILED;
    // Calculate space for each component, ensuring 16-byte alignment for safety and simplicity.
    size_t return_size = (context->return_type->size + 15) & ~15;
    size_t args_array_size = (context->num_args * sizeof(void *) + 15) & ~15;
    size_t saved_args_data_size = 0;
    size_t max_align = 16;  // Start with 16 for stack safety
    for (size_t i = 0; i < context->num_args; ++i) {
        // Security: Reject excessively large types before they reach the code generator.
        if (context->arg_types[i]->size > INFIX_MAX_ARG_SIZE) {

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

    if (saved_args_data_size > INFIX_MAX_ARG_SIZE) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    size_t total_local_space = return_size + args_array_size + saved_args_data_size + max_align;
    // Safety check against allocating too much stack.
    if (total_local_space > INFIX_MAX_STACK_ALLOC) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    // The total allocation for the stack frame must be aligned to the maximum required alignment.
    layout->total_stack_alloc = (uint32_t)_infix_align_up(total_local_space, max_align);

    // Local variables are accessed via negative offsets from the frame pointer (RBP).
    // The layout is [ return_buffer | args_array | (padding) | saved_args_data ]
    layout->return_buffer_offset = -(int32_t)layout->total_stack_alloc;
    layout->args_array_offset = layout->return_buffer_offset + (int32_t)return_size;

    // Align the start of the saved data area
    layout->saved_args_offset =
        (int32_t)_infix_align_up((size_t)(layout->args_array_offset + args_array_size), max_align);
    layout->max_align = (uint32_t)max_align;

    *out_layout = layout;
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 2 (Reverse): Generates the prologue for the reverse trampoline stub.
 * @details Emits standard System V function entry code, creates a stack frame,
 *          and allocates all necessary local stack space.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_prologue_sysv_x64(code_buffer * buf, infix_reverse_call_frame_layout * layout) {
    emit_push_reg(buf, RBP_REG);              // push rbp
    emit_mov_reg_reg(buf, RBP_REG, RSP_REG);  // mov rbp, rsp

    // FORCE ALIGNMENT.
    // AND RSP, -max_align
    emit_and_reg_imm8(buf, RSP_REG, (int8_t)-(int8_t)layout->max_align);

    emit_sub_reg_imm32(buf, RSP_REG, layout->total_stack_alloc);  // Allocate our calculated space.
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 3 (Reverse): Generates code to marshal arguments from their native
 *          locations into the generic `void**` array for the C dispatcher.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param context The reverse trampoline context.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_argument_marshalling_sysv_x64(code_buffer * buf,
                                                                   infix_reverse_call_frame_layout * layout,
                                                                   infix_reverse_t * context) {
    size_t gpr_idx = 0, xmm_idx = 0, current_saved_data_offset = 0;
    // Correctly determine if the return value uses a hidden pointer by performing a full ABI classification.
    bool return_in_memory = false;
    infix_type * ret_type = context->return_type;
    bool ret_is_aggregate = (ret_type->category == INFIX_TYPE_STRUCT || ret_type->category == INFIX_TYPE_UNION ||
                             ret_type->category == INFIX_TYPE_ARRAY || ret_type->category == INFIX_TYPE_COMPLEX);
    if (ret_is_aggregate) {
        if (ret_type->size > 16)
            return_in_memory = true;

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

 *             caller in RDI if the function returns a large struct by reference.
 *          3. `RDX` (Arg 3): The pointer to the `args_array` on the local stack.
 *          4. The address of the dispatcher function itself is loaded into a scratch
 *             register (`RAX`), which is then called.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param context The reverse context.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_dispatcher_call_sysv_x64(code_buffer * buf,
                                                              infix_reverse_call_frame_layout * layout,
                                                              infix_reverse_t * context) {
    // Arg 1 (RDI): The infix_reverse_t context pointer.
    emit_mov_reg_imm64(buf, RDI_REG, (uint64_t)context);  // mov rdi, #context_addr
    // Arg 2 (RSI): Pointer to the return buffer.
    // Correctly determine if the hidden pointer was used for the return value.
    bool return_in_memory = false;
    infix_type * ret_type = context->return_type;
    bool ret_is_aggregate = (ret_type->category == INFIX_TYPE_STRUCT || ret_type->category == INFIX_TYPE_UNION ||
                             ret_type->category == INFIX_TYPE_ARRAY || ret_type->category == INFIX_TYPE_COMPLEX);
    if (ret_is_aggregate) {

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

    // Load the dispatcher's address into a scratch register and call it.
    emit_mov_reg_imm64(buf, RAX_REG, (uint64_t)context->internal_dispatcher);  // mov rax, #dispatcher_addr
    emit_call_reg(buf, RAX_REG);
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 5 (Reverse): Generates the epilogue for the reverse trampoline stub.
 * @details Retrieves the return value from the local buffer and places it into the
 *          correct return registers (RAX/RDX, XMM0/XMM1) or the x87 FPU stack. Then,
 *          it tears down the stack frame and returns to the native caller.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param context The reverse context.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_epilogue_sysv_x64(code_buffer * buf,
                                                       infix_reverse_call_frame_layout * layout,
                                                       infix_reverse_t * context) {
    if (context->return_type->category != INFIX_TYPE_VOID) {
        // Correctly determine if the return value uses a hidden pointer by performing a full ABI classification.
        bool return_in_memory = false;
        infix_type * ret_type = context->return_type;
        bool ret_is_aggregate = (ret_type->category == INFIX_TYPE_STRUCT || ret_type->category == INFIX_TYPE_UNION ||
                                 ret_type->category == INFIX_TYPE_ARRAY || ret_type->category == INFIX_TYPE_COMPLEX);
        if (ret_is_aggregate) {
            if (ret_type->size > 16)
                return_in_memory = true;

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

                if (classes[1] == SSE)
                    if (context->return_type->category == INFIX_TYPE_VECTOR && context->return_type->size == 32)
                        emit_vmovupd_ymm_mem(buf, XMM1_REG, RBP_REG, layout->return_buffer_offset + 32);
                    else
                        emit_movsd_xmm_mem(buf, XMM1_REG, RBP_REG, layout->return_buffer_offset + 8);
                else  // INTEGER
                    emit_mov_reg_mem(buf, RDX_REG, RBP_REG, layout->return_buffer_offset + 8);
            }
        }
    }
    // Standard function epilogue: tear down stack frame and return.
    emit_mov_reg_reg(buf, RSP_REG, RBP_REG);
    emit_pop_reg(buf, RBP_REG);
    emit_ret(buf);
    return INFIX_SUCCESS;
}

/**
 * @internal
 * @brief Stage 1 (Direct): Analyzes a signature and creates a call frame layout for System V.
 * @details This is the direct-marshalling equivalent of the standard `prepare` function.
 * It performs the same argument classification, but populates the new `infix_direct_call_frame_layout`
 * struct, which also stores pointers to the argument types and user-provided handlers.
 * It also calculates the necessary scratch space on the stack for marshalling.
 */
static infix_status prepare_direct_forward_call_frame_sysv_x64(infix_arena_t * arena,
                                                               infix_direct_call_frame_layout ** out_layout,
                                                               infix_type * ret_type,
                                                               infix_type ** arg_types,
                                                               size_t num_args,
                                                               infix_direct_arg_handler_t * handlers,
                                                               void * target_fn) {
    // Use the standard classifier to determine the final ABI locations for all arguments.
    infix_call_frame_layout * standard_layout = nullptr;
    infix_status status = prepare_forward_call_frame_sysv_x64(
        arena, &standard_layout, ret_type, arg_types, num_args, num_args, target_fn);
    if (status != INFIX_SUCCESS)
        return status;

    // Create the new direct layout and copy basic info.
    infix_direct_call_frame_layout * layout =
        infix_arena_calloc(arena, 1, sizeof(infix_direct_call_frame_layout), _Alignof(infix_direct_call_frame_layout));
    if (!layout)
        return INFIX_ERROR_ALLOCATION_FAILED;

    layout->args =
        infix_arena_calloc(arena, num_args, sizeof(infix_direct_arg_layout), _Alignof(infix_direct_arg_layout));
    if (!layout->args && num_args > 0)
        return INFIX_ERROR_ALLOCATION_FAILED;

    layout->num_args = num_args;
    layout->target_fn = target_fn;

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    *out_layout = layout;
    return INFIX_SUCCESS;
}

/**
 * @internal
 * @brief Stage 2 (Direct): Generates the direct marshalling prologue for System V.
 * @details Establishes a stack frame, saves callee-saved registers for context,
 * moves the direct CIF arguments (`ret_ptr`, `lang_args`) into them, and allocates all
 * stack space required for outgoing arguments and local marshalling buffers.
 */
static infix_status generate_direct_forward_prologue_sysv_x64(code_buffer * buf,
                                                              infix_direct_call_frame_layout * layout) {
    emit_push_reg(buf, RBP_REG);
    emit_mov_reg_reg(buf, RBP_REG, RSP_REG);

    // Save callee-saved registers we will use for our context.
    // We push 4 registers (32 bytes) to maintain 16-byte stack alignment
    // (Previous stack state: [RetAddr]+[OldRBP] = 16 bytes. +32 bytes = 48 bytes. Aligned.)
    emit_push_reg(buf, R12_REG);  // Will hold scratch data / target function
    emit_push_reg(buf, R13_REG);  // Will hold return value pointer
    emit_push_reg(buf, R14_REG);  // Will hold language objects array pointer
    emit_push_reg(buf, R15_REG);  // Padding/Scratch (keeps stack aligned)

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

/**
 * @internal
 * @brief Stage 3 (Direct): Generates code to call marshallers and move arguments for System V.
 * @details This corrected implementation uses a two-phase approach:
 * 1. MARSHALL & SAVE: Call each user handler and save the C value to a temporary
 *    local stack buffer. This prevents register clobbering.
 * 2. PLACE: Load the C value from its temporary location and move it to its final
 *    destination (the register or stack slot required by the System V ABI).
 */
static infix_status generate_direct_forward_argument_moves_sysv_x64(code_buffer * buf,
                                                                    infix_direct_call_frame_layout * layout) {
    // PHASE 1: MARSHALL & SAVE
    for (size_t i = 0; i < layout->num_args; ++i) {
        const infix_direct_arg_layout * arg_layout = &layout->args[i];
        int32_t temp_offset = (int32_t)arg_layout->location.num_regs;

        if (!arg_layout->handler->scalar_marshaller && !arg_layout->handler->aggregate_marshaller)
            continue;

        // Arg 1 (RDI) for marshaller: the language object pointer.
        emit_mov_reg_mem(buf, RDI_REG, R14_REG, i * sizeof(void *));

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

            break;
        }
    }
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 3.5 (Direct): Generates the call instruction for System V.
 */
static infix_status generate_direct_forward_call_instruction_sysv_x64(code_buffer * buf,
                                                                      infix_direct_call_frame_layout * layout) {
    emit_mov_reg_imm64(buf, R12_REG, (uint64_t)layout->target_fn);
    emit_test_reg_reg(buf, R12_REG, R12_REG);
    emit_jnz_short(buf, 2);
    emit_ud2(buf);
    emit_call_reg(buf, R12_REG);
    return INFIX_SUCCESS;
}

/**
 * @internal
 * @brief Stage 4 (Direct): Generates the function epilogue for System V.
 */
static infix_status generate_direct_forward_epilogue_sysv_x64(code_buffer * buf,
                                                              infix_direct_call_frame_layout * layout,
                                                              infix_type * ret_type) {
    layout->epilogue_offset = (uint32_t)buf->size;
    if (ret_type->category != INFIX_TYPE_VOID && !layout->return_value_in_memory) {
        // Use full ABI classification for return values
        if (is_long_double(ret_type))
            emit_fstpt_mem(buf, R13_REG, 0);
        else {
            arg_class_t classes[2];
            size_t num_classes = 0;
            bool is_aggregate = ret_type->category == INFIX_TYPE_STRUCT || ret_type->category == INFIX_TYPE_UNION ||

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

            emit_sub_reg_imm32(buf, RSP_REG, 32);  // +32 (space for XMM0/XMM1)
            // Total stack shift: +48 bytes

            emit_movsd_mem_xmm(buf, RSP_REG, 0, XMM0_REG);

            // Set up args for write-back call
            emit_mov_reg_mem(buf, RDI_REG, R14_REG, i * sizeof(void *));

            // Arg 2 (RSI): Pointer to the C data (in our scratch space)
            // Offsets are relative to the *original* RSP of the body.
            // Since we just pushed/subbed 48 bytes, we must add 48 to reach the original frame.
            int32_t temp_offset = (int32_t)arg->location.num_regs;
            emit_lea_reg_mem(buf, RSI_REG, RSP_REG, temp_offset + 48);

            emit_mov_reg_imm64(buf, RDX_REG, (uint64_t)arg->type);

            emit_mov_reg_imm64(buf, R10_REG, (uint64_t)arg->handler->writeback_handler);
            emit_call_reg(buf, R10_REG);

            // Restore return registers
            emit_movsd_xmm_mem(buf, XMM0_REG, RSP_REG, 0);

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

/** An array of XMM registers used for passing the first four floating-point arguments. */
static const x64_xmm XMM_ARGS[] = {XMM0_REG, XMM1_REG, XMM2_REG, XMM3_REG};
/** The number of register "slots" available for arguments. */
#define NUM_GPR_ARGS 4
/** The number of XMM registers used for arguments. */
#define NUM_XMM_ARGS 4
/** The size in bytes of the mandatory stack space reserved by the caller for the callee. */
#define SHADOW_SPACE 32

/** @brief The v-table of Windows x64 functions for generating forward trampolines. */
static infix_status prepare_forward_call_frame_win_x64(infix_arena_t * arena,
                                                       infix_call_frame_layout ** out_layout,
                                                       infix_type * ret_type,
                                                       infix_type ** arg_types,
                                                       size_t num_args,
                                                       size_t num_fixed_args,
                                                       void * target_fn);
static infix_status generate_forward_prologue_win_x64(code_buffer * buf, infix_call_frame_layout * layout);
static infix_status generate_forward_argument_moves_win_x64(code_buffer * buf,
                                                            infix_call_frame_layout * layout,
                                                            infix_type ** arg_types,
                                                            size_t num_args,
                                                            size_t num_fixed_args);
static infix_status generate_forward_call_instruction_win_x64(code_buffer *, infix_call_frame_layout *);
static infix_status generate_forward_epilogue_win_x64(code_buffer * buf,
                                                      infix_call_frame_layout * layout,
                                                      infix_type * ret_type);
const infix_forward_abi_spec g_win_x64_forward_spec = {
    .prepare_forward_call_frame = prepare_forward_call_frame_win_x64,
    .generate_forward_prologue = generate_forward_prologue_win_x64,
    .generate_forward_argument_moves = generate_forward_argument_moves_win_x64,
    .generate_forward_call_instruction = generate_forward_call_instruction_win_x64,
    .generate_forward_epilogue = generate_forward_epilogue_win_x64};
/** @brief The v-table of Windows x64 functions for generating reverse trampolines. */
static infix_status prepare_reverse_call_frame_win_x64(infix_arena_t * arena,
                                                       infix_reverse_call_frame_layout ** out_layout,
                                                       infix_reverse_t * context);
static infix_status generate_reverse_prologue_win_x64(code_buffer * buf, infix_reverse_call_frame_layout * layout);
static infix_status generate_reverse_argument_marshalling_win_x64(code_buffer * buf,
                                                                  infix_reverse_call_frame_layout * layout,
                                                                  infix_reverse_t * context);
static infix_status generate_reverse_dispatcher_call_win_x64(code_buffer * buf,
                                                             infix_reverse_call_frame_layout * layout,
                                                             infix_reverse_t * context);
static infix_status generate_reverse_epilogue_win_x64(code_buffer * buf,
                                                      infix_reverse_call_frame_layout * layout,
                                                      infix_reverse_t * context);
const infix_reverse_abi_spec g_win_x64_reverse_spec = {
    .prepare_reverse_call_frame = prepare_reverse_call_frame_win_x64,
    .generate_reverse_prologue = generate_reverse_prologue_win_x64,
    .generate_reverse_argument_marshalling = generate_reverse_argument_marshalling_win_x64,
    .generate_reverse_dispatcher_call = generate_reverse_dispatcher_call_win_x64,
    .generate_reverse_epilogue = generate_reverse_epilogue_win_x64};

/** @brief The v-table for the new Direct Marshalling ABI. */
static infix_status prepare_direct_forward_call_frame_win_x64(infix_arena_t * arena,
                                                              infix_direct_call_frame_layout ** out_layout,
                                                              infix_type * ret_type,
                                                              infix_type ** arg_types,
                                                              size_t num_args,
                                                              infix_direct_arg_handler_t * handlers,
                                                              void * target_fn);
static infix_status generate_direct_forward_prologue_win_x64(code_buffer * buf,
                                                             infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_argument_moves_win_x64(code_buffer * buf,
                                                                   infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_call_instruction_win_x64(code_buffer * buf,
                                                                     infix_direct_call_frame_layout * layout);
static infix_status generate_direct_forward_epilogue_win_x64(code_buffer * buf,
                                                             infix_direct_call_frame_layout * layout,
                                                             infix_type * ret_type);
const infix_direct_forward_abi_spec g_win_x64_direct_forward_spec = {
    .prepare_direct_forward_call_frame = prepare_direct_forward_call_frame_win_x64,
    .generate_direct_forward_prologue = generate_direct_forward_prologue_win_x64,
    .generate_direct_forward_argument_moves = generate_direct_forward_argument_moves_win_x64,
    .generate_direct_forward_call_instruction = generate_direct_forward_call_instruction_win_x64,
    .generate_direct_forward_epilogue = generate_direct_forward_epilogue_win_x64};

/**
 * @internal
 * @brief Determines if a type is returned by value in RAX/XMM0 or via a hidden pointer.
 * @details On Windows x64:
 * - Scalars and __m128 are returned in registers (RAX or XMM0).

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN


    // Small scalar primitives (including float16) are passed by value.
    if (type->category == INFIX_TYPE_PRIMITIVE && type->size <= 8)
        return false;

    return type->size != 1 && type->size != 2 && type->size != 4 && type->size != 8;
}

/**
 * @internal
 * @brief Stage 1 (Forward): Analyzes a signature and creates a call frame layout for Windows x64.
 * @details Assigns each argument to a register "slot" or the stack. If the return value is
 *          passed by reference, it consumes the first slot (RCX).
 * @param arena The temporary arena for allocations.
 * @param out_layout Receives the created layout blueprint.
 * @param ret_type The function's return type.
 * @param arg_types Array of argument types.
 * @param num_args Total number of arguments.
 * @param num_fixed_args Number of non-variadic arguments.
 * @param target_fn The target function address.
 * @return `INFIX_SUCCESS` on success.
 */
static infix_status prepare_forward_call_frame_win_x64(infix_arena_t * arena,
                                                       infix_call_frame_layout ** out_layout,
                                                       infix_type * ret_type,
                                                       infix_type ** arg_types,
                                                       size_t num_args,
                                                       size_t num_fixed_args,
                                                       void * target_fn) {
    if (out_layout == nullptr)
        return INFIX_ERROR_INVALID_ARGUMENT;
    infix_call_frame_layout * layout =
        infix_arena_calloc(arena, 1, sizeof(infix_call_frame_layout), _Alignof(infix_call_frame_layout));
    if (layout == nullptr) {
        *out_layout = nullptr;
        return INFIX_ERROR_ALLOCATION_FAILED;
    }
    layout->is_variadic = num_args > num_fixed_args;
    layout->target_fn = target_fn;
    INFIX_DEBUG_PRINTF("Allocating %llu bytes for arg_locations in temp_arena",
                       (unsigned long long)(num_args * sizeof(infix_arg_location)));
    layout->arg_locations =
        infix_arena_calloc(arena, num_args, sizeof(infix_arg_location), _Alignof(infix_arg_location));

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

        return INFIX_ERROR_LAYOUT_FAILED;
    }
    *out_layout = layout;
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 2 (Forward): Generates the function prologue for the Windows x64 trampoline.
 * @details This function emits the standard machine code required at the beginning of a function.
 *          The generated assembly performs these steps:
 *          1.  `push rbp` / `mov rbp, rsp`: Creates a standard stack frame.
 *          2.  `push r12-r15`: Saves all callee-saved registers that the trampoline will
 *              use to hold its context.
 *          3.  `and rsp, -16`: **Forces 16-byte stack alignment**. This is critical because
 *              SIMD instructions in the target function may segfault if the stack is misaligned.
 *          4.  `mov r12, rcx`, etc.: Moves the trampoline's own arguments into preserved registers.
 *          5.  `sub rsp, imm32`: Allocates the required space on the stack.
 *
 * @param buf The code buffer to write the assembly into.
 * @param layout The call frame layout containing total stack allocation information.
 * @return `INFIX_SUCCESS` on successful code generation.
 */
static infix_status generate_forward_prologue_win_x64(code_buffer * buf, infix_call_frame_layout * layout) {
    emit_push_reg(buf, RBP_REG);  // push rbp
    // Save callee-saved registers we will use to hold our context.
    emit_push_reg(buf, R12_REG);              // push r12 (will hold target function address)
    emit_push_reg(buf, R13_REG);              // push r13 (will hold return value pointer)
    emit_push_reg(buf, R14_REG);              // push r14 (will hold argument pointers array)
    emit_push_reg(buf, R15_REG);              // push r15 (will be a scratch register for data moves)
    emit_mov_reg_reg(buf, RBP_REG, RSP_REG);  // mov rbp, rsp

    layout->prologue_size = (uint32_t)buf->size;

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

 *          - **Variadic Floats:** Correctly passes float/double arguments in both the
 *            appropriate GPR and XMM register for variadic functions.
 * @param buf The code buffer.
 * @param layout The layout blueprint.
 * @param arg_types The array of argument types.
 * @param num_args Total number of arguments.
 * @param num_fixed_args Number of fixed arguments.
 * @return `INFIX_SUCCESS` on success.
 */
static infix_status generate_forward_argument_moves_win_x64(code_buffer * buf,
                                                            infix_call_frame_layout * layout,
                                                            infix_type ** arg_types,
                                                            size_t num_args,
                                                            size_t num_fixed_args) {
    // If returning a large struct, the hidden pointer (stored in r13) must be moved to RCX.
    if (layout->return_value_in_memory)
        emit_mov_reg_reg(buf, GPR_ARGS[0], R13_REG);
    // Marshall Register Arguments
    for (size_t i = 0; i < num_args; ++i) {
        infix_arg_location * loc = &layout->arg_locations[i];
        if (loc->type == ARG_LOCATION_STACK)

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

                emit_mov_mem_reg(buf, RSP_REG, loc->stack_offset + offset, RAX_REG);  // Store to stack
            }
        }
    }
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 3.5 (Forward): Generates the null-check and call instruction.
 * @param buf The code buffer.
 * @param layout The call frame layout.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_forward_call_instruction_win_x64(code_buffer * buf,
                                                              c23_maybe_unused infix_call_frame_layout * layout) {
    if (layout->target_fn) {
        // For a bound trampoline, the target is hardcoded. Load it into R12.
        emit_mov_reg_imm64(buf, R12_REG, (uint64_t)layout->target_fn);
    }
    // For an unbound trampoline, R12 was already loaded from the first argument in the prologue.
    // On Windows x64, the target function pointer is stored in R12.
    emit_test_reg_reg(buf, R12_REG, R12_REG);  // test r12, r12
    emit_jnz_short(buf, 2);                    // jnz +2
    emit_ud2(buf);                             // ud2
    emit_call_reg(buf, R12_REG);               // call r12
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 4 (Forward): Generates the function epilogue for the Windows x64 trampoline.
 * @details This function emits the code to handle the function's return value and
 *          properly tear down the stack frame.
 *
 *          Since the prologue used `AND RSP, -16`, we cannot restore `RSP` by simply adding
 *          to it. Instead, we use `LEA RSP, [RBP - 32]` to restore `RSP` to point exactly
 *          to where the saved registers (R12-R15) are stored.
 *          Offset calculation: RBP is pushed, then R12, R13, R14, R15.
 *          RBP points to saved RBP.
 *          R12 @ RBP-8
 *          R13 @ RBP-16
 *          R14 @ RBP-24
 *          R15 @ RBP-32
 *
 * @param buf The code buffer.
 * @param layout The call frame layout.
 * @param ret_type The `infix_type` of the function's return value.
 * @return `INFIX_SUCCESS` on successful code generation.
 */
static infix_status generate_forward_epilogue_win_x64(code_buffer * buf,
                                                      infix_call_frame_layout * layout,
                                                      infix_type * ret_type) {
    layout->epilogue_offset = (uint32_t)buf->size;
    // R13 holds the pointer to the FFI return buffer.
    if (ret_type->category != INFIX_TYPE_VOID && !layout->return_value_in_memory) {
        if (is_float16(ret_type)) {
            // Half-precision is returned in the low 16 bits of XMM0.
            // movd eax, xmm0 ; mov [r13], ax
            emit_movq_gpr_xmm(buf, RAX_REG, XMM0_REG);
            emit_mov_mem_reg16(buf, R13_REG, 0, RAX_REG);
        }

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

}
/**
 * @internal
 * @brief Stage 1 (Reverse): Calculates the stack layout for a reverse trampoline stub.
 * @details This function determines the total stack space needed by the JIT-compiled stub.
 * This space includes areas to save all incoming argument registers, a buffer for the
 * return value, the `args_array`, a data area for by-value arguments, and the
 * shadow space the stub must provide for the C dispatcher it calls.
 *
 * @param arena The temporary arena for allocations.
 * @param[out] out_layout The resulting reverse call frame layout blueprint, populated with offsets.
 * @param context The reverse trampoline context with full signature information.
 * @return `INFIX_SUCCESS` on success, or an error code on failure.
 */
static infix_status prepare_reverse_call_frame_win_x64(infix_arena_t * arena,
                                                       infix_reverse_call_frame_layout ** out_layout,
                                                       infix_reverse_t * context) {
    infix_reverse_call_frame_layout * layout = infix_arena_calloc(
        arena, 1, sizeof(infix_reverse_call_frame_layout), _Alignof(infix_reverse_call_frame_layout));
    if (!layout)
        return INFIX_ERROR_ALLOCATION_FAILED;
    if (!context || !context->return_type)
        return INFIX_ERROR_INVALID_ARGUMENT;
    // Calculate space needed for each component, ensuring 16-byte alignment for safety.
    size_t return_size = (context->return_type->size + 15) & ~15;
    size_t args_array_size = context->num_args * sizeof(void *);

    size_t gpr_reg_save_area_size = NUM_GPR_ARGS * 8;
    size_t xmm_reg_save_area_size = NUM_XMM_ARGS * 64;  // Reserve 64 bytes for each XMM/YMM/ZMM

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

    // Add max_align to account for potential internal padding
    total_local_space += max_align;

    // Prevent integer overflow from fuzzer-provided types that are impractically large by ensuring the total required
    // stack space is within a safe limit.
    if (total_local_space > INFIX_MAX_STACK_ALLOC) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }

    // The total allocation for the stack frame must be aligned to the maximum required alignment.
    layout->total_stack_alloc = (uint32_t)_infix_align_up(total_local_space, max_align);

    // Define the layout of our local stack variables relative to RSP after allocation.
    // [ shadow space (32) | return_buffer | gpr_save | xmm_save | args_array | (padding) | saved_args_data ]
    layout->return_buffer_offset = (int32_t)_infix_align_up(SHADOW_SPACE, max_align);
    layout->gpr_save_area_offset = layout->return_buffer_offset + (int32_t)_infix_align_up(return_size, max_align);
    layout->xmm_save_area_offset =
        layout->gpr_save_area_offset + (int32_t)_infix_align_up(gpr_reg_save_area_size, max_align);
    layout->args_array_offset =
        layout->xmm_save_area_offset + (int32_t)_infix_align_up(xmm_reg_save_area_size, max_align);

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN


    layout->max_align = (uint32_t)max_align;

    *out_layout = layout;
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 2 (Reverse): Generates the prologue for the reverse trampoline stub.
 * @details Emits the standard Windows x64 function entry code. This involves:
 *          1. Creating a standard stack frame (`push rbp; mov rbp, rsp`).
 *          2. Saving any non-volatile registers that the stub will use as scratch space
 *             (RSI and RDI in this implementation).
 *          3. **Forcing stack alignment** (`and rsp, -16`).
 *          4. Allocating all necessary local stack space for the stub's internal
 *             data structures, as calculated in the `prepare` stage.
 *
 * @param buf The code buffer to write the assembly into.
 * @param layout The blueprint containing the total stack space to allocate.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_prologue_win_x64(code_buffer * buf, infix_reverse_call_frame_layout * layout) {
    // Standard function prologue to establish a stack frame.
    emit_push_reg(buf, RBP_REG);
    // Save callee-saved registers that we might use as scratch registers.
    emit_push_reg(buf, RSI_REG);
    emit_push_reg(buf, RDI_REG);
    emit_mov_reg_reg(buf, RBP_REG, RSP_REG);

    layout->prologue_size = (uint32_t)buf->size;

    // FORCE STACK ALIGNMENT.
    // Use the maximum alignment required by the signature (16, 32, or 64).

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

 *                 save area or the caller's stack.
 *              c. If passed by value, it gets a pointer *to the saved copy* of the value.
 *              d. This pointer is then stored in the correct slot of the `args_array`.
 *
 * @param buf The code buffer.
 * @param layout The blueprint containing stack offsets for the save areas and `args_array`.
 * @param context The context containing the argument type information for the callback.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_argument_marshalling_win_x64(code_buffer * buf,
                                                                  infix_reverse_call_frame_layout * layout,
                                                                  infix_reverse_t * context) {
    // Step 1: Save all potential incoming argument registers to our local stack.
    // Use 64-byte offsets to support AVX-512 in the stack layout.
    emit_mov_mem_reg(buf, RSP_REG, layout->gpr_save_area_offset + 0 * 8, RCX_REG);
    emit_mov_mem_reg(buf, RSP_REG, layout->gpr_save_area_offset + 1 * 8, RDX_REG);
    emit_mov_mem_reg(buf, RSP_REG, layout->gpr_save_area_offset + 2 * 8, R8_REG);
    emit_mov_mem_reg(buf, RSP_REG, layout->gpr_save_area_offset + 3 * 8, R9_REG);

    if (layout->max_align >= 32) {
        // AVX enabled: Save full 256 bits

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

 *             caller in RCX if the function returns a large struct by reference.
 *          3. `R8` (Arg 3): The pointer to the `args_array` on the local stack.
 *          4. The address of the dispatcher function itself is loaded into `R9`,
 *             which is then called.
 * @param buf The code buffer.
 * @param layout The blueprint containing stack offsets.
 * @param context The context, containing the dispatcher's address.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_dispatcher_call_win_x64(code_buffer * buf,
                                                             infix_reverse_call_frame_layout * layout,
                                                             infix_reverse_t * context) {
    // Arg 1 (RCX): Load the `context` pointer.
    emit_mov_reg_imm64(buf, RCX_REG, (uint64_t)context);
    // Arg 2 (RDX): Load the pointer to the return value buffer.
    if (return_value_is_by_reference(context->return_type))
        // If the return is by reference, the original caller passed the destination
        // pointer in RCX. We saved it in our GPR save area (Step 1 of marshalling).
        emit_mov_reg_mem(buf, RDX_REG, RSP_REG, layout->gpr_save_area_offset + 0 * 8);
    else
        // Otherwise, the return buffer is on our local stack. Load its address.

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

 *
 *          It then restores the stack pointer using `LEA RSP, [RBP - 16]` to undo the
 *          dynamic alignment performed in the prologue, restores saved registers, and returns.
 *
 * @param buf The code buffer.
 * @param layout The blueprint containing stack offsets.
 * @param context The context containing the return type information.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_epilogue_win_x64(code_buffer * buf,
                                                      infix_reverse_call_frame_layout * layout,
                                                      infix_reverse_t * context) {
    if (layout->max_align >= 32) {
        // Only call VZEROUPPER if we aren't returning a value in YMM/ZMM registers,
        // as VZEROUPPER would zero the upper half of the result.
        bool returning_large_vector =
            (context->return_type->category == INFIX_TYPE_VECTOR && context->return_type->size >= 32 &&
             !return_value_is_by_reference(context->return_type));
        if (!returning_large_vector)
            emit_vzeroupper(buf);
    }

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN


    emit_pop_reg(buf, RDI_REG);
    emit_pop_reg(buf, RSI_REG);
    emit_pop_reg(buf, RBP_REG);

    emit_ret(buf);
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 1 (Direct): Analyzes a signature and creates a call frame layout for Windows x64.
 * @details This function defines the on-stack layout for a direct marshalling trampoline.
 * It allocates space for outgoing stack arguments, a scratch buffer for each aggregate
 * marshaller, and a temporary save slot for each scalar marshaller.
 */
static infix_status prepare_direct_forward_call_frame_win_x64(infix_arena_t * arena,
                                                              infix_direct_call_frame_layout ** out_layout,
                                                              infix_type * ret_type,
                                                              infix_type ** arg_types,
                                                              size_t num_args,
                                                              infix_direct_arg_handler_t * handlers,
                                                              void * target_fn) {
    infix_direct_call_frame_layout * layout =
        infix_arena_calloc(arena, 1, sizeof(infix_direct_call_frame_layout), _Alignof(infix_direct_call_frame_layout));
    if (!layout)
        return INFIX_ERROR_ALLOCATION_FAILED;

    layout->args =
        infix_arena_calloc(arena, num_args, sizeof(infix_direct_arg_layout), _Alignof(infix_direct_arg_layout));
    if (!layout->args && num_args > 0)
        return INFIX_ERROR_ALLOCATION_FAILED;

    layout->num_args = num_args;
    layout->target_fn = target_fn;

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

    if (layout->total_stack_alloc > INFIX_MAX_STACK_ALLOC) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    *out_layout = layout;
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 2 (Direct): Generates the function prologue.
 * @details Establishes a stack frame, saves callee-saved registers (R12-R15) for context,
 * moves the direct CIF arguments (`ret_ptr`, `lang_args`) into them, and allocates all
 * stack space required for outgoing arguments, shadow space, and local marshalling buffers.
 *
 * This version uses **forced 16-byte stack alignment** via `AND RSP, -16`.
 */
static infix_status generate_direct_forward_prologue_win_x64(code_buffer * buf,
                                                             infix_direct_call_frame_layout * layout) {
    emit_push_reg(buf, RBP_REG);
    // Save callee-saved registers we will use for our context.
    emit_push_reg(buf, R12_REG);  // Will hold scratch data
    emit_push_reg(buf, R13_REG);  // Will hold return value pointer
    emit_push_reg(buf, R14_REG);  // Will hold language objects array pointer
    emit_push_reg(buf, R15_REG);  // Will hold target function address
    emit_mov_reg_reg(buf, RBP_REG, RSP_REG);

    layout->prologue_size = (uint32_t)buf->size;

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

 * @internal
 * @brief Stage 3 (Direct): Generates code to call marshallers and move arguments for Windows x64.
 * @details This corrected implementation uses a two-phase approach for each argument:
 * 1. MARSHALL: Call the user's handler to get the C value into a temporary location
 *    (RAX/XMM0 for scalars, a local stack buffer for aggregates).
 * 2. PLACE: Move the value from its temporary location to its final destination
 *    (the register or stack slot required by the ABI for the target C call).
 * This separation prevents register clobbering and ensures correctness.
 */
static infix_status generate_direct_forward_argument_moves_win_x64(code_buffer * buf,
                                                                   infix_direct_call_frame_layout * layout) {
    // PHASE 1: MARSHALL & SAVE
    for (size_t i = 0; i < layout->num_args; ++i) {
        const infix_direct_arg_layout * arg_layout = &layout->args[i];
        int32_t temp_offset = (int32_t)arg_layout->location.num_regs;

        if (arg_layout->handler->scalar_marshaller || arg_layout->handler->aggregate_marshaller) {

            // Arg 1 (RCX) for marshaller: the language object pointer.
            emit_mov_reg_mem(buf, RCX_REG, R14_REG, i * sizeof(void *));

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

            break;
        }
    }
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 3.5 (Direct): Generates the call instruction.
 */
static infix_status generate_direct_forward_call_instruction_win_x64(code_buffer * buf,
                                                                     infix_direct_call_frame_layout * layout) {
    emit_mov_reg_imm64(buf, R15_REG, (uint64_t)layout->target_fn);  // Use R15 for target function
    emit_test_reg_reg(buf, R15_REG, R15_REG);
    emit_jnz_short(buf, 2);
    emit_ud2(buf);
    emit_call_reg(buf, R15_REG);
    return INFIX_SUCCESS;
}

/**
 * @internal
 * @brief Stage 4 (Direct): Generates the epilogue, including write-back calls.
 *
 * Uses `LEA RSP, [RBP - 32]` to safely restore the stack pointer.
 */
static infix_status generate_direct_forward_epilogue_win_x64(code_buffer * buf,
                                                             infix_direct_call_frame_layout * layout,
                                                             infix_type * ret_type) {
    layout->epilogue_offset = (uint32_t)buf->size;
    // Handle C function's return value.
    if (ret_type->category != INFIX_TYPE_VOID && !layout->return_value_in_memory) {
        if (is_float16(ret_type)) {
            // Half-precision is returned in the low 16 bits of XMM0.
            // movd eax, xmm0 ; mov [r13], ax
            emit_movq_gpr_xmm(buf, RAX_REG, XMM0_REG);
            emit_mov_mem_reg16(buf, R13_REG, 0, RAX_REG);
        }

infix/src/arch/x64/abi_x64_common.h  view on Meta::CPAN

 * ModR/M and REX byte encodings of x86-64 instructions. The comments on each
 * register describe its primary role and whether it is caller-saved (volatile)
 * or callee-saved (must be preserved across a function call), highlighting the
 * key differences between the Windows and System V ABIs.
 */
typedef enum {
    RAX_REG = 0,  ///< Volatile (caller-saved). Primary integer/pointer return value in both ABIs.
    RCX_REG = 1,  ///< Volatile. 1st integer argument on Windows x64; 4th on System V.
    RDX_REG = 2,  ///< Volatile. 2nd integer argument on Windows x64; 3rd on System V.
    RBX_REG = 3,  ///< Callee-saved. Must be preserved across function calls.
    RSP_REG = 4,  ///< Stack Pointer. Preserved relative to the frame pointer.
    RBP_REG = 5,  ///< Frame Pointer (Base Pointer). Callee-saved.
    RSI_REG = 6,  ///< Volatile on System V (2nd integer arg). Callee-saved on Windows.
    RDI_REG = 7,  ///< Volatile on System V (1st integer arg). Callee-saved on Windows.
    R8_REG = 8,   ///< Volatile. 3rd integer argument on Windows x64; 5th on System V.
    R9_REG,       ///< Volatile. 4th integer argument on Windows x64; 6th on System V.
    R10_REG,      ///< Volatile (caller-saved) scratch register.
    R11_REG,      ///< Volatile (caller-saved) scratch register.
    R12_REG,      ///< Callee-saved.
    R13_REG,      ///< Callee-saved.
    R14_REG,      ///< Callee-saved.

infix/src/arch/x64/abi_x64_emitters.c  view on Meta::CPAN

 */
INFIX_INTERNAL void emit_ud2(code_buffer * buf) { EMIT_BYTES(buf, 0x0F, 0x0B); }
/**
 * @internal
 * @brief Emits the two-byte `syscall` instruction.
 * @details Opcode: 0F 05
 */
INFIX_INTERNAL void emit_syscall(code_buffer * buf) { EMIT_BYTES(buf, 0x0F, 0x05); }
/**
 * @internal
 * @brief Emits the `leave` instruction to tear down a stack frame.
 * @details This is equivalent to `mov rsp, rbp` followed by `pop rbp`.
 *          Opcode: C9
 */
INFIX_INTERNAL void emit_leave(code_buffer * buf) { emit_byte(buf, 0xC9); }

infix/src/common/infix_internals.h  view on Meta::CPAN

 *     (`rw_ptr`) and once as Read-Execute (`rx_ptr`). The pointers have different
 *     virtual addresses but point to the same physical memory. This is required
 *     on systems with stricter W^X enforcement.
 */
typedef struct {
#if defined(INFIX_OS_WINDOWS)
    HANDLE handle;           /**< The handle from `VirtualAlloc`, needed for `VirtualFree`. */
    void * seh_registration; /**< (Windows x64) Opaque handle from `RtlAddFunctionTable`. */
#else
    int shm_fd;          /**< The file descriptor for shared memory on dual-mapping POSIX systems. -1 otherwise. */
    void * eh_frame_ptr; /**< (POSIX) Pointer to the registered .eh_frame data. */
#endif
    void * rx_ptr; /**< The read-execute memory address. This is the callable function pointer. */
    void * rw_ptr; /**< The read-write memory address. The JIT compiler writes machine code here. */
    size_t size;   /**< The size of the allocated memory region in bytes. */
} infix_executable_t;
/**
 * @struct infix_protected_t
 * @brief Internal representation of a memory block that will be made read-only.
 *
 * @details This is used to harden the `infix_reverse_t` context against runtime

infix/src/common/infix_internals.h  view on Meta::CPAN

 * the correct move/load/store instructions.
 */
typedef struct {
    infix_arg_location_type type; /**< The classification of the argument's location. */
    uint8_t reg_index;            /**< The index of the primary register used. */
    uint8_t reg_index2;           /**< The index of the second register (for pairs). */
    uint32_t num_regs;            /**< Number of regs OR scratch buffer offset. */
    uint32_t stack_offset;        /**< The byte offset from the stack pointer. */
} infix_arg_location;
/**
 * @struct infix_call_frame_layout
 * @brief A complete layout blueprint for a forward call frame.
 * @details This structure is the primary output of `prepare_forward_call_frame`. It serves
 * as a complete plan for the JIT engine, detailing every register and stack slot
 * that needs to be populated before making the `call` instruction.
 */
typedef struct {
    size_t total_stack_alloc; /**< Total bytes to allocate on the stack for arguments and ABI-required space. */
    uint8_t num_gpr_args;     /**< The number of GPRs used for arguments. */
#if defined(INFIX_ABI_AAPCS64)
    uint8_t num_vpr_args; /**< The number of VPRs used for arguments. */
#else
    uint8_t num_xmm_args; /**< The number of XMMs used for arguments. */
#endif
    infix_arg_location * arg_locations; /**< An array of location info for each argument. */
    bool return_value_in_memory; /**< `true` if the return value uses a hidden pointer argument (struct return). */
    bool is_variadic;            /**< `true` if the function is variadic. */
    size_t num_stack_args;       /**< The number of arguments passed on the stack. */
    size_t num_args;             /**< The total number of arguments. */
    void * target_fn;            /**< The target function address. */
    uint32_t max_align;          /**< Maximum required alignment for any argument or the stack. */
    uint32_t prologue_size;      /**< Size of the generated prologue in bytes. */
    uint32_t epilogue_offset;    /**< Offset from the start of the JIT block to the epilogue. */
} infix_call_frame_layout;
/**
 * @struct infix_reverse_call_frame_layout
 * @brief A complete layout blueprint for a reverse call frame.
 * @details This structure serves as a plan for the JIT-compiled reverse call stub.
 * It contains the offsets for all data structures that the stub needs to create
 * on its stack frame before calling the universal C dispatcher.
 */
typedef struct {
    size_t total_stack_alloc;     /**< Total bytes of local stack space needed. */
    int32_t return_buffer_offset; /**< Stack offset for the buffer to store the return value. */
    int32_t args_array_offset;    /**< Stack offset for the `void**` array passed to the C dispatcher. */
    int32_t saved_args_offset;    /**< Stack offset for the area where argument data is stored/marshalled. */
    int32_t gpr_save_area_offset; /**< (Win x64) Stack offset for saving non-volatile GPRs. */
    int32_t xmm_save_area_offset; /**< (Win x64) Stack offset for saving non-volatile XMMs. */
    uint32_t max_align;           /**< Maximum required alignment for any argument or the stack. */
    uint32_t prologue_size;       /**< Size of the generated prologue in bytes. */
} infix_reverse_call_frame_layout;
/**
 * @brief Defines the ABI-specific implementation interface for forward trampolines.
 *
 * @details This structure is a virtual function table (v-table) that decouples the
 * platform-agnostic JIT engine (`trampoline.c`) from the platform-specific
 * code generation logic (`arch/...`). Each supported ABI (e.g., SysV x64,
 * Win x64, AArch64) provides a concrete implementation of this interface.
 *
 * The JIT pipeline for a forward call proceeds in a well-defined order:
 * 1. `prepare_forward_call_frame` is called first to analyze the function
 *    signature and produce a complete `infix_call_frame_layout` blueprint.
 * 2. The `generate_*` functions are then called in sequence, consuming the layout
 *    blueprint to emit the corresponding machine code into a `code_buffer`.
 */
typedef struct {
    /**
     * @brief Analyzes a function signature to create a complete call frame layout.
     * @details This is the "classification" stage. It determines where each argument
     *          and the return value will be placed (in which registers or on what
     *          stack offset) according to the target ABI's rules. The resulting
     *          layout is a complete plan for the code emitters.
     * @param[in] arena A temporary arena for allocating the layout struct.
     * @param[out] out_layout Receives the newly created layout blueprint.
     * @param[in] ret_type The function's return type.
     * @param[in] arg_types Array of argument types.
     * @param[in] num_args Total number of arguments.
     * @param[in] num_fixed_args Number of non-variadic arguments.
     * @param[in] target_fn The target function address.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*prepare_forward_call_frame)(infix_arena_t * arena,
                                               infix_call_frame_layout ** out_layout,
                                               infix_type * ret_type,
                                               infix_type ** arg_types,
                                               size_t num_args,
                                               size_t num_fixed_args,
                                               void * target_fn);
    /**
     * @brief Generates the function prologue (stack setup, saving registers).
     * @param[in,out] buf The code buffer to append machine code to.
     * @param[in] layout The layout blueprint from the previous step.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_forward_prologue)(code_buffer * buf, infix_call_frame_layout * layout);
    /**
     * @brief Generates code to move arguments from the `void**` array into registers and/or the stack.
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] arg_types The array of argument types.
     * @param[in] num_args Total number of arguments.
     * @param[in] num_fixed_args Number of fixed arguments.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_forward_argument_moves)(code_buffer * buf,
                                                    infix_call_frame_layout * layout,
                                                    infix_type ** arg_types,
                                                    size_t num_args,
                                                    size_t num_fixed_args);
    /**
     * @brief Generates the `call` instruction to the target function.
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_forward_call_instruction)(code_buffer * buf, infix_call_frame_layout * layout);
    /**
     * @brief Generates the function epilogue (handling return value, restoring stack, returning).
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] ret_type The function's return type.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_forward_epilogue)(code_buffer * buf,
                                              infix_call_frame_layout * layout,
                                              infix_type * ret_type);
} infix_forward_abi_spec;
/**
 * @brief Defines the ABI-specific implementation interface for reverse trampolines.
 * @details This v-table defines the contract for generating the JIT stub for a
 * reverse call (callback). The stub's primary job is to receive arguments in
 * native ABI format, marshal them into a generic `void**` array, and call the
 * universal C dispatcher.
 */
typedef struct {
    /**
     * @brief Analyzes a function signature to create a layout for the reverse call stub's stack frame.
     * @param[in] arena The temporary arena for allocations.
     * @param[out] out_layout Receives the newly created layout blueprint.
     * @param[in] context The reverse trampoline context, containing all type info.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*prepare_reverse_call_frame)(infix_arena_t * arena,
                                               infix_reverse_call_frame_layout ** out_layout,
                                               infix_reverse_t * context);
    /**
     * @brief Generates the reverse stub's prologue (stack setup).
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_reverse_prologue)(code_buffer * buf, infix_reverse_call_frame_layout * layout);
    /**
     * @brief Generates code to marshal arguments from their native locations (registers/stack) into a `void**` array.
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] context The reverse context.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_reverse_argument_marshalling)(code_buffer * buf,
                                                          infix_reverse_call_frame_layout * layout,
                                                          infix_reverse_t * context);
    /**
     * @brief Generates the call to the universal C dispatcher (`infix_internal_dispatch_callback_fn_impl`).
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] context The reverse context.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_reverse_dispatcher_call)(code_buffer * buf,
                                                     infix_reverse_call_frame_layout * layout,
                                                     infix_reverse_t * context);
    /**
     * @brief Generates the reverse stub's epilogue (handling return value, restoring stack, returning).
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] context The reverse context.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_reverse_epilogue)(code_buffer * buf,
                                              infix_reverse_call_frame_layout * layout,
                                              infix_reverse_t * context);
} infix_reverse_abi_spec;

/**
 * @struct infix_direct_arg_layout
 * @brief Internal layout information for a single argument in a direct marshalling trampoline.
 *
 * This struct combines the ABI location information with pointers to the type and
 * handler information needed by the JIT emitters.
 */
typedef struct {
    infix_arg_location location;                 ///< The physical location (register/stack) of the argument.
    const infix_type * type;                     ///< The `infix_type` of this argument.
    const infix_direct_arg_handler_t * handler;  ///< Pointer to the user-provided handler struct for this argument.
} infix_direct_arg_layout;

/**
 * @struct infix_direct_call_frame_layout
 * @brief A complete layout blueprint for a direct marshalling forward call frame.
 *
 * This structure serves as the plan for the JIT engine, detailing every register,
 * stack slot, and marshaller/write-back call needed to execute a direct FFI call.
 */
typedef struct {
    size_t total_stack_alloc;        ///< Total bytes to allocate on the stack for arguments and ABI-required space.
    size_t num_args;                 ///< The total number of arguments.
    void * target_fn;                ///< The target C function address.
    bool return_value_in_memory;     ///< `true` if the return value uses a hidden pointer argument.
    infix_direct_arg_layout * args;  ///< An array of layout info for each argument.
    uint32_t prologue_size;          ///< Size of the generated prologue in bytes.
    uint32_t epilogue_offset;        ///< Offset from the start of the JIT block to the epilogue.
} infix_direct_call_frame_layout;

/**
 * @brief Defines the ABI-specific implementation interface for direct marshalling forward trampolines.
 *
 * This v-table defines the contract for generating a high-performance, direct-marshalling
 * trampoline. It is parallel to `infix_forward_abi_spec`.
 */
typedef struct {
    /** @brief Analyzes a function signature to create a complete direct call frame layout.     */
    infix_status (*prepare_direct_forward_call_frame)(infix_arena_t * arena,
                                                      infix_direct_call_frame_layout ** out_layout,
                                                      infix_type * ret_type,
                                                      infix_type ** arg_types,
                                                      size_t num_args,
                                                      infix_direct_arg_handler_t * handlers,
                                                      void * target_fn);
    /** @brief Generates the function prologue (stack setup, saving registers).   */
    infix_status (*generate_direct_forward_prologue)(code_buffer * buf, infix_direct_call_frame_layout * layout);
    /** @brief Generates code to call marshallers and move arguments into their native locations.     */
    infix_status (*generate_direct_forward_argument_moves)(code_buffer * buf, infix_direct_call_frame_layout * layout);
    /** @brief Generates the `call` instruction to the target function. */
    infix_status (*generate_direct_forward_call_instruction)(code_buffer * buf,
                                                             infix_direct_call_frame_layout * layout);
    /** @brief Generates the function epilogue (handling return value, calling write-back handlers, returning).   */
    infix_status (*generate_direct_forward_epilogue)(code_buffer * buf,
                                                     infix_direct_call_frame_layout * layout,
                                                     infix_type * ret_type);

} infix_direct_forward_abi_spec;

// Internal Function Prototypes (Shared across modules)
/**
 * @brief Sets the thread-local error state with detailed information.
 * @details Located in `src/core/error.c`, this function is the primary mechanism
 * for reporting errors from within the library. It populates the thread-local
 * `g_infix_last_error` struct. For parser errors, it generates a rich diagnostic

infix/src/jit/executor.c  view on Meta::CPAN

 * This logic performs a runtime check for these APIs and the entitlement, gracefully
 * falling back to the legacy (but less secure) `mprotect` method if they are not
 * available. This provides maximum security for production apps while maintaining
 * maximum convenience for developers who may not have codesigned their test executables.
 */
typedef const struct __CFString * CFStringRef;
typedef const void * CFTypeRef;
typedef struct __SecTask * SecTaskRef;
typedef struct __CFError * CFErrorRef;
#define kCFStringEncodingUTF8 0x08000100
// A struct to hold dynamically loaded function pointers from macOS frameworks.
static struct {
    void (*CFRelease)(CFTypeRef);
    bool (*CFBooleanGetValue)(CFTypeRef boolean);
    CFStringRef (*CFStringCreateWithCString)(CFTypeRef allocator, const char * cStr, uint32_t encoding);
    CFTypeRef kCFAllocatorDefault;
    SecTaskRef (*SecTaskCreateFromSelf)(CFTypeRef allocator);
    CFTypeRef (*SecTaskCopyValueForEntitlement)(SecTaskRef task, CFStringRef entitlement, CFErrorRef * error);
    void (*pthread_jit_write_protect_np)(int enabled);
    void (*sys_icache_invalidate)(void * start, size_t len);
} g_macos_apis;
/**
 * @internal
 * @brief One-time initialization to dynamically load macOS framework functions.
 * @details Uses `dlopen` and `dlsym` to find the necessary CoreFoundation and Security
 * framework functions at runtime. This avoids a hard link-time dependency,
 * making the library more portable and resilient if these frameworks change.
 */
static void initialize_macos_apis(void) {
    // We don't need to link against these frameworks, which makes building simpler.
    void * cf = dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", RTLD_LAZY);
    void * sec = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY);

    // Hardened Runtime helpers found in libSystem/libpthread
    g_macos_apis.pthread_jit_write_protect_np = dlsym(RTLD_DEFAULT, "pthread_jit_write_protect_np");
    g_macos_apis.sys_icache_invalidate = dlsym(RTLD_DEFAULT, "sys_icache_invalidate");

    if (!cf || !sec) {
        INFIX_DEBUG_PRINTF("Warning: Could not dlopen macOS frameworks. JIT security features will be degraded.");
        if (cf)
            dlclose(cf);
        if (sec)
            dlclose(sec);
        return;
    }
    g_macos_apis.CFRelease = dlsym(cf, "CFRelease");
    g_macos_apis.CFBooleanGetValue = dlsym(cf, "CFBooleanGetValue");
    g_macos_apis.CFStringCreateWithCString = dlsym(cf, "CFStringCreateWithCString");
    void ** pAlloc = (void **)dlsym(cf, "kCFAllocatorDefault");

infix/src/jit/executor.c  view on Meta::CPAN

 * @brief Allocates a block of memory suitable for holding JIT-compiled code,
 *        respecting platform-specific W^X (Write XOR Execute) security policies.
 * @param size The number of bytes to allocate. Must be a multiple of the system page size.
 * @return An `infix_executable_t` structure. On failure, its pointers will be `nullptr`.
 */
c23_nodiscard infix_executable_t infix_executable_alloc(size_t size) {
#if defined(INFIX_OS_WINDOWS)
    infix_executable_t exec = {
        .rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .handle = nullptr, .seh_registration = nullptr};
#else
    infix_executable_t exec = {.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .shm_fd = -1, .eh_frame_ptr = nullptr};
#endif
    if (size == 0)
        return exec;

#if defined(INFIX_OS_WINDOWS)
    // Add headroom for SEH metadata on Windows.
    size_t total_size = size + INFIX_SEH_METADATA_SIZE;

    // Windows: Single-mapping W^X. Allocate as RW, later change to RX via VirtualProtect.
    void * code = VirtualAlloc(nullptr, total_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

infix/src/jit/executor.c  view on Meta::CPAN

    }
}
#endif
#endif

#if defined(INFIX_OS_LINUX) && defined(INFIX_ARCH_X64)
/**
 * @internal
 * @brief Registers DWARF unwind information for a JIT-compiled block on Linux x64.
 * @details This allows the C++ exception unwinder to correctly walk through
 *          JIT-compiled frames. We manually construct a Common Information Entry (CIE)
 *          and a Frame Description Entry (FDE) that match the stack behavior
 *          of our trampolines (standard RBP-based frame).
 */
static void _infix_register_eh_frame_linux_x64(infix_executable_t * exec, infix_executable_category_t category) {
    // Simplified .eh_frame layout: [ CIE | FDE | Terminator ]
    const size_t cie_size = 32;
    const size_t fde_size = 64;
    const size_t total_size = cie_size + fde_size + 4;  // +4 for null terminator

    uint8_t * eh = infix_malloc(total_size);
    if (!eh)
        return;
    infix_memset(eh, 0, total_size);

    uint8_t * p = eh;

infix/src/jit/executor.c  view on Meta::CPAN

        *p++ = 0x05;  // offset r14, 5
        *p++ = 0x42;  // loc +2 (after push r15)
        *p++ = 0x8f;
        *p++ = 0x06;  // offset r15, 6
    }

    while ((size_t)(p - eh) < (cie_size + fde_size))
        *p++ = 0;
    *(uint32_t *)p = 0;  // Terminator

    extern void __register_frame(void *);
    pthread_mutex_lock(&g_dwarf_mutex);
    __register_frame(eh);
    pthread_mutex_unlock(&g_dwarf_mutex);

    exec->eh_frame_ptr = eh;
    INFIX_DEBUG_PRINTF("Registered DWARF .eh_frame at %p for JIT code at %p", (void *)eh, exec->rx_ptr);
}
#elif defined(INFIX_OS_LINUX) && defined(INFIX_ARCH_AARCH64)
/**
 * @internal
 * @brief Registers DWARF unwind information for a JIT-compiled block on ARM64 Linux.
 * @details This allows the C++ exception unwinder to correctly walk through
 *          JIT-compiled frames. We manually construct a Common Information Entry (CIE)
 *          and a Frame Description Entry (FDE) that match the stack behavior
 *          of our ARM64 trampolines.
 */
static void _infix_register_eh_frame_arm64(infix_executable_t * exec, infix_executable_category_t category) {
    // Simplified .eh_frame layout: [ CIE | FDE | Terminator ]
    const size_t cie_size = 32;
    const size_t fde_size = 64;
    const size_t total_size = cie_size + fde_size + 4;  // +4 for null terminator

    uint8_t * eh = infix_malloc(total_size);
    if (!eh)
        return;
    infix_memset(eh, 0, total_size);

    uint8_t * p = eh;

infix/src/jit/executor.c  view on Meta::CPAN

        *p++ = 5;     // x22 at CFA - 40
        *p++ = 0x41;  // after mov x29, sp
        *p++ = 0x0d;
        *p++ = 29;  // def_cfa_register x29 (offset remains 48)
    }

    while ((size_t)(p - eh) < (cie_size + fde_size))
        *p++ = 0;
    *(uint32_t *)p = 0;  // Terminator

    // Register the frame with the runtime.
    extern void __register_frame(void *);
    pthread_mutex_lock(&g_dwarf_mutex);
    __register_frame(eh);
    pthread_mutex_unlock(&g_dwarf_mutex);

    exec->eh_frame_ptr = eh;
    INFIX_DEBUG_PRINTF("Registered ARM64 DWARF .eh_frame at %p for JIT code at %p", (void *)eh, exec->rx_ptr);
}
#endif

/**
 * @internal
 * @brief Frees a block of executable memory with use-after-free hardening.
 *
 * @details Before freeing the memory, this function first attempts to change the
 * memory protection to be inaccessible (`PROT_NONE` or `PAGE_NOACCESS`). This
 * creates a "guard page" that will cause an immediate, safe crash if a dangling

infix/src/jit/executor.c  view on Meta::CPAN

        munmap(exec.rw_ptr, exec.size);
    }
#elif defined(INFIX_OS_ANDROID) || defined(INFIX_OS_OPENBSD) || defined(INFIX_OS_DRAGONFLY)
    // Other single-mapping POSIX systems.
    if (exec.rw_ptr) {
        mprotect(exec.rw_ptr, exec.size, PROT_NONE);
        munmap(exec.rw_ptr, exec.size);
    }
#else
    // Dual-mapping POSIX: protect and unmap both views.
    if (exec.eh_frame_ptr) {
        extern void __deregister_frame(void *);
        pthread_mutex_lock(&g_dwarf_mutex);
        __deregister_frame(exec.eh_frame_ptr);
        pthread_mutex_unlock(&g_dwarf_mutex);
        infix_free(exec.eh_frame_ptr);
    }
    if (exec.rx_ptr)
        mprotect(exec.rx_ptr, exec.size, PROT_NONE);
    if (exec.rw_ptr)
        munmap(exec.rw_ptr, exec.size);
    if (exec.rx_ptr && exec.rx_ptr != exec.rw_ptr)  // rw_ptr might be same as rx_ptr on some platforms
        munmap(exec.rx_ptr, exec.size);
    if (exec.shm_fd >= 0)
        close(exec.shm_fd);
#endif

infix/src/jit/executor.c  view on Meta::CPAN

        _infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_PROTECTION_FAILURE, errno, nullptr);
#elif defined(INFIX_OS_ANDROID) || defined(INFIX_OS_OPENBSD) || defined(INFIX_OS_DRAGONFLY)
    // Other single-mapping POSIX platforms use mprotect.
    result = (mprotect(exec->rw_ptr, exec->size, PROT_READ | PROT_EXEC) == 0);
    if (!result)
        _infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_PROTECTION_FAILURE, errno, nullptr);
#else
    // Dual-mapping POSIX (Linux, FreeBSD).
    // The RX mapping is already executable.
#if defined(INFIX_OS_LINUX) && defined(INFIX_ARCH_X64)
    _infix_register_eh_frame_linux_x64(exec, category);
#elif defined(INFIX_OS_LINUX) && defined(INFIX_ARCH_AARCH64)
    _infix_register_eh_frame_arm64(exec, category);
#endif
    // SECURITY CRITICAL: We MUST unmap the RW view now. If we leave it mapped,
    // an attacker with a heap disclosure could find it and overwrite the JIT code,
    // bypassing W^X.
    if (munmap(exec->rw_ptr, exec->size) == 0) {
        exec->rw_ptr = nullptr;  // Clear the pointer to prevent double-free or misuse.
        result = true;
    }
    else {
        _infix_set_system_error(

infix/src/jit/trampoline.c  view on Meta::CPAN

 * executable machine code at runtime.
 *
 * It implements both the high-level Signature API (e.g., `infix_forward_create`)
 * and the low-level Manual API (e.g., `infix_forward_create_manual`). The high-level
 * functions are convenient wrappers that use the signature parser to create the
 * necessary `infix_type` objects before calling the core internal implementation.
 *
 * The core logic is encapsulated in `_infix_forward_create_internal` and
 * `_infix_reverse_create_internal`. These functions follow a clear pipeline:
 * 1.  **Prepare:** Analyze the function signature with the appropriate ABI-specific
 *     `prepare_*_call_frame` function to create a layout blueprint.
 * 2.  **Generate:** Use the layout blueprint to call a sequence of ABI-specific
 *     `generate_*` functions, which emit machine code into a temporary `code_buffer`.
 * 3.  **Finalize:** Allocate executable memory, copy the generated code into it,
 *     create the final self-contained trampoline handle (deep-copying all type
 *     metadata), and make the code executable.
 */
#include "common/infix_internals.h"
#include "common/utility.h"
#include <stdio.h>
#include <stdlib.h>

infix/src/jit/trampoline.c  view on Meta::CPAN

    return (infix_direct_cif_func)trampoline->exec.rx_ptr;
}
/**
 * @internal
 * @brief The core implementation for creating a forward trampoline.
 *
 * This function orchestrates the JIT compilation pipeline:
 * 1. Validates input types to ensure they are fully resolved.
 * 2. Selects the appropriate ABI specification v-table for the target platform.
 * 3. Creates a temporary arena for all intermediate allocations (layout, code buffer).
 * 4. Invokes the ABI spec functions in sequence to generate the call frame layout and machine code.
 * 5. Allocates the final `infix_forward_t` handle.
 * 6. Creates a private arena for the handle and deep-copies all type info into it,
 *    making the handle completely self-contained and independent of its source types.
 * 7. Allocates executable memory, copies the generated code, and makes it executable.
 *
 * @param[out] out_trampoline Receives the created trampoline handle.
 * @param[in] target_arena The arena to eventually store the trampoline in.
 * @param[in] return_type The fully resolved return type.
 * @param[in] arg_types An array of fully resolved argument types.
 * @param[in] num_args Total number of arguments.

infix/src/jit/trampoline.c  view on Meta::CPAN

        return INFIX_ERROR_INVALID_ARGUMENT;
    }
    for (size_t i = 0; i < num_args; ++i) {
        if (arg_types[i] == nullptr || !_is_type_graph_resolved(arg_types[i])) {
            _infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_UNRESOLVED_NAMED_TYPE, 0);
            return INFIX_ERROR_INVALID_ARGUMENT;
        }
    }

    infix_status status = INFIX_SUCCESS;
    infix_call_frame_layout * layout = nullptr;
    infix_forward_t * handle = nullptr;

    // Use a temporary arena for all intermediate allocations during code generation.
    infix_arena_t * temp_arena = infix_arena_create(65536);
    if (!temp_arena) {
        _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
        return INFIX_ERROR_ALLOCATION_FAILED;
    }

    // --- Cache Lookup Stage ---

infix/src/jit/trampoline.c  view on Meta::CPAN

    if (spec == nullptr) {
        _infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_UNSUPPORTED_ABI, 0);
        status = INFIX_ERROR_UNSUPPORTED_ABI;
        goto cleanup;
    }

    code_buffer buf;
    code_buffer_init(&buf, temp_arena);
    // JIT Compilation Pipeline
    // Prepare: Classify arguments and create the layout blueprint.
    status = spec->prepare_forward_call_frame(
        temp_arena, &layout, return_type, arg_types, num_args, num_fixed_args, target_fn);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    // 2. Generate: Emit machine code based on the layout.
    status = spec->generate_forward_prologue(&buf, layout);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_forward_argument_moves(&buf, layout, arg_types, num_args, num_fixed_args);
    if (status != INFIX_SUCCESS)
        goto cleanup;

infix/src/jit/trampoline.c  view on Meta::CPAN

/**
 * @internal
 * @brief The core implementation for creating a direct marshalling forward trampoline.
 *
 * This function orchestrates the JIT compilation pipeline for the direct marshalling
 * feature. It is the internal counterpart to the public `infix_forward_create_direct`
 * function and is called after the signature string has been parsed into a type graph.
 *
 * The pipeline is as follows:
 * 1.  Selects the appropriate `infix_direct_forward_abi_spec` v-table for the target platform.
 * 2.  Invokes `prepare_direct_forward_call_frame` to analyze the signature and handlers,
 *     producing a complete layout blueprint.
 * 3.  Calls the `generate_*` functions from the v-table in sequence. This emits machine code
 *     that includes direct calls to the user-provided marshaller and write-back functions.
 * 4.  Finalizes the `infix_forward_t` handle, marking it as a `is_direct_trampoline`.
 * 5.  Allocates executable memory, copies the generated code, and makes it executable.
 *
 * @param[out] out_trampoline Receives the created trampoline handle.
 * @param[in] return_type The fully resolved return type.
 * @param[in] arg_types An array of fully resolved argument types.
 * @param[in] num_args Total number of arguments.

infix/src/jit/trampoline.c  view on Meta::CPAN

        return INFIX_ERROR_INVALID_ARGUMENT;
    }

    const infix_direct_forward_abi_spec * spec = get_current_direct_forward_abi_spec();
    if (spec == nullptr) {
        _infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_UNSUPPORTED_ABI, 0);
        return INFIX_ERROR_UNSUPPORTED_ABI;
    }

    infix_status status = INFIX_SUCCESS;
    infix_direct_call_frame_layout * layout = nullptr;
    infix_forward_t * handle = nullptr;
    infix_arena_t * temp_arena = infix_arena_create(65536);
    if (!temp_arena) {
        _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
        return INFIX_ERROR_ALLOCATION_FAILED;
    }
    code_buffer buf;
    code_buffer_init(&buf, temp_arena);

    // 2. JIT Compilation Pipeline
    status = spec->prepare_direct_forward_call_frame(
        temp_arena, &layout, return_type, arg_types, num_args, handlers, target_fn);
    if (status != INFIX_SUCCESS)
        goto cleanup;

    status = spec->generate_direct_forward_prologue(&buf, layout);
    if (status != INFIX_SUCCESS)
        goto cleanup;

    status = spec->generate_direct_forward_argument_moves(&buf, layout);
    if (status != INFIX_SUCCESS)

infix/src/jit/trampoline.c  view on Meta::CPAN

            _infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_UNRESOLVED_NAMED_TYPE, 0);
            return INFIX_ERROR_INVALID_ARGUMENT;
        }
    }
    const infix_reverse_abi_spec * spec = get_current_reverse_abi_spec();
    if (spec == nullptr) {
        _infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_UNSUPPORTED_ABI, 0);
        return INFIX_ERROR_UNSUPPORTED_ABI;
    }
    infix_status status = INFIX_SUCCESS;
    infix_reverse_call_frame_layout * layout = nullptr;
    infix_reverse_t * context = nullptr;
    infix_arena_t * temp_arena = nullptr;
    infix_protected_t prot = {.rw_ptr = nullptr, .size = 0};
    code_buffer buf;
    temp_arena = infix_arena_create(65536);
    if (!temp_arena) {
        _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
        return INFIX_ERROR_ALLOCATION_FAILED;
    }
    code_buffer_init(&buf, temp_arena);

infix/src/jit/trampoline.c  view on Meta::CPAN

        status = infix_forward_create_manual(&context->cached_forward_trampoline,
                                             context->return_type,
                                             context->arg_types,
                                             context->num_args,
                                             context->num_fixed_args,
                                             user_callback_fn);
        if (status != INFIX_SUCCESS)
            goto cleanup;
    }
    // JIT Compilation Pipeline for Reverse Stub
    status = spec->prepare_reverse_call_frame(temp_arena, &layout, context);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_reverse_prologue(&buf, layout);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_reverse_argument_marshalling(&buf, layout, context);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_reverse_dispatcher_call(&buf, layout, context);
    if (status != INFIX_SUCCESS)

lib/Affix.c  view on Meta::CPAN

    dXSTARG;

    Affix_Backend * backend = (Affix_Backend *)CvXSUBANY(cv).any_ptr;

    if (UNLIKELY((SP - MARK) != backend->num_args))
        croak("Wrong number of arguments to affixed function. Expected %" UVuf ", got %" UVuf,
              (UV)backend->num_args,
              (UV)(SP - MARK));

    void * ret_buffer = alloca(infix_type_get_size(backend->ret_type));
    SV ** perl_stack_frame = &ST(0);

    backend->cif(ret_buffer, (void **)perl_stack_frame);

    switch (backend->ret_opcode) {
    case OP_RET_VOID:
        sv_setsv(TARG, &PL_sv_undef);
        break;
    case OP_RET_BOOL:
        sv_setbool(TARG, *(bool *)ret_buffer);
        break;
    case OP_RET_SINT8:
        sv_setiv(TARG, *(int8_t *)ret_buffer);

lib/Affix.c  view on Meta::CPAN

static void pull_perlio(pTHX_ Affix *, SV *, const infix_type *, void *);
static void pull_stringlist(pTHX_ Affix *, SV *, const infix_type *, void *);
#if !defined(INFIX_COMPILER_MSVC)
static void pull_sint128(pTHX_ Affix *, SV *, const infix_type *, void *);
static void pull_uint128(pTHX_ Affix *, SV *, const infix_type *, void *);
#endif

#define DEFINE_PUSH_PRIMITIVE_EXECUTOR(name, c_type, sv_accessor)         \
    static void plan_step_push_##name(pTHX_ Affix * affix,                \
                                      Affix_Plan_Step * step,             \
                                      SV ** perl_stack_frame,             \
                                      void * args_buffer,                 \
                                      void ** c_args,                     \
                                      void * ret_buffer) {                \
        PERL_UNUSED_VAR(affix);                                           \
        PERL_UNUSED_VAR(ret_buffer);                                      \
        SV * sv = perl_stack_frame[step->data.index];                     \
        void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset; \
        *(c_type *)c_arg_ptr = (c_type)sv_accessor(sv);                   \
        c_args[step->data.index] = c_arg_ptr;                             \
    }

#define DEFINE_IV_PUSH_HANDLER(name, c_type)                                      \
    static void push_handler_##name(pTHX_ Affix * affix, SV * sv, void * c_ptr) { \
        PERL_UNUSED_VAR(affix);                                                   \
        U32 flags = SvFLAGS(sv);                                                  \
        if (flags & SVf_IOK) {                                                    \

lib/Affix.c  view on Meta::CPAN

DEFINE_PUSH_PRIMITIVE_EXECUTOR(sint8, int8_t, SvIV)
DEFINE_PUSH_PRIMITIVE_EXECUTOR(uint8, uint8_t, SvUV)
DEFINE_PUSH_PRIMITIVE_EXECUTOR(sint16, int16_t, SvIV)
DEFINE_PUSH_PRIMITIVE_EXECUTOR(uint16, uint16_t, SvUV)
DEFINE_PUSH_PRIMITIVE_EXECUTOR(sint32, int32_t, SvIV)
DEFINE_PUSH_PRIMITIVE_EXECUTOR(uint32, uint32_t, SvUV)
DEFINE_PUSH_PRIMITIVE_EXECUTOR(sint64, int64_t, SvIV)
DEFINE_PUSH_PRIMITIVE_EXECUTOR(uint64, uint64_t, SvUV)
static void plan_step_push_float16(pTHX_ Affix * affix,
                                   Affix_Plan_Step * step,
                                   SV ** perl_stack_frame,
                                   void * args_buffer,
                                   void ** c_args,
                                   void * ret_buffer) {
    SV * sv = perl_stack_frame[step->data.index];
    infix_float16_t h = float_to_half((float)SvNV(sv));
    void * p = (char *)args_buffer + step->data.c_arg_offset;
    *(infix_float16_t *)p = h;
    c_args[step->data.index] = p;
}
DEFINE_PUSH_PRIMITIVE_EXECUTOR(float, float, SvNV)
DEFINE_PUSH_PRIMITIVE_EXECUTOR(double, double, SvNV)
DEFINE_PUSH_PRIMITIVE_EXECUTOR(long_double, long double, SvNV)

#if !defined(INFIX_COMPILER_MSVC)
static void plan_step_push_sint128(pTHX_ Affix * affix,
                                   Affix_Plan_Step * step,
                                   SV ** perl_stack_frame,
                                   void * args_buffer,
                                   void ** c_args,
                                   void * ret_buffer) {
    SV * sv = perl_stack_frame[step->data.index];
    void * ptr = (char *)args_buffer + step->data.c_arg_offset;
    sv_to_int128_safe(sv, ptr);
    c_args[step->data.index] = ptr;
}
static void plan_step_push_uint128(pTHX_ Affix * affix,
                                   Affix_Plan_Step * step,
                                   SV ** perl_stack_frame,
                                   void * args_buffer,
                                   void ** c_args,
                                   void * ret_buffer) {
    SV * sv = perl_stack_frame[step->data.index];
    void * ptr = (char *)args_buffer + step->data.c_arg_offset;
    sv_to_uint128_safe(sv, ptr);
    c_args[step->data.index] = ptr;
}
#endif

static MGVTBL Affix_pin_vtbl = {
    Affix_get_pin, Affix_set_pin, Affix_len_pin, nullptr, Affix_free_pin, nullptr, Affix_pin_dup, nullptr};

static const Affix_Step_Executor primitive_executors[] = {

lib/Affix.c  view on Meta::CPAN

    [INFIX_PRIMITIVE_FLOAT] = push_handler_float,
    [INFIX_PRIMITIVE_DOUBLE] = push_handler_double,
    [INFIX_PRIMITIVE_LONG_DOUBLE] = push_handler_long_double,
#ifdef __SIZEOF_INT128__
    [INFIX_PRIMITIVE_SINT128] = push_handler_sint128,
    [INFIX_PRIMITIVE_UINT128] = push_handler_uint128,
#endif
};
static void plan_step_push_pointer(pTHX_ Affix * affix,
                                   Affix_Plan_Step * step,
                                   SV ** perl_stack_frame,
                                   void * args_buffer,
                                   void ** c_args,
                                   void * ret_buffer) {
    PERL_UNUSED_VAR(ret_buffer);
    const infix_type * type = step->data.type;
    SV * sv = perl_stack_frame[step->data.index];
    void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset;
    c_args[step->data.index] = c_arg_ptr;

    if (is_pin(aTHX_ sv)) {
        *(void **)c_arg_ptr = _get_pin_from_sv(aTHX_ sv)->pointer;
        return;
    }
    const infix_type * pointee_type = type->meta.pointer_info.pointee_type;
    if (pointee_type == nullptr)
        croak("Internal error in push_pointer: pointee_type is nullptr");

lib/Affix.c  view on Meta::CPAN

    char signature_buf[256];
    if (infix_type_print(signature_buf, sizeof(signature_buf), (infix_type *)type, INFIX_DIALECT_SIGNATURE) !=
        INFIX_SUCCESS) {
        strncpy(signature_buf, "[error printing type]", sizeof(signature_buf));
    }
    croak("Don't know how to handle this type of scalar as a pointer argument yet: %s", signature_buf);
}

static void plan_step_push_struct(pTHX_ Affix * affix,
                                  Affix_Plan_Step * step,
                                  SV ** perl_stack_frame,
                                  void * args_buffer,
                                  void ** c_args,
                                  void * ret_buffer) {
    PERL_UNUSED_VAR(ret_buffer);
    const infix_type * type = step->data.type;
    SV * sv = perl_stack_frame[step->data.index];
    void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset;
    c_args[step->data.index] = c_arg_ptr;
    push_struct(aTHX_ affix, type, sv, c_arg_ptr);
}

static void plan_step_push_union(pTHX_ Affix * affix,
                                 Affix_Plan_Step * step,
                                 SV ** perl_stack_frame,
                                 void * args_buffer,
                                 void ** c_args,
                                 void * ret_buffer) {
    PERL_UNUSED_VAR(ret_buffer);
    const infix_type * type = step->data.type;
    SV * sv = perl_stack_frame[step->data.index];
    void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset;
    c_args[step->data.index] = c_arg_ptr;
    push_union(aTHX_ affix, type, sv, c_arg_ptr);
}

static void plan_step_push_array(pTHX_ Affix * affix,
                                 Affix_Plan_Step * step,
                                 SV ** perl_stack_frame,
                                 void * args_buffer,
                                 void ** c_args,
                                 void * ret_buffer) {
    PERL_UNUSED_VAR(ret_buffer);
    const infix_type * type = step->data.type;
    SV * sv = perl_stack_frame[step->data.index];

    // args_buffer slot is sizeof(void*) because we substituted Pointer for Array in the JIT
    void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset;
    c_args[step->data.index] = c_arg_ptr;

    // Handle NULL/Undef
    if (!SvOK(sv)) {
        *(void **)c_arg_ptr = nullptr;
        return;
    }

lib/Affix.c  view on Meta::CPAN

            sv2ptr(aTHX_ affix, *elem_sv_ptr, elem_ptr, element_type);
        }
    }

    // Write the POINTER to the argument slot
    *(void **)c_arg_ptr = temp_array;
}

static void plan_step_push_enum(pTHX_ Affix * affix,
                                Affix_Plan_Step * step,
                                SV ** perl_stack_frame,
                                void * args_buffer,
                                void ** c_args,
                                void * ret_buffer) {
    PERL_UNUSED_VAR(ret_buffer);
    const infix_type * type = step->data.type;
    SV * sv = perl_stack_frame[step->data.index];
    void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset;
    c_args[step->data.index] = c_arg_ptr;
    sv2ptr(aTHX_ affix, sv, c_arg_ptr, type);
}

static void plan_step_push_complex(pTHX_ Affix * affix,
                                   Affix_Plan_Step * step,
                                   SV ** perl_stack_frame,
                                   void * args_buffer,
                                   void ** c_args,
                                   void * ret_buffer) {
    PERL_UNUSED_VAR(ret_buffer);
    const infix_type * type = step->data.type;
    SV * sv = perl_stack_frame[step->data.index];
    void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset;
    c_args[step->data.index] = c_arg_ptr;
    if (!SvROK(sv) || SvTYPE(SvRV(sv)) != SVt_PVAV)
        croak("Expected an ARRAY reference with two numbers for complex type marshalling");
    AV * av = (AV *)SvRV(sv);
    if (av_len(av) != 1)
        croak("Expected exactly two elements (real, imaginary) for complex type");
    const infix_type * base_type = type->meta.complex_info.base_type;
    size_t base_size = infix_type_get_size(base_type);
    SV ** real_sv_ptr = av_fetch(av, 0, 0);
    SV ** imag_sv_ptr = av_fetch(av, 1, 0);
    if (!real_sv_ptr || !imag_sv_ptr)
        croak("Failed to fetch real or imaginary part from array for complex type");
    sv2ptr(aTHX_ affix, *real_sv_ptr, c_arg_ptr, base_type);
    sv2ptr(aTHX_ affix, *imag_sv_ptr, (char *)c_arg_ptr + base_size, base_type);
}

static void plan_step_push_vector(pTHX_ Affix * affix,
                                  Affix_Plan_Step * step,
                                  SV ** perl_stack_frame,
                                  void * args_buffer,
                                  void ** c_args,
                                  void * ret_buffer) {
    PERL_UNUSED_VAR(ret_buffer);
    const infix_type * type = step->data.type;
    SV * sv = perl_stack_frame[step->data.index];
    void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset;
    c_args[step->data.index] = c_arg_ptr;

    // If it's a string, assume it's a packed buffer (e.g. pack 'f4')
    // and copy it directly. This is much faster than iterating an AV.
    if (SvPOK(sv)) {
        STRLEN len;
        const char * buf = SvPV(sv, len);
        size_t expected_size = infix_type_get_size(type);
        if (len >= expected_size) {

lib/Affix.c  view on Meta::CPAN

        SV ** element_sv_ptr = av_fetch(av, i, 0);
        if (element_sv_ptr) {
            void * element_ptr = (char *)c_arg_ptr + (i * element_size);
            sv2ptr(aTHX_ affix, *element_sv_ptr, element_ptr, element_type);
        }
    }
}

static void plan_step_push_sv(pTHX_ Affix * affix,
                              Affix_Plan_Step * step,
                              SV ** perl_stack_frame,
                              void * args_buffer,
                              void ** c_args,
                              void * ret_buffer) {
    PERL_UNUSED_VAR(affix);
    PERL_UNUSED_VAR(ret_buffer);
    SV * sv = perl_stack_frame[step->data.index];
    void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset;
    c_args[step->data.index] = c_arg_ptr;
    *(void **)c_arg_ptr = sv;
}

static void plan_step_push_callback(pTHX_ Affix * affix,
                                    Affix_Plan_Step * step,
                                    SV ** perl_stack_frame,
                                    void * args_buffer,
                                    void ** c_args,
                                    void * ret_buffer) {
    PERL_UNUSED_VAR(ret_buffer);
    const infix_type * type = step->data.type;
    SV * sv = perl_stack_frame[step->data.index];
    void * c_arg_ptr = (char *)args_buffer + step->data.c_arg_offset;
    c_args[step->data.index] = c_arg_ptr;
    push_reverse_trampoline(aTHX_ affix, type, sv, c_arg_ptr);
}

static void plan_step_call_c_function(pTHX_ Affix * affix,
                                      Affix_Plan_Step * step,
                                      SV ** perl_stack_frame,
                                      void * args_buffer,
                                      void ** c_args,
                                      void * ret_buffer) {
    PERL_UNUSED_VAR(step);
    PERL_UNUSED_VAR(perl_stack_frame);
    PERL_UNUSED_VAR(args_buffer);
    affix->cif(ret_buffer, c_args);
}

static void plan_step_pull_return_value(pTHX_ Affix * affix,
                                        Affix_Plan_Step * step,
                                        SV ** perl_stack_frame,
                                        void * args_buffer,
                                        void ** c_args,
                                        void * ret_buffer) {
    PERL_UNUSED_VAR(perl_stack_frame);
    PERL_UNUSED_VAR(args_buffer);
    PERL_UNUSED_VAR(c_args);
    step->data.pull_handler(aTHX_ affix, affix->return_sv, step->data.type, ret_buffer);
}

Affix_Step_Executor get_plan_step_executor(const infix_type * type) {
    switch (type->category) {
    case INFIX_TYPE_PRIMITIVE:
        return primitive_executors[type->meta.primitive_id];
    case INFIX_TYPE_POINTER:

lib/Affix.h  view on Meta::CPAN

// Forward-declare the primary structures.
typedef struct Affix Affix;
typedef struct Affix_Backend Affix_Backend;
typedef struct Affix_Plan_Step Affix_Plan_Step;
typedef struct OutParamInfo OutParamInfo;
/**
 * The single, homogeneous function pointer signature for all steps in the execution plan.
 * @param pTHX_ The Perl interpreter context.
 * @param affix The main Affix context object.
 * @param step A pointer to the current plan step, containing its pre-calculated data.
 * @param perl_stack_frame A pointer to the base of the Perl stack frame (&ST(0)).
 * @param c_args The array of pointers to be passed to the C function.
 * @param ret_buffer A pointer to the memory allocated for the C function's return value.
 */
typedef void (*Affix_Step_Executor)(pTHX_ Affix * affix,
                                    Affix_Plan_Step * step,
                                    SV ** perl_stack_frame,
                                    void * args_buffer,
                                    void ** c_args,
                                    void * ret_buffer);
/// Function pointer type for a "pull" operation: marshalling from C (void*) to Perl (SV).
typedef void (*Affix_Pull)(pTHX_ Affix * affix, SV *, const infix_type *, void *);
/// Function pointer type for a "push" operation: marshalling from Perl (SV) to C (void*).
typedef void (*Affix_Push_Handler)(pTHX_ Affix * affix, SV *, void *);
/**
 * Function pointer for a specialized out-parameter write-back handler.
 * By pre-resolving this function, we avoid conditional logic in the hot path.
 * @param pTHX_ The Perl interpreter context.
 * @param affix The main Affix context object.
 * @param info A pointer to the OutParamInfo struct for this parameter.
 * @param perl_sv The referenced SV* to be modified (e.g., the scalar backing `$$foo`).
 * @param c_arg_ptr The pointer from the c_args array (e.g., `T**` for a `T*` out-param).
 */
typedef void (*Affix_Out_Param_Writer)(pTHX_ Affix * affix, const OutParamInfo * info, SV * perl_sv, void * c_arg_ptr);
/// Stores the pre-calculated information needed to write back an "out" parameter.
struct OutParamInfo {
    size_t perl_stack_index;          // Index of the SV* in the perl_stack_frame
    const infix_type * pointee_type;  // The type of the data pointed to (e.g., 'int' for 'int*')
    Affix_Out_Param_Writer writer;    // Pre-resolved handler to perform the write-back.
};
/// The data payload for a single step in the execution plan.
typedef struct {
    const infix_type * type;  // Type info for this step (arg or ret).
    size_t index;             // Index into perl_stack_frame for args, or c_args for out-params.
    Affix_Pull pull_handler;  // Pre-resolved pull handler for the return step.
    size_t c_arg_offset;      // Pre-calculated offset into the C arguments buffer.
} Affix_Step_Data;

typedef enum {
    // argument marshalling opcodes
    OP_PUSH_BOOL,
    OP_PUSH_SINT8,
    OP_PUSH_UINT8,
    OP_PUSH_SINT16,

lib/Affix/Platform/MacOS.pm  view on Meta::CPAN

package Affix::Platform::MacOS v0.12.0 {
    use v5.40;
    use DynaLoader;
    use parent 'Affix::Platform::Unix';
    use parent 'Exporter';
    our @EXPORT_OK   = qw[find_library];
    our %EXPORT_TAGS = ( all => \@EXPORT_OK );

    sub find_library ($name) {
        return $name if -f $name;
        for my $file ( "lib$name.dylib", "$name.dylib", "$name.framework/$name" ) {
            my $path = DynaLoader::dl_findfile($file);
            return $path if $path;
        }
    }
};
1;

lib/Test2/Tools/Affix.pm  view on Meta::CPAN

                $content = parse_xml($content) if $content =~ /$re/;
                $content = dec_ent($content) unless ref $content;
                if ( $tag eq 'error' ) {

                    # use Data::Dump;
                    # ddx $content;
                    diag $content->{what} // $content->{xwhat}{text};
                    if ( ref $content->{auxwhat} eq 'ARRAY' ) {
                        for my $i ( 0 .. scalar @{ $content->{stack} } ) {
                            note $content->{auxwhat}[$i] if $content->{auxwhat}[$i];
                            note stacktrace $content->{stack}[$i]{frame};
                        }
                    }
                    else {
                        note $content->{auxwhat};
                        for my $i ( 0 .. scalar @{ $content->{stack} } ) {
                            note stacktrace $content->{stack}[$i]{frame};
                        }
                    }
                }
                $hash->{$tag}
                    = defined $content ?
                    (
                    defined $hash->{$tag} ?
                        ref $hash->{$tag} eq 'ARRAY' ?
                            [ @{ $hash->{$tag} }, $content ] :
                            [ $hash->{$tag}, $content ] :



( run in 1.331 second using v1.01-cache-2.11-cpan-df04353d9ac )