Affix

 view release on metacpan or  search on metacpan

Changes.md  view on Meta::CPAN

- Fully implemented marshalling for `Int128` and `UInt128` (sint128/uint128) primitive types.
- Added `Affix::Wrap->generate( $lib, $pkg, $file )` for static binding generation. This emits standalone Perl modules that depend only on `Affix`, eliminating the need for `Clang` or header files at runtime.
- Recursive macro resolution support in `Affix::Wrap` for bitwise OR expressions like `(FLAG_A | FLAG_B)`.
- Support for passing string names of enum constants directly to functions.
- Added `params()` method to `Affix::Type::Callback` to allow inspecting and modifying callback parameters.
- Added string-to-integer conversion when passing Perl strings to C functions expecting enums.

### Fixed

- Optimized `Pointer` returns in the XSUB dispatcher for performance by inlining the marshalling path and caching the stash.
- Fixed several issues in `CLONE` where metadata, managed memory, and enum registries were not correctly duplicated across perl's ithreads.
- Improved `_get_pin_from_sv` and `is_pin` to safely handle both references to pins and direct magical scalars like those found in Unions.
- Fixed potential double-frees and leaks in `Affix_Lib_DESTROY` and `Affix_free_pin` by improving reference counting and ownership tracking.
- Symbols found via `find_symbol` now correctly track the parent `Affix::Lib` object to prevent the library from being unloaded while symbols are still in use.
- Corrected a memory corruption bug in `Affix_malloc` and `Affix_strdup` caused by uninitialized internal `Affix_Pin` structures.
- Fixed `dualvar` behavior for enums returned from C, ensuring they correctly function as both strings and integers in Perl.
- Fixed the `clean` action in `Affix::Builder` which was failing due to an undefined `rmtree` call.
- Fixed an issue where blessing a return value could prematurely trigger 'set' magic on the underlying SV.
- Fixed `typedef` parsing: Named types now return proper `Affix::Type::Reference` objects instead of strings, ensuring they are correctly resolved when nested in other aggregates.
- Fixed `cast` to correctly return blessed `Affix::Live` objects when the `+` hint is used for live struct views.
- Hardened pointer indexing: Added strict type checks to `$ptr->[$i]` to ensure indexing is only performed on `Array` types or `Void*` (byte-indexed).

Changes.md  view on Meta::CPAN


- 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`).
- [infix] Refined the Windows x64 ABI to pass all vector types by reference (pointer in GPR). This ensures compatibility with MSVC which expects even 128-bit vectors to be passed via pointer in many scenarios, while still returning them by value in `...
- [infix] Move to a pre-calculated hash field in `_infix_registry_entry_t`. Lookups and rehashing now use this stored hash, significantly reducing string hashing overhead during type resolution and registry scaling.
- [infix] Optimized Type Registry memory management: Internal hash table buckets are now heap-allocated and freed during rehashes, preventing memory "leaks" within the registry's arena.

## [v1.0.6] - 2026-01-22

Most of this version's work went into threading stability, ABI correctness, and security within the JIT engine.

### Changed

  - [[infix]] The JIT memory allocator on Linux now uses `memfd_create` (on kernels 3.17+) to create anonymous file descriptors for dual-mapped W^X memory. This avoids creating visible temporary files in `/dev/shm` and improves hygiene and security. ...
  - \[infix] On dual-mapped platforms (Linux/BSD), the Read-Write view of the JIT memory is now **unmapped immediately** after code generation. This closes a security window where an attacker with a heap read/write primitive could potentially modify ...
  - \[infix] `infix_library_open` now uses `RTLD_LOCAL` instead of `RTLD_GLOBAL` on POSIX systems. This prevents symbols from loaded libraries from polluting the global namespace and causing conflicts with other plugins or the host application.

### Fixed

  - Fixed `CLONE` to correctly copy user-defined types (typedefs, structs) to new threads. Previously, child threads started with an empty registry, causing lookup failures for types defined in the parent.
  - Thread safety: Fixed a crash when callbacks are invoked from foreign threads. Affix now correctly injects the Perl interpreter context into the TLS before executing the callback.
  - Added stack overflow protection to the FFI trigger. Argument marshalling buffers larger than 2KB are now allocated on the heap (arena) instead of the stack, preventing crashes on Windows and other platforms with limited stack sizes.
  - Type resolution: Fixed a logic bug where `Pointer[SV]` types were incorrectly treated as generic pointers if `typedef`'d. They are now correctly unwrapped into Perl CODE refs or blessed objects.
  - 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::Wrap allows you to define your own types just in case the headers fail to parse completely.

## [v1.0.4] - 2026-01-10

This should just be a documentation cleanup cycle.

Changes.md  view on Meta::CPAN


  - `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

MANIFEST  view on Meta::CPAN

t/017_affix_build.t
t/018_sv_type.t
t/019_fileio.t
t/020_deep_types.t
t/021_shakedown.t
t/022_stringlist.t
t/023_sockaddr.t
t/024_buffer.t
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

README.md  view on Meta::CPAN

## Functions

### `load_library( $path_or_name )`

Locates and loads a dynamic library into memory, returning an opaque `Affix::Lib` handle.

```perl
my $lib = load_library('sqlite3');
```

**Lifecycle:** Library handles are thread-safe and internally reference-counted. The underlying OS library is only
closed (e.g., via `dlclose` or `FreeLibrary`) when all Affix wrappers and pins relying on it are destroyed.

_Note:_ When using `affix()` or `wrap()`, you can safely pass the string name directly (e.g., `affix('sqlite3',
...)`) and Affix will call `load_library` for you internally. If you pass `undef` instead of a library name, Affix
will search the currently running executable process.

### `locate_lib( $name, [$version] )`

Searches for a library using Affix's discovery engine and returns its absolute file path as a string. It **does not**
load the library into memory. This is useful if you need to pass the library path to another tool or check for its

README.md  view on Meta::CPAN


# COMPANION MODULES

Affix ships with two powerful companion modules to streamline your FFI development:

- [**Affix::Wrap**](https://metacpan.org/pod/Affix%3A%3AWrap): Parses C/C++ headers using the Clang AST to automatically generate Affix bindings for entire libraries.
- [**Affix::Build**](https://metacpan.org/pod/Affix%3A%3ABuild): A polyglot builder that compiles inline C, C++, Rust, Zig, Go, and 15+ other languages into dynamic libraries you can bind instantly.

# THREAD SAFETY & CONCURRENCY

Affix bridges Perl (a single-threaded interpreter, generally) with libraries that may be multi-threaded. This creates
potential hazards that you must manage.

## 1. Initialization Phase vs. Execution Phase

Functions that modify Affix's global state are **not thread-safe**. You must perform all definitions in the main thread
before starting any background threads or loops in the library.

Unsafe operations that you should never call from Callbacks or in a threaded context:

- `affix( ... )` - Binding new functions.
- `typedef( ... )` - Registering new types.

## 2. Callbacks

When passing a Perl subroutine as a `Callback`, avoid performing complex Perl operations like loading modules or
defining subs inside callbacks triggered on a foreign thread. Such callbacks should remain simple: process data, update
a shared variable, and return.

If the library executes the callback from a background thread (e.g., window managers, audio callbacks), Affix attempts
to attach a temporary Perl context to that thread. This should be sufficient but Perl is gonna be Perl.

# RECIPES & EXAMPLES

See [The Affix Cookbook](https://github.com/sanko/Affix.pm/discussions/categories/recipes) for comprehensive guides to
using Affix.

## Linked List Implementation

```perl
# C equivalent:

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

                ' -Wduplicated-branches';
            $cflags .= ' -fvar-tracking-assignments' unless $Config{osname} eq 'darwin';
        }
        elsif ( !$is_win ) {
            $cflags
                .= ' -DNDEBUG -DBOOST_DISABLE_ASSERTS -Ofast -ftree-vectorize -ffast-math -fno-align-functions -fno-align-loops -fno-omit-frame-pointer -flto=auto';
        }

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

    method step_build() {
        $self->step_affix;
        my %modules       = map { $_ => catfile( 'blib', $_ ) } find( qr/\.pm$/,  'lib' );
        my %docs          = map { $_ => catfile( 'blib', $_ ) } find( qr/\.pod$/, 'lib' );
        my %scripts       = map { $_ => catfile( 'blib', $_ ) } find( qr/(?:)/,   'script' );

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

        }
        else {
            # GCC / Clang
            $ar_cmd      = 'ar';
            @cflags      = ( '-std=c11', '-Wall', '-Wextra', '-O2', '-fPIC', @includes );
            @cflags      = ( @cflags, '-DINFIX_DEBUG_ENABLED=1' ) if $verbose;
            @arflags     = ('rcs');
            $out_flag_cc = '-o';
            $out_flag_ar = '';

            # Pass pthread to compilation of static lib too (safe practice)
            push @cflags, '-pthread' unless $^O eq 'MSWin32';
        }

        # Compile infix.c -> infix.o
        my $obj_ext  = $cc_type eq 'msvc' ? '.obj' : '.o';
        my $obj_file = $build_lib->child( 'infix' . $obj_ext );
        my @compile_cmd;
        if ( $cc_type eq 'msvc' ) {
            @compile_cmd = ( $cc_cmd, @cflags, $out_flag_cc . $obj_file, $src_file );
        }
        else {

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

        }

        # Point to the Architecture-specific build lib
        my $infix_build_lib = cwd->absolute->child('infix')->child( 'build_lib', $Config{archname} )->stringify;

        # Check for -lrt requirement
        my $lrt_flag = $self->check_for_lrt();
        my $data     = {

            # Removed incorrect -lstdc++ logic. Added -lm for math.
            # -pthread is already in $ldflags via ADJUST
            extra_linker_flags => ( $ldflags . ' -L' . $infix_build_lib . ' -linfix ' . $lrt_flag . ' -lm' ),
            objects            => [@objs],
            lib_file           => $lib_file,
            module_name        => join '::',
            @parts
        };
        return $builder->link(%$data);
    }
    };
1;

infix/include/infix/infix.h  view on Meta::CPAN

 * 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 INFIX_NODISCARD infix_registry_t * infix_registry_create(void);
/**
 * @brief Creates a deep copy of an existing type registry.
 *
 * This copies all defined types and their dependency graphs into a new registry with its own arena.
 * This is essential for thread safety in languages that spawn threads by cloning interpreter state (like Perl).
 *
 * @param[in] registry The registry to clone.
 * @return A pointer to the new registry, or `nullptr` on failure.
 */
INFIX_API INFIX_NODISCARD infix_registry_t * infix_registry_clone(const infix_registry_t *);
/**
 * @brief Destroys a type registry and frees all associated memory.
 *
 * This includes freeing the registry handle itself, its internal hash table, and
 * all `infix_type` objects that were created as part of a definition.

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 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/include/infix/infix.h  view on Meta::CPAN

    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

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

 * testing tools. It is used for all unit and regression tests within the `infix` project.
 *
 * The library is designed to be trivial to use:
 * 1.  Define `DBLTAP_ENABLE` and `DBLTAP_IMPLEMENTATION` in a single test file.
 * 2.  Write all test logic within a function named `test_body(void)` using the `TEST` macro.
 * 3.  Use the provided macros (`plan`, `ok`, `subtest`, etc.) to structure tests.
 *
 * The library provides its own `main` function that initializes the harness, calls
 * the user-defined `test_body`, and reports the final results.
 *
 * @section thread_safety Thread Safety
 *
 * The design uses thread-local storage (`_Thread_local`, `__thread`) to manage the
 * test state (test counts, subtest nesting, etc.). This allows multiple threads to
 * run tests concurrently without interfering with each other's output or results,
 * making it suitable for testing thread-safe code. Global counters use atomic
 * operations where available to ensure correctness.
 *
 * @internal
 */
#pragma once
#ifdef DBLTAP_ENABLE
#define TAP_VERSION 13
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__unix__) || defined(__APPLE__) || defined(__OpenBSD__)
#include <unistd.h>
#endif
#if defined(_WIN32) || defined(__CYGWIN__)
#include <windows.h>
#elif (defined(__unix__) || defined(__APPLE__)) && !defined(__OpenBSD__)
// Do not include pthread.h on OpenBSD to prevent linking/cleanup issues if -pthread is not used.
#include <pthread.h>
#endif

// C++ Headers must be included BEFORE extern "C"
#if defined(__cplusplus)
#include <atomic>
#endif

#ifdef __cplusplus
extern "C" {
#endif

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

#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
#include <stdatomic.h>
#define TAP_ATOMIC_SIZE_T _Atomic size_t
#define TAP_ATOMIC_FETCH_ADD(ptr, val) atomic_fetch_add(ptr, val)
#define TAP_ATOMIC_INIT(val) = val
#elif defined(__GNUC__) || defined(__clang__)
#define TAP_ATOMIC_SIZE_T size_t
#define TAP_ATOMIC_FETCH_ADD(ptr, val) __atomic_fetch_add(ptr, (size_t)(val), __ATOMIC_SEQ_CST)
#define TAP_ATOMIC_INIT(val) = val
#else
// Fallback for older compilers without atomics support. This is not thread-safe.
#define TAP_ATOMIC_SIZE_T size_t
#define TAP_ATOMIC_FETCH_ADD(ptr, val) ((*ptr) += (val))
#define TAP_ATOMIC_INIT(val) = val
#if !defined(_MSC_VER)
#warning "Compiler does not support C11 atomics or GCC builtins; global counters will not be thread-safe."
#endif
#endif

#if defined(__OpenBSD__)
// OpenBSD has known issues with TLS cleanup in some linking scenarios.
// Disable TLS to prevent segfaults at exit.
#define TAP_THREAD_LOCAL
#elif defined(__cplusplus)
#define TAP_THREAD_LOCAL thread_local
#elif defined(_MSC_VER)
// Microsoft Visual C++
#define TAP_THREAD_LOCAL __declspec(thread)
#elif defined(_WIN32) && defined(__clang__)
// Clang on Windows
#define TAP_THREAD_LOCAL __declspec(thread)
#elif defined(__GNUC__) || defined(__clang__)
// GCC (including MinGW) and Clang on *nix
#define TAP_THREAD_LOCAL __thread
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
#define TAP_THREAD_LOCAL _Thread_local
#else
#define TAP_THREAD_LOCAL
#if !defined(_MSC_VER)
#warning "Compiler does not support thread-local storage; tests will not be thread-safe."
#endif
#endif

#if defined(__GNUC__) || defined(__clang__)
#define DBLTAP_NOINLINE __attribute__((noinline))
#elif defined(_MSC_VER)
#define DBLTAP_NOINLINE __declspec(noinline)
#else
#define DBLTAP_NOINLINE
#endif

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

#define TEST \
    int main(void) { return 0; }
#endif  // DBLTAP_ENABLE

#if defined(DBLTAP_ENABLE) && defined(DBLTAP_IMPLEMENTATION)
// Internal Test State Management
/**
 * @internal
 * @brief Holds the complete state for a single test scope (main test or a subtest).
 *
 * A stack of these structs is maintained in thread-local storage to allow for
 * nested subtests and concurrent test execution across threads.
 */
typedef struct {
    size_t plan;            /**< The number of planned tests for this scope. */
    size_t count;           /**< The number of tests executed so far. */
    size_t failed;          /**< The number of failed tests in this scope. */
    size_t failed_todo;     /**< The number of failed tests within a TODO block. */
    int indent_level;       /**< The nesting level for indented TAP output. */
    bool has_plan;          /**< `true` if a plan has been declared for this scope. */
    bool skipping;          /**< `true` if `skip_all` is active for this scope. */
    bool todo;              /**< `true` if inside a `TODO` block. */
    char subtest_name[256]; /**< The name of the current subtest. */
    char todo_reason[256];  /**< The reason for the current `TODO` block. */
    char skip_reason[256];  /**< The reason for the current `skip_all`. */
} tap_state_t;

#define MAX_DEPTH 16         /**< Maximum nesting depth for subtests. */
#define NO_PLAN ((size_t)-1) /**< Sentinel value for an undeclared plan. */

/** @internal The thread-local stack of test states. */
static TAP_THREAD_LOCAL tap_state_t state_stack[MAX_DEPTH];
/** @internal A pointer to the current test state on the thread-local stack. */
static TAP_THREAD_LOCAL tap_state_t * current_state = NULL;
/** @internal A global, thread-safe counter for the total number of failed tests across all threads. */
static TAP_ATOMIC_SIZE_T g_total_failed TAP_ATOMIC_INIT(0);

// One-Time Initialization for TAP Header
#if defined(_WIN32) || defined(__CYGWIN__)
static INIT_ONCE g_tap_init_once = INIT_ONCE_STATIC_INIT;
static BOOL CALLBACK _tap_init_routine(PINIT_ONCE initOnce, PVOID param, PVOID * context) {
    (void)initOnce;
    (void)param;
    (void)context;
    printf("TAP version %d\n", TAP_VERSION);
    fflush(stdout);
    return TRUE;
}
#elif (defined(__unix__) || defined(__APPLE__)) && !defined(__OpenBSD__)
static pthread_once_t g_tap_init_once = PTHREAD_ONCE_INIT;
static void _tap_init_routine(void) {
    printf("TAP version %d\n", TAP_VERSION);
    fflush(stdout);
}
#else  // OpenBSD or other platforms without robust pthread_once support in this context
static bool g_tap_initialized = false;
#endif

/**
 * @internal
 * @brief Ensures the TAP header has been printed and thread-local state is initialized.
 * Uses `pthread_once` or `InitOnceExecuteOnce` to guarantee the TAP version header
 * is printed exactly once per process, even with multiple threads. It also initializes
 * the thread-local state for the current thread if it's the first test call on that thread.
 */
static void _tap_ensure_initialized(void) {
#if defined(_WIN32) || defined(__CYGWIN__)
    InitOnceExecuteOnce(&g_tap_init_once, _tap_init_routine, NULL, NULL);
#elif (defined(__unix__) || defined(__APPLE__)) && !defined(__OpenBSD__)
    pthread_once(&g_tap_init_once, _tap_init_routine);
#else
    // Fallback for OpenBSD/single-threaded builds
    if (!g_tap_initialized) {
        printf("TAP version %d\n", TAP_VERSION);
        fflush(stdout);
        g_tap_initialized = true;
    }
#endif
    if (!current_state) {
        current_state = &state_stack[0];
        memset(current_state, 0, sizeof(tap_state_t));
        current_state->plan = NO_PLAN;

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

}

// Internal Helper Functions
/** @internal Prints the indentation corresponding to the current subtest depth. */
static void print_indent(FILE * stream) {
    _tap_ensure_initialized();
    for (int i = 0; i < current_state->indent_level; ++i)
        fprintf(stream, "    ");
}

/** @internal Pushes a new state onto the thread-local stack for entering a subtest. */
static void push_state(void) {
    if (current_state >= &state_stack[MAX_DEPTH - 1])
        tap_bail_out("Exceeded maximum subtest depth of %d", MAX_DEPTH);
    tap_state_t * parent = current_state;
    current_state++;
    memset(current_state, 0, sizeof(tap_state_t));
    current_state->plan = NO_PLAN;
    current_state->indent_level = parent->indent_level + 1;
    // A subtest inherits the 'todo' state from its parent.
    if (parent->todo) {

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

#define INFIX_ENV_TERMUX 1
#elif defined(__ANDROID__)
#define INFIX_OS_ANDROID
#define INFIX_OS_LINUX
#define INFIX_ENV_POSIX
#elif defined(__APPLE__)
#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

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

};
// 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)
#define INFIX_MUTEX_DESTROY(m) ((void)0)
#else
#include <pthread.h>
typedef pthread_mutex_t infix_mutex_t;
#define INFIX_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER
#define INFIX_MUTEX_LOCK(m) pthread_mutex_lock(m)
#define INFIX_MUTEX_UNLOCK(m) pthread_mutex_unlock(m)
#define INFIX_MUTEX_DESTROY(m) pthread_mutex_destroy(m)
#endif

/**
 * @struct _infix_registry_entry_t
 * @brief A single entry in the registry's hash table.
 * @details This is a node in a singly-linked list used for chaining in the
 * event of a hash collision.
 */
typedef struct _infix_registry_entry_t {
    const char * name;                     /**< The registered name of the type. */

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);

/**

infix/src/core/error.c  view on Meta::CPAN

 *
 * 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)
// OpenBSD has known issues with TLS cleanup in some linking scenarios (segfault on exit).
// Disable TLS entirely on this platform to ensure stability, at the cost of thread-safety.
#define INFIX_TLS
#elif defined(INFIX_COMPILER_MSVC)
// Microsoft Visual C++
#define INFIX_TLS __declspec(thread)
#elif defined(INFIX_OS_WINDOWS) && defined(INFIX_COMPILER_CLANG)
// Clang on Windows: check if behaving like MSVC or GCC.
// If using MSVC codegen/headers, use declspec.
#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.

infix/src/core/error.c  view on Meta::CPAN

        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.
 */

infix/src/core/error.c  view on Meta::CPAN

    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);

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

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/types.c  view on Meta::CPAN

    type->meta.named_reference.aggregate_category = agg_cat;
    *out_type = type;
    return INFIX_SUCCESS;
}
// Internal Type Graph Management
/**
 * @internal
 * @struct recalc_visited_node_t
 * @brief A node for a visited-list to prevent infinite recursion on cyclic types during layout calculation.
 *
 * @details This is a temporary structure, typically allocated on the main thread's heap,
 * used during the layout recalculation process (`_infix_type_recalculate_layout_recursive`).
 * It forms a singly-linked list that acts as a "visited set" for a depth-first
 * traversal of the type graph. Its purpose is to detect and correctly handle cycles
 * in type definitions (e.g., `struct Node { struct Node* next; };`) and prevent
 * a stack overflow from infinite recursion.
 */
typedef struct recalc_visited_node_t {
    infix_type * type; /**< The `infix_type` object that has been visited during the current traversal path. */
    struct recalc_visited_node_t * next; /**< A pointer to the next node in the visited list. */
} recalc_visited_node_t;

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

 * 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).

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Platform-Specific Includes
#if defined(INFIX_OS_WINDOWS)
#include <windows.h>
#else
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#if defined(INFIX_OS_MACOS)
#include <dlfcn.h>
#include <libkern/OSCacheControl.h>
#endif
// Polyfills for mmap flags for maximum POSIX compatibility.
#if defined(INFIX_ENV_POSIX) && !defined(INFIX_OS_WINDOWS)
#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS)
#define MAP_ANON MAP_ANONYMOUS
#endif
static pthread_mutex_t g_dwarf_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif

#if defined(INFIX_OS_WINDOWS) && defined(INFIX_ARCH_X64)
// SEH Unwind Info Opcodes and Structures for JIT code on Windows x64.
// These are defined in winnt.h but we redefine them here for clarity and to ensure availability.
#define UWOP_PUSH_NONVOL 0
#define UWOP_ALLOC_LARGE 1
#define UWOP_ALLOC_SMALL 2
#define UWOP_SET_FPREG 3

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

#define INFIX_SEH_METADATA_SIZE 0
#endif

// macOS JIT Security Hardening Logic
#if defined(INFIX_OS_MACOS)
/**
 * @internal
 * @brief macOS-specific function pointers and types for checking JIT entitlements.
 *
 * @details To support hardened runtimes on Apple platforms (especially Apple Silicon),
 * `infix` must use special APIs like `MAP_JIT` and `pthread_jit_write_protect_np`.
 * However, these are only effective if the host application has been granted the
 * `com.apple.security.cs.allow-jit` entitlement.
 *
 * This logic performs a runtime check for these APIs and the entitlement, gracefully
 * falling back to the legacy (but less secure) `mprotect` method if they are not
 * available. This provides maximum security for production apps while maintaining
 * maximum convenience for developers who may not have codesigned their test executables.
 */
typedef const struct __CFString * CFStringRef;
typedef const void * CFTypeRef;

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

typedef struct __CFError * CFErrorRef;
#define kCFStringEncodingUTF8 0x08000100
// A struct to hold dynamically loaded function pointers from macOS frameworks.
static struct {
    void (*CFRelease)(CFTypeRef);
    bool (*CFBooleanGetValue)(CFTypeRef boolean);
    CFStringRef (*CFStringCreateWithCString)(CFTypeRef allocator, const char * cStr, uint32_t encoding);
    CFTypeRef kCFAllocatorDefault;
    SecTaskRef (*SecTaskCreateFromSelf)(CFTypeRef allocator);
    CFTypeRef (*SecTaskCopyValueForEntitlement)(SecTaskRef task, CFStringRef entitlement, CFErrorRef * error);
    void (*pthread_jit_write_protect_np)(int enabled);
    void (*sys_icache_invalidate)(void * start, size_t len);
} g_macos_apis;
/**
 * @internal
 * @brief One-time initialization to dynamically load macOS framework functions.
 * @details Uses `dlopen` and `dlsym` to find the necessary CoreFoundation and Security
 * framework functions at runtime. This avoids a hard link-time dependency,
 * making the library more portable and resilient if these frameworks change.
 */
static void initialize_macos_apis(void) {
    // We don't need to link against these frameworks, which makes building simpler.
    void * cf = dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", RTLD_LAZY);
    void * sec = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY);

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

    if (!cf || !sec) {
        INFIX_DEBUG_PRINTF("Warning: Could not dlopen macOS frameworks. JIT security features will be degraded.");
        if (cf)
            dlclose(cf);
        if (sec)
            dlclose(sec);
        return;
    }

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

    g_macos_apis.SecTaskCopyValueForEntitlement = dlsym(sec, "SecTaskCopyValueForEntitlement");
    dlclose(cf);
    dlclose(sec);
}
/**
 * @internal
 * @brief Checks if the current process has the `com.apple.security.cs.allow-jit` entitlement.
 * @return `true` if the entitlement is present and set to true, `false` otherwise.
 */
static bool has_jit_entitlement(void) {
    // Use pthread_once to ensure the dynamic loading happens exactly once, thread-safely.
    static pthread_once_t init_once = PTHREAD_ONCE_INIT;
    pthread_once(&init_once, initialize_macos_apis);

    // Secure JIT path on macOS requires both the entitlement check and the toggle API.
    if (!g_macos_apis.pthread_jit_write_protect_np)
        return false;

    if (!g_macos_apis.SecTaskCopyValueForEntitlement || !g_macos_apis.CFStringCreateWithCString)
        return false;
    bool result = false;
    SecTaskRef task = g_macos_apis.SecTaskCreateFromSelf(g_macos_apis.kCFAllocatorDefault);
    if (!task)
        return false;
    CFStringRef key = g_macos_apis.CFStringCreateWithCString(
        g_macos_apis.kCFAllocatorDefault, "com.apple.security.cs.allow-jit", kCFStringEncodingUTF8);

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

                           g_use_secure_jit_path ? "secure (MAP_JIT)" : "legacy (mprotect)");
        g_checked_jit_support = true;
    }
    // If entitled, use the modern, more secure MAP_JIT flag.
    if (g_use_secure_jit_path)
        flags |= MAP_JIT;
#endif  // INFIX_OS_MACOS
    code = mmap(nullptr, size, PROT_READ | PROT_WRITE, flags, -1, 0);
#if defined(INFIX_OS_MACOS)
    if (code != MAP_FAILED && g_use_secure_jit_path) {
        // Switch thread to Write mode. enabled=0 means Write allowed.
        g_macos_apis.pthread_jit_write_protect_np(0);
    }
#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);
        }
    }

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

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);

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

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

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

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

    exec->eh_frame_ptr = eh;
    INFIX_DEBUG_PRINTF("Registered DWARF .eh_frame at %p for JIT code at %p", (void *)eh, exec->rx_ptr);
}
#elif defined(INFIX_OS_LINUX) && defined(INFIX_ARCH_AARCH64)
/**
 * @internal
 * @brief Registers DWARF unwind information for a JIT-compiled block on ARM64 Linux.
 * @details This allows the C++ exception unwinder to correctly walk through
 *          JIT-compiled frames. We manually construct a Common Information Entry (CIE)

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

        *p++ = 0x0d;
        *p++ = 29;  // def_cfa_register x29 (offset remains 48)
    }

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

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

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

/**
 * @internal
 * @brief Frees a block of executable memory with use-after-free hardening.
 *

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

    if (exec.seh_registration)
        RtlDeleteFunctionTable((PRUNTIME_FUNCTION)exec.seh_registration);
#endif
    if (exec.rw_ptr) {
        // Change protection to NOACCESS to catch use-after-free bugs immediately.
        if (!VirtualProtect(exec.rw_ptr, exec.size, PAGE_NOACCESS, &(DWORD){0}))
            INFIX_DEBUG_PRINTF("WARNING: VirtualProtect failed to set PAGE_NOACCESS guard page.");
        VirtualFree(exec.rw_ptr, 0, MEM_RELEASE);
    }
#elif defined(INFIX_OS_MACOS)
    // On macOS with MAP_JIT, the memory is managed with special thread-local permissions.
    // We only need to unmap the single mapping.
    if (exec.rw_ptr) {
        // Creating a guard page before unmapping is good practice.
        mprotect(exec.rw_ptr, exec.size, PROT_NONE);
        munmap(exec.rw_ptr, exec.size);
    }
#elif defined(INFIX_OS_ANDROID) || defined(INFIX_OS_OPENBSD) || defined(INFIX_OS_DRAGONFLY)
    // Other single-mapping POSIX systems.
    if (exec.rw_ptr) {
        mprotect(exec.rw_ptr, exec.size, PROT_NONE);
        munmap(exec.rw_ptr, exec.size);
    }
#else
    // Dual-mapping POSIX: protect and unmap both views.
    if (exec.eh_frame_ptr) {
        extern void __deregister_frame(void *);
        pthread_mutex_lock(&g_dwarf_mutex);
        __deregister_frame(exec.eh_frame_ptr);
        pthread_mutex_unlock(&g_dwarf_mutex);
        infix_free(exec.eh_frame_ptr);
    }
    if (exec.rx_ptr)
        mprotect(exec.rx_ptr, exec.size, PROT_NONE);
    if (exec.rw_ptr)
        munmap(exec.rw_ptr, exec.size);
    if (exec.rx_ptr && exec.rx_ptr != exec.rw_ptr)  // rw_ptr might be same as rx_ptr on some platforms
        munmap(exec.rx_ptr, exec.size);
    if (exec.shm_fd >= 0)
        close(exec.shm_fd);

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

    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);

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

 * 3.  **Finalize:** Allocate executable memory, copy the generated code into it,
 *     create the final self-contained trampoline handle (deep-copying all type
 *     metadata), and make the code executable.
 */
#include "common/infix_internals.h"
#include "common/utility.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(INFIX_OS_MACOS)
#include <pthread.h>
#endif
#if defined(INFIX_OS_WINDOWS)
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
// Forward Declaration for Internal Creation Function
static infix_status _infix_reverse_create_internal(infix_reverse_t ** out_context,
                                                   infix_type * return_type,

lib/Affix.c  view on Meta::CPAN

        memcpy(&f, &i, 4);
        return f;
    }
    e = e + (127 - 15);
    uint32_t i = s | (e << 23) | m;
    float f;
    memcpy(&f, &i, 4);
    return f;
}

// Handles thread cloning for pins. Deep copies metadata and managed memory
static int Affix_pin_dup(pTHX_ MAGIC * mg, CLONE_PARAMS * param) {
    Affix_Pin * old_pin = (Affix_Pin *)mg->mg_ptr;

    if (!old_pin)
        return 0;

    Affix_Pin * new_pin;
    Newxz(new_pin, 1, Affix_Pin);

    // Copy metadata
    new_pin->size = old_pin->size;
    new_pin->destructor = old_pin->destructor;

    // Handle data ownership
    if (old_pin->managed && old_pin->pointer && old_pin->size > 0) {
        // Deep copy managed memory so new thread owns its own block.
        // This prevents double-free and context violations.
        new_pin->pointer = safemalloc(new_pin->size);  // Allocates on heap
        memcpy(new_pin->pointer, old_pin->pointer, new_pin->size);
        new_pin->managed = true;  // Explicitly set to true: pointer is heap-allocated and managed by safefree.
    }
    else {
        // Unmanaged/Global/Null: Shallow copy pointer.
        new_pin->pointer = old_pin->pointer;
        new_pin->managed = false;  // Explicitly set to false: pointer is not managed by safefree.
    }

lib/Affix.c  view on Meta::CPAN

// Forward declarations for static helpers
static infix_direct_value_t affix_marshaller_sint(void * sv_raw);
static infix_direct_value_t affix_marshaller_uint(void * sv_raw);
static infix_direct_value_t affix_marshaller_double(void * sv_raw);
static infix_direct_value_t affix_marshaller_pointer(void * sv_raw);
static void affix_aggregate_marshaller(void * sv_raw, void * dest, const infix_type * type);
static void affix_aggregate_writeback(void * sv_raw, void * src, const infix_type * type);
static infix_direct_arg_handler_t get_direct_handler_for_type(const infix_type * type);

void Affix_trigger_backend(pTHX_ CV * cv) {
    // Backend optimization is not yet thread-clone friendly in this patch.
    // For now, assume it works or isn't used in the threading test.
    dSP;
    dAXMARK;
    dXSTARG;

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

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

lib/Affix.c  view on Meta::CPAN

// We use a macro to generate two variants (Stack vs Arena) to ensure logic sync.
#define GENERATE_TRIGGER_XSUB(NAME, USE_STACK_ALLOC)                                                         \
    void NAME(pTHX_ CV * cv) {                                                                               \
        if (UNLIKELY(PL_dirty))                                                                              \
            return;                                                                                          \
        dSP;                                                                                                 \
        dAXMARK;                                                                                             \
        dXSTARG;                                                                                             \
        Affix * affix = (Affix *)CvXSUBANY(cv).any_ptr;                                                      \
                                                                                                             \
        /* LAZY REBUILD: If we are in a new thread and data hasn't been built yet */                         \
        if (UNLIKELY(!affix->infix))                                                                         \
            rebuild_affix_data(aTHX_ affix);                                                                 \
                                                                                                             \
        if (UNLIKELY((SP - MARK) != affix->num_args))                                                        \
            croak("Wrong number of arguments. Expected %d, got %d", (int)affix->num_args, (int)(SP - MARK)); \
                                                                                                             \
        register Affix_Plan_Step * step = affix->plan;                                                       \
                                                                                                             \
        /* ALLOCATION STRATEGY */                                                                            \
        size_t arena_mark = affix->args_arena->current_offset;                                               \

lib/Affix.c  view on Meta::CPAN

            if (entry->lib == affix->lib_handle) {
                entry->ref_count--;
                if (entry->ref_count == 0) {
                    STRLEN klen;
                    const char * kstr = HePV(he, klen);
                    SV * key_sv = newSVpvn(kstr, klen);
                    if (HeKUTF8(he))
                        SvUTF8_on(key_sv);

                    // On Linux, dlclose() is notoriously dangerous for libraries that
                    // spawn background threads or register global handlers (Go, .NET, Audio, etc.)
                    // unmapping the code while these threads are active causes a SEGV.
#if defined(__linux__) || defined(__linux)
                    // Leak the library handle but free our wrapper
                    infix_free(entry->lib);
#else
                    infix_library_close(entry->lib);
#endif
                    safefree(entry);
                    hv_delete_ent(MY_CXT.lib_registry, key_sv, G_DISCARD, 0);
                    SvREFCNT_dec(key_sv);
                }

lib/Affix.c  view on Meta::CPAN

    new_affix->owner_perl = aTHX;
#endif

    // Update the new CV's fast access pointer
    CV * new_cv = (CV *)mg->mg_obj;
    CvXSUBANY(new_cv).any_ptr = (void *)new_affix;

    return 1;
}

// Helper to rebuild Affix data in the new thread
static void rebuild_affix_data(pTHX_ Affix * affix) {
    //~ warn("rebuild_affix_data: %p", affix);
    dMY_CXT;
    infix_arena_t * parse_arena = nullptr;
    infix_type * ret_type = nullptr;
    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 =
        infix_forward_create_manual(&affix->infix, ret_type, jit_arg_types, num_args, num_fixed, affix->target_addr);

    if (jit_arg_types)
        safefree(jit_arg_types);

    if (status != INFIX_SUCCESS) {
        infix_arena_destroy(parse_arena);
        croak("Affix failed to rebuild trampoline in new thread");
    }

    affix->cif = infix_forward_get_code(affix->infix);
    affix->ret_type = infix_forward_get_return_type(affix->infix);
    affix->unwrapped_ret_type = _unwrap_pin_type(affix->ret_type);
    affix->ret_pull_handler = get_pull_handler(aTHX_ affix->ret_type);
    // affix->ret_opcode is already set from parent, but safe to assume it matches

    // Allocate arenas & SV
    affix->args_arena = infix_arena_create(4096);

lib/Affix.c  view on Meta::CPAN

    pin->type = _copy_type_graph_to_arena(pin->type_arena, type);
    if (!pin->type) {
        infix_arena_destroy(pin->type_arena);
        safefree(pin);
        mg->mg_ptr = nullptr;
        croak("Failed to copy type information into pin");
    }
}
XS_INTERNAL(Affix_find_symbol) {
    dXSARGS;
    dMY_CXT;  // Require the thread-local context
    if (items != 2 || !sv_isobject(ST(0)) || !sv_derived_from(ST(0), "Affix::Lib"))
        croak_xs_usage(cv, "Affix_Lib_object, symbol_name");
    IV tmp = SvIV((SV *)SvRV(ST(0)));
    infix_library_t * lib = INT2PTR(infix_library_t *, tmp);
    const char * name = SvPV_nolen(ST(1));
    void * symbol = infix_library_get_symbol(lib, name);
    if (symbol) {
        Affix_Pin * pin;
        Newxz(pin, 1, Affix_Pin);
        pin->pointer = symbol;

lib/Affix.c  view on Meta::CPAN

#if DEBUG > 0
                if (entry->ref_count > 0)
                    warn("Affix: library handle for '%s' has %d outstanding references at END.",
                         HeKEY(he),
                         (int)entry->ref_count);
#endif

                // Temp fix: Disable library unloading at process exit.
                //
                // Many modern C libraries (WebUI, Go runtimes, Audio libs) spawn background
                // threads that persist until the process dies. If we dlclose() the library
                // here, the code segment is unmapped. When the background thread wakes up
                // to do cleanup or work, it executes garbage memory and segfaults.
                //
                // Since the process is ending, the OS will reclaim file handles and memory
                // automatically. It's (in my opinion) safer to leak the handle than to crash the process.
#if defined(__linux__) || defined(__linux)
                // Leak the library handle but free our wrapper
                if (entry->lib)
                    infix_free(entry->lib);
#else
                // This extra symbol check is here to prevent shared libs written in Go from crashing Affix.

lib/Affix.c  view on Meta::CPAN

    }
    if (!pin->pointer) {
        warn("Cannot dump a nullptr pointer");
        XSRETURN_EMPTY;
    }
    UV length = SvUV(ST(1));
    if (length == 0) {
        warn("Dump length cannot be zero");
        XSRETURN_EMPTY;
    }
    // PL_curcop may be nullptr during thread destruction or callbacks?
    const char * file = "Unknown";
    int line = 0;
    if (LIKELY(PL_curcop)) {
        file = OutCopFILE(PL_curcop);
        line = CopLINE(PL_curcop);
    }
    _DumpHex(aTHX_ pin->pointer, length, file, line);
    ST(0) = ST(0);
    XSRETURN(1);
}

lib/Affix.c  view on Meta::CPAN

    if (infix_register_types(registry, "@Buffer = *void;") != INFIX_SUCCESS)
        croak("Failed to register internal type alias '@Buffer'");
    if (infix_register_types(registry, "@SockAddr = *void;") != INFIX_SUCCESS)
        croak("Failed to register internal type alias '@SockAddr'");
}

XS_INTERNAL(Affix_CLONE) {
    dXSARGS;
    PERL_UNUSED_VAR(items);

    // Initialize the new thread's context (copies bitwise from parent)
    MY_CXT_CLONE;

    // Capture the parent's registry pointer.
    // After MY_CXT_CLONE, MY_CXT refers to the new thread's context,
    // which has been initialized as a bitwise copy of the parent's context.
    infix_registry_t * parent_registry = MY_CXT.registry;

    // Overwrite shared pointers with fresh objects for the new thread
    MY_CXT.lib_registry = newHV();
    MY_CXT.callback_registry = newHV();
    MY_CXT.enum_registry = newHV();
    MY_CXT.coercion_cache = newHV();
    MY_CXT.stash_pointer = nullptr;

    // Deep copy the type registry.
    // This ensures typedefs and structs defined in the parent thread exist in the child thread,
    // but the child owns its own memory arena, making it thread-safe.
    if (parent_registry)
        MY_CXT.registry = infix_registry_clone(parent_registry);
    else
        MY_CXT.registry = infix_registry_create();

    if (!MY_CXT.registry)
        warn("Failed to initialize the global type registry in new thread");

    // Don't ccall _register_core_types here if we cloned, because the clone already contains @SV, @File, etc.
    if (!parent_registry)
        _register_core_types(MY_CXT.registry);

    XSRETURN_EMPTY;
}

void boot_Affix(pTHX_ CV * cv) {
    dVAR;

lib/Affix.h  view on Meta::CPAN

#pragma once

#define DEBUG 0

// Disables the implicit 'pTHX_' context pointer argument, which is good practice for
// modern Perl XS code that uses the 'aTHX_' macro explicitly.
#define PERL_NO_GET_CONTEXT 1
#include <EXTERN.h>
#include <perl.h>
// Disables Perl's internal locking mechanisms for certain structures.
// This is often used when the XS module manages its own thread safety.
#define NO_XSLOCKS
#include <XSUB.h>
// Redirect infix's internal memory allocation to use Perl's safe allocation functions.
// This ensures all memory is tracked by Perl's memory manager, which is safer and
// helps with leak detection tools like valgrind.
#define infix_malloc safemalloc
#define infix_calloc safecalloc
#define infix_free safefree
#define infix_realloc saferealloc

#include "common/infix_internals.h"
#include <infix/infix.h>
// This structure defines the thread-local storage for our module. Under ithreads,
// each Perl thread will get its own private instance of this struct.
typedef struct {
    /// A per-thread hash table to store loaded libraries.
    /// Maps library path -> LibRegistryEntry*.
    /// This prevents reloading the same .so/.dll and manages its lifecycle.
    HV * lib_registry;
    // A per-thread hash table to cache callback trampolines, preventing re-creation and leaks.
    // Maps the memory address of a Perl CV* to its corresponding Implicit_Callback_Magic* struct.
    HV * callback_registry;
    /// Type alias for an infix type registry. Represents a collection of named types.
    infix_registry_t * registry;
    /// // Smart enums
    HV * enum_registry;
    // Cache for coercion strings to avoid re-fetching from SV objects
    HV * coercion_cache;
    HV * stash_pointer;  // Cache for Affix::Pointer stash
} my_cxt_t;
START_MY_CXT;
// Helper macro to fetch a value from a hash if it exists, otherwise return a default.
#define hv_existsor(hv, key, _or) hv_exists(hv, key, strlen(key)) ? *hv_fetch(hv, key, strlen(key), 0) : _or
// Macros to handle passing the Perl interpreter context ('THX') explicitly,
// which is necessary for thread-safe code.
#ifdef MULTIPLICITY
#define storeTHX(var) (var) = aTHX
#define dTHXfield(var) tTHX var;
#else
#define storeTHX(var) dNOOP
#define dTHXfield(var)
#endif

// Forward-declare the primary structures.
typedef struct Affix Affix;

lib/Affix.h  view on Meta::CPAN

    OP_PUSH_PTR_CHAR,   // char* optimization
    OP_PUSH_PTR_WCHAR,  // wchar_t* optimization
    OP_PUSH_SV,         // SV*
    OP_PUSH_STRUCT,     // aggregates ...
    OP_PUSH_UNION,
    OP_PUSH_ARRAY,
    OP_PUSH_CALLBACK,
    OP_PUSH_ENUM,
    OP_PUSH_COMPLEX,
    OP_PUSH_VECTOR,  // optimized vector
    OP_DONE,         // sentinel to stop the threaded dispatcher
    OP_RET_VOID,     // retval marshalling opcodes ...
    OP_RET_BOOL,
    OP_RET_SINT8,
    OP_RET_UINT8,
    OP_RET_SINT16,
    OP_RET_UINT16,
    OP_RET_SINT32,
    OP_RET_UINT32,
    OP_RET_SINT64,
    OP_RET_UINT64,

lib/Affix.h  view on Meta::CPAN

    size_t total_args_size;        ///< Pre-calculated total size of the C arguments buffer.
    // Pre-compiled plan for handling "out" parameters after the C call.
    OutParamInfo * out_param_info;
    size_t num_out_params;
    const infix_type * ret_type;
    const infix_type * unwrapped_ret_type;  // Pre-unwrapped for OP_RET_PTR
    Affix_Pull ret_pull_handler;            ///< Cached handler for marshalling the return value.
    Affix_Opcode ret_opcode;                ///< Optimized return opcode.
    void ** c_args;

    // Reconstruction info for threading/cloning
    char * sig_str;
    char * sym_name;
    void * target_addr;
    char * lib_path;
    dTHXfield(owner_perl)

        // Variadic demo
        HV * variadic_cache;
    size_t num_fixed_args;
};

lib/Affix.h  view on Meta::CPAN

    size_t size;                 ///< Size of malloc'd void pointers.
    void (*destructor)(void *);  ///< Custom destructor function (e.g. SDL_DestroyWindow).
    SV * destructor_lib_sv;      ///< Perl object (Affix::Lib) to keep alive for the destructor.
    SV * owner_sv;               ///< Perl object that owns the memory, kept alive by this pin.
    size_t bit_offset;           ///< Bit offset (for bitfields)
    size_t bit_width;            ///< Bit width (for bitfields, 0 = not a bitfield)
} Affix_Pin;
/// Holds the necessary data for a callback, specifically the Perl subroutine to call.
typedef struct {
    SV * coderef_rv;  ///< A reference (RV) to the Perl coderef. We hold this to keep it alive.
    dTHXfield(perl)   ///< The thread context in which the callback was created.
} Affix_Callback_Data;
/// Internal struct holding the C resources that are magically attached
///        to a user's coderef (CV*) when it is first used as a callback.
typedef struct {
    infix_reverse_t * reverse_ctx;  ///< Handle to the infix reverse-call trampoline.
} Implicit_Callback_Magic;
/// An entry in the thread-local library registry hash.
typedef struct {
    infix_library_t * lib;  ///< The handle to the opened library.
    UV ref_count;           ///< Reference count. The library is closed only when this reaches 0.
} LibRegistryEntry;

// Struct for the Direct Marshalling (aka "bundle") backend.
/// Represents a forward FFI call created with the high-performance direct marshalling API.
struct Affix_Backend {
    infix_forward_t * infix;       ///< Handle to the infix trampoline and type info.
    infix_direct_cif_func cif;     ///< Direct pointer to the specialized JIT code.

lib/Affix.pod  view on Meta::CPAN

=back

=head2 Functions

=head3 C<load_library( $path_or_name )>

Locates and loads a dynamic library into memory, returning an opaque C<Affix::Lib> handle.

    my $lib = load_library('sqlite3');

B<Lifecycle:> Library handles are thread-safe and internally reference-counted. The underlying OS library is only
closed (e.g., via C<dlclose> or C<FreeLibrary>) when all Affix wrappers and pins relying on it are destroyed.

I<Note:> When using C<affix()> or C<wrap()>, you can safely pass the string name directly (e.g., C<affix('sqlite3',
...)>) and Affix will call C<load_library> for you internally. If you pass C<undef> instead of a library name, Affix
will search the currently running executable process.

=head3 C<locate_lib( $name, [$version] )>

Searches for a library using Affix's discovery engine and returns its absolute file path as a string. It B<does not>
load the library into memory. This is useful if you need to pass the library path to another tool or check for its

lib/Affix.pod  view on Meta::CPAN

=over

=item * L<B<Affix::Wrap>|Affix::Wrap>: Parses C/C++ headers using the Clang AST to automatically generate Affix bindings for entire libraries.

=item * L<B<Affix::Build>|Affix::Build>: A polyglot builder that compiles inline C, C++, Rust, Zig, Go, and 15+ other languages into dynamic libraries you can bind instantly.

=back

=head1 THREAD SAFETY & CONCURRENCY

Affix bridges Perl (a single-threaded interpreter, generally) with libraries that may be multi-threaded. This creates
potential hazards that you must manage.

=head2 1. Initialization Phase vs. Execution Phase

Functions that modify Affix's global state are B<not thread-safe>. You must perform all definitions in the main thread
before starting any background threads or loops in the library.

Unsafe operations that you should never call from Callbacks or in a threaded context:

=over

=item * C<affix( ... )> - Binding new functions.

=item * C<typedef( ... )> - Registering new types.

=back

=head2 2. Callbacks

When passing a Perl subroutine as a C<Callback>, avoid performing complex Perl operations like loading modules or
defining subs inside callbacks triggered on a foreign thread. Such callbacks should remain simple: process data, update
a shared variable, and return.

If the library executes the callback from a background thread (e.g., window managers, audio callbacks), Affix attempts
to attach a temporary Perl context to that thread. This should be sufficient but Perl is gonna be Perl.

=head1 RECIPES & EXAMPLES

See L<The Affix Cookbook|https://github.com/sanko/Affix.pm/discussions/categories/recipes> for comprehensive guides to
using Affix.

=head2 Linked List Implementation

    # C equivalent:
    # typedef struct Node {

lib/Affix/Build.pm  view on Meta::CPAN

                my @cmd = ( $rc, '--crate-type=staticlib', '--emit=link', '-C', 'panic=abort', @local, "$file", '-o', "$lib" );

                # Force GNU target on MinGW to ensure compatibility with Perl's linker
                if ( $os eq 'MSWin32' && $Config{cc} =~ /gcc/ ) {
                    push @cmd, '--target', 'x86_64-pc-windows-gnu';
                }
                elsif ( $os ne 'MSWin32' ) {
                    push @cmd, '-C', 'relocation-model=pic';
                }
                $self->_run(@cmd);
                my @deps = $os eq 'MSWin32' ? qw(ws2_32 userenv bcrypt advapi32 ntdll) : qw(dl pthread m);
                return { file => $lib, libs => \@deps };
            }
        }

        #~ https://medium.com/@walkert/fun-building-shared-libraries-in-go-639500a6a669
        #~ https://github.com/vladimirvivien/go-cshared-examples
        method _build_go ( $src, $out, $mode ) {
            my $file  = $src->{path};
            my @local = @{ $src->{flags} };    # passed to go build args

lib/Affix/Build.pm  view on Meta::CPAN

            # 8-byte aligned stack (which was common in older GCC optimization flags), Go will segfault
            # immediately when it tries to access the stack.
            push @local, q[-ldflags "-extldflags '-static -static-libgcc -static-libstdc++'"] if $^O eq 'MSWin32';
            if ( $mode eq 'dynamic' ) {
                $self->_run( 'go', 'build', '-buildmode=c-shared', @local, '-o', "$out", "$file" );
                return $out;
            }
            else {
                my $lib = $build_dir->child( $self->_base($file) . $Config{_a} );
                $self->_run( 'go', 'build', '-buildmode=c-archive', @local, '-o', "$lib", "$file" );
                return { file => $lib, libs => ['pthread'] };
            }
        }

        #~ https://odin-lang.org/news/calling-odin-from-python/
        #~ https://odin-lang.org/docs/install/#release-requirements--notes
        method _build_odin ( $src, $out, $mode ) {
            my $file  = $src->{path};
            my @local = @{ $src->{flags} };
            my $odin  = $self->_can_run('odin') // croak "Odin not found";
            if ( $mode eq 'dynamic' ) {

lib/Affix/Build.pod  view on Meta::CPAN

=item * B<Compiler:> C<go> or C<gccgo>

=back

B<Caveats:>

=over

=item * In Polyglot mode, requires C<gccgo> or standard C<go> with C<c-archive> support.

=item * The Go runtime spins up background threads (for GC and scheduling) that do not shut down cleanly when a shared library is unloaded. This can cause access violations on Windows during program exit. If you encounter segmentation faults during g...

=back

=head3 Zig

=over

=item * B<Extensions:> C<.zig>

=item * B<Compiler:> C<zig>

t/027_thread_safety.t  view on Meta::CPAN

use v5.40;
use lib '../lib', 'lib';
use blib;
use Test2::Tools::Affix qw[:all];
use Affix               qw[:all];
use Config;
#
$|++;
#
subtest 'os threads' => sub {
    typedef Callback_t => Callback [ [Int] => Void ];
    typedef ThreadTask => Struct [ cb   => Callback_t(), val  => Int ];
    typedef TaskNode   => Struct [ task => ThreadTask(), next => Pointer [ ThreadTask() ] ];
    typedef TaskQueue =>
        Struct [ head => Pointer [ TaskNode() ], tail => Pointer [ TaskNode() ], lock => Pointer [Void], cond => Pointer [Void], stop => Int ];
    typedef ThreadPool => Struct [
        threads         => Pointer [Void],
        thread_count    => Int,
        task_queue      => TaskQueue(),
        pool_lock       => Pointer [Void],
        task_available  => Pointer [Void],
        active_threads  => Int,
        tasks_remaining => Int
    ];
    my $c_source = <<~'C';
        #include "std.h"
        //ext: .c
        #if _WIN32
        #include <windows.h>
        #else
        #include <pthread.h>
        #include <unistd.h>
        #endif
        #include <stdlib.h>

        typedef void (*callback_t)(int);

        typedef struct {
            callback_t cb;
            int val;
        } ThreadArgs;

        #if _WIN32
        unsigned __stdcall
        #else
        void *
        #endif
        thread_func(void* arg) {
            ThreadArgs* args = (ThreadArgs*)arg;
            // Brief sleep to ensure we aren't just getting lucky on the main thread stack
            usleep(2000000);
            // Execute Perl callback from this foreign thread
            // This will SEGFAULT if Affix doesn't inject Perl context!
            args->cb(args->val);
            return 0;
        }

        void run_in_foreign_thread(callback_t cb, int val) {
        #if _WIN32
            ThreadArgs* args = (ThreadArgs*)malloc(sizeof(ThreadArgs));
            args->cb = cb;
            args->val = val;
            unsigned threadID;
            HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &thread_func, args, 0, &threadID);
            // Block main thread to simulate WebUI/MainLoop behavior
            WaitForSingleObject(hThread, INFINITE);
            CloseHandle(hThread);
            free(args);
        #else
            pthread_t thread_id;
            ThreadArgs* args = (ThreadArgs*)malloc(sizeof(ThreadArgs));
            args->cb = cb;
            args->val = val;
            pthread_create(&thread_id, NULL, thread_func, args);
            // Block main thread to simulate WebUI/MainLoop behavior
            pthread_join(thread_id, NULL);
            free(args);
        #endif
        }
    C

    # Pass reference (\$c_source) so Affix::Build treats it as code, not a filename
    my $lib = compile_ok($c_source);
    ok $lib && -e $lib, 'Compiled a threaded test library';
    #
    ok affix( $lib, 'run_in_foreign_thread', [ Callback_t(), Int ] => Void ), 'affix run_in_foreign_thread';
    #
    my $ok_flag = 0;
    my $str;

    # This calls C, which spawns a thread, which calls this sub
    run_in_foreign_thread(
        sub ($val) {

            # Allocating memory here (creating SVs) tests the memory allocator context
            $str     = 'Received: ' . $val;
            $ok_flag = $val;
        },
        123
    );
    is $ok_flag, 123, 'Callback executed successfully from foreign thread without crashing';
    diag $str;
};
#
subtest ithreads => sub {
    skip_all 'No ithreads', 1 unless $Config{useithreads};
    require threads;

    # Test core affix cloning and usage across threads
    ok affix libc(), [ abs => 'absolute' ], [Int] => Int;
    is absolute(-42), 42, 'Main thread: absolute(-42) == 42';
    my @threads = map {
        threads->create(
            sub {
                my $tid = threads->tid();
                my $res = absolute( -100 * $tid );
                return $res == ( 100 * $tid );
            }
        )
    } 1 .. 5;
    for my $thr (@threads) {
        my $tid = $thr->tid();
        ok $thr->join(), "Thread $tid: absolute() worked correctly after cloning";
    }
};
#
done_testing();



( run in 3.376 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )