Affix
view release on metacpan or search on metacpan
infix/src/common/infix_internals.h view on Meta::CPAN
/**
* Copyright (c) 2025 Sanko Robinson
*
* This source code is dual-licensed under the Artistic License 2.0 or the MIT License.
* You may choose to use this code under the terms of either license.
*
* SPDX-License-Identifier: (Artistic-2.0 OR MIT)
*
* The documentation blocks within this file are licensed under the
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file infix_internals.h
* @brief Internal data structures, function prototypes, and constants.
* @ingroup internal_common
*
* @details This is the primary internal header for the `infix` library. It defines the
* complete layout of all opaque public structs (like `infix_forward_t`) and
* declares internal-only functions (`_infix_*`) that are shared between modules.
*
* Its most important role is to define the core ABI abstraction layer through v-tables
* (`infix_forward_abi_spec`, `infix_reverse_abi_spec`). These structures form the
* contract between the platform-agnostic JIT engine (`trampoline.c`) and the
* platform-specific ABI implementations (`arch/...`), making them key to the
* library's portability and architectural design.
*
* This header also brings together all other internal type definitions, creating a
* single source of truth for the library's internal data model.
* @internal
*/
#pragma once
#include "common/infix_config.h"
#include "common/platform.h"
#include <infix/infix.h>
/**
* @struct infix_executable_t
* @brief Internal representation of an executable memory block for JIT code.
*
* @details This struct encapsulates the platform-specific details of allocating and
* managing executable memory in a way that is compliant with modern OS security
* features like W^X (Write XOR Execute). It supports two primary strategies:
*
* 1. **Single-Mapping W^X (Windows/macOS/Android):** A single memory region is
* allocated as Read-Write (`rw_ptr`). After the JIT compiler writes the
* machine code to this region, its permissions are changed to Read-Execute.
* In this model, `rx_ptr` and `rw_ptr` point to the same address.
*
* 2. **Dual-Mapping W^X (Linux/BSD):** A single underlying shared memory object
* is mapped into the process's address space twice: once as Read-Write
* (`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
* memory corruption. The context is allocated in a standard read-write memory
* region, fully populated, and then its permissions are changed to read-only
* using this handle.
*/
typedef struct {
void * rw_ptr; /**< The read-write pointer before being made read-only. */
size_t size; /**< The size of the allocated memory region in bytes. */
} infix_protected_t;
/**
* @struct infix_forward_t
* @brief Internal definition of a forward trampoline handle.
* @details This is the concrete implementation of the opaque `infix_forward_t` pointer
* returned to the user. It is a self-contained object that owns all memory and
* metadata required for its operation. The type information (`return_type`,
* `arg_types`) is a deep copy stored in the trampoline's private `arena`,
* ensuring its lifetime is independent of the types used to create it.
*/
struct infix_forward_t {
infix_arena_t * arena; /**< Private or shared arena holding all type metadata for this trampoline. */
bool is_external_arena; /**< True if the arena is user-provided and should not be freed by `infix_forward_destroy`.
*/
infix_executable_t exec; /**< The executable memory containing the JIT-compiled code. */
infix_type * return_type; /**< A deep copy of the function's return type. */
infix_type ** arg_types; /**< A deep copy of the function's argument types. */
size_t num_args; /**< The total number of arguments. */
size_t num_fixed_args; /**< The number of non-variadic arguments. */
void * target_fn; /**< The target C function pointer (for bound trampolines), or `nullptr` for unbound. */
bool is_direct_trampoline; /**< If true, this is a high-performance direct marshalling trampoline. */
bool is_safe; /**< If true, the trampoline wraps the call in an exception handler. */
size_t ref_count; /**< Reference count for deduplication and shared ownership. */
infix/src/common/infix_internals.h view on Meta::CPAN
const char * start; /**< The beginning of the signature string, used for calculating error positions. */
infix_arena_t * arena; /**< The temporary arena for allocating the raw, unresolved type graph. */
int depth; /**< The current recursion depth, to prevent stack overflows. */
} parser_state;
/**
* @struct code_buffer
* @brief A dynamic buffer for staged machine code generation.
*/
typedef struct {
uint8_t * code; /**< A pointer to the code buffer, allocated from the arena. */
size_t capacity; /**< The current capacity of the buffer. */
size_t size; /**< The number of bytes currently written to the buffer. */
bool error; /**< A flag set on allocation failure. */
infix_arena_t * arena; /**< The temporary arena used for code generation. */
} code_buffer;
/**
* @struct infix_library_t
* @brief Internal definition of a dynamic library handle.
* @details This is a simple wrapper around the platform's native library handle to provide a consistent API.
*
* On Windows, GetModuleHandle(NULL) returns a special handle to the main executable that must NOT be freed with
* FreeLibrary. This flag tracks that state to ensure infix_library_close behaves correctly.
*/
struct infix_library_t {
void * handle; /**< The platform-native library handle (`HMODULE` on Windows, `void*` on POSIX). */
#if defined(INFIX_OS_WINDOWS)
bool is_pseudo_handle; /**< True if the handle is a "pseudo-handle" from GetModuleHandle. */
#endif
};
// ABI Abstraction Layer
/**
* @def INFIX_MAX_STACK_ALLOC
* @brief A safety limit (4MB) for the total stack space a trampoline can allocate.
* This prevents stack exhaustion from malformed or malicious type layouts.
*/
#define INFIX_MAX_STACK_ALLOC (1024 * 1024 * 4)
/**
* @def INFIX_MAX_ARG_SIZE
* @brief A safety limit (64KB) for the size of a single argument.
*/
#define INFIX_MAX_ARG_SIZE (1024 * 64)
/**
* @enum infix_arg_location_type
* @brief Describes the physical location where a function argument is passed according to the ABI.
*
* This enumeration abstracts away the differences in how various ABIs use
* registers and the stack to pass data. It is the primary output of the ABI
* classification process.
*/
typedef enum {
/** @brief Argument is passed in a general-purpose integer register (e.g., `RCX`, `RDI`, `X0`). */
ARG_LOCATION_GPR,
#if defined(INFIX_ABI_AAPCS64)
/** @brief (AArch64) Argument is passed in a vector/floating-point register (e.g., `V0`). */
ARG_LOCATION_VPR,
/** @brief (AArch64) A struct <= 16 bytes passed in a pair of GPRs (e.g., `X0`, `X1`). */
ARG_LOCATION_GPR_PAIR,
/** @brief (AArch64) A large struct (> 16 bytes) passed by reference; the pointer is in a GPR. */
ARG_LOCATION_GPR_REFERENCE,
/** @brief (AArch64) A Homogeneous Floating-point Aggregate passed in consecutive VPRs. */
ARG_LOCATION_VPR_HFA,
#else // x64 ABIs
/** @brief (x64) Argument is passed in an SSE/XMM register (e.g., `XMM0`). */
ARG_LOCATION_XMM,
/** @brief (SysV x64) A struct passed in two GPRs (e.g., `RDI`, `RSI`). */
ARG_LOCATION_GPR_PAIR,
/** @brief (SysV x64) A struct passed in two SSE registers (e.g., `XMM0`, `XMM1`). */
ARG_LOCATION_SSE_SSE_PAIR,
/** @brief (SysV x64) A struct split between a GPR and an SSE register. */
ARG_LOCATION_INTEGER_SSE_PAIR,
/** @brief (SysV x64) A struct split between an SSE and a GPR register. */
ARG_LOCATION_SSE_INTEGER_PAIR,
#endif
/** @brief Argument is passed on the stack. */
ARG_LOCATION_STACK
} infix_arg_location_type;
/**
* @struct infix_arg_location
* @brief Detailed location information for a single function argument.
* @details This struct is the result of the ABI classification process for one
* argument. It provides all the information the code emitters need to generate
* 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 {
infix/src/common/infix_internals.h view on Meta::CPAN
* @param[in] value The 32-bit integer to append.
*/
INFIX_INTERNAL void emit_int32(code_buffer * buf, int32_t value);
/**
* @brief A convenience wrapper to append a 64-bit integer (little-endian) to a code buffer.
* @param[in,out] buf The code buffer.
* @param[in] value The 64-bit integer to append.
*/
INFIX_INTERNAL void emit_int64(code_buffer * buf, int64_t value);
/**
* @brief Allocates a block of executable memory using the platform's W^X strategy.
* @details Located in `src/jit/executor.c`. This is a platform-specific function
* that abstracts `VirtualAlloc`, `mmap` with `MAP_JIT`, or `shm_open` with dual-mapping.
* @param size The number of bytes to allocate.
* @return An `infix_executable_t` handle containing pointers to the allocated memory.
*/
INFIX_INTERNAL c23_nodiscard infix_executable_t infix_executable_alloc(size_t size);
/**
* @brief Frees a block of executable memory and applies guard pages to prevent use-after-free.
* @details Located in `src/jit/executor.c`. Before freeing, it attempts to change
* the memory's protection to be inaccessible, causing an immediate crash on a UAF.
* @param exec The handle to the memory block to free.
*/
INFIX_INTERNAL void infix_executable_free(infix_executable_t exec);
typedef enum {
INFIX_EXECUTABLE_FORWARD,
INFIX_EXECUTABLE_SAFE_FORWARD,
INFIX_EXECUTABLE_REVERSE,
INFIX_EXECUTABLE_DIRECT
} infix_executable_category_t;
/**
* @brief Makes a block of JIT memory executable, completing the W^X process.
* @details Located in `src/jit/executor.c`. For single-map platforms, this calls
* `VirtualProtect` or `mprotect`. For dual-map platforms, this is a no-op. It
* also handles instruction cache flushing on relevant architectures like AArch64.
* @param exec The handle to the memory block to make executable.
* @param prologue_size The size of the generated prologue in bytes.
* @param category The type of trampoline being finalized.
* @return `true` on success, `false` on failure.
*/
INFIX_INTERNAL c23_nodiscard bool infix_executable_make_executable(infix_executable_t * exec,
infix_executable_category_t category,
uint32_t prologue_size,
uint32_t epilogue_offset);
/**
* @brief Allocates a block of standard memory for later protection.
* @details Located in `src/jit/executor.c`. This is used to allocate the memory
* for an `infix_reverse_t` context before it is made read-only.
* @param size The number of bytes to allocate.
* @return An `infix_protected_t` handle.
*/
INFIX_INTERNAL c23_nodiscard infix_protected_t infix_protected_alloc(size_t size);
/**
* @brief Frees a block of protected memory.
* @details Located in `src/jit/executor.c`.
* @param prot The memory block to free.
*/
INFIX_INTERNAL void infix_protected_free(infix_protected_t prot);
/**
* @brief Makes a block of memory read-only for security hardening.
* @details Located in `src/jit/executor.c`. This is called on the `infix_reverse_t`
* context after it has been fully initialized.
* @param prot The memory block to make read-only.
* @return `true` on success, `false` on failure.
*/
INFIX_INTERNAL c23_nodiscard bool infix_protected_make_readonly(infix_protected_t prot);
/**
* @brief The universal C entry point for all reverse call trampolines.
* @details Located in `src/jit/executor.c`, this function is called by the JIT-compiled
* stub. It receives the marshalled arguments and dispatches the call to either
* the type-safe callback (via a cached forward trampoline) or the generic closure handler.
* @param[in] context The `infix_reverse_t` context for this call.
* @param[out] return_value_ptr A pointer to the stack buffer for the return value.
* @param[in] args_array A pointer to the `void**` array of argument pointers.
*/
INFIX_INTERNAL void infix_internal_dispatch_callback_fn_impl(infix_reverse_t * context,
void * return_value_ptr,
void ** args_array);
// Utility Macros & Inlines
/** @brief Appends a sequence of bytes (e.g., an instruction opcode) to a code buffer. */
#define EMIT_BYTES(buf, ...) \
do { \
const uint8_t bytes[] = {__VA_ARGS__}; \
code_buffer_append((buf), bytes, sizeof(bytes)); \
} while (0)
/**
* @brief Aligns a value up to the next multiple of a power-of-two alignment.
* @param value The value to align.
* @param alignment The alignment boundary (must be a power of two).
* @return The aligned value.
*/
static inline size_t _infix_align_up(size_t value, size_t alignment) {
return (value + alignment - 1) & ~(alignment - 1);
}
/**
* @brief A fast inline check to determine if an `infix_type` is a half-precision float (`float16`).
* @param type The type to check.
* @return `true` if the type is a float16 primitive.
*/
static inline bool is_float16(const infix_type * type) {
return type->category == INFIX_TYPE_PRIMITIVE && type->meta.primitive_id == INFIX_PRIMITIVE_FLOAT16;
}
/**
* @brief A fast inline check to determine if an `infix_type` is a `float` (32-bit).
* @param type The type to check.
* @return `true` if the type is a float primitive.
*/
static inline bool is_float(const infix_type * type) {
return type->category == INFIX_TYPE_PRIMITIVE && type->meta.primitive_id == INFIX_PRIMITIVE_FLOAT;
}
/**
* @brief A fast inline check to determine if an `infix_type` is a `double`.
* @param type The type to check.
* @return `true` if the type is a double primitive.
*/
static inline bool is_double(const infix_type * type) {
return type->category == INFIX_TYPE_PRIMITIVE && type->meta.primitive_id == INFIX_PRIMITIVE_DOUBLE;
}
/**
( run in 0.846 second using v1.01-cache-2.11-cpan-39bf76dae61 )