view release on metacpan or search on metacpan
- 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.
- [infix] Explicitly enabled 16-byte stack alignment in Windows x64 trampolines to ensure SIMD compatibility.
- [infix] Updated `infix_type_create_vector` to use the vector's full size for its natural alignment (e.g., 32-byte alignment for `__m256`).
- Process exit: Disabled explicit library unloading (`dlclose`/`FreeLibrary`) during global destruction. This prevents segmentation faults when background threads from loaded libraries try to execute code that has been unmapped from memory during s...
I tried to just limit it to Go lang libs but it's just more trouble than it's worth until I resolve a few more things.
- \[infix] Fixed stack corruption on macOS ARM64 (Apple Silicon). `long double` on this platform is 8 bytes (an alias for `double`), unlike standard AAPCS64 where it is 16 bytes. The JIT previously emitted 16-byte stores (`STR Qn`) for these types,...
- \[infix] Fixed `long double` handling on macOS Intel (Darwin). Verified that Apple adheres to the System V ABI for this type: it requires 16-byte stack alignment and returns values on the x87 FPU stack (`ST(0)`).
- \[infix] Fixed a generic System V ABI bug where 128-bit types (vectors, `__int128`) were not correctly aligned to 16 bytes on the stack relative to the return address, causing data corruption when mixed with odd numbers of 8-byte arguments.
- \[infix] Enforced natural alignment for stack arguments in the AAPCS64 implementation. Previously, arguments were packed to 8-byte boundaries, which violated alignment requirements for 128-bit types.
- \[infix] Fixed a critical deployment issue where the public `infix.h` header included an internal file (`common/compat_c23.h`). The header is now fully self-contained and defines `INFIX_NODISCARD` for attribute compatibility.
- \[infix] Fixed 128-bit vector truncation on System V x64 (Linux/macOS). Reverse trampolines previously used 64-bit moves (`MOVSD`) for all SSE arguments, corrupting the upper half of vector arguments. They now correctly use `MOVUPS`.
- \[infix] Fixed vector argument corruption on AArch64. The reverse trampoline generator now correctly identifies vector types and uses 128-bit stores (`STR Qn`) instead of falling back to 64-bit/32-bit stores or GPRs.
- \[infix] Fixed floating-point corruption on Windows on ARM64. Reverse trampolines now force full 128-bit register saves for all floating-point arguments to ensure robust handling of volatile register states.
- \[infix] Fixed a logic error in the System V reverse argument classifier where vectors were defaulting to `INTEGER` class, causing the trampoline to look in `RDI`/`RSI` instead of `XMM` registers.
- \[infix] Fixed potential cache coherency issues on Windows x64. The library now unconditionally calls `FlushInstructionCache` after JIT compilation.
- \[infix] Capped the maximum alignment in `infix_type_create_packed_struct` to 1MB to prevent integer wrap-around bugs in layout calculation.
- \[infix] Fixed a buffer overread on macOS ARM64 where small signed integers were loaded using 32-bit `LDRSW`. Implemented `LDRSH` and `LDRSB`.
- \[infix] Added native support for Apple's Hardened Runtime security policy.
- The JIT engine now utilizes `MAP_JIT` when the `com.apple.security.cs.allow-jit` entitlement is detected.
- Implemented thread-local permission toggling via `pthread_jit_write_protect_np` to maintain W^X compliance.
## [v1.0.5] - 2026-01-11
### Changed
- Affix::Build: A polyglot shared library builder. Currently supports Ada, Assembly, C, C#, C++, Cobol, Crystal, Dlang, Eiffel, F#, Fortran, Futhark, Go, Haskell, Nim, OCaml, Odin, Pascal, Rust, Swift, Vlang, and Zig.
- Affix::Wrap: An experimental tool to introspect C header files and generate Affix bindings and documentation.
- Dual-Driver Architecture:
- `Affix::Wrap::Driver::Clang`: Uses the system `clang` executable to parse the AST for high-fidelity extraction of types, macros, and comments.
- `Affix::Wrap::Driver::Regex`: A zero-dependency fallback driver that parses headers using heuristics.
### Changed
- `Array[Char]` function arguments now accept Perl strings directly, copying the string data into the temporary C array.
- `Affix::errno()` now returns a dualvar containing both the numeric error code (`errno`/`GetLastError`) and the system error string (`strerror`/`FormatMessage`).
### Fixed
- Correctly implemented array decay for function arguments on ARM and Win64. `Array[...]` types are now marshalled into temporary C arrays and passed as pointers, matching standard C behavior. Previously, they were incorrectly passed by value, caus...
- Fixed binary safety for `Array[Char/UChar]`. Reading these arrays now respects the explicit length rather than stopping at the first null byte.
- The write-back mechanism no longer attempts to overwrite the read-only ArrayRef scalar with the pointer address.
- `Pointer[SV]` is now handled properly as args, return values, and in callbacks. Reference counting is automatic to prevent premature garbage collection of passed scalars.
- Shared libs written in Go spin up background threads (for GC and scheduling) that do not shut down cleanly when a shared library is unloaded. This often causes access violations on Windows during program exit. We attempt to work around this by de...
## [v1.0.2] - 2025-12-14
### Changed
- In an attempt to debug mystery failures in SDL3.pm, Affix.pm will warn and return `undef` instead of `croak`ing.
- Improved error reporting: if the internal error message is empty, the numeric error code is now included in the warning.
### Fixed
- [[infix]] Fixed a critical file descriptor leak on POSIX platforms (Linux/FreeBSD) where the file descriptor returned by `shm_open` was kept open for the lifetime of the trampoline, eventually hitting the process file descriptor limit (EMFILE). T...
- Fixed memory leaks that occurred when trampoline creation failed midway (cleaning up partial arenas, strings, and backend structures).
## [v1.0.1] - 2025-12-13
### Changed
infix/src/arch/x64/abi_x64_emitters.c
infix/src/arch/x64/abi_x64_emitters.h
infix/src/common/compat_c23.h
infix/src/common/double_tap.h
infix/src/common/infix_config.h
infix/src/common/infix_internals.h
infix/src/common/platform.h
infix/src/common/utility.h
infix/src/core/arena.c
infix/src/core/cache.c
infix/src/core/error.c
infix/src/core/loader.c
infix/src/core/platform.c
infix/src/core/signature.c
infix/src/core/type_registry.c
infix/src/core/types.c
infix/src/core/utility.c
infix/src/infix.c
infix/src/jit/executor.c
infix/src/jit/trampoline.c
lib/Affix.c
t/025_affix_wrap.t
t/026_context.t
t/027_thread_safety.t
t/028_pointer_indexing.t
t/029_union_pins.t
t/030_live_struct.t
t/031_live_array.t
t/032_recursive_liveness.t
t/033_unified_access.t
t/034_live_classifier.t
t/040_error.t
t/050_affix_build.t
t/900_leak.t
t/src/std.h
macOS), using these helpers guarantees you get the correct library.
```perl
# Bind 'puts' from the standard C library
affix libc(), 'puts', [String] => Int;
# Bind 'cos' from the math library
affix libm(), 'cos', [Double] => Double;
```
### `get_last_error_message()`
If `load_library`, `find_symbol`, or a signature parsing step fails, this function returns a string describing the
most recent internal or operating system error (via `dlerror` or `FormatMessage`).
```perl
my $lib = load_library('does_not_exist');
if (!$lib) {
die "Failed to load library: " . get_last_error_message();
}
```
# INTROSPECTION
When working with C APIs, you often need to know exactly how much memory a structure consumes or where a specific field
is located within a block of memory. Affix provides compiler-grade type introspection.
### `sizeof( $type )`
# ERROR HANDLING & DEBUGGING
Bridging two entirely different runtimes can lead to spectacular crashes if types or memory boundaries are mismatched.
Affix provides built-in tools to help you identify what went wrong.
## Error Handling
### `errno()`
Accesses the system error code from the most recent FFI or standard library call (reads `errno` on Unix and
`GetLastError` on Windows).
This function returns a **dualvar**. It behaves as an integer in numeric context, and magically resolves to the
human-readable system error message (via `strerror` or `FormatMessage`) in string context.
```perl
# Suppose a C file-open function fails
my $fd = c_open("/does/not/exist");
if (!$fd) {
my $err = errno();
# String context
say "Failed to open: $err"; # "No such file or directory"
# Numeric context
if (int($err) == 2) {
say "Code 2 specifically triggered.";
}
}
```
**Note:** You must call `errno()` immediately after the C function invokes, as subsequent Perl operations (like
printing to STDOUT) might overwrite the system's error register.
## Memory Inspection
### `dump( $pin, $length_in_bytes )`
Prints a formatted hex dump of the memory pointed to by a Pin directly to `STDOUT`. This is an invaluable tool for
verifying that C structs or buffers contain the data you expect.
```perl
my $ptr = strdup("Affix Debugging");
builder/Affix/Builder.pm view on Meta::CPAN
method step_test() {
$self->step_build() unless -d 'blib';
require TAP::Harness::Env;
my %test_args = (
( verbosity => $verbose ),
( jobs => $jobs ),
( color => -t STDOUT ),
lib => [ map { rel2abs( catdir( 'blib', $_ ) ) } qw[arch lib] ],
);
TAP::Harness::Env->create( \%test_args )->runtests( sort map { $_->stringify } find( qr/\.t$/, 't' ) )->has_errors;
}
method get_arguments (@sources) {
$_ = detildefy($_) for grep {defined} $install_base, $destdir, $prefix, values %{$install_paths};
$install_paths = ExtUtils::InstallPaths->new( dist_name => $meta->name );
return;
}
method Build(@args) {
my $method = $self->can( 'step_' . $action );
builder/Affix/Builder.pm view on Meta::CPAN
my $cwd = cwd->absolute;
my @objs;
require ExtUtils::CBuilder;
my $builder = ExtUtils::CBuilder->new( quiet => !$verbose, config => {} );
my $pre = $cwd->child(qw[blib arch auto])->absolute;
require DynaLoader;
my $mod2fname = defined &DynaLoader::mod2fname ? \&DynaLoader::mod2fname : sub { return $_[0][-1] };
my @parts = ('Affix');
my $archdir = rel2abs catdir( curdir, qw[. blib arch auto], @parts );
my $err;
make_path( $archdir, { chmod => 0755, error => \$err, verbose => $verbose } );
my $lib_file = catfile( $archdir, $mod2fname->( \@parts ) . '.' . $Config{dlext} );
my @dirs;
push @dirs, '../';
my $has_cxx = !1;
my @sources = $cwd->child('lib/Affix.c');
#~ warn "Sources to process: @sources\n";
for my $source (@sources) {
#~ warn "Processing source: $source\n";
infix/include/infix/infix.h view on Meta::CPAN
#if defined(__has_c_attribute)
#define _INFIX_HAS_C_ATTRIBUTE(x) __has_c_attribute(x)
#else
#define _INFIX_HAS_C_ATTRIBUTE(x) 0
#endif
/**
* @def INFIX_API
* @brief Symbol visibility macro
*
* @details infix relies on a unity build so we've been lax about symbol visibility. Functions like `_infix_set_error`
* or `_infix_type_recalculate_layout` are shared between internal modules (files included by `infix.c`) and thus cannot
* be static. However, this means that if infix.c is compiled into a shared library (`libinfix.so`), all of these
* internal _infix_* functions are exported in the dynamic symbol table. This pollutes the ABI and allows users to link
* against internal functions that might change.
*/
#if defined(_WIN32) || defined(__CYGWIN__)
#if defined(INFIX_BUILDING_DLL)
#define INFIX_API __declspec(dllexport)
#elif defined(INFIX_USING_DLL)
#define INFIX_API __declspec(dllimport)
infix/include/infix/infix.h view on Meta::CPAN
#else
#define INFIX_API
#endif
/**
* @def INFIX_NODISCARD
* @brief A compatibility macro for the C23 `[[nodiscard]]` attribute.
*
* @details This attribute is used to issue a compiler warning if the return value
* of a function is ignored by the caller. This is extremely useful for catching
* bugs where an error code or an important result is not checked.
*
* This macro expands to:
* - `[[nodiscard]]` on compilers that support the C23 standard syntax.
* - `__attribute__((warn_unused_result))` on GCC and Clang.
* - `_Check_return_` on Microsoft Visual C++.
* - Nothing on other compilers.
*
* This is aliased as `c23_nodiscard` in `compat_c23.h`.
*/
#if _INFIX_HAS_C_ATTRIBUTE(nodiscard) && !defined(__GNUC__) && !defined(__clang__)
infix/include/infix/infix.h view on Meta::CPAN
* `infix_reverse_get_user_data`.
* @param return_value_ptr A pointer to a buffer where the handler must write the function's return value.
* @param args_array An array of pointers to the argument values passed by the native C caller.
*/
typedef void (*infix_closure_handler_fn)(infix_context_t *, void *, void **);
/**
* @brief Enumerates the possible status codes returned by `infix` API functions.
*/
typedef enum {
INFIX_SUCCESS = 0, /**< The operation completed successfully. */
INFIX_ERROR_ALLOCATION_FAILED, /**< A memory allocation failed. Check `infix_get_last_error` for details. */
INFIX_ERROR_INVALID_ARGUMENT, /**< An invalid argument was provided. Check `infix_get_last_error`. */
INFIX_ERROR_UNSUPPORTED_ABI, /**< The current platform's ABI is not supported. */
INFIX_ERROR_LAYOUT_FAILED, /**< Failed to calculate a valid memory layout for a type. */
INFIX_ERROR_PROTECTION_FAILED, /**< Failed to set memory protection flags (e.g., for W^X). */
INFIX_ERROR_ /**< Placeholder to ensure enum is sized correctly. */
} infix_status;
/**
* @defgroup registry_api Named Type Registry
* @brief APIs for defining, storing, and reusing complex types by name.
* @ingroup high_level_api
* @{
infix/include/infix/infix.h view on Meta::CPAN
INFIX_API void infix_registry_destroy(infix_registry_t *);
/**
* @brief Parses a string of type definitions and adds them to a registry.
*
* This is the primary way to populate a registry. Definitions are separated by
* semicolons. The parser supports forward declarations (`@Name;`) and out-of-order
* definitions, making it easy to define mutually recursive types.
*
* @param[in] registry The registry to populate.
* @param[in] definitions A semicolon-separated string of definitions.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
* @code
* const char* my_types =
* "@Point = { x: double, y: double };" // Define a struct
* "@Node;" // Forward-declare Node
* "@List = { head: *@Node };" // Use the forward declaration
* "@Node = { value: int, next: *@Node };" // Define the recursive struct
* ;
* infix_register_types(registry, my_types);
* @endcode
*/
infix/include/infix/infix.h view on Meta::CPAN
* @code
* // C function to call
* int add(int a, int b) { return a + b; }
*
* infix_forward_t* trampoline = NULL;
* const char* signature = "(int, int) -> int";
*
* // Create a trampoline bound to the `add` function.
* infix_status status = infix_forward_create(&trampoline, signature, (void*)add, NULL);
* if (status != INFIX_SUCCESS) {
* // Handle error...
* }
*
* // Get the callable JIT-compiled function pointer.
* infix_cif_func cif = infix_forward_get_code(trampoline);
*
* // Prepare arguments and return buffer.
* int a = 10, b = 32;
* void* args[] = { &a, &b };
* int result;
*
infix/include/infix/infix.h view on Meta::CPAN
*/
INFIX_API INFIX_NODISCARD infix_status infix_forward_create(infix_forward_t **,
const char *,
void *,
infix_registry_t *);
/**
* @brief Creates a "safe" bound forward trampoline that catches native exceptions.
* @details This is identical to `infix_forward_create`, but the generated trampoline
* is wrapped in a platform-specific exception handler (e.g., SEH on Windows).
* If the target function throws an exception, the trampoline will catch it
* and set the thread-local error to `INFIX_CODE_NATIVE_EXCEPTION`.
*
* @param[out] out_trampoline Receives the created handle.
* @param[in] signature The function signature.
* @param[in] target_function The address of the C function.
* @param[in] registry An optional type registry.
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API INFIX_NODISCARD infix_status infix_forward_create_safe(infix_forward_t **,
const char *,
void *,
infix/include/infix/infix.h view on Meta::CPAN
* @brief Reads the value of a global variable from a library into a buffer.
*
* Uses the signature parser to determine the size of the variable to ensure the
* correct number of bytes are copied.
*
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the global variable.
* @param[in] type_signature The `infix` signature string describing the variable's type.
* @param[out] buffer A pointer to the destination buffer to receive the data.
* @param[in] registry An optional registry for resolving named types in the signature.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
INFIX_API INFIX_NODISCARD infix_status
infix_read_global(infix_library_t *, const char *, const char *, void *, infix_registry_t *);
/**
* @brief Writes data from a buffer into a global variable in a library.
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the global variable.
* @param[in] type_signature The `infix` signature string describing the variable's type.
* @param[in] buffer A pointer to the source buffer containing the data to write.
* @param[in] registry An optional registry for resolving named types in the signature.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
INFIX_API INFIX_NODISCARD infix_status
infix_write_global(infix_library_t *, const char *, const char *, void *, infix_registry_t *);
/** @} */ // end of exports_api group
/**
* @defgroup manual_api Manual API
* @brief A lower-level, programmatic API for creating trampolines from `infix_type` objects.
*
* This API is intended for performance-critical applications or language bindings that
* need to construct type information dynamically without the overhead of string parsing.
infix/include/infix/infix.h view on Meta::CPAN
/**
* @brief Gets the type of a specific argument from a function type.
* @param[in] func_type The function type object to inspect.
* @param[in] index The zero-based index of the argument.
* @return A pointer to the `infix_type`, or `nullptr` if the index is out of bounds.
*/
INFIX_API INFIX_NODISCARD const infix_type * infix_type_get_arg_type(const infix_type *, size_t);
/** @} */ // end addtogroup type_system
/** @} */ // end of introspection_api group
/**
* @defgroup error_api Error Handling API
* @brief Functions and types for detailed, thread-safe error reporting.
* @{
*/
/**
* @brief Enumerates the high-level categories of errors that can occur.
*/
typedef enum {
INFIX_CATEGORY_NONE, /**< No error. */
INFIX_CATEGORY_GENERAL, /**< A general or miscellaneous error. */
INFIX_CATEGORY_ALLOCATION, /**< A memory allocation error. */
INFIX_CATEGORY_PARSER, /**< A syntax error in a signature string. */
INFIX_CATEGORY_ABI /**< An error related to ABI classification or layout. */
} infix_error_category_t;
/**
* @brief Enumerates specific error codes.
*/
typedef enum {
// General Codes (0-99)
INFIX_CODE_SUCCESS = 0, /**< No error occurred. */
INFIX_CODE_UNKNOWN, /**< An unspecified error occurred. */
INFIX_CODE_NULL_POINTER, /**< A required pointer argument was NULL. */
INFIX_CODE_MISSING_REGISTRY, /**< A type registry was required but not provided. */
INFIX_CODE_NATIVE_EXCEPTION, /**< A native exception (C++/SEH) was thrown during execution. */
// Allocation Codes (100-199)
INFIX_CODE_OUT_OF_MEMORY = 100, /**< A call to `malloc`, `calloc`, etc. failed. */
INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, /**< Failed to allocate executable memory from the OS. */
INFIX_CODE_PROTECTION_FAILURE, /**< Failed to change memory protection flags (e.g., `mprotect`). */
INFIX_CODE_INVALID_ALIGNMENT, /**< An invalid alignment (0 or not power-of-two) was requested. */
infix/include/infix/infix.h view on Meta::CPAN
INFIX_CODE_UNSUPPORTED_ABI = 300, /**< The current platform's ABI is not supported by `infix`. */
INFIX_CODE_TYPE_TOO_LARGE, /**< A data type exceeded the ABI's size limits. */
INFIX_CODE_UNRESOLVED_NAMED_TYPE, /**< A named type (`@Name`) was not found in the provided registry. */
INFIX_CODE_INVALID_MEMBER_TYPE, /**< An aggregate contained an illegal member type (e.g., `void`). */
INFIX_CODE_LAYOUT_FAILED, /**< The ABI layer failed to calculate a valid memory layout for a type. */
// Library Loading Codes (400-499)
INFIX_CODE_LIBRARY_NOT_FOUND = 400, /**< The requested dynamic library could not be found. */
INFIX_CODE_SYMBOL_NOT_FOUND, /**< The requested symbol was not found in the library. */
INFIX_CODE_LIBRARY_LOAD_FAILED /**< The dynamic library failed to load for other reasons. */
} infix_error_code_t;
/**
* @struct infix_error_details_t
* @brief Provides detailed, thread-local information about the last error that occurred.
*/
typedef struct {
infix_error_category_t category; /**< The general category of the error. */
infix_error_code_t code; /**< The specific error code. */
size_t position; /**< For parser errors, the byte offset into the signature string. */
long system_error_code; /**< The OS-specific error code (e.g., from `GetLastError()` or `errno`). */
char message[256]; /**< A human-readable description of the error. For parser errors, this includes a code snippet.
*/
} infix_error_details_t;
/**
* @brief Retrieves detailed information about the last error that occurred on the current thread.
* @return A copy of the last error details structure. This function is thread-safe.
*/
INFIX_API infix_error_details_t infix_get_last_error(void);
/** @} */ // end of error_api group
/**
* @defgroup direct_marshalling_api Direct Marshalling API
* @brief An advanced, high-performance API for language bindings.
* @ingroup high_level_api
*
* This API provides a way to create highly optimized forward trampolines that
* bypass the standard `void**` argument array. Instead, the JIT-compiled code
* directly calls user-provided "marshaller" functions to convert language-specific
* objects into native C arguments just-in-time. This reduces memory indirection
* and copying, yielding significant performance gains for FFI calls in tight loops.
infix/include/infix/infix.h view on Meta::CPAN
* highest performance for forward calls.
*
* @param[out] out_trampoline Receives the created trampoline handle upon success.
* @param[in] signature The C signature of the target function (e.g., `"(int, *char)->void"`).
* @param[in] target_function The address of the C function to be called.
* @param[in] handlers An array of `infix_direct_arg_handler_t` structs, one for each
* argument of the C function. The array must have exactly as many
* elements as the function has arguments.
* @param[in] registry An optional type registry for resolving named types (`@Name`)
* used within the signature. Can be `nullptr`.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
INFIX_API INFIX_NODISCARD infix_status infix_forward_create_direct(infix_forward_t ** out_trampoline,
const char * signature,
void * target_function,
infix_direct_arg_handler_t * handlers,
infix_registry_t * registry);
/**
* @brief Gets the callable function pointer from a direct marshalling trampoline.
*
* @param[in] trampoline The `infix_forward_t` handle created with `infix_forward_create_direct`.
infix/src/arch/aarch64/abi_arm64.c view on Meta::CPAN
* 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/src/arch/aarch64/abi_arm64.c view on Meta::CPAN
* @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;
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
* Opcode format (MOVK, 64-bit): 1 1 1 100101 hw imm16 Rd (base 0xF2800000)
*
* @param buf The code buffer to append the instruction to.
* @param is_movz If true, emits `MOVZ`; otherwise, emits `MOVK`.
* @param dest_reg The destination GPR (X0-X30).
* @param imm The 16-bit immediate value to load.
* @param shift_count The left shift to apply (0 for LSL #0, 1 for LSL #16, etc.).
*/
INFIX_INTERNAL void emit_arm64_mov_imm_chunk(
code_buffer * buf, bool is_movz, uint64_t dest_reg, uint16_t imm, uint8_t shift_count) {
if (buf->error)
return;
// Base encoding for MOVZ Xd, #imm, LSL #shift
uint32_t instr = A64_SF_64BIT | A64_OP_MOVE_WIDE_IMM | A64_OPC_MOVZ;
if (!is_movz)
// Change opcode from MOVZ to MOVK by setting the 'opc' field to '11'.
instr = (instr & ~A64_OPC_MOVZ) | A64_OPC_MOVK;
// 'hw' field encodes the shift: 00=LSL 0, 01=LSL 16, 10=LSL 32, 11=LSL 48.
instr |= ((uint32_t)shift_count & 0x3) << 21;
// 'imm16' field holds the 16-bit immediate.
instr |= ((uint32_t)imm & 0xFFFF) << 5;
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
*
* Opcode (64-bit): 10101010000111110000001111100000 (0xAA1F03E0) + dest
*
* This requires a special case for moving the stack pointer.
* @param buf The code buffer.
* @param is64 True for a 64-bit move (X registers), false for 32-bit (W registers).
* @param dest The destination register.
* @param src The source register.
*/
INFIX_INTERNAL void emit_arm64_mov_reg(code_buffer * buf, bool is64, arm64_gpr dest, arm64_gpr src) {
if (buf->error)
return;
// Special case: MOV to/from SP is an alias for ADD Xd, SP, #0.
// The generic ORR-based alias treats register 31 as XZR, not SP.
if (dest == SP_REG || src == SP_REG) {
uint32_t instr = (is64 ? A64_SF_64BIT : A64_SF_32BIT) | A64_OP_ADD_SUB_IMM | A64_OPC_ADD;
instr |= (uint32_t)(src & 0x1F) << 5; // Rn
instr |= (uint32_t)(dest & 0x1F); // Rd
emit_int32(buf, instr);
return;
}
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
* Opcode (64-bit): 11_111_00_1_01_... (base 0xB9400000)
* Opcode (32-bit): 10_111_00_1_01_... (base 0x79400000)
*
* @param buf The code buffer.
* @param is64 True to load 64 bits (`Xt`), false to load 32 bits (`Wt`).
* @param dest The destination GPR.
* @param base The base address register (GPR or SP).
* @param offset The byte offset from the base register. Must be a multiple of the access size.
*/
INFIX_INTERNAL void emit_arm64_ldr_imm(code_buffer * buf, bool is64, arm64_gpr dest, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
const int scale = is64 ? 8 : 4;
if (offset >= 0 && offset % scale == 0 && (offset / scale) <= 0xFFF) {
uint32_t size_bits = is64 ? (0b11U << 30) : (0b10U << 30);
uint32_t instr = size_bits | A64_OP_LOAD_STORE_IMM_UNSIGNED | A64_LDR_OP;
instr |= ((uint32_t)(offset / scale) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(dest & 0x1F);
emit_int32(buf, instr);
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
emit_arm64_sub_imm(buf, true, false, X16_REG, base, (uint32_t)(-offset));
emit_arm64_ldr_imm(buf, is64, dest, X16_REG, 0);
}
}
/**
* @internal
* @brief Emits a `LDRB` (Load Register Byte) instruction.
* @details Opcode: 00_111_00_1_01_... (base 0x39400000)
*/
INFIX_INTERNAL void emit_arm64_ldrb_imm(code_buffer * buf, arm64_gpr dest, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
if (offset >= 0 && offset <= 0xFFF) {
uint32_t instr = 0x39400000;
instr |= ((uint32_t)offset & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(dest & 0x1F);
emit_int32(buf, instr);
}
else {
if (offset >= 0)
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
emit_arm64_sub_imm(buf, true, false, X16_REG, base, (uint32_t)(-offset));
emit_arm64_ldrb_imm(buf, dest, X16_REG, 0);
}
}
/**
* @internal
* @brief Emits a `LDRH` (Load Register Halfword) instruction.
* @details Opcode: 01_111_00_1_01_... (base 0x79400000)
*/
INFIX_INTERNAL void emit_arm64_ldrh_imm(code_buffer * buf, arm64_gpr dest, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
if (offset >= 0 && offset % 2 == 0 && (offset / 2) <= 0xFFF) {
uint32_t instr = 0x79400000;
instr |= ((uint32_t)(offset / 2) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(dest & 0x1F);
emit_int32(buf, instr);
}
else {
if (offset >= 0)
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
* This loads a 32-bit value from memory and sign-extends it to 64 bits.
*
* Opcode: 10_111_00_1_10_... (base 0xB9800000)
*
* @param buf The code buffer.
* @param dest The 64-bit destination GPR (`Xt`).
* @param base The base address register.
* @param offset The byte offset, which must be a multiple of 4.
*/
INFIX_INTERNAL void emit_arm64_ldrsw_imm(code_buffer * buf, arm64_gpr dest, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
if (offset >= 0 && offset % 4 == 0 && (offset / 4) <= 0xFFF) {
uint32_t instr = (0b10U << 30) | A64_OP_LOAD_STORE_IMM_UNSIGNED | (0b10U << 22);
instr |= ((uint32_t)(offset / 4) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(dest & 0x1F);
emit_int32(buf, instr);
}
else {
// Fallback
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
emit_arm64_sub_imm(buf, true, false, X16_REG, base, (uint32_t)(-offset));
emit_arm64_ldrsw_imm(buf, dest, X16_REG, 0);
}
}
/**
* @internal
* @brief Emits a `LDRSB` (Load Register Signed Byte) instruction (64-bit destination).
* @details Opcode: 00_111_00_1_10_... (base 0x39800000)
*/
INFIX_INTERNAL void emit_arm64_ldrsb_imm(code_buffer * buf, arm64_gpr dest, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
if (offset >= 0 && offset <= 0xFFF) {
uint32_t instr = 0x39800000;
instr |= ((uint32_t)offset & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(dest & 0x1F);
emit_int32(buf, instr);
}
else {
if (offset >= 0)
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
emit_arm64_sub_imm(buf, true, false, X16_REG, base, (uint32_t)(-offset));
emit_arm64_ldrsb_imm(buf, dest, X16_REG, 0);
}
}
/**
* @internal
* @brief Emits a `LDRSH` (Load Register Signed Halfword) instruction (64-bit destination).
* @details Opcode: 01_111_00_1_10_... (base 0x79800000)
*/
INFIX_INTERNAL void emit_arm64_ldrsh_imm(code_buffer * buf, arm64_gpr dest, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
if (offset >= 0 && offset % 2 == 0 && (offset / 2) <= 0xFFF) {
uint32_t instr = 0x79800000;
instr |= ((uint32_t)(offset / 2) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(dest & 0x1F);
emit_int32(buf, instr);
}
else {
if (offset >= 0)
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
* Opcode (64-bit): 11_111_00_1_00_... (base 0xB9000000)
* Opcode (32-bit): 10_111_00_1_00_... (base 0x79000000)
*
* @param buf The code buffer.
* @param is64 True to store 64 bits (`Xt`), false to store 32 bits (`Wt`).
* @param src The source GPR.
* @param base The base address register.
* @param offset The byte offset, a multiple of the access size.
*/
INFIX_INTERNAL void emit_arm64_str_imm(code_buffer * buf, bool is64, arm64_gpr src, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
const int scale = is64 ? 8 : 4;
if (offset >= 0 && offset % scale == 0 && (offset / scale) <= 0xFFF) {
uint32_t size_bits = is64 ? (0b11U << 30) : (0b10U << 30);
uint32_t instr = size_bits | A64_OP_LOAD_STORE_IMM_UNSIGNED;
instr |= ((uint32_t)(offset / scale) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(src & 0x1F);
emit_int32(buf, instr);
}
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
*
* Opcode: 00_111_00_1_00_... (base 0x39000000)
*
* @param buf The code buffer.
* @param is64 True to store 64 bits (`Xt`), false to store 32 bits (`Wt`).
* @param src The source GPR.
* @param base The base address register.
* @param offset The byte offset, a multiple of the access size.
*/
INFIX_INTERNAL void emit_arm64_strb_imm(code_buffer * buf, arm64_gpr src, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
if (offset >= 0 && offset <= 0xFFF) {
uint32_t instr = (0b00U << 30) | A64_OP_LOAD_STORE_IMM_UNSIGNED; // STRB opcode
instr |= ((uint32_t)offset & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(src & 0x1F);
emit_int32(buf, instr);
}
else {
// Fallback
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
/**
* @internal
* @brief Emits a `STRH` (Store Register Halfword) instruction.
* @details Stores the low 16 bits of a register
* Assembly: `STRH <Wt>, [<Xn|SP>, #imm]`
*
* Opcode: 01_111_00_1_00_... (base 0x79000000)
*
*/
INFIX_INTERNAL void emit_arm64_strh_imm(code_buffer * buf, arm64_gpr src, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
if (offset >= 0 && offset % 2 == 0 && (offset / 2) <= 0xFFF) {
uint32_t instr = (0b01U << 30) | A64_OP_LOAD_STORE_IMM_UNSIGNED; // STRH opcode
instr |= ((uint32_t)(offset / 2) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(src & 0x1F);
emit_int32(buf, instr);
}
else {
// Fallback
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
* @brief Emits an `STP` (Store Pair) instruction with pre-indexing.
* @details Assembly: `STP <Xt1>, <Xt2>, [Xn|SP, #imm]!`
* This instruction stores two registers and updates the base register.
*
* Opcode (64-bit): 1010100110...
*
* @param offset A signed, scaled immediate offset.
*/
INFIX_INTERNAL void emit_arm64_stp_pre_index(
code_buffer * buf, bool is64, arm64_gpr src1, arm64_gpr src2, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
int scale = is64 ? 8 : 4;
if (offset % scale != 0 || (offset / scale) < -64 || (offset / scale) > 63) {
buf->error = true;
return;
}
// Instruction format: opc:101001:L=0:imm7:Rt2:Rn:Rt
// For STP: opc=?, L=0
uint32_t instr =
(is64 ? A64_SF_64BIT : A64_SF_32BIT) | A64_OPC_STP | A64_OP_LOAD_STORE_PAIR_BASE | A64_ADDR_PRE_INDEX;
instr |= ((uint32_t)(offset / scale) & 0x7F) << 15;
instr |= (uint32_t)(src2 & 0x1F) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(src1 & 0x1F);
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
}
// Memory <-> VPR (SIMD/FP) Emitters
/*
* Implementation for emit_arm64_ldr_vpr.
* Encodes `LDR <Ht|St|Dt>, [<Xn|SP>, #imm]`.
* Opcode (64-bit, D reg): 11_111_10_1_01_... (base 0xBD400000)
* Opcode (32-bit, S reg): 10_111_10_1_01_... (base 0x7D400000)
* Opcode (16-bit, H reg): 01_111_10_1_01_... (base 0x3D400000)
*/
INFIX_INTERNAL void emit_arm64_ldr_vpr(code_buffer * buf, size_t size, arm64_vpr dest, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
const int scale = (int)size;
if (offset >= 0 && offset % scale == 0 && (offset / scale) <= 0xFFF) {
uint32_t instr = 0x3d400000;
uint32_t size_bits = (size == 8) ? 0b11 : (size == 4) ? 0b10 : 0b01;
instr |= (size_bits << 30);
instr |= ((uint32_t)(offset / scale) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(dest & 0x1F);
emit_int32(buf, instr);
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
}
}
/*
* Implementation for emit_arm64_str_vpr.
* Encodes `STR <Ht|St|Dt>, [<Xn|SP>, #imm]`.
* Opcode (64-bit, D reg): 11_111_10_1_00_... (base 0xBD000000)
* Opcode (32-bit, S reg): 10_111_10_1_00_... (base 0x7D000000)
* Opcode (16-bit, H reg): 01_111_10_1_00_... (base 0x3D000000)
*/
INFIX_INTERNAL void emit_arm64_str_vpr(code_buffer * buf, size_t size, arm64_vpr src, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
const int scale = (int)size;
if (offset >= 0 && offset % scale == 0 && (offset / scale) <= 0xFFF) {
uint32_t instr = 0x3d000000;
uint32_t size_bits = (size == 8) ? 0b11 : (size == 4) ? 0b10 : 0b01;
instr |= (size_bits << 30);
instr |= ((uint32_t)(offset / scale) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(src & 0x1F);
emit_int32(buf, instr);
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
emit_arm64_sub_imm(buf, true, false, X16_REG, base, (uint32_t)(-offset));
emit_arm64_str_vpr(buf, size, src, X16_REG, 0);
}
}
/*
* Implementation for emit_arm64_ldr_q_imm.
* Encodes `LDR <Qt>, [Xn, #imm]` for a 128-bit load into a full V-register.
* Opcode: 00_111_10_1_01... (base 0x3DC00000)
*/
INFIX_INTERNAL void emit_arm64_ldr_q_imm(code_buffer * buf, arm64_vpr dest, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
// Validate immediate offset for 128-bit (16-byte) access
if (offset >= 0 && offset % 16 == 0 && (offset / 16) <= 0xFFF) {
uint32_t instr = 0x3DC00000;
instr |= ((uint32_t)(offset / 16) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(dest & 0x1F);
emit_int32(buf, instr);
}
else {
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
emit_arm64_sub_imm(buf, true, false, X16_REG, base, (uint32_t)(-offset));
emit_arm64_ldr_q_imm(buf, dest, X16_REG, 0);
}
}
/*
* Implementation for emit_arm64_str_q_imm.
* Encodes `STR <Qt>, [Xn, #imm]` for a 128-bit store from a full V-register.
* Opcode: 00_111_10_1_00... (base 0x3D800000)
*/
INFIX_INTERNAL void emit_arm64_str_q_imm(code_buffer * buf, arm64_vpr src, arm64_gpr base, int32_t offset) {
if (buf->error)
return;
// Validate immediate offset for 128-bit (16-byte) access
if (offset >= 0 && offset % 16 == 0 && (offset / 16) <= 0xFFF) {
uint32_t instr = 0x3D800000;
instr |= ((uint32_t)(offset / 16) & 0xFFF) << 10;
instr |= (uint32_t)(base & 0x1F) << 5;
instr |= (uint32_t)(src & 0x1F);
emit_int32(buf, instr);
}
else {
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
INFIX_INTERNAL void emit_arm64_sub_imm(
code_buffer * buf, bool is64, bool set_flags, arm64_gpr dest, arm64_gpr base, uint32_t imm) {
emit_arm64_arith_imm(buf, true, is64, set_flags, dest, base, imm);
}
/**
* @internal
* @brief Emits `CMP <Xn|Wn>, <Xm|Wm>` instruction (alias for SUBS <Xd>, <Xn>, <Xm> with XZR destination).
* @details Opcode (64-bit): 11101011...
*/
INFIX_INTERNAL void emit_arm64_cmp_reg_reg(code_buffer * buf, bool is64, arm64_gpr reg1, arm64_gpr reg2) {
if (buf->error)
return;
// SUBS <Xd>, <Xn>, <Xm> { , <shift> #<amount> }
// We use Rd = 31 (XZR), shift = 0.
uint32_t instr = (is64 ? A64_SF_64BIT : A64_SF_32BIT) | 0x6B000000;
instr |= (uint32_t)(reg2 & 0x1F) << 16; // Rm
instr |= (uint32_t)(reg1 & 0x1F) << 5; // Rn
instr |= 31U; // Rd = XZR (zero register)
emit_int32(buf, instr);
}
// Control Flow Emitters
infix/src/arch/aarch64/abi_arm64_emitters.c view on Meta::CPAN
/**
* @internal
* @brief Emits a `B.<cond>` (Branch Conditionally) instruction.
* @details Assembly: `B.<cond> #imm`.
*
* Opcode: 01010100...
*
* @param offset A signed byte offset from the current instruction, which must be a multiple of 4.
*/
INFIX_INTERNAL void emit_arm64_b_cond(code_buffer * buf, arm64_cond cond, int32_t offset) {
if (buf->error)
return;
// Offset is encoded as a 19-bit immediate, scaled by 4 bytes.
if (offset % 4 != 0 || (offset / 4) < -262144 || (offset / 4) > 262143) {
buf->error = true;
return;
}
uint32_t instr = 0x54000000 | ((uint32_t)cond & 0xF);
instr |= ((uint32_t)(offset / 4) & 0x7FFFF) << 5;
emit_int32(buf, instr);
}
/**
* @internal
* @brief Emits a `CBNZ` (Compare and Branch on Non-Zero) instruction.
* @details Assembly: `CBNZ <Xt>, #imm`.
*
* Opcode (64-bit): 10110101... (0xB5...)
*
* @param offset A signed byte offset from the current instruction, which must be a multiple of 4.
*/
INFIX_INTERNAL void emit_arm64_cbnz(code_buffer * buf, bool is64, arm64_gpr reg, int32_t offset) {
if (buf->error)
return;
// Offset is encoded as a 19-bit immediate, scaled by 4 bytes.
// 262144 is the max alloc size
if (offset % 4 != 0 || (offset / 4) < -262144 || (offset / 4) > 262143) {
buf->error = true;
return;
}
uint32_t instr = (is64 ? A64_SF_64BIT : A64_SF_32BIT) | A64_OP_COMPARE_BRANCH_IMM | A64_OPC_CBNZ;
instr |= ((uint32_t)(offset / 4) & 0x7FFFF) << 5;
instr |= (uint32_t)(reg & 0x1F);
emit_int32(buf, instr);
}
/**
* @internal
* @brief Emits a `BRK` (Breakpoint) instruction.
* @details Assembly: `BRK #imm`. This causes a software breakpoint exception,
* useful for safely crashing on fatal errors (like a null function call).
*
* Opcode: 11010100001... (0xD42...)
*/
INFIX_INTERNAL void emit_arm64_brk(code_buffer * buf, uint16_t imm) {
if (buf->error)
return;
uint32_t instr = A64_OP_SYSTEM | A64_OP_BRK;
instr |= (uint32_t)(imm & 0xFFFF) << 5;
emit_int32(buf, instr);
}
/**
* @internal
* @brief Emits a `BR` (Branch to Register) instruction.
* @details This instruction performs an indirect, unconditional branch to the
* address contained in the specified register. It is functionally similar to
* `JMP` on x86.
*
* Assembly: `BR <Xn>`. An unconditional indirect jump.
*
* Opcode: 1101011000011111000000... (0xD61F0000)
*/
INFIX_INTERNAL void emit_arm64_b_reg(code_buffer * buf, arm64_gpr reg) {
if (buf->error)
return;
uint32_t instr = A64_OP_BRANCH_REG | A64_OPC_BR;
instr |= (uint32_t)(reg & 0x1F) << 5;
emit_int32(buf, instr);
}
/**
* @internal
* @brief Emits `SVC #imm` (Supervisor Call) instruction.
* @details Opcode: 11010100_00_imm16_0001
*/
INFIX_INTERNAL void emit_arm64_svc_imm(code_buffer * buf, uint16_t imm) {
if (buf->error)
return;
uint32_t instr = A64_OP_SYSTEM | A64_OP_SVC;
instr |= (uint32_t)(imm & 0xFFFF) << 5;
emit_int32(buf, instr);
}
infix/src/arch/x64/abi_sysv_x64.c view on Meta::CPAN
* @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;
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;
infix/src/arch/x64/abi_win_x64.c view on Meta::CPAN
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
size_t saved_args_data_size = 0;
size_t max_align = 16;
for (size_t i = 0; i < context->num_args; ++i) {
if (context->arg_types[i] == nullptr) {
*out_layout = nullptr;
_infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_INVALID_MEMBER_TYPE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
size_t align = context->arg_types[i]->alignment;
if (align < 8)
align = 8;
if (align > max_align)
max_align = align;
if (!is_passed_by_reference(context->arg_types[i])) {
saved_args_data_size = _infix_align_up(saved_args_data_size, align);
infix/src/common/compat_c23.h view on Meta::CPAN
#include <infix/infix.h>
#include <stdbool.h>
#include <stddef.h>
/**
* @def nullptr
* @brief Defines `nullptr` as a standard C-style null pointer constant (`(void*)0`).
*
* @details This provides a consistent null pointer constant across C and C++
* compilation environments. While `NULL` is standard in C, `nullptr` is preferred
* in modern C++ and is being adopted in newer C standards. This macro ensures
* the codebase can use `nullptr` consistently without causing compilation errors
* in a strict C11/C17 environment.
*/
#if !defined(nullptr) && !defined(__cplusplus)
#define nullptr ((void *)0)
#endif
/**
* @def static_assert
* @brief Defines a `static_assert` macro that maps to the C11 `_Static_assert`.
*
* @note This is a polyfill for older compilers or environments that might not
infix/src/common/compat_c23.h view on Meta::CPAN
#define static_assert(cond, msg) _Static_assert(cond, msg)
#endif
#endif
/**
* @def COMPAT_HAS_C_ATTRIBUTE
* @brief A utility macro to safely check for the existence of a C attribute.
*
* @details This macro wraps the `__has_c_attribute` feature test macro, which is
* not universally available across all compilers. By defining this wrapper,
* the code can safely check for attribute support without causing a preprocessor
* error on compilers that do not define `__has_c_attribute`.
*
* @param[in] x The name of the attribute to check (e.g., `nodiscard`).
* @return `1` if the attribute is supported, `0` otherwise.
*/
#if defined(__has_c_attribute)
#define COMPAT_HAS_C_ATTRIBUTE(x) __has_c_attribute(x)
#else
#define COMPAT_HAS_C_ATTRIBUTE(x) 0
#endif
/**
infix/src/common/double_tap.h view on Meta::CPAN
#ifdef __cplusplus
}
#endif
// Public Test Harness Macros
/** @brief Declares the total number of tests to be run in the current scope. Must be called before any tests. */
#define plan(count) tap_plan(count)
/** @brief Concludes testing, validates the plan, and returns an exit code based on success or failure. */
#define done() tap_done()
/** @brief Immediately terminates the entire test suite with a failure message. Useful for fatal setup errors. */
#define bail_out(...) tap_bail_out(__VA_ARGS__)
/** @brief The core assertion macro. Checks a condition and prints an "ok" or "not ok" TAP line with diagnostics on
* failure. */
#define ok(cond, ...) tap_ok(!!(cond), __FILE__, __LINE__, __func__, #cond, __VA_ARGS__)
/** @brief A convenience macro that always passes. Equivalent to `ok(true, ...)`. */
#define pass(...) ok(true, __VA_ARGS__)
/** @brief A convenience macro that always fails. Equivalent to `ok(false, ...)`. */
#define fail(...) ok(false, __VA_ARGS__)
/** @brief Defines a block of tests as a nested subtest, which gets its own plan and pass/fail status. */
#define subtest(name) \
infix/src/common/double_tap.h view on Meta::CPAN
// A subtest inherits the 'todo' state from its parent.
if (parent->todo) {
current_state->todo = true;
snprintf(current_state->todo_reason, sizeof(current_state->todo_reason), "%s", parent->todo_reason);
}
}
/** @internal Pops the current state from the stack when a subtest ends. */
static void pop_state(void) {
if (current_state <= &state_stack[0])
tap_bail_out("Internal error: Attempted to pop base test state");
current_state--;
}
// Public API Implementation
void tap_init(void) { _tap_ensure_initialized(); }
void tap_plan(size_t count) {
_tap_ensure_initialized();
if (current_state->has_plan || current_state->count > 0)
tap_bail_out("Plan declared after tests have run or a plan was already set");
infix/src/common/infix_config.h view on Meta::CPAN
#define INFIX_ENV_POSIX
#define _DARWIN_C_SOURCE
#include <TargetConditionals.h>
#include <libkern/OSCacheControl.h>
#include <pthread.h>
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
#define INFIX_OS_IOS
#elif TARGET_OS_MAC
#define INFIX_OS_MACOS
#else
#error "Unsupported/unknown Apple platform"
#endif
#elif defined(__linux__)
#define INFIX_OS_LINUX
#define INFIX_ENV_POSIX
#elif defined(__FreeBSD__)
#define INFIX_OS_FREEBSD
#define INFIX_ENV_POSIX
#elif defined(__OpenBSD__)
#define INFIX_OS_OPENBSD
#define INFIX_ENV_POSIX
infix/src/common/infix_config.h view on Meta::CPAN
/**
* @details Defines `INFIX_ARCH_*` for the two currently supported architectures.
* The library will fail to compile if the architecture is not one of these, as
* the JIT code emitters are architecture-specific.
*/
#if defined(__aarch64__) || defined(_M_ARM64)
#define INFIX_ARCH_AARCH64
#elif defined(__x86_64__) || defined(_M_X64)
#define INFIX_ARCH_X64
#else
#error "Unsupported architecture. Only x86-64 and AArch64 are currently supported."
#endif
// Target ABI Logic Selection
/**
* @details This is the most critical section of the configuration. It determines
* which ABI implementation will be compiled and used by the JIT engine.
*
* It supports two modes:
* 1. **Forced ABI:** A user can define `INFIX_FORCE_ABI_*` (e.g., via a compiler
* flag like `-DINFIX_FORCE_ABI_SYSV_X64`) to override automatic detection.
* This is essential for cross-compilation, where the host compiler's macros
infix/src/common/infix_internals.h view on Meta::CPAN
* @brief Internal definition of a memory arena.
* @details An arena is a fast, region-based allocator. It pre-allocates a single
* block of memory and serves subsequent small allocation requests by simply
* "bumping" a pointer. All memory allocated from an arena is freed at once by
* destroying the arena itself, eliminating the need to track individual allocations.
*/
struct infix_arena_t {
char * buffer; /**< The backing memory buffer for the arena. */
size_t capacity; /**< The total size of the buffer. */
size_t current_offset; /**< The current high-water mark of allocation. */
bool error; /**< A flag set if any allocation fails, preventing subsequent allocations. */
struct infix_arena_t * next_block; /**< A pointer to the next block in the chain, if this one is full. */
size_t block_size; /**< The size of this specific block's buffer, for chained arenas. */
};
// Mutex Abstraction for Internal Synchronization
#if defined(INFIX_OS_WINDOWS)
#include <windows.h>
typedef SRWLOCK infix_mutex_t;
#define INFIX_MUTEX_INITIALIZER SRWLOCK_INIT
#define INFIX_MUTEX_LOCK(m) AcquireSRWLockExclusive(m)
#define INFIX_MUTEX_UNLOCK(m) ReleaseSRWLockExclusive(m)
infix/src/common/infix_internals.h view on Meta::CPAN
size_t num_buckets; /**< The number of buckets in the hash table. */
size_t num_items; /**< The total number of items in the registry. */
_infix_registry_entry_t ** buckets; /**< The array of hash table buckets (heap-allocated). */
};
/**
* @struct parser_state
* @brief Holds the complete state of the recursive descent parser during a single parse operation.
*/
typedef struct {
const char * p; /**< The current read position (cursor) in the signature string. */
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.
*/
infix/src/common/infix_internals.h view on Meta::CPAN
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
* message with a code snippet.
* @param category The general category of the error.
* @param code The specific error code.
* @param position For parser errors, the byte offset into the signature string where the error occurred.
*/
INFIX_INTERNAL void _infix_set_error(infix_error_category_t category, infix_error_code_t code, size_t position);
/**
* @brief Sets the thread-local error state for a system-level error.
* @details Located in `src/core/error.c`, this is used for errors originating from
* the operating system, such as `dlopen` or `mmap` failures.
* @param category The general category of the error.
* @param code The `infix` error code that corresponds to the failure.
* @param system_code The OS-specific error code (e.g., from `errno` or `GetLastError`).
* @param msg An optional custom message from the OS (e.g., from `dlerror`).
*/
INFIX_INTERNAL void _infix_set_system_error(infix_error_category_t category,
infix_error_code_t code,
long system_code,
const char * msg);
/**
* @brief Clears the thread-local error state.
* @details Located in `src/core/error.c`. This is called at the beginning of every public
* API function to ensure that a prior error from an unrelated call is not accidentally returned.
*/
INFIX_INTERNAL void _infix_clear_error(void);
INFIX_INTERNAL void skip_whitespace(parser_state * state);
INFIX_INTERNAL void _infix_set_parser_error(parser_state * state, infix_error_code_t code);
INFIX_INTERNAL infix_type * parse_type(parser_state * state);
INFIX_INTERNAL infix_type * parse_primitive(parser_state * state);
/**
* @brief Recalculates the layout of a fully resolved type graph.
* @details Located in `src/core/types.c`. This is the "Layout" stage of the data pipeline.
* It recursively walks a type graph and computes the final `size`, `alignment`, and
* member `offset` fields for all aggregate types. It must only be called on a fully
* resolved graph.
* @param[in,out] type The root of the type graph to recalculate. The graph is modified in-place.
*/
INFIX_INTERNAL void _infix_type_recalculate_layout(infix_type * type);
/**
* @brief Resolves all named type references in a type graph in-place.
* @details Located in `src/core/type_registry.c`. This is the "Resolve" stage of the
* data pipeline. It traverses a type graph and replaces all `INFIX_TYPE_NAMED_REFERENCE`
* nodes (`@Name`) with direct pointers to the canonical `infix_type` objects from the registry.
* @param[in,out] type_ptr A pointer to the root of the type graph to resolve. The pointer may be changed.
* @param[in] registry The registry to use for lookups.
* @return `INFIX_SUCCESS` on success, or an error if a name cannot be resolved.
*/
INFIX_INTERNAL c23_nodiscard infix_status _infix_resolve_type_graph_inplace(infix_type ** type_ptr,
infix_registry_t * registry);
/**
* @brief The internal core of the signature parser.
* @details Located in `src/core/signature.c`. This is the "Parse" stage of the data pipeline.
* It takes a signature string and produces a raw, unresolved `infix_type` graph in a new,
* temporary arena. It does not perform any copying, resolution, or layout calculation.
* @param[out] out_type On success, receives the parsed type graph.
* @param[out] out_arena On success, receives the temporary arena holding the graph.
infix/src/common/utility.h view on Meta::CPAN
* @brief A no-op macro for printing debug messages in release builds.
* @details In release builds, this macro is defined as `((void)0)`, a standard C idiom
* for creating a statement that does nothing and has no side effects. The
* compiler will completely remove any calls to it, ensuring zero performance impact.
*/
#define INFIX_DEBUG_PRINTF(...) ((void)0)
/**
* @internal
* @brief A no-op version of `infix_dump_hex` for use in release builds.
* @details This function is defined as an empty `static inline` function.
* - `static`: Prevents linker errors if this header is included in multiple files.
* - `inline`: Suggests to the compiler that the function body is empty,
* allowing it to completely remove any calls to this function at the call site.
* - `c23_maybe_unused`: Suppresses compiler warnings about the parameters
* being unused in this empty implementation.
*
* @param data Unused in release builds.
* @param size Unused in release builds.
* @param title Unused in release builds.
*/
static inline void infix_dump_hex(c23_maybe_unused const void * data,
c23_maybe_unused size_t size,
c23_maybe_unused const char * title) {
// This function does nothing in release builds and will be optimized away entirely.
}
/**
* @internal
* @brief A no-op version of `infix_dump_state` for use in release builds.
* @details This function is defined as an empty `static inline` function.
* - `static`: Prevents linker errors if this header is included in multiple files.
* - `inline`: Suggests to the compiler that the function body is empty,
* allowing it to completely remove any calls to this function at the call site.
* - `c23_maybe_unused`: Suppresses compiler warnings about the parameters
* being unused in this empty implementation.
*
* @param file The file name where the function is being called.
* @param title The line number.
*/
static inline void infix_dump_state(c23_maybe_unused const char * file, c23_maybe_unused int line) {
// This function does nothing in release builds and will be optimized away entirely.
infix/src/core/arena.c view on Meta::CPAN
#include "common/infix_internals.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/**
* @internal
* @brief Creates a new memory arena with a specified initial size.
*
* Allocates an `infix_arena_t` struct and its backing buffer in a single block
* of memory. If allocation fails at any point, it cleans up successfully allocated
* parts, sets a detailed error, and returns `nullptr`.
*
* @param initial_size The number of bytes for the initial backing buffer. A larger
* size can reduce the chance of reallocation for complex types.
* @return A pointer to the new `infix_arena_t`, or `nullptr` on failure.
*/
INFIX_API c23_nodiscard infix_arena_t * infix_arena_create(size_t initial_size) {
// Use calloc to ensure the initial struct state is zeroed.
infix_arena_t * arena = infix_calloc(1, sizeof(infix_arena_t));
if (arena == nullptr) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
arena->buffer = infix_calloc(1, initial_size);
if (arena->buffer == nullptr && initial_size > 0) {
infix_free(arena);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
arena->capacity = initial_size;
arena->current_offset = 0;
arena->error = false;
arena->next_block = nullptr;
arena->block_size = initial_size;
return arena;
}
/**
* @internal
* @brief Destroys an arena and frees all memory associated with it.
*
* This function frees the arena's single backing buffer and the `infix_arena_t`
* struct itself. Any pointers returned by `infix_arena_alloc` from this arena
infix/src/core/arena.c view on Meta::CPAN
* @brief Allocates a block of memory from an arena with a specified alignment.
*
* This is a "bump" allocator. It calculates the next memory address that satisfies
* the requested alignment, checks if there is sufficient capacity in the arena's
* buffer, and if so, "bumps" the `current_offset` pointer and returns the address.
*
* This operation is extremely fast as it involves no system calls, only simple
* integer and pointer arithmetic.
*
* If an allocation fails (due to insufficient space or invalid arguments), the
* arena's `error` flag is set, a detailed error is reported, and all subsequent
* allocations from this arena will also fail.
*
* @param arena The arena to allocate from.
* @param size The number of bytes to allocate.
* @param alignment The required alignment for the allocation. Must be a power of two.
* @return A pointer to the allocated memory, or `nullptr` if the arena is out of
* memory, has its error flag set, or an invalid alignment is requested.
*/
INFIX_API c23_nodiscard void * infix_arena_alloc(infix_arena_t * arena, size_t size, size_t alignment) {
if (arena == nullptr)
return nullptr;
// Ensure alignment is power of 2
if (alignment == 0 || (alignment & (alignment - 1)) != 0) {
arena->error = true;
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_INVALID_ALIGNMENT, 0);
return nullptr;
}
infix_arena_t * block = arena;
while (true) {
if (block->error)
return nullptr;
// Calculate current absolute address
uintptr_t current_ptr = (uintptr_t)(block->buffer + block->current_offset);
// Calculate aligned address
// (x + align - 1) & ~(align - 1)
uintptr_t aligned_ptr = (current_ptr + (alignment - 1)) & ~(alignment - 1);
// Calculate padding needed
infix/src/core/arena.c view on Meta::CPAN
continue;
}
// Create new block. Ensure it's large enough for alignment + size.
size_t next_cap = block->block_size * 2;
if (next_cap < size + alignment)
next_cap = size + alignment;
block->next_block = infix_arena_create(next_cap);
if (!block->next_block) {
block->error = true;
return nullptr;
}
block = block->next_block;
}
}
/**
* @internal
* @brief Allocates and zero-initializes a block of memory from an arena.
*
* This function is a convenience wrapper around `infix_arena_alloc` that also
* ensures the allocated memory is set to zero, mimicking the behavior of `calloc`.
* It includes a check for integer overflow on the `num * size` calculation and
* will set a detailed error on failure.
*
* @param arena The arena to allocate from.
* @param num The number of elements to allocate.
* @param size The size of each element.
* @param alignment The required alignment for the allocation. Must be a power of two.
* @return A pointer to the zero-initialized memory, or `nullptr` on failure.
*/
INFIX_API c23_nodiscard void * infix_arena_calloc(infix_arena_t * arena, size_t num, size_t size, size_t alignment) {
// Security: Check for multiplication overflow.
if (size > 0 && num > SIZE_MAX / size) {
if (arena)
arena->error = true;
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_INTEGER_OVERFLOW, 0);
return nullptr;
}
size_t total_size = num * size;
void * ptr = infix_arena_alloc(arena, total_size, alignment);
if (ptr != nullptr)
memset(ptr, 0, total_size);
return ptr;
}
infix/src/core/error.c view on Meta::CPAN
* 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 error.c
* @brief Implements the thread-local error reporting system.
* @ingroup internal_core
*
* @details This module provides the infrastructure for robust and thread-safe error
* handling within the `infix` library.
*
* The core principle is that all detailed error information is stored in
* **thread-local storage (TLS)**. This means that an error occurring in one thread
* will never interfere with or be accidentally reported by an operation in another
* thread.
*
* The workflow is as follows:
* 1. Every public API function calls `_infix_clear_error()` upon entry to reset the
* error state for the current thread.
* 2. If an internal function encounters an error, it calls `_infix_set_error()` or
* `_infix_set_system_error()` to record detailed diagnostic information, including
* an error code, category, and a descriptive message.
* 3. For parser errors, `_infix_set_error()` generates a rich, multi-line diagnostic
* message with a code snippet and a caret pointing to the error location, similar
* to a compiler error.
* 4. The user can call the public `infix_get_last_error()` function at any time to
* retrieve a copy of the last error that occurred on their thread.
*/
#include "common/infix_internals.h"
#include <infix/infix.h>
#include <stdarg.h>
#include <stdio.h> // For snprintf
#include <string.h>
// Use a portable mechanism for thread-local storage (TLS).
// The order of checks is critical for cross-platform compatibility.
#if defined(INFIX_OS_OPENBSD)
infix/src/core/error.c view on Meta::CPAN
#define INFIX_TLS __declspec(thread)
#elif defined(INFIX_COMPILER_GCC)
// MinGW (GCC on Windows) and standard GCC/Clang on *nix.
// MinGW prefers __thread or _Thread_local over __declspec(thread).
#define INFIX_TLS __thread
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
// Fallback to C11 standard
#define INFIX_TLS _Thread_local
#else
// Fallback for compilers that do not support TLS. This is not thread-safe.
#warning "Compiler does not support thread-local storage; error handling will not be thread-safe."
#define INFIX_TLS
#endif
// A portable macro for safe string copying to prevent buffer overflows.
#if defined(INFIX_COMPILER_MSVC)
#define _INFIX_SAFE_STRNCPY(dest, src, count) strncpy_s(dest, sizeof(dest), src, count)
#else
#define _INFIX_SAFE_STRNCPY(dest, src, count) \
do { \
strncpy(dest, src, (count)); \
(dest)[(sizeof(dest)) - 1] = '\0'; \
} while (0)
#endif
/**
* @var g_infix_last_error
* @brief The thread-local variable that stores the details of the last error.
*
* @details Each thread gets its own independent instance of this variable. It is
* initialized to a "no error" state.
*/
static INFIX_TLS infix_error_details_t g_infix_last_error = {INFIX_CATEGORY_NONE, INFIX_CODE_SUCCESS, 0, 0, {0}};
/**
* @var g_infix_last_signature_context
* @brief A thread-local pointer to the full signature string being parsed.
*
* @details This is set by the high-level API functions (`infix_type_from_signature`, etc.)
* before parsing begins. If a parser error occurs, `_infix_set_error` uses this
* context to generate a rich, contextual error message.
*/
INFIX_TLS const char * g_infix_last_signature_context = nullptr;
/**
* @internal
* @brief Maps an `infix_error_code_t` to its human-readable string representation.
* @param code The error code to map.
* @return A constant string describing the error.
*/
static const char * _get_error_message_for_code(infix_error_code_t code) {
switch (code) {
case INFIX_CODE_SUCCESS:
return "Success";
case INFIX_CODE_UNKNOWN:
return "An unknown error occurred";
case INFIX_CODE_NULL_POINTER:
return "A required pointer argument was NULL";
case INFIX_CODE_MISSING_REGISTRY:
return "A type registry was required but not provided";
case INFIX_CODE_NATIVE_EXCEPTION:
return "A native exception was thrown across the FFI boundary";
case INFIX_CODE_OUT_OF_MEMORY:
return "Out of memory";
case INFIX_CODE_EXECUTABLE_MEMORY_FAILURE:
return "Failed to allocate executable memory";
infix/src/core/error.c view on Meta::CPAN
return "Named type not found in registry or is an undefined forward declaration";
case INFIX_CODE_INVALID_MEMBER_TYPE:
return "Aggregate contains an illegal member type (e.g., a struct with a void member)";
case INFIX_CODE_LIBRARY_NOT_FOUND:
return "The requested dynamic library could not be found";
case INFIX_CODE_SYMBOL_NOT_FOUND:
return "The requested symbol was not found in the library";
case INFIX_CODE_LIBRARY_LOAD_FAILED:
return "Loading the dynamic library failed";
default:
return "An unknown or unspecified error occurred";
}
}
/**
* @internal
* @brief Sets the last error details for the current thread.
*
* If the error is from the parser and a signature context is available, this
* function generates a rich, multi-line diagnostic message with a code snippet
* and a caret pointing to the error location. Otherwise, it uses the standard,
* single-line message for the given error code.
*
* @param category The category of the error.
* @param code The specific error code.
* @param position For parser errors, the byte offset into the signature string where the error occurred.
*/
void _infix_set_error(infix_error_category_t category, infix_error_code_t code, size_t position) {
g_infix_last_error.category = category;
g_infix_last_error.code = code;
g_infix_last_error.position = position;
g_infix_last_error.system_error_code = 0;
// Check if we can generate a rich parser error message.
if (category == INFIX_CATEGORY_PARSER && g_infix_last_signature_context != nullptr) {
// Generate a rich, GCC-style error message for parser failures.
const char * signature = g_infix_last_signature_context;
size_t sig_len = strlen(signature);
const size_t radius = 20; // Number of characters to show around the error position.
// Calculate the start and end of the snippet to display.
size_t start = (position > radius) ? (position - radius) : 0;
size_t end = (position + radius < sig_len) ? (position + radius) : sig_len;
// Add indicators if the snippet is truncated.
const char * start_indicator = (start > 0) ? "... " : "";
const char * end_indicator = (end < sig_len) ? " ..." : "";
size_t start_indicator_len = (start > 0) ? 4 : 0;
// Create the code snippet line.
char snippet[128];
snprintf(snippet,
sizeof(snippet),
"%s%.*s%s",
start_indicator,
(int)(end - start),
signature + start,
end_indicator);
// Create the pointer line with a caret '^' under the error.
char pointer[128];
size_t caret_pos = position - start + start_indicator_len;
snprintf(pointer, sizeof(pointer), "%*s^", (int)caret_pos, "");
// Build the final multi-line message piece by piece to avoid buffer overflows.
char * p = g_infix_last_error.message;
size_t remaining = sizeof(g_infix_last_error.message);
int written;
// Write the snippet and pointer lines.
written = snprintf(p, remaining, "\n\n %s\n %s", snippet, pointer);
if (written < 0 || (size_t)written >= remaining) {
// Fallback to a simple message on snprintf failure or buffer overflow.
const char * msg = _get_error_message_for_code(code);
_INFIX_SAFE_STRNCPY(g_infix_last_error.message, msg, sizeof(g_infix_last_error.message) - 1);
return;
}
p += written;
remaining -= written;
// Append the standard error description.
snprintf(p, remaining, "\n\nError: %s", _get_error_message_for_code(code));
}
else {
// For non-parser errors, just copy the standard message.
const char * msg = _get_error_message_for_code(code);
_INFIX_SAFE_STRNCPY(g_infix_last_error.message, msg, sizeof(g_infix_last_error.message) - 1);
}
}
/**
* @internal
* @brief Sets a detailed system error with a platform-specific error code and message.
*
* @details This is used for errors originating from OS-level functions like `dlopen`,
* `mmap`, or `VirtualAlloc`. It records both the `infix` error code and the
* underlying system error code (`errno` or `GetLastError`).
*
* @param category The category of the error.
* @param code The `infix` error code that corresponds to the failure.
* @param system_code The OS-specific error code (e.g., from `errno` or `GetLastError`).
* @param msg An optional custom message from the OS (e.g., from `dlerror`). If `nullptr`, the default message for
* `code` is used.
*/
void _infix_set_system_error(infix_error_category_t category,
infix_error_code_t code,
long system_code,
const char * msg) {
g_infix_last_error.category = category;
g_infix_last_error.code = code;
g_infix_last_error.position = 0;
g_infix_last_error.system_error_code = system_code;
if (msg)
_INFIX_SAFE_STRNCPY(g_infix_last_error.message, msg, sizeof(g_infix_last_error.message) - 1);
else {
const char * default_msg = _get_error_message_for_code(code);
_INFIX_SAFE_STRNCPY(g_infix_last_error.message, default_msg, sizeof(g_infix_last_error.message) - 1);
}
}
/**
* @internal
* @brief Resets the error state for the current thread to "no error".
*
* This should be called at the beginning of every public API function to ensure
* that a prior error from an unrelated call on the same thread is not accidentally
* returned to the user.
*/
void _infix_clear_error(void) {
g_infix_last_error.category = INFIX_CATEGORY_NONE;
g_infix_last_error.code = INFIX_CODE_SUCCESS;
g_infix_last_error.position = 0;
g_infix_last_error.system_error_code = 0;
g_infix_last_error.message[0] = '\0';
g_infix_last_signature_context = nullptr;
}
/**
* @brief Retrieves detailed information about the last error that occurred on the current thread.
* @return A copy of the last error details structure. This function is thread-safe.
*/
INFIX_API infix_error_details_t infix_get_last_error(void) { return g_infix_last_error; }
infix/src/core/loader.c view on Meta::CPAN
#include "common/infix_internals.h"
#if defined(INFIX_OS_WINDOWS)
#include <windows.h>
#else
#include <dlfcn.h>
#endif
/**
* @brief Opens a dynamic library and returns a handle to it.
*
* This function is a cross-platform wrapper around `LoadLibraryA` (Windows) and
* `dlopen` (POSIX). On failure, it sets the thread-local error state with
* detailed system-specific information using `_infix_set_system_error`.
*
* @param[in] path The file path to the library (e.g., `"./mylib.so"`, `"user32.dll"`).
* @return A pointer to an `infix_library_t` handle on success, or `nullptr` on failure.
* The returned handle must be freed with `infix_library_close`.
*/
INFIX_API INFIX_NODISCARD infix_library_t * infix_library_open(const char * path) {
infix_library_t * lib = infix_calloc(1, sizeof(infix_library_t));
if (lib == nullptr) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
#if defined(INFIX_OS_WINDOWS)
// On Windows, passing NULL to GetModuleHandleA returns a handle to the main executable file of the current process.
if (path == nullptr) {
lib->handle = GetModuleHandleA(path);
lib->is_pseudo_handle = true; // Mark this as a special, non-freeable handle.
}
else {
lib->handle = LoadLibraryA(path);
infix/src/core/loader.c view on Meta::CPAN
// symbol pollution if multiple plugins link against different versions of the same
// dependency. Dependencies must be explicitly linked or loaded.
// Use RTLD_GLOBAL to make symbols from this library available for resolution
// by other libraries that might be loaded later. This is important for complex
// dependency chains.
// On POSIX, passing NULL to dlopen returns a handle to the main executable.
lib->handle = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
#endif
if (lib->handle == nullptr) {
#if defined(INFIX_OS_WINDOWS)
// On Windows, GetLastError() provides the specific error code.
_infix_set_system_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_LIBRARY_LOAD_FAILED, GetLastError(), nullptr);
#else
// On POSIX, dlerror() returns a human-readable string.
_infix_set_system_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_LIBRARY_LOAD_FAILED, 0, dlerror());
#endif
infix_free(lib);
return nullptr;
}
return lib;
}
/**
* @brief Closes a dynamic library handle and frees associated resources.
*
* This is a cross-platform wrapper around `FreeLibrary` (Windows) and `dlclose` (POSIX).
infix/src/core/loader.c view on Meta::CPAN
dlclose(lib->handle);
#endif
}
infix_free(lib);
}
/**
* @brief Retrieves the address of a symbol (function or variable) from a loaded library.
*
* This is a cross-platform wrapper around `GetProcAddress` (Windows) and `dlsym` (POSIX).
*
* @note On POSIX, `dlsym` returning `NULL` is not a definitive error condition, as a
* symbol's address could itself be `NULL`. The official way to check for an
* error is to call `dlerror()` afterwards. This function does not perform
* that check and does not set the `infix` error state, as its primary callers
* (`infix_read_global`, etc.) will set a more specific `INFIX_CODE_SYMBOL_NOT_FOUND`
* error if the lookup fails.
*
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the symbol to look up (e.g., `"my_function"`).
* @return A `void*` pointer to the symbol's address, or `nullptr` if not found.
*/
INFIX_API INFIX_NODISCARD void * infix_library_get_symbol(infix_library_t * lib, const char * symbol_name) {
if (lib == nullptr || lib->handle == nullptr || symbol_name == nullptr)
return nullptr;
#if defined(INFIX_OS_WINDOWS)
return (void *)GetProcAddress((HMODULE)lib->handle, symbol_name);
infix/src/core/loader.c view on Meta::CPAN
* variable. This ensures that the correct number of bytes are copied from the
* library's data segment into the user's buffer, preventing buffer overflows.
*
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the global variable.
* @param[in] type_signature The `infix` signature string describing the variable's type (e.g., `"int32"`,
* `"{double,double}"`).
* @param[out] buffer A pointer to the destination buffer to receive the data. This buffer must be large enough to hold
* the type described by the signature.
* @param[in] registry An optional registry for resolving named types in the signature.
* @return `INFIX_SUCCESS` on success, or an error code on failure (e.g., symbol not found, invalid signature).
*/
INFIX_API INFIX_NODISCARD infix_status infix_read_global(infix_library_t * lib,
const char * symbol_name,
const char * type_signature,
void * buffer,
infix_registry_t * registry) {
if (buffer == nullptr)
return INFIX_ERROR_INVALID_ARGUMENT;
void * symbol_addr = infix_library_get_symbol(lib, symbol_name);
if (symbol_addr == nullptr) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_SYMBOL_NOT_FOUND, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
// Parse the signature to get the type's size.
infix_type * type = nullptr;
infix_arena_t * arena = nullptr;
infix_status status = infix_type_from_signature(&type, &arena, type_signature, registry);
if (status != INFIX_SUCCESS)
return status;
// Safely copy the data using the parsed size.
infix_memcpy(buffer, symbol_addr, type->size);
infix/src/core/loader.c view on Meta::CPAN
* @note This operation assumes that the memory page containing the global variable
* is writable. This is typical for `.data` or `.bss` segments but may fail
* if the variable is in a read-only segment (e.g., a `const` global). The
* function does not attempt to change memory permissions.
*
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the global variable.
* @param[in] type_signature The `infix` signature string describing the variable's type.
* @param[in] buffer A pointer to the source buffer containing the data to write.
* @param[in] registry An optional registry for resolving named types in the signature.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
INFIX_API INFIX_NODISCARD infix_status infix_write_global(infix_library_t * lib,
const char * symbol_name,
const char * type_signature,
void * buffer,
infix_registry_t * registry) {
if (buffer == nullptr)
return INFIX_ERROR_INVALID_ARGUMENT;
void * symbol_addr = infix_library_get_symbol(lib, symbol_name);
if (symbol_addr == nullptr) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_SYMBOL_NOT_FOUND, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = nullptr;
infix_arena_t * arena = nullptr;
infix_status status = infix_type_from_signature(&type, &arena, type_signature, registry);
if (status != INFIX_SUCCESS)
return status;
// Note: This assumes the memory page containing the global is writable.
// This is standard for data segments but could fail in unusual cases.
infix_memcpy(symbol_addr, buffer, type->size);
infix/src/core/signature.c view on Meta::CPAN
* pipeline, providing the user with a fully validated, self-contained, and ready-to-use
* type object that is safe to use for the lifetime of its returned arena.
*/
#include "common/infix_internals.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/** @internal A thread-local pointer to the full signature string being parsed, used by `error.c` for rich error
* reporting. */
extern INFIX_TLS const char * g_infix_last_signature_context;
/** @internal A safeguard against stack overflows from malicious or deeply nested signatures (e.g., `{{{{...}}}}`). */
#define MAX_RECURSION_DEPTH 32
static infix_status parse_function_signature_details(parser_state * state,
infix_type ** out_ret_type,
infix_function_argument ** out_args,
size_t * out_num_args,
size_t * out_num_fixed_args);
// Parser Helper Functions
/**
* @internal
* @brief Sets a detailed parser error, capturing the current position in the string.
* @param[in,out] state The current parser state.
* @param[in] code The error code to set.
*/
INFIX_INTERNAL void _infix_set_parser_error(parser_state * state, infix_error_code_t code) {
_infix_set_error(INFIX_CATEGORY_PARSER, code, (size_t)(state->p - state->start));
}
INFIX_INTERNAL void skip_whitespace(parser_state * state);
/**
* @internal
* @brief Advances the parser's cursor past any whitespace or C-style line comments.
* @param[in,out] state The parser state to modify.
*/
INFIX_INTERNAL void skip_whitespace(parser_state * state) {
while (true) {
infix/src/core/signature.c view on Meta::CPAN
* @return `true` on success, `false` on failure.
*/
static bool parse_size_t(parser_state * state, size_t * out_val) {
const char * start = state->p;
char * end;
errno = 0; // Reset errno before call
unsigned long long val = strtoull(start, &end, 10);
// Check for no conversion (end==start) OR overflow (ERANGE)
if (end == start || errno == ERANGE) {
// Use INTEGER_OVERFLOW code for range errors
_infix_set_parser_error(state, errno == ERANGE ? INFIX_CODE_INTEGER_OVERFLOW : INFIX_CODE_UNEXPECTED_TOKEN);
return false;
}
// Check for truncation if size_t is smaller than unsigned long long (e.g. 32-bit builds)
if (val > SIZE_MAX) {
_infix_set_parser_error(state, INFIX_CODE_INTEGER_OVERFLOW);
return false;
}
*out_val = (size_t)val;
state->p = end;
return true;
}
/**
* @internal
* @brief Parses a C-style identifier from the string.
* @details This is used for member names, named types, and function argument names.
infix/src/core/signature.c view on Meta::CPAN
break; // A single ':' is not part of an identifier.
if (*state->p == ':')
state->p++; // Consume first ':' of '::'
state->p++;
}
size_t len = state->p - start;
if (len == 0)
return nullptr;
char * name = infix_arena_calloc(state->arena, 1, len + 1, 1);
if (!name) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
return nullptr;
}
infix_memcpy((void *)name, start, len);
name[len] = '\0';
return name;
}
/**
* @internal
* @brief Consumes a specific keyword from the string (e.g., "int", "struct").
* @details This function is careful to match whole words only. For example, it will
infix/src/core/signature.c view on Meta::CPAN
} member_node;
member_node *head = nullptr, *tail = nullptr;
size_t num_members = 0;
skip_whitespace(state);
if (*state->p != end_char) {
while (1) {
const char * p_before_member = state->p;
const char * name = parse_optional_name_prefix(state);
// Disallow an empty member definition like `name,` without a type.
if (name && (*state->p == ',' || *state->p == end_char)) {
state->p = p_before_member + strlen(name); // Position error at end of name
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return nullptr;
}
infix_type * member_type = parse_type(state);
if (!member_type)
return nullptr;
// Structs and unions cannot have `void` members.
if (member_type->category == INFIX_TYPE_VOID) {
_infix_set_parser_error(state, INFIX_CODE_INVALID_MEMBER_TYPE);
return nullptr;
}
// Check for bitfield syntax: "name: type : width"
uint8_t bit_width = 0;
bool is_bitfield = false;
const char * p_before_colon = state->p;
skip_whitespace(state);
if (*state->p == ':') {
state->p++; // Consume ':'
skip_whitespace(state);
if (!isdigit((unsigned char)*state->p)) {
// Not a bitfield width, backtrack. This handles "name: type" where ':' is part of name prefix.
state->p = p_before_colon;
}
else {
size_t width_val = 0;
if (!parse_size_t(state, &width_val))
return nullptr; // Error set by parse_size_t
if (width_val > 255) {
_infix_set_parser_error(state, INFIX_CODE_TYPE_TOO_LARGE);
return nullptr;
}
bit_width = (uint8_t)width_val;
is_bitfield = true;
}
}
member_node * node = infix_arena_calloc(state->arena, 1, sizeof(member_node), _Alignof(member_node));
if (!node) {
_infix_set_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
return nullptr;
}
// The member offset is not calculated here; it will be done later
// by `infix_type_create_struct` or `_infix_type_recalculate_layout`.
if (is_bitfield)
node->m = infix_type_create_bitfield_member(name, member_type, 0, bit_width);
else
node->m = infix_type_create_member(name, member_type, 0);
infix/src/core/signature.c view on Meta::CPAN
else {
tail->next = node;
tail = node;
}
num_members++;
// Check for next token: ',' or end_char
skip_whitespace(state);
if (*state->p == ',') {
state->p++; // Consume comma.
skip_whitespace(state);
// A trailing comma like `{int,}` is a syntax error.
if (*state->p == end_char) {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return nullptr;
}
}
else if (*state->p == end_char)
break;
else { // Unexpected token (e.g., missing comma).
if (*state->p == '\0') {
_infix_set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
return nullptr;
}
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return nullptr;
}
}
}
*out_num_members = num_members;
if (num_members == 0)
return nullptr;
// Convert the temporary linked list to a flat array in the arena.
infix_struct_member * members =
infix_arena_calloc(state->arena, num_members, sizeof(infix_struct_member), _Alignof(infix_struct_member));
if (!members) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
return nullptr;
}
member_node * current = head;
for (size_t i = 0; i < num_members; i++) {
members[i] = current->m;
current = current->next;
}
return members;
}
/**
* @internal
* @brief Parses a struct (`{...}`) or union (`<...>`).
* @param[in,out] state The parser state.
* @param[in] start_char The opening delimiter ('{' or '<').
* @param[in] end_char The closing delimiter ('}' or '>').
* @return A pointer to the new `infix_type`, or `nullptr` on failure.
*/
static infix_type * parse_aggregate(parser_state * state, char start_char, char end_char) {
if (state->depth >= MAX_RECURSION_DEPTH) {
_infix_set_parser_error(state, INFIX_CODE_RECURSION_DEPTH_EXCEEDED);
return nullptr;
}
state->depth++;
if (*state->p != start_char) {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
state->depth--;
return nullptr;
}
state->p++;
size_t num_members = 0;
infix_struct_member * members = parse_aggregate_members(state, end_char, &num_members);
// If member parsing failed, an error is already set. Propagate the failure.
if (!members && infix_get_last_error().code != INFIX_CODE_SUCCESS) {
state->depth--;
return nullptr;
}
if (*state->p != end_char) {
_infix_set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
state->depth--;
return nullptr;
}
state->p++;
infix_type * agg_type = nullptr;
infix_status status = (start_char == '{') ? infix_type_create_struct(state->arena, &agg_type, members, num_members)
: infix_type_create_union(state->arena, &agg_type, members, num_members);
if (status != INFIX_SUCCESS) {
state->depth--;
return nullptr;
infix/src/core/signature.c view on Meta::CPAN
*/
static infix_type * parse_packed_struct(parser_state * state) {
size_t alignment = 1; // Default alignment for `!{...}` is 1.
if (*state->p == '!') {
state->p++;
if (isdigit((unsigned char)*state->p)) {
// This is the `!N:{...}` form with an explicit alignment.
if (!parse_size_t(state, &alignment))
return nullptr;
if (*state->p != ':') {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return nullptr;
}
state->p++;
}
}
skip_whitespace(state);
if (*state->p != '{') {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return nullptr;
}
state->p++;
size_t num_members = 0;
infix_struct_member * members = parse_aggregate_members(state, '}', &num_members);
if (!members && infix_get_last_error().code != INFIX_CODE_SUCCESS)
return nullptr;
if (*state->p != '}') {
_infix_set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
return nullptr;
}
state->p++;
infix_type * packed_type = nullptr;
// For packed structs, the total size is simply the sum of member sizes without padding.
// The user of `infix_type_create_packed_struct` must provide pre-calculated offsets.
// Since our parser doesn't know the offsets, we pass a preliminary size. The final
// layout pass will fix this if needed, but for packed structs, the user's offsets
// are king.
size_t total_size = 0;
infix/src/core/signature.c view on Meta::CPAN
* @brief The main recursive parsing function.
* @details This is the heart of the parser. It acts as a dispatcher, inspecting the
* next character in the string to determine which type of construct to parse. It
* then calls the appropriate helper function (e.g., `parse_aggregate`,
* `parse_primitive`, or a recursive call to itself for pointers/arrays).
* @param[in,out] state The parser state.
* @return A pointer to the parsed `infix_type`, or `nullptr` on failure.
*/
INFIX_INTERNAL infix_type * parse_type(parser_state * state) {
if (state->depth >= MAX_RECURSION_DEPTH) {
_infix_set_parser_error(state, INFIX_CODE_RECURSION_DEPTH_EXCEEDED);
return nullptr;
}
state->depth++;
skip_whitespace(state);
// Capture the offset from the start of the signature string *before* parsing the type.
size_t current_offset = state->p - state->start;
infix_type * result_type = nullptr;
const char * p_before_type = state->p;
if (*state->p == '@') { // Named type reference: `@MyStruct`
state->p++;
const char * name = parse_identifier(state);
if (!name) {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
state->depth--;
return nullptr;
}
if (infix_type_create_named_reference(state->arena, &result_type, name, INFIX_AGGREGATE_STRUCT) !=
INFIX_SUCCESS)
result_type = nullptr;
}
else if (*state->p == '*') { // Pointer type: `*int`
state->p++;
skip_whitespace(state);
infix/src/core/signature.c view on Meta::CPAN
infix_function_argument * args = nullptr;
size_t num_args = 0, num_fixed = 0;
if (parse_function_signature_details(state, &ret_type, &args, &num_args, &num_fixed) != INFIX_SUCCESS) {
state->depth--;
return nullptr;
}
// Manually construct a function pointer type object.
// This is represented internally as a pointer-like type with extra metadata.
infix_type * func_type = infix_arena_calloc(state->arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (!func_type) {
_infix_set_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
state->depth--;
return nullptr;
}
func_type->size = sizeof(void *);
func_type->alignment = _Alignof(void *);
func_type->is_arena_allocated = true;
func_type->category = INFIX_TYPE_REVERSE_TRAMPOLINE; // Special category for function types.
func_type->meta.func_ptr_info.return_type = ret_type;
func_type->meta.func_ptr_info.args = args;
infix/src/core/signature.c view on Meta::CPAN
else { // Grouped type: `(type)`
state->p++;
skip_whitespace(state);
result_type = parse_type(state);
if (!result_type) {
state->depth--;
return nullptr;
}
skip_whitespace(state);
if (*state->p != ')') {
_infix_set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
result_type = nullptr;
}
else
state->p++;
}
}
else if (*state->p == '[') { // Array type: `[size:type]`
state->p++;
skip_whitespace(state);
size_t num_elements = 0;
infix/src/core/signature.c view on Meta::CPAN
is_flexible = true;
state->p++;
}
else if (!parse_size_t(state, &num_elements)) {
state->depth--;
return nullptr;
}
skip_whitespace(state);
if (*state->p != ':') {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
state->depth--;
return nullptr;
}
state->p++;
skip_whitespace(state);
infix_type * element_type = parse_type(state);
if (!element_type) {
state->depth--;
return nullptr;
}
if (element_type->category == INFIX_TYPE_VOID) { // An array of `void` is illegal in C.
_infix_set_parser_error(state, INFIX_CODE_INVALID_MEMBER_TYPE);
state->depth--;
return nullptr;
}
skip_whitespace(state);
if (*state->p != ']') {
_infix_set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
state->depth--;
return nullptr;
}
state->p++;
if (is_flexible) {
if (infix_type_create_flexible_array(state->arena, &result_type, element_type) != INFIX_SUCCESS)
result_type = nullptr;
}
else {
infix/src/core/signature.c view on Meta::CPAN
result_type = parse_packed_struct(state);
else if (*state->p == '{') // Struct
result_type = parse_aggregate(state, '{', '}');
else if (*state->p == '<') // Union
result_type = parse_aggregate(state, '<', '>');
else if (*state->p == 'e' && state->p[1] == ':') { // Enum: `e:type`
state->p += 2;
skip_whitespace(state);
infix_type * underlying_type = parse_type(state);
if (!underlying_type || underlying_type->category != INFIX_TYPE_PRIMITIVE) {
_infix_set_parser_error(state, INFIX_CODE_INVALID_MEMBER_TYPE);
state->depth--;
return nullptr;
}
if (infix_type_create_enum(state->arena, &result_type, underlying_type) != INFIX_SUCCESS)
result_type = nullptr;
}
else if (*state->p == 'c' && state->p[1] == '[') { // Complex: `c[type]`
state->p += 2;
skip_whitespace(state);
infix_type * base_type = parse_type(state);
if (!base_type) {
state->depth--;
return nullptr;
}
skip_whitespace(state);
if (*state->p != ']') {
_infix_set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
state->depth--;
return nullptr;
}
state->p++;
if (infix_type_create_complex(state->arena, &result_type, base_type) != INFIX_SUCCESS)
result_type = nullptr;
}
else if (*state->p == 'v' && state->p[1] == '[') { // Vector: `v[size:type]`
state->p += 2;
skip_whitespace(state);
size_t num_elements;
if (!parse_size_t(state, &num_elements)) {
state->depth--;
return nullptr;
}
if (*state->p != ':') {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
state->depth--;
return nullptr;
}
state->p++;
infix_type * element_type = parse_type(state);
if (!element_type) {
state->depth--;
return nullptr;
}
if (*state->p != ']') {
_infix_set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
state->depth--;
return nullptr;
}
state->p++;
if (infix_type_create_vector(state->arena, &result_type, element_type, num_elements) != INFIX_SUCCESS)
result_type = nullptr;
}
else { // Primitive type or error
result_type = parse_primitive(state);
if (!result_type) {
// If no error was set by a failed `consume_keyword`, set a generic one.
if (infix_get_last_error().code == INFIX_CODE_SUCCESS) {
state->p = p_before_type;
if (isalpha((unsigned char)*state->p) || *state->p == '_')
_infix_set_parser_error(state, INFIX_CODE_INVALID_KEYWORD);
else
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
}
}
}
// Only set source offset for dynamically allocated types (primitives are static singletons).
if (result_type && result_type->is_arena_allocated)
result_type->source_offset = current_offset;
state->depth--;
return result_type;
}
/**
infix/src/core/signature.c view on Meta::CPAN
* @param[out] out_num_args Receives the total number of arguments (fixed + variadic).
* @param[out] out_num_fixed_args Receives the number of fixed (non-variadic) arguments.
* @return `INFIX_SUCCESS` on success.
*/
static infix_status parse_function_signature_details(parser_state * state,
infix_type ** out_ret_type,
infix_function_argument ** out_args,
size_t * out_num_args,
size_t * out_num_fixed_args) {
if (*state->p != '(') {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return INFIX_ERROR_INVALID_ARGUMENT;
}
state->p++;
skip_whitespace(state);
// Use a temporary linked list to collect arguments.
typedef struct arg_node {
infix_function_argument arg;
struct arg_node * next;
} arg_node;
arg_node *head = nullptr, *tail = nullptr;
infix/src/core/signature.c view on Meta::CPAN
while (1) {
skip_whitespace(state);
if (*state->p == ')' || *state->p == ';')
break;
const char * name = parse_optional_name_prefix(state);
infix_type * arg_type = parse_type(state);
if (!arg_type)
return INFIX_ERROR_INVALID_ARGUMENT;
arg_node * node = infix_arena_calloc(state->arena, 1, sizeof(arg_node), _Alignof(arg_node));
if (!node) {
_infix_set_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
return INFIX_ERROR_ALLOCATION_FAILED;
}
node->arg.type = arg_type;
node->arg.name = name;
node->next = nullptr;
if (!head)
head = tail = node;
else {
tail->next = node;
tail = node;
}
num_args++;
skip_whitespace(state);
if (*state->p == ',') {
state->p++;
skip_whitespace(state);
if (*state->p == ')' || *state->p == ';') { // Trailing comma error.
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return INFIX_ERROR_INVALID_ARGUMENT;
}
}
else if (*state->p != ')' && *state->p != ';') {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return INFIX_ERROR_INVALID_ARGUMENT;
}
else
break;
}
}
*out_num_fixed_args = num_args;
// Parse Variadic Arguments
if (*state->p == ';') {
state->p++;
infix/src/core/signature.c view on Meta::CPAN
while (1) {
skip_whitespace(state);
if (*state->p == ')')
break;
const char * name = parse_optional_name_prefix(state);
infix_type * arg_type = parse_type(state);
if (!arg_type)
return INFIX_ERROR_INVALID_ARGUMENT;
arg_node * node = infix_arena_calloc(state->arena, 1, sizeof(arg_node), _Alignof(arg_node));
if (!node) {
_infix_set_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
return INFIX_ERROR_ALLOCATION_FAILED;
}
node->arg.type = arg_type;
node->arg.name = name;
node->next = nullptr;
if (!head)
head = tail = node;
else {
tail->next = node;
tail = node;
}
num_args++;
skip_whitespace(state);
if (*state->p == ',') {
state->p++;
skip_whitespace(state);
if (*state->p == ')') { // Trailing comma error.
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return INFIX_ERROR_INVALID_ARGUMENT;
}
}
else if (*state->p != ')') {
_infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
return INFIX_ERROR_INVALID_ARGUMENT;
}
else
break;
}
}
}
skip_whitespace(state);
if (*state->p != ')') {
_infix_set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
return INFIX_ERROR_INVALID_ARGUMENT;
}
state->p++;
// Parse Return Type
skip_whitespace(state);
if (state->p[0] != '-' || state->p[1] != '>') {
_infix_set_parser_error(state, INFIX_CODE_MISSING_RETURN_TYPE);
return INFIX_ERROR_INVALID_ARGUMENT;
}
state->p += 2;
*out_ret_type = parse_type(state);
if (!*out_ret_type)
return INFIX_ERROR_INVALID_ARGUMENT;
// Convert linked list of args to a flat array.
infix_function_argument * args = (num_args > 0)
? infix_arena_calloc(state->arena, num_args, sizeof(infix_function_argument), _Alignof(infix_function_argument))
: nullptr;
if (num_args > 0 && !args) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
return INFIX_ERROR_ALLOCATION_FAILED;
}
arg_node * current = head;
for (size_t i = 0; i < num_args; i++) {
args[i] = current->arg;
current = current->next;
}
*out_args = args;
*out_num_args = num_args;
return INFIX_SUCCESS;
}
// High-Level API Implementation
/**
* @internal
* @brief The internal entry point for the signature parser (the "Parse" stage).
*
* This function takes a signature string and produces a raw, unresolved type
* graph in a new, temporary arena. It is the core parsing logic, separated from the
* higher-level functions that manage the full data pipeline. It is careful not to
* modify the global error context string (`g_infix_last_signature_context`), which
* is the responsibility of its public API callers.
*
* @param[out] out_type On success, receives the parsed type graph.
* @param[out] out_arena On success, receives the temporary arena holding the graph. The caller is responsible for
* destroying it.
* @param[in] signature The signature string to parse.
* @return `INFIX_SUCCESS` on success.
*/
c23_nodiscard infix_status _infix_parse_type_internal(infix_type ** out_type,
infix_arena_t ** out_arena,
const char * signature) {
if (!out_type || !out_arena) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
if (!signature || *signature == '\0') {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_EMPTY_SIGNATURE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
// The top-level public API is responsible for setting g_infix_last_signature_context.
*out_arena = infix_arena_create(4096);
if (!*out_arena) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
parser_state state = {.p = signature, .start = signature, .arena = *out_arena, .depth = 0};
infix_type * type = parse_type(&state);
if (type) {
skip_whitespace(&state);
// After successfully parsing a type, ensure there is no trailing junk.
if (state.p[0] != '\0') {
_infix_set_parser_error(&state, INFIX_CODE_UNEXPECTED_TOKEN);
type = nullptr;
}
}
if (!type) {
// If parsing failed at any point, clean up the temporary arena.
infix_arena_destroy(*out_arena);
*out_arena = nullptr;
*out_type = nullptr;
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix/src/core/signature.c view on Meta::CPAN
* @param[out] out_arena On success, receives a pointer to the arena holding the type. The caller is responsible for
* freeing this with `infix_arena_destroy`.
* @param[in] signature The signature string of the data type (e.g., `"{id:int, name:*char}"`).
* @param[in] registry An optional type registry for resolving named types. Can be `nullptr`.
* @return `INFIX_SUCCESS` on success.
*/
c23_nodiscard infix_status infix_type_from_signature(infix_type ** out_type,
infix_arena_t ** out_arena,
const char * signature,
infix_registry_t * registry) {
_infix_clear_error();
g_infix_last_signature_context = signature; // Set context for rich error reporting.
// "Parse" stage: Create a raw, unresolved type graph in a temporary arena.
infix_type * raw_type = nullptr;
infix_arena_t * parser_arena = nullptr;
infix_status status = _infix_parse_type_internal(&raw_type, &parser_arena, signature);
if (status != INFIX_SUCCESS)
return status;
// Create the final arena that will be returned to the caller.
*out_arena = infix_arena_create(4096);
if (!*out_arena) {
infix_arena_destroy(parser_arena);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
// "Copy" stage: Deep copy the raw graph into the final arena.
infix_type * final_type = _copy_type_graph_to_arena(*out_arena, raw_type);
infix_arena_destroy(parser_arena); // The temporary graph is no longer needed.
if (!final_type) {
infix_arena_destroy(*out_arena);
*out_arena = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
// "Resolve" stage: Replace all named references (`@Name`) with concrete types.
status = _infix_resolve_type_graph_inplace(&final_type, registry);
if (status != INFIX_SUCCESS) {
infix_arena_destroy(*out_arena);
*out_arena = nullptr;
*out_type = nullptr;
}
else {
infix/src/core/signature.c view on Meta::CPAN
* @param[in] registry An optional type registry.
* @return `INFIX_SUCCESS` on success.
*/
c23_nodiscard infix_status infix_signature_parse(const char * signature,
infix_arena_t ** out_arena,
infix_type ** out_ret_type,
infix_function_argument ** out_args,
size_t * out_num_args,
size_t * out_num_fixed_args,
infix_registry_t * registry) {
_infix_clear_error();
//
if (!signature) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
if (*signature == '\0') {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_EMPTY_SIGNATURE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
if (!out_arena || !out_ret_type || !out_args || !out_num_args || !out_num_fixed_args) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
g_infix_last_signature_context = signature;
// Parse stage
infix_type * raw_func_type = nullptr;
infix_arena_t * parser_arena = nullptr;
infix_status status = _infix_parse_type_internal(&raw_func_type, &parser_arena, signature);
if (status != INFIX_SUCCESS)
return status;
if (raw_func_type->category != INFIX_TYPE_REVERSE_TRAMPOLINE) {
infix_arena_destroy(parser_arena);
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
// Create final arena
*out_arena = infix_arena_create(8192);
if (!*out_arena) {
infix_arena_destroy(parser_arena);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
// "Copy" stage
infix_type * final_func_type = _copy_type_graph_to_arena(*out_arena, raw_func_type);
infix_arena_destroy(parser_arena);
if (!final_func_type) {
infix_arena_destroy(*out_arena);
*out_arena = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
// Resolve and layout stages
status = _infix_resolve_type_graph_inplace(&final_func_type, registry);
if (status != INFIX_SUCCESS) {
infix_arena_destroy(*out_arena);
*out_arena = nullptr;
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix/src/core/signature.c view on Meta::CPAN
// Type Printing Logic
/**
* @internal
* @struct printer_state
* @brief Holds the state for the recursive type-to-string printer.
*/
typedef struct {
char * p; /**< The current write position in the output buffer. */
size_t remaining; /**< The number of bytes remaining in the buffer. */
infix_status status; /**< The current status, set to an error if the buffer is too small. */
// Itanium mangling state
const void * itanium_subs[64]; /**< Dictionary of substitutable components. */
size_t itanium_sub_count; /**< Number of components in the dictionary. */
// MSVC mangling state
const infix_type * msvc_types[10]; /**< First 10 encountered types for back-referencing. */
size_t msvc_type_count; /**< Number of types encountered. */
} printer_state;
/**
* @internal
* @brief A safe `vsnprintf` wrapper for building the signature string.
* Updates the printer state and sets an error on buffer overflow.
* @param[in,out] state The printer state.
* @param[in] fmt The `printf`-style format string.
* @param[in] ... Arguments for the format string.
*/
static void _print(printer_state * state, const char * fmt, ...) {
if (state->status != INFIX_SUCCESS)
return;
va_list args;
va_start(args, fmt);
int written = vsnprintf(state->p, state->remaining, fmt, args);
va_end(args);
if (written < 0 || (size_t)written >= state->remaining)
// If snprintf failed or would have overflowed, mark an error.
state->status = INFIX_ERROR_INVALID_ARGUMENT;
else {
state->p += written;
state->remaining -= written;
}
}
// Forward declaration for mutual recursion in printers.
static void _infix_type_print_signature_recursive(printer_state * state, const infix_type * type);
static void _infix_type_print_itanium_recursive(printer_state * state, const infix_type * type);
static void _infix_type_print_msvc_recursive(printer_state * state, const infix_type * type);
infix/src/core/signature.c view on Meta::CPAN
* @param[out] buffer The output buffer to write the string into.
* @param[in] buffer_size The size of the output buffer.
* @param[in] type The `infix_type` to print.
* @param[in] dialect The desired output format dialect.
* @return `INFIX_SUCCESS` on success, or `INFIX_ERROR_INVALID_ARGUMENT` if the buffer is too small.
*/
INFIX_API c23_nodiscard infix_status infix_type_print(char * buffer,
size_t buffer_size,
const infix_type * type,
infix_print_dialect_t dialect) {
_infix_clear_error();
if (!buffer || buffer_size == 0 || !type) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
printer_state state = {buffer, buffer_size, INFIX_SUCCESS, {0}, 0, {0}, 0};
*buffer = '\0';
if (dialect == INFIX_DIALECT_SIGNATURE)
_infix_type_print_signature_recursive(&state, type);
else if (dialect == INFIX_DIALECT_ITANIUM_MANGLING)
_infix_type_print_itanium_recursive(&state, type);
else if (dialect == INFIX_DIALECT_MSVC_MANGLING)
_infix_type_print_msvc_recursive(&state, type);
infix/src/core/signature.c view on Meta::CPAN
if (state.status == INFIX_SUCCESS) {
if (state.remaining > 0)
*state.p = '\0'; // Null-terminate if there is space.
else {
// Buffer was exactly full. Ensure null termination at the very end.
buffer[buffer_size - 1] = '\0';
return INFIX_ERROR_INVALID_ARGUMENT; // Indicate truncation.
}
}
else if (buffer_size > 0)
// Ensure null termination even on error (e.g., buffer too small).
buffer[buffer_size - 1] = '\0';
return state.status;
}
/**
* @brief Serializes a function signature's components into a string.
* @param[out] buffer The output buffer.
* @param[in] buffer_size The size of the output buffer.
* @param[in] function_name Optional name for dialects that support it.
* @param[in] ret_type The return type.
* @param[in] args The array of arguments.
infix/src/core/signature.c view on Meta::CPAN
* @return `INFIX_SUCCESS` on success, or `INFIX_ERROR_INVALID_ARGUMENT` if the buffer is too small.
*/
INFIX_API c23_nodiscard infix_status infix_function_print(char * buffer,
size_t buffer_size,
const char * function_name,
const infix_type * ret_type,
const infix_function_argument * args,
size_t num_args,
size_t num_fixed_args,
infix_print_dialect_t dialect) {
_infix_clear_error();
if (!buffer || buffer_size == 0 || !ret_type || (num_args > 0 && !args)) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
printer_state state = {buffer, buffer_size, INFIX_SUCCESS, {0}, 0, {0}, 0};
*buffer = '\0';
if (dialect == INFIX_DIALECT_SIGNATURE) {
(void)function_name; // Unused
_print(&state, "(");
for (size_t i = 0; i < num_fixed_args; ++i) {
if (i > 0)
_print(&state, ",");
infix/src/core/signature.c view on Meta::CPAN
*
* @details The output format is a sequence of definitions (e.g., `@Name = { ... };`) separated
* by newlines, suitable for logging, debugging, or saving to a file. This function
* will not print forward declarations that have not been fully defined. The order of
* definitions in the output string is not guaranteed.
*
* @param[out] buffer The output buffer to write the string into.
* @param[in] buffer_size The size of the output buffer.
* @param[in] registry The registry to serialize.
* @return `INFIX_SUCCESS` on success, or `INFIX_ERROR_INVALID_ARGUMENT` if the buffer is too small
* or another error occurs.
*/
c23_nodiscard infix_status infix_registry_print(char * buffer, size_t buffer_size, const infix_registry_t * registry) {
if (!buffer || buffer_size == 0 || !registry)
return INFIX_ERROR_INVALID_ARGUMENT;
printer_state state = {buffer, buffer_size, INFIX_SUCCESS, {0}, 0, {0}, 0};
*state.p = '\0';
// Iterate through all buckets and their chains.
for (size_t i = 0; i < registry->num_buckets; ++i) {
for (const _infix_registry_entry_t * entry = registry->buckets[i]; entry != nullptr; entry = entry->next) {
// Only print fully defined types, not forward declarations.
infix/src/core/type_registry.c view on Meta::CPAN
*
* 2. `_infix_resolve_type_graph_inplace()`: This internal function implements the
* **"Resolve"** stage of the library's core "Parse -> Copy -> Resolve -> Layout"
* data pipeline. It traverses a type graph and replaces all named placeholders
* (`@MyType`) with direct pointers to the canonical type objects stored in the
* registry.
*/
#include "common/infix_internals.h"
#include <ctype.h>
#include <string.h>
/** @internal A thread-local pointer to the full signature string for rich error reporting. */
extern INFIX_TLS const char * g_infix_last_signature_context;
/**
* @internal
* @brief The initial number of buckets for the registry's internal hash table.
* @details A prime number is chosen to help with better key distribution,
* reducing the likelihood of hash collisions.
*/
#define INITIAL_REGISTRY_BUCKETS 61
/**
* @internal
infix/src/core/type_registry.c view on Meta::CPAN
// Check load factor. If num_items > num_buckets * 0.75, resize.
// Equivalent integer math: num_items * 4 > num_buckets * 3
if (registry->num_items * 4 > registry->num_buckets * 3)
_registry_rehash(registry);
uint64_t hash = _registry_hash_string(name);
size_t index = hash % registry->num_buckets;
_infix_registry_entry_t * new_entry =
infix_arena_alloc(registry->arena, sizeof(_infix_registry_entry_t), _Alignof(_infix_registry_entry_t));
if (!new_entry) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
size_t name_len = strlen(name) + 1;
char * name_copy = infix_arena_alloc(registry->arena, name_len, 1);
if (!name_copy) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
infix_memcpy((void *)name_copy, name, name_len);
new_entry->name = name_copy;
new_entry->hash = hash;
new_entry->type = nullptr;
new_entry->is_forward_declaration = false;
// Prepend to the linked list in the bucket.
new_entry->next = registry->buckets[index];
registry->buckets[index] = new_entry;
infix/src/core/type_registry.c view on Meta::CPAN
*
* A registry acts as a dictionary for `infix` types, allowing you to define complex
* structs, unions, or aliases once and refer to them by name (e.g., `@MyStruct`)
* in any signature string. This is essential for managing complex, recursive, or
* mutually-dependent types.
*
* @return A pointer to the new registry, or `nullptr` on allocation failure. The returned
* handle must be freed with `infix_registry_destroy`.
*/
INFIX_API c23_nodiscard infix_registry_t * infix_registry_create(void) {
_infix_clear_error();
infix_registry_t * registry = infix_malloc(sizeof(infix_registry_t));
if (!registry) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
registry->arena = infix_arena_create(16384); // Default initial size
if (!registry->arena) {
infix_free(registry);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
registry->is_external_arena = false; // Mark that this arena is owned by the registry.
registry->num_buckets = INITIAL_REGISTRY_BUCKETS;
registry->buckets = infix_calloc(registry->num_buckets, sizeof(_infix_registry_entry_t *));
if (!registry->buckets) {
infix_arena_destroy(registry->arena);
infix_free(registry);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
registry->num_items = 0;
return registry;
}
/**
* @brief Creates a new, empty named type registry that allocates from a user-provided arena.
*
* A registry acts as a dictionary for `infix` types, allowing you to define complex
* structs, unions, or aliases once and refer to them by name (e.g., `@MyStruct`)
* in any signature string. This is essential for managing complex, recursive, or
* mutually-dependent types.
*
* @param[in] arena The arena to allocate from.
* @return A pointer to the new registry, or `nullptr` on allocation failure. The returned
* handle must be freed with `infix_registry_destroy`.
*/
INFIX_API c23_nodiscard infix_registry_t * infix_registry_create_in_arena(infix_arena_t * arena) {
_infix_clear_error();
if (!arena) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return nullptr;
}
infix_registry_t * registry = infix_malloc(sizeof(infix_registry_t));
if (!registry) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
registry->arena = arena;
registry->is_external_arena = true; // Mark the arena as user-owned
registry->num_buckets = INITIAL_REGISTRY_BUCKETS;
registry->buckets = infix_calloc(registry->num_buckets, sizeof(_infix_registry_entry_t *));
if (!registry->buckets) {
infix_free(registry);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return nullptr;
}
registry->num_items = 0;
return registry;
}
/**
* @brief Creates a deep copy of an existing registry.
* @param src The source registry to clone.
* @return A new registry containing deep copies of all types in 'src', or NULL on failure.
*/
infix/src/core/type_registry.c view on Meta::CPAN
infix_type * type = *type_ptr;
// Cycle detection: If we've seen this node before, we're in a cycle.
// Return success to break the loop.
for (resolve_memo_node_t * node = *memo_head; node != nullptr; node = node->next)
if (node->src == type)
return INFIX_SUCCESS;
// Allocate the memoization node from the stable temporary arena.
resolve_memo_node_t * memo_node =
infix_arena_alloc(temp_arena, sizeof(resolve_memo_node_t), _Alignof(resolve_memo_node_t));
if (!memo_node) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
memo_node->src = type;
memo_node->next = *memo_head;
*memo_head = memo_node;
if (type->category == INFIX_TYPE_NAMED_REFERENCE) {
if (!registry) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNRESOLVED_NAMED_TYPE, type->source_offset);
return INFIX_ERROR_INVALID_ARGUMENT;
}
const char * name = type->meta.named_reference.name;
_infix_registry_entry_t * entry = _registry_lookup(registry, name);
if (!entry || !entry->type) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNRESOLVED_NAMED_TYPE, type->source_offset);
return INFIX_ERROR_INVALID_ARGUMENT;
}
*type_ptr = entry->type;
return INFIX_SUCCESS;
}
infix_status status = INFIX_SUCCESS;
switch (type->category) {
case INFIX_TYPE_POINTER:
status = _resolve_type_graph_inplace_recursive(
temp_arena, &type->meta.pointer_info.pointee_type, registry, memo_head);
infix/src/core/type_registry.c view on Meta::CPAN
* @internal
* @brief Public-internal wrapper for the recursive resolution function.
* @param[in,out] type_ptr A pointer to the root of the type graph to resolve.
* @param[in] registry The registry to use for lookups.
* @return `INFIX_SUCCESS` on success.
*/
c23_nodiscard infix_status _infix_resolve_type_graph_inplace(infix_type ** type_ptr, infix_registry_t * registry) {
// Create a temporary arena solely for the visited list's lifetime.
infix_arena_t * temp_arena = infix_arena_create(1024);
if (!temp_arena) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
resolve_memo_node_t * memo_head = nullptr;
infix_status status = _resolve_type_graph_inplace_recursive(temp_arena, type_ptr, registry, &memo_head);
infix_arena_destroy(temp_arena);
return status;
}
// Public API: Type Registration
/**
* @internal
* @struct _registry_parser_state_t
* @brief A minimal parser state for processing registry definition strings.
* @details This is distinct from the main signature `parser_state` because the
* grammar for definitions (`@Name=...;`) is much simpler and can be handled
* with a simpler, non-recursive parser.
*/
typedef struct {
const char * p; /**< The current position in the definition string. */
const char * start; /**< The start of the definition string for error reporting. */
} _registry_parser_state_t;
/**
* @internal
* @brief Skips whitespace and C++-style line comments in a definition string.
* @param state The parser state to advance.
*/
static void _registry_parser_skip_whitespace(_registry_parser_state_t * state) {
while (1) {
while (isspace((unsigned char)*state->p))
state->p++;
infix/src/core/type_registry.c view on Meta::CPAN
* placeholder already exists from Pass 1, the new definition is copied *in-place*
* over the placeholder to preserve existing pointers.
*
* - **Pass 3 (Resolve & Layout):** The function iterates through all the newly
* created types from Pass 2. For each one, it performs the "Resolve" and
* "Layout" stages. Because all type graphs now exist (either fully defined or
* as valid placeholders), resolution succeeds for all recursive references.
*
* @param[in] registry The registry to populate.
* @param[in] definitions A semicolon-separated string of definitions.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
INFIX_API c23_nodiscard infix_status infix_register_types(infix_registry_t * registry, const char * definitions) {
_infix_clear_error();
if (!registry || !definitions) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
_registry_parser_state_t state = {.p = definitions, .start = definitions};
g_infix_last_signature_context = definitions;
// A temporary structure to hold information about each definition found in Pass 1.
struct def_info {
_infix_registry_entry_t * entry;
const char * def_body_start;
size_t def_body_len;
};
size_t defs_capacity = 64; // Start with an initial capacity.
struct def_info * defs_found = infix_malloc(sizeof(struct def_info) * defs_capacity);
if (!defs_found) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
size_t num_defs_found = 0;
infix_status final_status = INFIX_SUCCESS;
// Pass 1: Scan & Index all names and their definition bodies.
while (*state.p != '\0') {
_registry_parser_skip_whitespace(&state);
if (*state.p == '\0')
break;
if (*state.p != '@') {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
final_status = INFIX_ERROR_INVALID_ARGUMENT;
goto cleanup;
}
state.p++;
char name_buffer[256];
if (!_registry_parser_parse_name(&state, name_buffer, sizeof(name_buffer))) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
final_status = INFIX_ERROR_INVALID_ARGUMENT;
goto cleanup;
}
_infix_registry_entry_t * entry = _registry_lookup(registry, name_buffer);
_registry_parser_skip_whitespace(&state);
if (*state.p == '=') { // This is a full definition.
state.p++;
_registry_parser_skip_whitespace(&state);
// It's an error to redefine a type that wasn't a forward declaration.
if (entry && !entry->is_forward_declaration) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
final_status = INFIX_ERROR_INVALID_ARGUMENT;
goto cleanup;
}
if (!entry) { // If it doesn't exist, create it.
entry = _registry_insert(registry, name_buffer);
if (!entry) {
final_status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
}
if (num_defs_found >= defs_capacity) {
size_t new_capacity = defs_capacity * 2;
struct def_info * new_defs = infix_realloc(defs_found, sizeof(struct def_info) * new_capacity);
if (!new_defs) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, state.p - state.start);
final_status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
defs_found = new_defs;
defs_capacity = new_capacity;
}
// Find the end of the type definition body (the next top-level ';').
defs_found[num_defs_found].entry = entry;
defs_found[num_defs_found].def_body_start = state.p;
int nest_level = 0;
infix/src/core/type_registry.c view on Meta::CPAN
if (*body_end == '}' || *body_end == '>' || *body_end == ')' || *body_end == ']')
nest_level--;
body_end++;
}
defs_found[num_defs_found].def_body_len = body_end - state.p;
state.p = body_end;
num_defs_found++;
}
else if (*state.p == ';') { // This is a forward declaration.
if (entry) {
// If the type is already fully defined, re-declaring it as a forward decl is an error.
if (!entry->is_forward_declaration) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
final_status = INFIX_ERROR_INVALID_ARGUMENT;
goto cleanup;
}
// If it's already a forward declaration, this is a no-op.
}
else {
entry = _registry_insert(registry, name_buffer);
if (!entry) {
final_status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
infix/src/core/type_registry.c view on Meta::CPAN
entry->type->is_incomplete = true; // Mark as incomplete
entry->type->arena = registry->arena;
entry->type->category = INFIX_TYPE_STRUCT;
entry->type->size = 0;
entry->type->alignment = 1; // Minimal alignment to pass basic validation
entry->type->name = entry->name;
}
entry->is_forward_declaration = true;
}
else {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
final_status = INFIX_ERROR_INVALID_ARGUMENT;
goto cleanup;
}
if (*state.p == ';')
state.p++;
}
// Use a single temporary arena for ALL definitions in this batch to reduce malloc pressure.
infix_arena_t * parser_arena = infix_arena_create(65536);
if (!parser_arena) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
final_status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
// Pass 2: Parse the bodies of all found definitions into the registry.
for (size_t i = 0; i < num_defs_found; ++i) {
_infix_registry_entry_t * entry = defs_found[i].entry;
// FAST-PATH: Check if the definition body is just a single primitive keyword.
// This avoids the overhead of the recursive-descent parser for simple aliases.
infix/src/core/type_registry.c view on Meta::CPAN
infix_type * raw_type = nullptr;
if (fast_primitive) {
raw_type = fast_primitive;
}
else {
// SLOW-PATH: Fall back to full signature parser.
// Make a temporary, null-terminated copy of the definition body substring.
char * body_copy = infix_malloc(defs_found[i].def_body_len + 1);
if (!body_copy) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
final_status = INFIX_ERROR_ALLOCATION_FAILED;
infix_arena_destroy(parser_arena);
goto cleanup;
}
infix_memcpy(body_copy, defs_found[i].def_body_start, defs_found[i].def_body_len);
body_copy[defs_found[i].def_body_len] = '\0';
parser_state state = {.p = body_copy, .start = body_copy, .arena = parser_arena, .depth = 0};
raw_type = parse_type(&state);
if (raw_type) {
skip_whitespace(&state);
if (*state.p != '\0') {
_infix_set_parser_error(&state, INFIX_CODE_UNEXPECTED_TOKEN);
raw_type = nullptr;
}
}
if (!raw_type) {
infix_error_details_t err = infix_get_last_error();
_infix_set_error(err.category, err.code, (defs_found[i].def_body_start - definitions) + err.position);
final_status = INFIX_ERROR_INVALID_ARGUMENT;
infix_free(body_copy);
infix_arena_destroy(parser_arena);
goto cleanup;
}
infix_free(body_copy);
}
const infix_type * type_to_alias = raw_type;
// If the RHS is another named type (e.g., @MyAlias = @ExistingType),
// we need to resolve it first to get the actual type we're aliasing.
if (raw_type->category == INFIX_TYPE_NAMED_REFERENCE) {
_infix_registry_entry_t * existing_entry = _registry_lookup(registry, raw_type->meta.named_reference.name);
if (existing_entry && existing_entry->type)
type_to_alias = existing_entry->type;
else {
size_t relative_pos = raw_type->source_offset;
_infix_set_error(INFIX_CATEGORY_PARSER,
INFIX_CODE_UNRESOLVED_NAMED_TYPE,
(size_t)(defs_found[i].def_body_start - definitions) + relative_pos);
final_status = INFIX_ERROR_INVALID_ARGUMENT;
infix_arena_destroy(parser_arena);
goto cleanup;
}
}
// Prepare the new definition.
infix_type * new_def = nullptr;
if (!type_to_alias->is_arena_allocated) {
infix/src/core/type_registry.c view on Meta::CPAN
if (new_def) {
*new_def = *type_to_alias;
new_def->is_arena_allocated = true;
new_def->arena = registry->arena;
}
}
else // Dynamic type: deep copy.
new_def = _copy_type_graph_to_arena(registry->arena, type_to_alias);
if (!new_def) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
final_status = INFIX_ERROR_ALLOCATION_FAILED;
infix_arena_destroy(parser_arena);
goto cleanup;
}
// Update the entry. If a placeholder already exists (from forward decl), we MUST update it in-place
// to preserve pointers from other types that have already resolved to it.
if (entry->type) {
// Struct-copy the new definition into the existing placeholder memory.
*entry->type = *new_def;
infix/src/core/type_registry.c view on Meta::CPAN
}
infix_arena_destroy(parser_arena);
// Pass 3: Resolve and layout all the newly defined types.
for (size_t i = 0; i < num_defs_found; ++i) {
_infix_registry_entry_t * entry = defs_found[i].entry;
if (entry->type) {
// "Resolve" and "Layout" steps.
if (_infix_resolve_type_graph_inplace(&entry->type, registry) != INFIX_SUCCESS) {
// The error was set inside resolve (relative to body).
// We need to re-base it to the full definitions string.
infix_error_details_t err = infix_get_last_error();
size_t body_offset = defs_found[i].def_body_start - definitions;
_infix_set_error(err.category, err.code, body_offset + err.position);
final_status = INFIX_ERROR_INVALID_ARGUMENT;
goto cleanup;
}
_infix_type_recalculate_layout(entry->type);
}
}
cleanup:
infix_free(defs_found);
return final_status;
}
infix/src/core/types.c view on Meta::CPAN
if (align > max_alignment && !type->meta.aggregate_info.is_packed)
max_alignment = align;
// Zero-width bitfield or type mismatch or doesn't fit -> start new unit
if (member->bit_width == 0 || !in_bitfield || mtype->size != current_unit_size ||
current_unit_bits_used + member->bit_width > mtype->size * 8) {
// Align to start of new unit
size_t start = _infix_align_up(current_byte_offset, align);
if (start < current_byte_offset) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INTEGER_OVERFLOW, 0);
return false;
}
current_unit_offset = start;
current_unit_size = mtype->size;
current_unit_bits_used = 0;
in_bitfield = true;
current_byte_offset = current_unit_offset;
}
infix/src/core/types.c view on Meta::CPAN
in_bitfield = false;
size_t align = mtype->alignment;
if (align == 0)
align = 1;
if (align > max_alignment && !type->meta.aggregate_info.is_packed)
max_alignment = align;
size_t aligned = _infix_align_up(current_byte_offset, align);
if (aligned < current_byte_offset) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INTEGER_OVERFLOW, 0);
return false;
}
current_byte_offset = aligned;
member->offset = current_byte_offset;
member->bit_offset = 0;
continue;
}
// Standard Member
in_bitfield = false;
size_t align = mtype->alignment;
if (align == 0)
align = 1;
if (align > max_alignment && !type->meta.aggregate_info.is_packed)
max_alignment = align;
size_t aligned = _infix_align_up(current_byte_offset, align);
if (aligned < current_byte_offset) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INTEGER_OVERFLOW, 0);
return false;
}
current_byte_offset = aligned;
member->offset = current_byte_offset;
member->bit_offset = 0;
if (current_byte_offset > SIZE_MAX - mtype->size) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INTEGER_OVERFLOW, 0);
return false;
}
current_byte_offset += mtype->size;
}
}
// If it is packed, the alignment is explicitly determined by the user.
if (type->meta.aggregate_info.is_packed)
max_alignment = type->alignment;
infix/src/core/types.c view on Meta::CPAN
* 1. Validating that member types are not null.
* 2. Allocating the main `infix_type` object from the arena.
* 3. Allocating a new array for the members within the arena and copying the
* user-provided member data into it.
*
* @param[in] arena The arena to allocate from.
* @param[out] out_type The output pointer for the new `infix_type`.
* @param[out] out_arena_members The output pointer for the newly copied members array.
* @param[in] members The user-provided array of members.
* @param[in] num_members The number of members in the array.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
static infix_status _create_aggregate_setup(infix_arena_t * arena,
infix_type ** out_type,
infix_struct_member ** out_arena_members,
infix_struct_member * members,
size_t num_members) {
if (out_type == nullptr)
return INFIX_ERROR_INVALID_ARGUMENT;
// Pre-flight check: ensure all provided member types are valid.
for (size_t i = 0; i < num_members; ++i) {
if (members[i].type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INVALID_MEMBER_TYPE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
}
infix_type * type = infix_arena_calloc(arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
infix_struct_member * arena_members = nullptr;
if (num_members > 0) {
arena_members =
infix_arena_alloc(arena, sizeof(infix_struct_member) * num_members, _Alignof(infix_struct_member));
if (arena_members == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
infix_memcpy(arena_members, members, sizeof(infix_struct_member) * num_members);
}
*out_type = type;
*out_arena_members = arena_members;
return INFIX_SUCCESS;
}
/**
* @brief Creates a new pointer type that points to a specific type.
* @param[in] arena The arena to allocate the new type object in.
* @param[out] out_type A pointer to receive the created `infix_type`.
* @param[in] pointee_type The `infix_type` that the new pointer will point to.
* @return `INFIX_SUCCESS` on success, or an error code on allocation failure.
*/
c23_nodiscard infix_status infix_type_create_pointer_to(infix_arena_t * arena,
infix_type ** out_type,
infix_type * pointee_type) {
_infix_clear_error();
if (!out_type || !pointee_type) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = infix_arena_calloc(arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
// Start by copying the layout of a generic pointer.
*type = *infix_type_create_pointer();
// Mark it as arena-allocated so it can be deep-copied and freed correctly.
type->is_arena_allocated = true;
// Set the specific pointee type.
type->meta.pointer_info.pointee_type = pointee_type;
*out_type = type;
return INFIX_SUCCESS;
infix/src/core/types.c view on Meta::CPAN
* @param[in] arena The arena for allocation.
* @param[out] out_type A pointer to receive the new `infix_type`.
* @param[in] element_type The type of each element in the array.
* @param[in] num_elements The number of elements.
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_type_create_array(infix_arena_t * arena,
infix_type ** out_type,
infix_type * element_type,
size_t num_elements) {
_infix_clear_error();
if (out_type == nullptr || element_type == nullptr) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
if (element_type->size > 0 && num_elements > SIZE_MAX / element_type->size) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INTEGER_OVERFLOW, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = infix_arena_calloc(arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
type->is_arena_allocated = true;
type->category = INFIX_TYPE_ARRAY;
type->meta.array_info.element_type = element_type;
type->meta.array_info.num_elements = num_elements;
type->meta.array_info.is_flexible = false;
// An array's alignment is the same as its element's alignment.
type->alignment = element_type->alignment;
type->size = element_type->size * num_elements;
infix/src/core/types.c view on Meta::CPAN
/**
* @brief Creates a new flexible array member type (`[?:type]`).
* @param[in] arena The arena for allocation.
* @param[out] out_type A pointer to receive the new `infix_type`.
* @param[in] element_type The type of the array elements.
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_type_create_flexible_array(infix_arena_t * arena,
infix_type ** out_type,
infix_type * element_type) {
_infix_clear_error();
if (out_type == nullptr || element_type == nullptr) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
// Flexible arrays of incomplete types (size 0) are generally not allowed.
if (element_type->category == INFIX_TYPE_VOID || element_type->size == 0) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INVALID_MEMBER_TYPE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = infix_arena_calloc(arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
type->is_arena_allocated = true;
type->category = INFIX_TYPE_ARRAY;
type->meta.array_info.element_type = element_type;
type->meta.array_info.num_elements = 0;
type->meta.array_info.is_flexible = true; // Mark as flexible
// A flexible array member itself has size 0 within the struct (it hangs off the end).
// However, its alignment requirement affects the struct.
infix/src/core/types.c view on Meta::CPAN
* @brief Creates a new enum type with a specified underlying integer type.
* @param[in] arena The arena for allocation.
* @param[out] out_type A pointer to receive the new `infix_type`.
* @param[in] underlying_type The integer `infix_type` (e.g., from
* `infix_type_create_primitive(INFIX_PRIMITIVE_SINT32)`).
* @return `INFIX_SUCCESS` on success, or `INFIX_ERROR_INVALID_ARGUMENT` if the underlying type is not an integer.
*/
INFIX_API c23_nodiscard infix_status infix_type_create_enum(infix_arena_t * arena,
infix_type ** out_type,
infix_type * underlying_type) {
_infix_clear_error();
if (out_type == nullptr || underlying_type == nullptr) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
if (underlying_type->category != INFIX_TYPE_PRIMITIVE ||
underlying_type->meta.primitive_id > INFIX_PRIMITIVE_SINT128) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INVALID_MEMBER_TYPE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = infix_arena_calloc(arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
type->is_arena_allocated = true;
type->category = INFIX_TYPE_ENUM;
// An enum has the same memory layout as its underlying integer type.
type->size = underlying_type->size;
type->alignment = underlying_type->alignment;
type->meta.enum_info.underlying_type = underlying_type;
*out_type = type;
return INFIX_SUCCESS;
infix/src/core/types.c view on Meta::CPAN
/**
* @brief Creates a new `_Complex` number type.
* @param[in] arena The arena for allocation.
* @param[out] out_type A pointer to receive the new `infix_type`.
* @param[in] base_type The base floating-point type (`float` or `double`).
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_type_create_complex(infix_arena_t * arena,
infix_type ** out_type,
infix_type * base_type) {
_infix_clear_error();
if (out_type == nullptr || base_type == nullptr || (!is_float(base_type) && !is_double(base_type))) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = infix_arena_calloc(arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
type->is_arena_allocated = true;
type->category = INFIX_TYPE_COMPLEX;
// A complex number is simply two floating-point numbers back-to-back.
type->size = base_type->size * 2;
type->alignment = base_type->alignment;
type->meta.complex_info.base_type = base_type;
*out_type = type;
return INFIX_SUCCESS;
infix/src/core/types.c view on Meta::CPAN
* @param[in] arena The arena for allocation.
* @param[out] out_type A pointer to receive the new `infix_type`.
* @param[in] element_type The primitive type of each element.
* @param[in] num_elements The number of elements in the vector.
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_type_create_vector(infix_arena_t * arena,
infix_type ** out_type,
infix_type * element_type,
size_t num_elements) {
_infix_clear_error();
if (out_type == nullptr || element_type == nullptr || element_type->category != INFIX_TYPE_PRIMITIVE) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
if (element_type->size > 0 && num_elements > SIZE_MAX / element_type->size) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INTEGER_OVERFLOW, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = infix_arena_calloc(arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
type->is_arena_allocated = true;
type->category = INFIX_TYPE_VECTOR;
type->meta.vector_info.element_type = element_type;
type->meta.vector_info.num_elements = num_elements;
type->size = element_type->size * num_elements;
// Vector alignment is its total size.
// This ensures __m128 is 16-byte aligned, __m256 is 32-byte, and __m512 is 64-byte.
type->alignment = type->size;
infix/src/core/types.c view on Meta::CPAN
* @param[in] arena The arena for allocation.
* @param[out] out_type A pointer to receive the new `infix_type`.
* @param[in] members An array of `infix_struct_member` describing the union's members.
* @param[in] num_members The number of members.
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_type_create_union(infix_arena_t * arena,
infix_type ** out_type,
infix_struct_member * members,
size_t num_members) {
_infix_clear_error();
infix_type * type = nullptr;
infix_struct_member * arena_members = nullptr;
infix_status status = _create_aggregate_setup(arena, &type, &arena_members, members, num_members);
if (status != INFIX_SUCCESS) {
*out_type = nullptr;
return status;
}
type->is_arena_allocated = true;
type->category = INFIX_TYPE_UNION;
type->meta.aggregate_info.members = arena_members;
infix/src/core/types.c view on Meta::CPAN
max_size = arena_members[i].type->size;
if (arena_members[i].type->alignment > max_alignment)
max_alignment = arena_members[i].type->alignment;
}
type->alignment = max_alignment;
// The total size is the size of the largest member, padded up to the required alignment.
type->size = _infix_align_up(max_size, max_alignment);
// Overflow check
if (type->size < max_size) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INTEGER_OVERFLOW, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
*out_type = type;
return INFIX_SUCCESS;
}
/**
* @brief Creates a new struct type from an array of members, calculating layout automatically.
* @param[in] arena The arena for allocation.
* @param[out] out_type A pointer to receive the new `infix_type`.
* @param[in] members An array of `infix_struct_member` describing the struct's members. The `offset` field is ignored.
* @param[in] num_members The number of members in the array.
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_type_create_struct(infix_arena_t * arena,
infix_type ** out_type,
infix_struct_member * members,
size_t num_members) {
_infix_clear_error();
infix_type * type = nullptr;
infix_struct_member * arena_members = nullptr;
infix_status status = _create_aggregate_setup(arena, &type, &arena_members, members, num_members);
if (status != INFIX_SUCCESS) {
*out_type = nullptr;
return status;
}
type->is_arena_allocated = true;
type->category = INFIX_TYPE_STRUCT;
type->meta.aggregate_info.members = arena_members;
infix/src/core/types.c view on Meta::CPAN
// But we can't create an arena here easily if we are in a strict context.
// So we do a simplified pass just like the old logic, ignoring complex bitfield rules for now.
// The proper bitfield layout happens in `_infix_type_recalculate_layout`.
for (size_t i = 0; i < num_members; ++i) {
infix_struct_member * member = &arena_members[i];
if (member->type->alignment == 0 && member->type->category != INFIX_TYPE_NAMED_REFERENCE &&
!(member->type->category == INFIX_TYPE_ARRAY && member->type->meta.array_info.is_flexible)) {
if (member->type->category != INFIX_TYPE_ARRAY) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INVALID_MEMBER_TYPE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
}
}
// Calculate Layout (including bitfields and FAMs)
if (!_layout_struct(type)) {
*out_type = nullptr;
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix/src/core/types.c view on Meta::CPAN
* @param[in] members An array of `infix_struct_member` with pre-calculated offsets.
* @param[in] num_members The number of members.
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_type_create_packed_struct(infix_arena_t * arena,
infix_type ** out_type,
size_t total_size,
size_t alignment,
infix_struct_member * members,
size_t num_members) {
_infix_clear_error();
if (out_type == nullptr || (num_members > 0 && members == nullptr)) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
// Validate alignment is power-of-two AND within sane limits.
if (alignment == 0 || (alignment & (alignment - 1)) != 0) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_INVALID_ALIGNMENT, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
if (alignment > INFIX_MAX_ALIGNMENT) {
_infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_TYPE_TOO_LARGE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = infix_arena_calloc(arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
infix_struct_member * arena_members = nullptr;
if (num_members > 0) {
arena_members =
infix_arena_alloc(arena, sizeof(infix_struct_member) * num_members, _Alignof(infix_struct_member));
if (arena_members == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
infix_memcpy(arena_members, members, sizeof(infix_struct_member) * num_members);
}
type->is_arena_allocated = true;
type->size = total_size;
type->alignment = alignment;
type->category = INFIX_TYPE_STRUCT; // Packed structs are still fundamentally structs.
type->meta.aggregate_info.members = arena_members;
type->meta.aggregate_info.num_members = num_members;
infix/src/core/types.c view on Meta::CPAN
* @param[in] arena The arena for allocation.
* @param[out] out_type A pointer to receive the new `infix_type`.
* @param[in] name The name of the type (e.g., "MyStruct").
* @param[in] agg_cat The expected category of the aggregate (struct or union).
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_type_create_named_reference(infix_arena_t * arena,
infix_type ** out_type,
const char * name,
infix_aggregate_category_t agg_cat) {
_infix_clear_error();
if (out_type == nullptr || name == nullptr) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = infix_arena_calloc(arena, 1, sizeof(infix_type), _Alignof(infix_type));
if (type == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
// The name must be copied into the arena to ensure its lifetime matches the type's.
size_t name_len = strlen(name) + 1;
char * arena_name = infix_arena_alloc(arena, name_len, 1);
if (arena_name == nullptr) {
*out_type = nullptr;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
infix_memcpy(arena_name, name, name_len);
type->is_arena_allocated = true;
type->category = INFIX_TYPE_NAMED_REFERENCE;
type->size = 0; // Size and alignment are unknown until resolution.
type->alignment = 1; // Default to 1 to be safe in preliminary layout calculations.
type->meta.named_reference.name = arena_name;
type->meta.named_reference.aggregate_category = agg_cat;
*out_type = type;
infix/src/infix.c view on Meta::CPAN
* - **Improved Optimization:** Compilers can perform more aggressive cross-file
* optimizations, such as inlining functions defined in different `.c` files,
* potentially improving performance.
* - **Reduced Build Times:** For smaller to medium-sized projects, a unity build
* can be faster as it reduces the overhead of opening and closing files and
* parsing headers multiple times.
*
* @section inclusion_order Inclusion Order
*
* The order of inclusion is critical to respect dependencies between modules. The
* files are ordered from the most foundational components (like error handling and
* memory allocation) to the highest-level ones (like the JIT engine). The final
* `trampoline.c` file itself includes the platform- and architecture-specific
* ABI files, completing the build.
*
* @note This file is not intended to be compiled on its own without the
* rest of the source tree. It is the entry point for the build system.
* @endinternal
*/
// 1. Error Handling: Provides the thread-local error reporting system.
// (No dependencies on other infix modules).
#include "core/error.c"
// 2. Arena Allocator: The fundamental memory management component.
// (Depends only on malloc/free).
#include "core/arena.c"
// 3. OS Executor: Handles OS-level memory management for executable code.
// (Depends on error handling, debugging utilities).
#include "jit/executor.c"
// 4. Type Registry: Manages named types.
// (Depends on arena for storage and signature parser for definitions).
#include "core/type_registry.c"
// 5. Signature Parser: Implements the high-level string-based API.
// (Depends on types, arena, and registry).
#include "core/signature.c"
// 6. Dynamic Library Loader: Implements cross-platform `dlopen`/`dlsym`.
// (Depends on error handling, types, and arena).
#include "core/loader.c"
// 7. Type System: Defines and manages `infix_type` objects and graph algorithms.
// (Depends on the arena and error handling).
#include "core/types.c"
// 8. Debugging Utilities: Low-level helpers for logging and inspection.
// (No dependencies).
#include "core/utility.c"
// 9. Platform and processor feature detection.
// (No dependencies).
#include "core/platform.c"
// 10. Trampoline Cache: Deduplication logic.
#include "core/cache.c"
// 11. Trampoline Engine: The central JIT compiler.
infix/src/jit/executor.c view on Meta::CPAN
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.
*/
infix/src/jit/executor.c view on Meta::CPAN
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);
if (code == nullptr) {
_infix_set_system_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, GetLastError(), nullptr);
return exec;
}
exec.rw_ptr = code;
exec.rx_ptr = code;
#elif defined(INFIX_OS_MACOS) || defined(INFIX_OS_ANDROID) || defined(INFIX_OS_OPENBSD) || defined(INFIX_OS_DRAGONFLY)
// Single-mapping POSIX platforms. Allocate as RW, later change to RX via mprotect.
void * code = MAP_FAILED;
#if defined(MAP_ANON)
int flags = MAP_PRIVATE | MAP_ANON;
infix/src/jit/executor.c view on Meta::CPAN
#endif
#endif // MAP_ANON
if (code == MAP_FAILED) { // Fallback for older systems without MAP_ANON
int fd = open("/dev/zero", O_RDWR);
if (fd != -1) {
code = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
close(fd);
}
}
if (code == MAP_FAILED) {
_infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, errno, nullptr);
return exec;
}
exec.rw_ptr = code;
exec.rx_ptr = code;
#else
// Dual-mapping POSIX platforms (e.g., Linux, FreeBSD). Create two separate views of the same memory.
exec.shm_fd = create_anonymous_file();
if (exec.shm_fd < 0) {
_infix_set_system_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, errno, "create_anonymous_file failed");
return exec;
}
if (ftruncate(exec.shm_fd, size) != 0) {
_infix_set_system_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, errno, "ftruncate failed");
close(exec.shm_fd);
exec.shm_fd = -1; // Ensure clean state
return exec;
}
// The RW mapping.
exec.rw_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, exec.shm_fd, 0);
// The RX mapping of the exact same physical memory.
exec.rx_ptr = mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_SHARED, exec.shm_fd, 0);
// If either mapping fails, clean up both and return an error.
if (exec.rw_ptr == MAP_FAILED || exec.rx_ptr == MAP_FAILED) {
int err = errno; // Capture errno before cleanup
if (exec.rw_ptr != MAP_FAILED)
munmap(exec.rw_ptr, size);
if (exec.rx_ptr != MAP_FAILED)
munmap(exec.rx_ptr, size);
close(exec.shm_fd);
_infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, err, "mmap failed");
return (infix_executable_t){.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .shm_fd = -1};
}
// The mmap mappings hold a reference to the shared memory object, so we don't
// need the FD anymore. Keeping it open consumes a file descriptor per trampoline,
// causing "shm_open failed" after ~1024 trampolines.
close(exec.shm_fd);
exec.shm_fd = -1;
#endif
exec.size = size;
infix/src/jit/executor.c view on Meta::CPAN
return exec;
}
#if defined(INFIX_OS_WINDOWS)
/**
* @internal
* @brief The personality routine for safe trampolines on Windows.
*
* @details This function is called by the Windows unwinder when an exception
* occurs within a safe trampoline or its callees. It catches the exception,
* sets the `INFIX_CODE_NATIVE_EXCEPTION` error, and redirects execution to
* the trampoline's epilogue by modifying the instruction pointer in the
* current context record and continuing execution.
*/
static EXCEPTION_DISPOSITION _infix_seh_personality_routine(PEXCEPTION_RECORD ExceptionRecord,
void * EstablisherFrame,
c23_maybe_unused PCONTEXT ContextRecord,
void * DispatcherContext) {
PDISPATCHER_CONTEXT dc = (PDISPATCHER_CONTEXT)DispatcherContext;
// If we are already unwinding, don't do anything.
if (ExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))
return ExceptionContinueSearch;
// Set the thread-local error.
_infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_NATIVE_EXCEPTION, 0);
// Retrieve the target epilogue IP from our HandlerData.
// The HandlerData points to the 4-byte epilogue offset we stored in UNWIND_INFO.
uint32_t epilogue_offset = *(uint32_t *)dc->HandlerData;
void * target_ip = (void *)(dc->ImageBase + epilogue_offset);
// Perform a non-local unwind to the epilogue.
RtlUnwind(EstablisherFrame, target_ip, ExceptionRecord, nullptr);
return ExceptionContinueSearch; // Unreachable
infix/src/jit/executor.c view on Meta::CPAN
// On Windows, we register SEH unwind info before making the memory executable.
#if defined(INFIX_ARCH_X64)
_infix_register_seh_windows_x64(exec, category, prologue_size, epilogue_offset);
#elif defined(INFIX_ARCH_AARCH64)
_infix_register_seh_windows_arm64(exec, category, prologue_size, epilogue_offset);
#endif
// Finalize permissions to Read+Execute.
// We include the SEH metadata in the protected region.
result = VirtualProtect(exec->rw_ptr, exec->size + INFIX_SEH_METADATA_SIZE, PAGE_EXECUTE_READ, &(DWORD){0});
if (!result)
_infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_PROTECTION_FAILURE, GetLastError(), nullptr);
#elif defined(INFIX_OS_MACOS)
static bool g_use_secure_jit_path = false;
static bool g_checked_jit_support = false;
if (!g_checked_jit_support) {
g_use_secure_jit_path = has_jit_entitlement();
g_checked_jit_support = true;
}
if (g_use_secure_jit_path && g_macos_apis.pthread_jit_write_protect_np) {
// Switch thread state to Execute allowed (enabled=1)
g_macos_apis.pthread_jit_write_protect_np(1);
result = true;
}
else {
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);
#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_CATEGORY_ALLOCATION, INFIX_CODE_PROTECTION_FAILURE, errno, "munmap of RW view failed");
result = false;
}
#endif
if (result)
INFIX_DEBUG_PRINTF("Memory at %p is now executable.", exec->rx_ptr);
return result;
}
// Public API: Protected (Read-Only) Memory
/**
infix/src/jit/executor.c view on Meta::CPAN
* @param size The number of bytes to allocate.
* @return An `infix_protected_t` handle, or a zeroed struct on failure.
*/
c23_nodiscard infix_protected_t infix_protected_alloc(size_t size) {
infix_protected_t prot = {.rw_ptr = nullptr, .size = 0};
if (size == 0)
return prot;
#if defined(INFIX_OS_WINDOWS)
prot.rw_ptr = VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!prot.rw_ptr)
_infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, GetLastError(), nullptr);
#else
#if defined(MAP_ANON)
prot.rw_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
#else
int fd = open("/dev/zero", O_RDWR);
if (fd == -1)
prot.rw_ptr = MAP_FAILED;
else {
prot.rw_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
close(fd);
}
#endif
if (prot.rw_ptr == MAP_FAILED) {
_infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, errno, nullptr);
prot.rw_ptr = nullptr;
}
#endif
if (prot.rw_ptr)
prot.size = size;
return prot;
}
/**
* @internal
* @brief Frees a block of protected memory.
infix/src/jit/executor.c view on Meta::CPAN
* @param prot The memory block to make read-only.
* @return `true` on success, `false` on failure.
*/
c23_nodiscard bool infix_protected_make_readonly(infix_protected_t prot) {
if (prot.size == 0)
return false;
bool result = false;
#if defined(INFIX_OS_WINDOWS)
result = VirtualProtect(prot.rw_ptr, prot.size, PAGE_READONLY, &(DWORD){0});
if (!result)
_infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_PROTECTION_FAILURE, GetLastError(), nullptr);
#else
result = (mprotect(prot.rw_ptr, prot.size, PROT_READ) == 0);
if (!result)
_infix_set_system_error(INFIX_CATEGORY_ABI, INFIX_CODE_PROTECTION_FAILURE, errno, nullptr);
#endif
return result;
}
// Universal Reverse Call Dispatcher
/**
* @internal
* @brief The universal C entry point for all reverse call trampolines.
*
* @details The JIT-compiled stub for a reverse call performs the minimal work of
* marshalling all arguments from their native ABI locations (registers and stack)
infix/src/jit/trampoline.c view on Meta::CPAN
* @internal
* @brief Initializes a `code_buffer` for JIT code generation.
* @param buf A pointer to the `code_buffer` to initialize.
* @param arena The temporary arena to use for the buffer's memory.
*/
void code_buffer_init(code_buffer * buf, infix_arena_t * arena) {
buf->capacity = 64; // Start with a small initial capacity.
buf->arena = arena;
buf->code = infix_arena_alloc(arena, buf->capacity, 16);
buf->size = 0;
buf->error = (buf->code == nullptr);
}
/**
* @internal
* @brief Appends data to a `code_buffer`, reallocating from its arena if necessary.
*
* @details If the buffer runs out of space, it doubles its capacity until the new data
* fits. All allocations happen within the temporary arena, so no manual `free` or
* `realloc` calls are needed; cleanup is automatic when the arena is destroyed.
*
* @param buf The code buffer.
* @param data A pointer to the data to append.
* @param len The length of the data in bytes.
*/
void code_buffer_append(code_buffer * buf, const void * data, size_t len) {
if (buf->error)
return;
if (len > SIZE_MAX - buf->size) { // Overflow check
buf->error = true;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_INTEGER_OVERFLOW, 0);
return;
}
if (buf->size + len > buf->capacity) {
size_t new_capacity = buf->capacity;
while (new_capacity < buf->size + len) {
if (new_capacity > SIZE_MAX / 2) { // Overflow check
buf->error = true;
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_INTEGER_OVERFLOW, 0);
return;
}
new_capacity *= 2;
}
void * new_code = infix_arena_alloc(buf->arena, new_capacity, 16);
if (new_code == nullptr) {
buf->error = true;
// infix_arena_alloc already sets INFIX_CODE_OUT_OF_MEMORY, so we don't need to override it here
return;
}
infix_memcpy(new_code, buf->code, buf->size);
buf->code = new_code;
buf->capacity = new_capacity;
}
infix_memcpy(buf->code + buf->size, data, len);
buf->size += len;
}
infix/src/jit/trampoline.c view on Meta::CPAN
*/
static infix_status _infix_forward_create_impl(infix_forward_t ** out_trampoline,
infix_arena_t * target_arena,
infix_type * return_type,
infix_type ** arg_types,
size_t num_args,
size_t num_fixed_args,
void * target_fn,
bool is_safe) {
if (out_trampoline == nullptr || return_type == nullptr || (arg_types == nullptr && num_args > 0)) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
// Pre-flight check: ensure all types are resolved before passing to ABI layer.
if (!_is_type_graph_resolved(return_type)) {
_infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_UNRESOLVED_NAMED_TYPE, 0);
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 ---
// Generate a canonical signature for deduplication.
char canonical_sig[8192];
infix_status sig_status = INFIX_SUCCESS;
{
// Construct a temporary argument array for the printer.
infix_function_argument * tmp_args = nullptr;
infix/src/jit/trampoline.c view on Meta::CPAN
infix_forward_t * cached = _infix_cache_lookup(canonical_sig, target_fn, is_safe);
if (cached) {
*out_trampoline = cached;
status = INFIX_SUCCESS;
goto cleanup;
}
}
const infix_forward_abi_spec * spec = get_current_forward_abi_spec();
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);
infix/src/jit/trampoline.c view on Meta::CPAN
goto cleanup;
status = spec->generate_forward_argument_moves(&buf, layout, arg_types, num_args, num_fixed_args);
if (status != INFIX_SUCCESS)
goto cleanup;
status = spec->generate_forward_call_instruction(&buf, layout);
if (status != INFIX_SUCCESS)
goto cleanup;
status = spec->generate_forward_epilogue(&buf, layout, return_type);
if (status != INFIX_SUCCESS)
goto cleanup;
if (buf.error || temp_arena->error) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
// Finalize Handle
handle = infix_calloc(1, sizeof(infix_forward_t));
if (handle == nullptr) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
// "Estimate" stage: Calculate the exact size needed for the handle's private arena.
infix/src/jit/trampoline.c view on Meta::CPAN
handle->return_type = _copy_type_graph_to_arena(handle->arena, return_type);
if (num_args > 0) {
handle->arg_types = infix_arena_alloc(handle->arena, sizeof(infix_type *) * num_args, _Alignof(infix_type *));
if (handle->arg_types == nullptr) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
for (size_t i = 0; i < num_args; ++i) {
handle->arg_types[i] = _copy_type_graph_to_arena(handle->arena, arg_types[i]);
// Check for allocation failure during copy
if (arg_types[i] != nullptr && handle->arg_types[i] == nullptr && !handle->arena->error) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
}
}
handle->num_args = num_args;
handle->num_fixed_args = num_fixed_args;
handle->target_fn = target_fn;
handle->is_safe = is_safe;
handle->ref_count = 1;
infix/src/jit/trampoline.c view on Meta::CPAN
* 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.
* @param[in] target_fn The target C function pointer.
* @param[in] handlers An array of handler structs provided by the user.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
static infix_status _infix_forward_create_direct_impl(infix_forward_t ** out_trampoline,
infix_type * return_type,
infix_type ** arg_types,
size_t num_args,
void * target_fn,
infix_direct_arg_handler_t * handlers) {
// Validation and Setup
if (!out_trampoline || !return_type || (!arg_types && num_args > 0) || !target_fn || !handlers) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
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;
infix/src/jit/trampoline.c view on Meta::CPAN
goto cleanup;
status = spec->generate_direct_forward_call_instruction(&buf, layout);
if (status != INFIX_SUCCESS)
goto cleanup;
status = spec->generate_direct_forward_epilogue(&buf, layout, return_type);
if (status != INFIX_SUCCESS)
goto cleanup;
if (buf.error || temp_arena->error) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
// 3. Finalize Handle
handle = infix_calloc(1, sizeof(infix_forward_t));
if (handle == nullptr) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
infix/src/jit/trampoline.c view on Meta::CPAN
handle->return_type = _copy_type_graph_to_arena(handle->arena, return_type);
if (num_args > 0) {
handle->arg_types = infix_arena_alloc(handle->arena, sizeof(infix_type *) * num_args, _Alignof(infix_type *));
if (!handle->arg_types) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
for (size_t i = 0; i < num_args; ++i) {
handle->arg_types[i] = _copy_type_graph_to_arena(handle->arena, arg_types[i]);
if (arg_types[i] && !handle->arg_types[i] && !handle->arena->error) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
}
}
handle->num_args = num_args;
handle->num_fixed_args = num_args; // Direct trampolines are always fixed-arity.
handle->target_fn = target_fn;
handle->ref_count = 1;
infix/src/jit/trampoline.c view on Meta::CPAN
*/
INFIX_API c23_nodiscard infix_status infix_forward_create_manual(infix_forward_t ** out_trampoline,
infix_type * return_type,
infix_type ** arg_types,
size_t num_args,
size_t num_fixed_args,
void * target_function) {
// This is part of the "Manual API". It calls the internal implementation directly
// without involving the signature parser. `source_arena` is null because the
// types are assumed to be managed by the user.
_infix_clear_error();
return _infix_forward_create_impl(
out_trampoline, nullptr, return_type, arg_types, num_args, num_fixed_args, target_function, false);
}
/**
* @brief Creates an unbound forward trampoline from `infix_type` objects (Manual API).
*
* @details This is the lower-level, programmatic way to create an unbound forward trampoline.
* It bypasses the signature string parser.
*
* @param[out] out_trampoline Receives the created trampoline handle.
infix/src/jit/trampoline.c view on Meta::CPAN
* @param[in] arg_types An array of `infix_type*` for the function's arguments.
* @param[in] num_args The number of arguments.
* @param[in] num_fixed_args The number of non-variadic arguments.
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_forward_create_unbound_manual(infix_forward_t ** out_trampoline,
infix_type * return_type,
infix_type ** arg_types,
size_t num_args,
size_t num_fixed_args) {
_infix_clear_error();
return _infix_forward_create_impl(
out_trampoline, nullptr, return_type, arg_types, num_args, num_fixed_args, nullptr, false);
}
/**
* @internal
* @brief Internal implementation of forward trampoline destruction.
*/
void _infix_forward_destroy_internal(infix_forward_t * trampoline) {
if (trampoline == nullptr)
return;
infix/src/jit/trampoline.c view on Meta::CPAN
*/
static infix_status _infix_reverse_create_internal(infix_reverse_t ** out_context,
infix_type * return_type,
infix_type ** arg_types,
size_t num_args,
size_t num_fixed_args,
void * user_callback_fn,
void * user_data,
bool is_callback) {
if (out_context == nullptr || return_type == nullptr || num_fixed_args > num_args) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
// Pre-flight check: ensure all types are fully resolved.
if (!_is_type_graph_resolved(return_type)) {
_infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_UNRESOLVED_NAMED_TYPE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
if (arg_types == nullptr && num_args > 0) {
_infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_UNRESOLVED_NAMED_TYPE, 0);
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;
}
}
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);
// Security Hardening: Allocate the context struct itself in special, page-aligned
// memory that can be made read-only after initialization.
size_t page_size = get_page_size();
size_t context_alloc_size = (sizeof(infix_reverse_t) + page_size - 1) & ~(page_size - 1);
prot = infix_protected_alloc(context_alloc_size);
if (prot.rw_ptr == nullptr) {
status = INFIX_ERROR_ALLOCATION_FAILED;
infix/src/jit/trampoline.c view on Meta::CPAN
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)
goto cleanup;
status = spec->generate_reverse_epilogue(&buf, layout, context);
if (status != INFIX_SUCCESS)
goto cleanup;
// End of Pipeline
if (buf.error || temp_arena->error) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
context->exec = infix_executable_alloc(buf.size);
if (context->exec.rw_ptr == nullptr) {
status = INFIX_ERROR_ALLOCATION_FAILED;
goto cleanup;
}
infix_memcpy(context->exec.rw_ptr, buf.code, buf.size);
if (!infix_executable_make_executable(&context->exec, INFIX_EXECUTABLE_REVERSE, layout->prologue_size, 0)) {
infix/src/jit/trampoline.c view on Meta::CPAN
* @param[in] user_callback_fn A pointer to the type-safe C handler function.
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_reverse_create_callback_manual(infix_reverse_t ** out_context,
infix_type * return_type,
infix_type ** arg_types,
size_t num_args,
size_t num_fixed_args,
void * user_callback_fn) {
_infix_clear_error();
return _infix_reverse_create_internal(
out_context, return_type, arg_types, num_args, num_fixed_args, user_callback_fn, nullptr, true);
}
/**
* @brief Creates a generic reverse trampoline (closure) from `infix_type` objects (Manual API).
* @param[out] out_context Receives the created context handle.
* @param[in] return_type The function's return type.
* @param[in] arg_types An array of argument types.
* @param[in] num_args The number of arguments.
* @param[in] num_fixed_args The number of non-variadic arguments.
infix/src/jit/trampoline.c view on Meta::CPAN
* @return `INFIX_SUCCESS` on success.
*/
INFIX_API c23_nodiscard infix_status infix_reverse_create_closure_manual(infix_reverse_t ** out_context,
infix_type * return_type,
infix_type ** arg_types,
size_t num_args,
size_t num_fixed_args,
infix_closure_handler_fn user_callback_fn,
void * user_data) {
_infix_clear_error();
return _infix_reverse_create_internal(
out_context, return_type, arg_types, num_args, num_fixed_args, (void *)user_callback_fn, user_data, false);
}
/**
* @brief Destroys a reverse trampoline and frees all associated memory.
* @details This function safely releases all resources owned by the reverse trampoline,
* including its JIT-compiled stub, its private memory arena, the cached forward
* trampoline (if any), and the special read-only memory region for the context itself.
* @param[in] reverse_trampoline The reverse trampoline context to destroy. Safe to call with `nullptr`.
*/
infix/src/jit/trampoline.c view on Meta::CPAN
if (reverse_trampoline == nullptr)
return nullptr;
return reverse_trampoline->user_data;
}
// High-Level Signature API Wrappers
INFIX_API c23_nodiscard infix_status infix_forward_create_in_arena(infix_forward_t ** out_trampoline,
infix_arena_t * target_arena,
const char * signature,
void * target_function,
infix_registry_t * registry) {
_infix_clear_error();
if (!signature) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_arena_t * arena = nullptr;
infix_type * ret_type = nullptr;
infix_function_argument * args = nullptr;
size_t num_args = 0, num_fixed = 0;
infix_type ** arg_types = nullptr;
infix_status status;
if (signature[0] == '@') {
if (registry == nullptr) {
_infix_set_error(
INFIX_CATEGORY_GENERAL, INFIX_CODE_MISSING_REGISTRY, 0); // Using @Name requires a registry
return INFIX_ERROR_INVALID_ARGUMENT;
}
const infix_type * func_type = infix_registry_lookup_type(registry, &signature[1]);
if (func_type == NULL) {
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNRESOLVED_NAMED_TYPE, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
if (func_type->category != INFIX_TYPE_REVERSE_TRAMPOLINE) {
// The user provided a name for a non-function type (e.g., "@Point")
_infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
// We have a valid function type from the registry. Now, unpack its components.
ret_type = func_type->meta.func_ptr_info.return_type;
num_args = func_type->meta.func_ptr_info.num_args;
num_fixed = func_type->meta.func_ptr_info.num_fixed_args;
args = func_type->meta.func_ptr_info.args;
// The Manual API needs a temporary arena to hold the arg_types array.
infix_arena_t * temp_arena = infix_arena_create(sizeof(infix_type *) * num_args + 128);
if (!temp_arena) {
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
if (num_args > 0) {
arg_types = infix_arena_alloc(temp_arena, sizeof(infix_type *) * num_args, _Alignof(infix_type *));
if (!arg_types) {
infix_arena_destroy(temp_arena);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
for (size_t i = 0; i < num_args; ++i)
arg_types[i] = args[i].type;
}
arena = temp_arena;
}
else {
// This is a high-level wrapper. It uses the parser to build the type info first.
status = infix_signature_parse(signature, &arena, &ret_type, &args, &num_args, &num_fixed, registry);
if (status != INFIX_SUCCESS) {
infix_arena_destroy(arena);
return status;
}
// Extract the `infix_type*` array from the parsed `infix_function_argument` array.
arg_types = (num_args > 0) ? infix_arena_alloc(arena, sizeof(infix_type *) * num_args, _Alignof(infix_type *))
: nullptr;
if (num_args > 0 && !arg_types) {
infix_arena_destroy(arena);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
for (size_t i = 0; i < num_args; ++i)
arg_types[i] = args[i].type;
}
// Call the core internal implementation with the parsed types.
status = _infix_forward_create_impl(
out_trampoline, target_arena, ret_type, arg_types, num_args, num_fixed, target_function, false);
infix_arena_destroy(arena);
return status;
}
INFIX_API c23_nodiscard infix_status infix_forward_create_safe(infix_forward_t ** out_trampoline,
const char * signature,
void * target_function,
infix_registry_t * registry) {
_infix_clear_error();
if (!signature || !target_function) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_arena_t * arena = nullptr;
infix_type * ret_type = nullptr;
infix_function_argument * args = nullptr;
size_t num_args = 0, num_fixed = 0;
infix_status status = infix_signature_parse(signature, &arena, &ret_type, &args, &num_args, &num_fixed, registry);
if (status != INFIX_SUCCESS) {
infix_arena_destroy(arena);
return status;
}
infix_type ** arg_types =
(num_args > 0) ? infix_arena_alloc(arena, sizeof(infix_type *) * num_args, _Alignof(infix_type *)) : nullptr;
if (num_args > 0 && !arg_types) {
infix_arena_destroy(arena);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
for (size_t i = 0; i < num_args; ++i)
arg_types[i] = args[i].type;
status = _infix_forward_create_impl(
out_trampoline, NULL, ret_type, arg_types, num_args, num_fixed, target_function, true);
infix_arena_destroy(arena);
return status;
}
infix/src/jit/trampoline.c view on Meta::CPAN
INFIX_API c23_nodiscard infix_status infix_forward_create_unbound(infix_forward_t ** out_trampoline,
const char * signature,
infix_registry_t * registry) {
return infix_forward_create_in_arena(out_trampoline, NULL, signature, NULL, registry);
}
INFIX_API c23_nodiscard infix_status infix_forward_create_direct(infix_forward_t ** out_trampoline,
const char * signature,
void * target_function,
infix_direct_arg_handler_t * handlers,
infix_registry_t * registry) {
_infix_clear_error();
if (!signature || !target_function || !handlers) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_arena_t * arena = nullptr;
infix_type * ret_type = nullptr;
infix_function_argument * args = nullptr;
size_t num_args = 0, num_fixed = 0;
infix_type ** arg_types = nullptr;
// Parse the signature to get the type graph.
infix/src/jit/trampoline.c view on Meta::CPAN
if (status != INFIX_SUCCESS) {
infix_arena_destroy(arena);
return status;
}
// Convert the parsed `infix_function_argument*` array to an `infix_type**` array.
if (num_args > 0) {
arg_types = infix_arena_alloc(arena, sizeof(infix_type *) * num_args, _Alignof(infix_type *));
if (!arg_types) {
infix_arena_destroy(arena);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
for (size_t i = 0; i < num_args; ++i)
arg_types[i] = args[i].type;
}
// Call the core internal implementation with the parsed types and provided handlers.
status =
_infix_forward_create_direct_impl(out_trampoline, ret_type, arg_types, num_args, target_function, handlers);
infix/src/jit/trampoline.c view on Meta::CPAN
size_t num_args = 0, num_fixed = 0;
infix_status status = infix_signature_parse(signature, &arena, &ret_type, &args, &num_args, &num_fixed, registry);
if (status != INFIX_SUCCESS) {
infix_arena_destroy(arena);
return status;
}
infix_type ** arg_types =
(num_args > 0) ? infix_arena_alloc(arena, sizeof(infix_type *) * num_args, _Alignof(infix_type *)) : nullptr;
if (num_args > 0 && !arg_types) {
infix_arena_destroy(arena);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
for (size_t i = 0; i < num_args; ++i)
arg_types[i] = args[i].type;
// Call the manual API with the parsed types.
status =
infix_reverse_create_callback_manual(out_context, ret_type, arg_types, num_args, num_fixed, user_callback_fn);
infix_arena_destroy(arena);
return status;
}
infix/src/jit/trampoline.c view on Meta::CPAN
size_t num_args = 0, num_fixed = 0;
infix_status status = infix_signature_parse(signature, &arena, &ret_type, &args, &num_args, &num_fixed, registry);
if (status != INFIX_SUCCESS) {
infix_arena_destroy(arena);
return status;
}
infix_type ** arg_types =
(num_args > 0) ? infix_arena_alloc(arena, sizeof(infix_type *) * num_args, _Alignof(infix_type *)) : nullptr;
if (num_args > 0 && !arg_types) {
infix_arena_destroy(arena);
_infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
return INFIX_ERROR_ALLOCATION_FAILED;
}
for (size_t i = 0; i < num_args; ++i)
arg_types[i] = args[i].type;
status = infix_reverse_create_closure_manual(
out_context, ret_type, arg_types, num_args, num_fixed, user_callback_fn, user_data);
infix_arena_destroy(arena);
return status;
}
// ============================================================================
infix/src/jit/trampoline.c view on Meta::CPAN
#if defined(INFIX_ABI_WINDOWS_X64)
#include "../arch/x64/abi_win_x64.c"
#include "../arch/x64/abi_x64_emitters.c"
#elif defined(INFIX_ABI_SYSV_X64)
#include "../arch/x64/abi_sysv_x64.c"
#include "../arch/x64/abi_x64_emitters.c"
#elif defined(INFIX_ABI_AAPCS64)
#include "../arch/aarch64/abi_arm64.c"
#include "../arch/aarch64/abi_arm64_emitters.c"
#else
#error "No supported ABI was selected for the unity build in trampoline.c."
#endif
lib/Affix.c view on Meta::CPAN
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");
if (!SvOK(sv)) {
if (!SvREADONLY(sv)) {
size_t size = infix_type_get_size(pointee_type);
size_t align = infix_type_get_alignment(pointee_type);
if (size == 0) {
size = sizeof(void *);
align = _Alignof(void *);
}
lib/Affix.c view on Meta::CPAN
bool is_void_ptr = (pointee_type->category == INFIX_TYPE_VOID);
if (is_char_ptr || is_void_ptr) {
*(const char **)c_arg_ptr = SvPV_nolen(sv);
return;
}
}
sv_dump(sv);
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) {
lib/Affix.c view on Meta::CPAN
// 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) {
memcpy(c_arg_ptr, buf, expected_size);
return;
}
// If string is too short, fall through to AV check or error
}
if (!SvROK(sv) || SvTYPE(SvRV(sv)) != SVt_PVAV)
croak("Expected an ARRAY reference or Packed String for vector marshalling");
AV * av = (AV *)SvRV(sv);
size_t num_elements = av_len(av) + 1;
size_t c_vector_len = type->meta.vector_info.num_elements;
if (num_elements != c_vector_len)
croak("Perl array has %lu elements, but C vector type requires %lu.",
(unsigned long)num_elements,
lib/Affix.c view on Meta::CPAN
infix_function_argument * args = nullptr;
size_t num_args = 0, num_fixed = 0;
// Re-parse signature using THIS thread's registry
infix_status status =
infix_signature_parse(affix->sig_str, &parse_arena, &ret_type, &args, &num_args, &num_fixed, MY_CXT.registry);
if (status != INFIX_SUCCESS) {
if (parse_arena)
infix_arena_destroy(parse_arena);
croak("Affix failed to rebuild in new thread: signature parse error");
}
// Prepare JIT types (handle array decay)
infix_type ** jit_arg_types = NULL;
if (num_args > 0) {
jit_arg_types = safemalloc(sizeof(infix_type *) * num_args);
for (size_t i = 0; i < num_args; ++i) {
infix_type * t = args[i].type;
if (t->category == INFIX_TYPE_ARRAY) {
infix_type * ptr_type = NULL;
status = infix_type_create_pointer_to(parse_arena, &ptr_type, t->meta.array_info.element_type);
if (status != INFIX_SUCCESS) {
if (parse_arena)
infix_arena_destroy(parse_arena);
croak("Affix failed to rebuild in new thread: type clone error");
}
jit_arg_types[i] = ptr_type;
}
else
jit_arg_types[i] = t;
}
}
// Create trampoline
status =
lib/Affix.c view on Meta::CPAN
SV * sig_sv = sv_2mortal(newSVpv("", 0));
// Reconstruct fixed part from the cached sig_str (which ends in '; ...' or similar)
// We need to parse the original signature string to get the fixed part cleanly,
// OR we can reconstruct it from the plan.
// Simplest: The affix->sig_str contains the fixed part and the ';'.
// We assume affix->sig_str is like "(*char; ...)->int"
char * semi_ptr = strchr(affix->sig_str, ';');
if (!semi_ptr)
croak("Internal error: Variadic function missing semicolon in signature");
// Append fixed part up to and including ';'
sv_catpvn(sig_sv, affix->sig_str, (semi_ptr - affix->sig_str) + 1);
// Iterate varargs to infer types and append to signature
for (size_t i = affix->num_fixed_args; i < items; ++i) {
SV * arg = ST(i);
const char * coerced_sig = _get_coerced_sig(aTHX_ arg);
if (i > affix->num_fixed_args)
lib/Affix.c view on Meta::CPAN
infix_status status =
infix_signature_parse(sig_to_parse, &parse_arena, &ret_type, &args, &num_args, &num_fixed, MY_CXT.registry);
if (clean_sig)
safefree(clean_sig);
if (status != INFIX_SUCCESS) {
safefree(backend);
if (parse_arena)
infix_arena_destroy(parse_arena);
infix_error_details_t err = infix_get_last_error();
if (err.message[0] != '\0')
warn("Failed to parse signature for affix_bundle: %s", err.message);
else
warn("Failed to parse signature for affix_bundle (Error Code: %d)", status);
XSRETURN_UNDEF;
}
infix_direct_arg_handler_t * handlers =
(infix_direct_arg_handler_t *)safecalloc(num_args, sizeof(infix_direct_arg_handler_t));
for (size_t i = 0; i < num_args; ++i)
handlers[i] = get_direct_handler_for_type(args[i].type);
status = infix_forward_create_direct(&backend->infix, signature, symbol, handlers, MY_CXT.registry);
safefree(handlers);
infix_arena_destroy(parse_arena);
if (status != INFIX_SUCCESS) {
safefree(backend);
infix_error_details_t err = infix_get_last_error();
warn("Failed to create direct trampoline: %s", err.message[0] ? err.message : "Unknown Error");
XSRETURN_UNDEF;
}
backend->cif = infix_forward_get_direct_code(backend->infix);
backend->num_args = num_args;
backend->ret_type = infix_forward_get_return_type(backend->infix);
backend->pull_handler = get_pull_handler(aTHX_ backend->ret_type);
backend->ret_opcode = get_ret_opcode_for_type(backend->ret_type);
lib/Affix.c view on Meta::CPAN
sig_to_parse = clean_sig;
}
infix_status status =
infix_signature_parse(sig_to_parse, &parse_arena, &ret_type, &args, &num_args, &num_fixed, MY_CXT.registry);
if (clean_sig)
safefree(clean_sig);
if (status != INFIX_SUCCESS) {
infix_error_details_t err = infix_get_last_error();
warn("Failed to parse signature: %s", err.message);
if (parse_arena)
infix_arena_destroy(parse_arena);
XSRETURN_UNDEF;
}
// JIT Type substitution (array decay)
// We create a separate list of types for JIT compilation where Arrays are replaced by Pointers.
// The original Array types are kept for the marshalling plan.
infix_type ** jit_arg_types = NULL;
lib/Affix.c view on Meta::CPAN
if (lib_handle_for_symbol)
affix->lib_handle = lib_handle_for_symbol;
// Create Trampoline using the JIT-optimized types
status = infix_forward_create_manual(&affix->infix, ret_type, jit_arg_types, num_args, num_fixed, symbol);
if (jit_arg_types)
safefree(jit_arg_types);
if (status != INFIX_SUCCESS) {
infix_error_details_t err = infix_get_last_error();
warn("Failed to create trampoline: %s", err.message);
_affix_destroy(aTHX_ affix);
infix_arena_destroy(parse_arena);
XSRETURN_UNDEF;
}
affix->cif = infix_forward_get_code(affix->infix);
affix->num_args = num_args;
affix->num_fixed_args = num_fixed;
lib/Affix.c view on Meta::CPAN
sv2ptr(aTHX_ affix, rv, temp_ptr, pointee_type);
else
sv2ptr(aTHX_ affix, perl_sv, temp_ptr, pointee_type);
*(void **)c_ptr = temp_ptr;
}
else {
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("sv2ptr cannot handle this kind of pointer conversion yet: %s", signature_buf);
}
}
break;
case INFIX_TYPE_STRUCT:
{
if (is_perl_sv_type(type)) {
*(SV **)c_ptr = perl_sv;
SvREFCNT_inc(perl_sv);
lib/Affix.c view on Meta::CPAN
arg_types,
num_args,
num_fixed_args,
(void *)_affix_callback_handler_entry,
(void *)cb_data);
if (arg_types)
Safefree(arg_types);
if (status != INFIX_SUCCESS) {
SvREFCNT_dec(cb_data->coderef_rv);
safefree(cb_data);
croak("Failed to create callback: %s", infix_get_last_error().message);
}
Implicit_Callback_Magic * magic_data;
Newxz(magic_data, 1, Implicit_Callback_Magic);
magic_data->reverse_ctx = reverse_ctx;
hv_store(MY_CXT.callback_registry, key, strlen(key), newSViv(PTR2IV(magic_data)), 0);
*(void **)p = infix_reverse_get_code(reverse_ctx);
}
}
else if (!SvOK(sv))
*(void **)p = nullptr;
else
croak("Argument for a callback must be a code reference or undef.");
}
static SV * _format_parse_error(pTHX_ const char * context_msg, const char * signature, infix_error_details_t err) {
STRLEN sig_len = strlen(signature);
int radius = 20;
size_t start = (err.position > radius) ? (err.position - radius) : 0;
size_t end = (err.position + radius < sig_len) ? (err.position + radius) : sig_len;
const char * start_indicator = (start > 0) ? "... " : "";
const char * end_indicator = (end < sig_len) ? " ..." : "";
int start_indicator_len = (start > 0) ? 4 : 0;
char snippet[128];
snprintf(
snippet, sizeof(snippet), "%s%.*s%s", start_indicator, (int)(end - start), signature + start, end_indicator);
lib/Affix.c view on Meta::CPAN
new_entry->lib = lib;
new_entry->ref_count = 1;
hv_store(MY_CXT.lib_registry, path, strlen(path), newSViv(PTR2IV(new_entry)), 0);
SV * obj_data = newSV(0);
sv_setiv(obj_data, PTR2IV(lib));
ST(0) = sv_2mortal(sv_bless(newRV_inc(obj_data), gv_stashpv("Affix::Lib", GV_ADD)));
XSRETURN(1);
}
XSRETURN_UNDEF;
}
XS_INTERNAL(Affix_get_last_error_message) {
dXSARGS;
PERL_UNUSED_VAR(items);
infix_error_details_t err = infix_get_last_error();
if (err.message[0] != '\0')
ST(0) = sv_2mortal(newSVpv(err.message, 0));
#if defined(INFIX_OS_WINDOWS)
else if (err.system_error_code != 0) {
char buf[256];
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
err.system_error_code,
0,
buf,
sizeof(buf),
nullptr);
ST(0) = sv_2mortal(newSVpvf("System error: %s (code %ld)", buf, err.system_error_code));
}
#endif
else
ST(0) = sv_2mortal(newSVpvf("Infix error code %d at position %zu", (int)err.code, err.position));
XSRETURN(1);
}
Affix_Pin * _get_pin_from_sv(pTHX_ SV * sv) {
if (!sv || !SvOK(sv) || !SvROK(sv) || !SvMAGICAL(SvRV(sv)))
return nullptr;
MAGIC * mg = mg_findext(SvRV(sv), PERL_MAGIC_ext, &Affix_pin_vtbl);
if (mg)
return (Affix_Pin *)mg->mg_ptr;
return nullptr;
lib/Affix.c view on Meta::CPAN
Newxz(pin, 1, Affix_Pin);
pin->pointer = symbol;
pin->managed = false;
pin->owner_sv = ST(0);
SvREFCNT_inc(pin->owner_sv);
pin->type_arena = infix_arena_create(256);
infix_type * void_ptr_type = nullptr;
if (infix_type_create_pointer_to(pin->type_arena, &void_ptr_type, infix_type_create_void()) != INFIX_SUCCESS) {
safefree(pin);
infix_arena_destroy(pin->type_arena);
croak("Internal error: Failed to create pointer type for pin");
}
pin->type = void_ptr_type;
SV * obj_data = newSV(0);
sv_setiv(obj_data, PTR2IV(pin));
SV * rv = newRV_inc(obj_data);
// Bless into Affix::Pointer BEFORE attaching magic to avoid triggering 'set' during blessing
(void)sv_bless(rv, gv_stashpv("Affix::Pointer", GV_ADD));
MAGIC * mg = sv_magicext(obj_data, nullptr, PERL_MAGIC_ext, &Affix_pin_vtbl, nullptr, 0);
lib/Affix.c view on Meta::CPAN
dXSARGS;
dMY_CXT;
if (items != 4)
croak_xs_usage(cv, "var, lib, symbol, type");
SV * target_sv = ST(0);
const char * lib_path_or_name = SvPV_nolen(ST(1));
const char * symbol_name = SvPV_nolen(ST(2));
const char * signature = SvPV_nolen(ST(3));
infix_library_t * lib = infix_library_open(lib_path_or_name);
if (lib == nullptr) {
warn("Failed to load library from path '%s' for pinning: %s", lib_path_or_name, infix_get_last_error().message);
XSRETURN_UNDEF;
}
void * ptr = infix_library_get_symbol(lib, symbol_name);
infix_library_close(lib);
if (ptr == nullptr) {
warn("Failed to locate symbol '%s' in library '%s'", symbol_name, lib_path_or_name);
XSRETURN_UNDEF;
}
infix_type * type = nullptr;
infix_arena_t * arena = nullptr;
lib/Affix.c view on Meta::CPAN
(p[1] == '*' || p[1] == '[' || p[1] == '{' || p[1] == '!' || p[1] == '<' || p[1] == '(' || p[1] == '@'))
p++;
else
*d++ = *p++;
}
*d = '\0';
sig_to_parse = clean_sig;
}
if (infix_type_from_signature(&type, &arena, sig_to_parse, MY_CXT.registry) != INFIX_SUCCESS) {
SV * err_sv = _format_parse_error(aTHX_ "for pin", sig_to_parse, infix_get_last_error());
warn_sv(err_sv);
if (arena)
infix_arena_destroy(arena);
if (clean_sig)
safefree(clean_sig);
XSRETURN_UNDEF;
}
_pin_sv(aTHX_ target_sv, type, ptr, false, nullptr, 0, 0);
infix_arena_destroy(arena);
if (clean_sig)
lib/Affix.c view on Meta::CPAN
XS_INTERNAL(Affix_sizeof) {
dXSARGS;
dMY_CXT;
if (items != 1)
croak_xs_usage(cv, "type_signature");
SV * type_sv = ST(0);
const char * signature = _get_string_from_type_obj(aTHX_ type_sv);
infix_type * type = nullptr;
infix_arena_t * arena = nullptr;
if (infix_type_from_signature(&type, &arena, signature, MY_CXT.registry) != INFIX_SUCCESS) {
SV * err_sv = _format_parse_error(aTHX_ "for sizeof", signature, infix_get_last_error());
warn_sv(err_sv);
if (arena)
infix_arena_destroy(arena);
XSRETURN_UNDEF;
}
size_t type_size = infix_type_get_size(type);
infix_arena_destroy(arena);
ST(0) = sv_2mortal(newSVuv(type_size));
XSRETURN(1);
}
lib/Affix.c view on Meta::CPAN
XS_INTERNAL(Affix_alignof) {
dXSARGS;
dMY_CXT;
if (items != 1)
croak_xs_usage(cv, "type_signature");
SV * type_sv = ST(0);
const char * signature = _get_string_from_type_obj(aTHX_ type_sv);
infix_type * type = nullptr;
infix_arena_t * arena = nullptr;
if (infix_type_from_signature(&type, &arena, signature, MY_CXT.registry) != INFIX_SUCCESS) {
SV * err_sv = _format_parse_error(aTHX_ "for alignof", signature, infix_get_last_error());
warn_sv(err_sv);
if (arena)
infix_arena_destroy(arena);
XSRETURN_UNDEF;
}
size_t align = (type->category == INFIX_TYPE_ARRAY) ? type->alignment : infix_type_get_alignment(type);
if (align == 0)
align = 1;
infix_arena_destroy(arena);
ST(0) = sv_2mortal(newSVuv(align));
lib/Affix.c view on Meta::CPAN
dXSARGS;
dMY_CXT;
if (items != 2)
croak_xs_usage(cv, "type_signature, member_name");
SV * type_sv = ST(0);
const char * signature = _get_string_from_type_obj(aTHX_ type_sv);
const char * member_name = SvPV_nolen(ST(1));
infix_type * type = nullptr;
infix_arena_t * arena = nullptr;
if (infix_type_from_signature(&type, &arena, signature, MY_CXT.registry) != INFIX_SUCCESS) {
SV * err_sv = _format_parse_error(aTHX_ "for offsetof", signature, infix_get_last_error());
warn_sv(err_sv);
if (arena)
infix_arena_destroy(arena);
XSRETURN_UNDEF;
}
if (type->category != INFIX_TYPE_STRUCT && type->category != INFIX_TYPE_UNION) {
infix_arena_destroy(arena);
warn("offsetof expects a Struct or Union type");
XSRETURN_UNDEF;
lib/Affix.c view on Meta::CPAN
if (!type_str)
type_str = SvPV_nolen(type_sv);
// Live() prepends '+' to signatures. Infix doesn't support this character.
if (type_str[0] == '+')
type_str++;
sv_catpv(def_sv, type_str);
}
sv_catpv(def_sv, ";");
//~ warn("Affix: Registering types: %s", SvPV_nolen(def_sv));
if (infix_register_types(MY_CXT.registry, SvPV_nolen(def_sv)) != INFIX_SUCCESS) {
SV * err_sv = _format_parse_error(aTHX_ "in typedef", SvPV_nolen(def_sv), infix_get_last_error());
warn_sv(err_sv);
XSRETURN_UNDEF;
}
#if DEBUG
char * blah;
Newxz(blah, 1024 * 5, char);
infix_registry_print(blah, 1024 * 5, MY_CXT.registry);
warn("registry: %s", blah);
#endif
lib/Affix.c view on Meta::CPAN
if (items < 1)
croak_xs_usage(cv, "size");
UV size = SvUV(ST(0));
infix_type * type = nullptr;
infix_arena_t * parse_arena = nullptr;
const char * sig = "*void";
if (infix_type_from_signature(&type, &parse_arena, sig, MY_CXT.registry) != INFIX_SUCCESS) {
SV * err_sv = _format_parse_error(aTHX_ "for malloc", sig, infix_get_last_error());
warn_sv(err_sv);
if (parse_arena)
infix_arena_destroy(parse_arena);
XSRETURN_UNDEF;
}
if (size == 0) {
infix_arena_destroy(parse_arena);
warn("Cannot malloc a zero-sized type");
XSRETURN_UNDEF;
lib/Affix.c view on Meta::CPAN
UV count = SvUV(ST(0));
const char * signature = nullptr;
SV * type_sv = ST(1);
signature = _get_string_from_type_obj(aTHX_ type_sv);
if (!signature)
signature = SvPV_nolen(type_sv);
infix_type * elem_type = nullptr;
infix_arena_t * parse_arena = nullptr;
if (infix_type_from_signature(&elem_type, &parse_arena, signature, MY_CXT.registry) != INFIX_SUCCESS) {
SV * err_sv = _format_parse_error(aTHX_ "for calloc", signature, infix_get_last_error());
warn_sv(err_sv);
if (parse_arena)
infix_arena_destroy(parse_arena);
XSRETURN_UNDEF;
}
size_t elem_size = infix_type_get_size(elem_type);
if (elem_size == 0) {
infix_arena_destroy(parse_arena);
warn("Cannot calloc a zero-sized type");
XSRETURN_UNDEF;
lib/Affix.c view on Meta::CPAN
signature = SvPV_nolen(type_sv);
bool live_hint = (signature[0] == '+');
if (live_hint)
signature++;
infix_type * new_type = nullptr;
infix_arena_t * parse_arena = nullptr;
if (infix_type_from_signature(&new_type, &parse_arena, signature, MY_CXT.registry) != INFIX_SUCCESS) {
SV * err_sv = _format_parse_error(aTHX_ "for cast", signature, infix_get_last_error());
warn_sv(err_sv);
if (parse_arena)
infix_arena_destroy(parse_arena);
XSRETURN_UNDEF;
}
/* Value (Copy) vs Pin (Reference) */
bool return_as_value = false;
bool is_string_type = false;
lib/Affix.c view on Meta::CPAN
0,
nullptr);
if (buf) {
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
buf[--len] = '\0';
sv_setpvn(dual, buf, len);
LocalFree(buf);
}
else
sv_setpvn(dual, "Unknown system error", 20);
SvIOK_on(dual);
SvIsUV_on(dual); // Mark as unsigned for DWORD
#else
int err_code = errno;
sv_setiv(dual, err_code);
const char * msg = strerror(err_code);
if (msg)
sv_setpv(dual, msg);
else
sv_setpv(dual, "Unknown system error");
SvIV_set(dual, (IV)err_code);
SvIOK_on(dual);
#endif
ST(0) = sv_2mortal(dual);
XSRETURN(1);
}
XS_INTERNAL(Affix_dump) {
lib/Affix.c view on Meta::CPAN
sv_setsv(get_sv("Affix::()", TRUE), &PL_sv_yes);
(void)newXSproto_portable("Affix::()", Affix_as_string, __FILE__, "$;@");
sv_setsv(get_sv("Affix::Lib::()", TRUE), &PL_sv_yes);
(void)newXSproto_portable("Affix::Lib::(0+", Affix_Lib_as_string, __FILE__, "$;@");
(void)newXSproto_portable("Affix::Lib::()", Affix_as_string, __FILE__, "$;@");
// Library & core utils
XSUB_EXPORT(load_library, "$", "lib");
XSUB_EXPORT(find_symbol, "$$", "lib");
XSUB_EXPORT(get_last_error_message, "", "core");
// Scalar pins
XSUB_EXPORT(pin, "$$$$", "pin");
XSUB_EXPORT(unpin, "$", "pin");
// Introspection
XSUB_EXPORT(sizeof, "$", "core");
XSUB_EXPORT(alignof, "$", "core");
XSUB_EXPORT(offsetof, "$$", "core");
lib/Affix.pm view on Meta::CPAN
use integer;
my @tokens = $expr =~ /(0x[0-9a-fA-F]+|\d+|[a-zA-Z_]\w*|<<|>>|&&|\|\||==|!=|<=|>=|[+\-*\/%|&^~!?:()<>])/g;
for my $t (@tokens) {
next if $t =~ /^(?:<<|>>|&&|\|\||==|!=|<=|>=|[+\-*\/%|&^~!?:()<>])$/;
next if $t =~ /^\d+$/;
next if $t =~ /^0x/;
if ( exists $lookup->{$t} ) {
$t = $lookup->{$t};
}
else {
Carp::croak("Enum definition error: Unknown symbol '$t' in expression '$expr'");
}
$t = hex($t) if $t =~ /^0x/;
}
my @output_queue;
my @op_stack;
my %prec = (
'*' => [ 13, 1 ],
'/' => [ 13, 1 ],
'%' => [ 13, 1 ],
'+' => [ 12, 1 ],
lib/Affix.pod view on Meta::CPAN
Helper functions that locate and return the file paths to the standard C library and the standard math library for the
current platform. Because platform implementations differ wildly (e.g., MSVCRT on Windows, glibc on Linux, libSystem on
macOS), using these helpers guarantees you get the correct library.
# Bind 'puts' from the standard C library
affix libc(), 'puts', [String] => Int;
# Bind 'cos' from the math library
affix libm(), 'cos', [Double] => Double;
=head3 C<get_last_error_message()>
If C<load_library>, C<find_symbol>, or a signature parsing step fails, this function returns a string describing the
most recent internal or operating system error (via C<dlerror> or C<FormatMessage>).
my $lib = load_library('does_not_exist');
if (!$lib) {
die "Failed to load library: " . get_last_error_message();
}
=head1 INTROSPECTION
When working with C APIs, you often need to know exactly how much memory a structure consumes or where a specific field
is located within a block of memory. Affix provides compiler-grade type introspection.
=head3 C<sizeof( $type )>
Returns the size, in bytes, of any Affix Type object or registered C<typedef> name.
lib/Affix.pod view on Meta::CPAN
=head1 ERROR HANDLING & DEBUGGING
Bridging two entirely different runtimes can lead to spectacular crashes if types or memory boundaries are mismatched.
Affix provides built-in tools to help you identify what went wrong.
=head2 Error Handling
=head3 C<errno()>
Accesses the system error code from the most recent FFI or standard library call (reads C<errno> on Unix and
C<GetLastError> on Windows).
This function returns a B<dualvar>. It behaves as an integer in numeric context, and magically resolves to the
human-readable system error message (via C<strerror> or C<FormatMessage>) in string context.
# Suppose a C file-open function fails
my $fd = c_open("/does/not/exist");
if (!$fd) {
my $err = errno();
# String context
say "Failed to open: $err"; # "No such file or directory"
# Numeric context
if (int($err) == 2) {
say "Code 2 specifically triggered.";
}
}
B<Note:> You must call C<errno()> immediately after the C function invokes, as subsequent Perl operations (like
printing to STDOUT) might overwrite the system's error register.
=head2 Memory Inspection
=head3 C<dump( $pin, $length_in_bytes )>
Prints a formatted hex dump of the memory pointed to by a Pin directly to C<STDOUT>. This is an invaluable tool for
verifying that C structs or buffers contain the data you expect.
my $ptr = strdup("Affix Debugging");
dump($ptr, 16);
lib/Affix/Build.pod view on Meta::CPAN
=over
=item 1. Inspects added sources to determine the Build Strategy.
=item 2. Compiles all sources to their appropriate intermediate or final formats.
=item 3. Links the artifacts (if necessary) into a shared library.
=back
Returns a L<Path::Tiny> object pointing to the generated shared library. Dies with a detailed error message (including
STDOUT/STDERR of the failed command) if compilation fails.
=head2 C<link( )>
An alias for C<compile_and_link()>. Safe to call multiple times; it will return the cached library handle if the build
has already completed.
=head1 SUPPORTED LANGUAGES
C<Affix::Build> attempts to locate the necessary binaries in your system C<PATH>.
lib/Affix/Wrap.pod view on Meta::CPAN
B<Note:> In C, C<typedef struct { ... } Name;> results in a C<Typedef> object where B<underlying> is the C<Struct>
object.
=head2 Affix::Wrap::Enum
An enumeration.
=over
=item * C<affix_type>: Returns signature string C<Enum[ Name =E<gt> Val, ... ]>. String values/expressions in enums are quoted automatically to prevent eval errors.
=back
=head2 Affix::Wrap::Variable
A global C<extern> variable.
=over
=item * C<affix_type>: Returns string C<pin my $var, $lib, name =E<gt> Type>.
lib/Test2/Tools/Affix.pm view on Meta::CPAN
else {
my ( $stdout, $stderr, $exit_code ) = Capture::Tiny::capture(
sub {
system('valgrind --version');
}
);
plan skip_all 'Valgrind is not installed' if $exit_code;
diag 'Valgrind v', ( $stdout =~ m/valgrind-(.+)$/ ), ' found';
diag 'Generating suppressions...';
my @cmd = (
qw[valgrind --leak-check=full --show-reachable=yes --error-limit=no
--gen-suppressions=all --log-fd=1], $^X, '-e',
sprintf <<'', ( join ', ', map {"'$_'"} sort { length $a <=> length $b } map { path($_)->absolute->canonpath } @INC ) );
use strict;
use warnings;
use lib %s;
use Affix;
no Test2::Plugin::ExitSummary;
use Test2::V0;
pass "generate valgrind suppressions";
done_testing;
lib/Test2/Tools/Affix.pm view on Meta::CPAN
}
sub parse_xml {
my ($xml) = @_;
my $hash = {};
my $re = qr{<([^>]+)>\s*(.*?)\s*</\1>}sm;
while ( $xml =~ m/$re/g ) {
my ( $tag, $content ) = ( $1, $2 );
$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};
}
}
lib/Test2/Tools/Affix.pm view on Meta::CPAN
}
}
}
$hash->{$tag}
= defined $content ?
(
defined $hash->{$tag} ?
ref $hash->{$tag} eq 'ARRAY' ?
[ @{ $hash->{$tag} }, $content ] :
[ $hash->{$tag}, $content ] :
$tag =~ m/^(error|stack)$/ ? [$content] :
dec_ent($content) ) :
undef;
}
$hash;
}
# Function to run anonymous sub in a new process with valgrind
sub leaks( $name, $code_ref ) {
init_valgrind();
#
lib/Test2/Tools/Affix.pm view on Meta::CPAN
$|++;
my $exit = sub {use Affix; Affix::set_destruct_level(3); %s;}->();
# Test2::API::test2_stack()->top->{count}++;
done_testing;
exit !$exit;
my $report = Path::Tiny->tempfile( { realpath => 1 }, 'valgrind_report_XXXXXXXXXX' );
push @cleanup, $report;
my @cmd = (
'valgrind', '-q', '--suppressions=' . $supp->canonpath,
'--leak-check=full', '--show-leak-kinds=all', '--show-reachable=yes', '--demangle=yes', '--error-limit=no', '--xml=yes',
'--gen-suppressions=all', '--xml-file=' . $report->stringify,
$^X, '-e', $source
);
my ( $out, $err, $exit ) = Capture::Tiny::capture(
sub {
system @cmd;
}
);
# $out =~ s[# Seeded srand with seed .+$][]m;
lib/Test2/Tools/Affix.pm view on Meta::CPAN
if ( $err =~ m[\S] ) {
$err =~ s[^((?:[ \t]*))(?=\S)][$1 ]gm;
print STDERR $err;
}
my $parsed = parse_xml( $report->slurp_utf8 );
# use Data::Dump;
# ddx $parsed;
# diag 'exit: '. $exit;
# Test2::API::test2_stack()->top->{count}++;
ok !$exit && !$parsed->{valgrindoutput}{errorcounts}, $name;
}
}
END {
for my $file ( grep {-f} @cleanup ) {
#~ note 'Removing ' . $file;
unlink $file;
}
for my $dir ( grep {-d} @cleanup ) {
t/001_affix.t view on Meta::CPAN
imported_ok qw[sizeof alignof offsetof];
imported_ok qw[calloc free malloc realloc dump own
memchr memcmp memcpy memmove memset
strdup strnlen
ptr_add ptr_diff
address
is_null
];
imported_ok qw[load_library find_symbol ];
imported_ok qw[typedef cast coerce];
imported_ok qw[get_last_error_message];
imported_ok qw[direct_affix direct_wrap]; # Secrets
};
subtest types => sub {
imported_ok qw[
Array Bool Callback Char CodeRef Complex Double Enum File
Float Float32 Float64 Int Int128 Int16 Int32 Int64 Int8 Long LongDouble
LongLong M256 M256d M512 M512d M512i Packed PerlIO Pointer SChar
SInt128 SInt16 SInt32 SInt64 SInt8 SSize_t SV
Short Size_t String Struct UChar UInt UInt128
UInt16 UInt32 UInt64 UInt8 ULong ULongLong UShort
t/001_affix.t view on Meta::CPAN
is $res, { a => 2, b => 4, c => 8, d => 16 }, 'make_bitfield returns correct hash';
};
subtest 'Forward Call with Many Arguments' => sub {
note 'Testing a C function with more arguments than available registers.';
my $sig = '(int64, int64, int64, int64, int64, int64, int64, int64, int64)->int64';
isa_ok my $summer = wrap( $lib_path, 'multi_arg_sum', $sig ), ['Affix'];
my $result = $summer->( 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 );
is $result, 111111111, 'Correctly passed 9 arguments to a C function';
};
subtest 'Parser Error Reporting' => sub {
note 'Testing that malformed signatures produce helpful error messages.';
like warning { Affix::wrap( $lib_path, 'add', '(int, ^, int)->int' ) }, qr[parse signature], 'wrap() warning on invalid signature';
like warning { Affix::sizeof('{int, double') }, qr[parse signature], 'sizeof() warning on unterminated aggregate';
};
subtest 'These are called under valgrind in 900_leak' => sub {
subtest 'use Affix' => sub {
use Affix qw[];
pass 'loaded';
};
subtest 'affix($$$$)' => sub {
no warnings 'redefine';