view release on metacpan or search on metacpan
CODE_OF_CONDUCT.md view on Meta::CPAN
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://github.com/sanko/Affix.pm/discussions.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
CONTRIBUTING.md view on Meta::CPAN
```bash
# For a new feature:
git checkout -b feature/add-riscv-support
# For a bug fix:
git checkout -b fix/struct-classification-bug
```
4. **Make Your Changes**: Write your code. Please adhere to the existing coding style and add comments to new or complex logic.
5. **Test Your Changes**: A pull request is far more likely to be accepted if it includes tests. If you add new functionality, please add a corresponding test case.
6. **Update the Changelog**: Add an entry to the `[Unreleased]` section of `CHANGELOG.md` describing your change.
7. **Submit a Pull Request**: Push your branch to your fork and open a pull request against the `main` branch of the original repository. Please provide a clear description of your changes and link to the relevant issue (e.g., `Fixes #123`).
- 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 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
SECURITY.md view on Meta::CPAN
# Security Policy
Until I have the version 1.0.0 release, I'll support only the last two minor versions with security updates.
## Supported Versions
| Version | Supported |
| ---------- | ------------------ |
| v1.0.x | :white_check_mark: |
| v1.0.6 | :white_check_mark: |
| \<= v1.0.5 | :x: |
# Reporting a Vulnerability
If you have any issue regarding security, please disclose the information responsibly by sending a report to https://github.com/sanko/Affix.pm/security/advisories/new and *not* at the public issue tracker or via email.
# Vulnerability Disclosure Policy
Maintaining the security of our open-source software is paramount. This policy outlines a responsible approach to addressing vulnerabilities, balancing transparency with the need to protect users.
- Security vulnerabilities identified in the project will be assigned a unique identifier and (if applicable) a Common Vulnerabilities and Exposures (CVE) identifier.
- The project's Maintainers will be responsible for addressing the vulnerability through a standard pull request, backporting the fix to immediate prior minor release branch, and including the fix in the next stable release.
- Release notes for the patched version will include the assigned identifier and, if applicable, the CVE identifier for the vulnerability.
- A grace period will be provided for Maintainers to update the vulerable minor version and remove vulerable releases from PAUSE (nothing can be done about backpan).
This period will be one month for non-critical vulnerabilities and three months for critical vulnerabilities.
infix/LICENSE-CC view on Meta::CPAN
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
infix/include/infix/infix.h view on Meta::CPAN
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file infix.h
* @brief The public interface for the infix FFI library.
*
* @mainpage infix FFI Library
*
* @section intro_sec Introduction
*
* `infix` is a powerful, modern, and lightweight C library for creating Foreign
* Function Interface (FFI) trampolines at runtime. It allows you to dynamically
* call C functions or create C callbacks from any language or environment, using
* a simple, human-readable string-based syntax to describe function signatures.
*
* @section features_sec Key Features
*
* - **Simple Signature Syntax:** Describe complex C types, structs, and function
* prototypes with an intuitive string format.
* - **Forward & Reverse Calls:** Create "forward" trampolines to call C from your
* code, and "reverse" trampolines (callbacks) to allow C to call back into your code.
* - **Named Type Registry:** Define, reuse, and link complex, recursive, and
* mutually-dependent structs by name.
* - **Cross-Platform:** Supports major architectures (x86-64, AArch64) and operating
* systems (Linux, macOS, Windows).
* - **Secure:** Designed with modern security principles like W^X (Write XOR Execute)
* and hardened against memory corruption.
* - **Lightweight & Embeddable:** A small, dependency-free library ideal for
* embedding in language runtimes, plugins, and other applications.
*
* @section usage_sec Basic Usage
*
* ```c
* #include <infix/infix.h>
* #include <stdio.h>
*
* // The C function we want to call.
* int add(int a, int b) { return a + b; }
*
* int main() {
* infix_forward_t* trampoline = NULL;
infix/src/arch/aarch64/abi_arm64.c view on Meta::CPAN
* @brief Implements the FFI logic for the AArch64 (ARM64) architecture.
* @ingroup internal_abi_aarch64
*
* @internal
* This file provides the concrete implementation of the `infix_forward_abi_spec`
* and `infix_reverse_abi_spec` for the ARM64 architecture. It primarily follows
* the standard "Procedure Call Standard for the ARM 64-bit Architecture" (AAPCS64),
* but also contains critical conditional logic to handle deviations for specific
* platforms like Apple macOS and Windows on ARM.
*
* @section aapcs64_rules Key AAPCS64 Rules Implemented
*
* - **Register Usage:**
* - The first 8 integer/pointer arguments are passed in GPRs (X0-X7).
* - The first 8 floating-point/vector arguments are passed in VPRs (V0-V7).
*
* - **Homogeneous Floating-point Aggregates (HFAs):** Structs or arrays composed
* entirely of 1 to 4 identical floating-point types (`float` or `double`) are
* passed in consecutive VPRs.
*
* - **Return Values:**
* - Aggregates up to 16 bytes are returned in registers (GPRs and/or VPRs).
* - Larger aggregates are returned via a hidden pointer passed by the caller
* in the dedicated "indirect result location register", `X8`.
*
* @section platform_deviations Platform-Specific Deviations
*
* - **Variadic Calls (Apple macOS):** All variadic arguments are passed on the
* stack. Arguments smaller than 8 bytes are promoted to fill 8-byte stack slots.
*
* - **Variadic Calls (Windows on ARM):** The HFA rule is disabled for variadic
* arguments. Floating-point scalars are passed in GPRs, not VPRs.
*
* - **16-Byte Argument Alignment:**
* - **Standard/macOS:** 16-byte aggregates passed in GPRs must start in an
* even-numbered register (X0, X2, X4, X6).
infix/src/arch/x64/abi_sysv_x64.c view on Meta::CPAN
if (type->category == INFIX_TYPE_COMPLEX) {
infix_type * base = type->meta.complex_info.base_type;
// A zero-sized base type would cause infinite recursion.
// Treat this as a malformed type and stop classification.
if (base == nullptr || base->size == 0)
return false;
// A complex number is just like a struct { base_type real; base_type imag; }
// So we classify the first element at offset 0.
if (classify_recursive(base, offset, classes, depth + 1, field_count, false))
return true; // Propagate unaligned discovery
// And the second element at offset + size of the base.
if (classify_recursive(base, offset + base->size, classes, depth + 1, field_count, false))
return true; // Propagate unaligned discovery
return false;
}
if (type->category == INFIX_TYPE_VECTOR) {
(*field_count)++;
size_t num_eightbytes = (type->size + 7) / 8;
for (size_t i = 0; i < num_eightbytes && (offset / 8 + i) < 2; ++i) {
// Merging rule: if an eightbyte contains both SSE and INTEGER parts, it is INTEGER.
// If it's NO_CLASS, it becomes SSE.
infix/src/arch/x64/abi_win_x64.c view on Meta::CPAN
* @brief Implements the FFI logic for the Windows x64 calling convention.
* @ingroup internal_abi_x64
*
* @internal
* This file provides the concrete implementation of the ABI spec for the Microsoft
* x64 calling convention, used on all 64-bit versions of Windows.
*
* Key features and differences from the System V ABI implemented here:
*
* - **Register "Slots":** The first four arguments are passed in registers, but the
* slots are shared. RCX/XMM0 is the first slot, RDX/XMM1 is the second, etc.
* An `int` followed by a `float` would use RCX and XMM1.
*
* - **Shadow Space:** The caller must allocate a 32-byte "shadow space" on the stack
* for the callee.
*
* - **By-Reference Passing:** Aggregates (structs/unions) are passed by reference
* if their size is not a power of two (1, 2, 4, or 8 bytes), or if they have
* special constructors.
* **Crucially, all SIMD vectors > 16 bytes (__m256, __m512) are passed by reference.**
* **__m128 (16 bytes) is passed by value in XMM.**
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
infix/src/common/infix_config.h view on Meta::CPAN
* stricter build environments.
*/
#if !defined(_POSIX_C_SOURCE)
#define _POSIX_C_SOURCE 200809L
#endif
#if (defined(__linux__) || defined(__gnu_linux__)) && !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
// Operating System Detection
/**
* @details This section defines `INFIX_OS_*` macros based on compiler-provided
* preprocessor definitions. It also defines the broader `INFIX_ENV_POSIX` for systems
* that follow POSIX conventions, which simplifies later `#ifdef` logic.
*/
#if defined(_WIN32)
#define INFIX_OS_WINDOWS
#include <windows.h> // Included early for common types like SYSTEM_INFO, HANDLE, etc.
// Compatibility shim for POSIX types not present in Clang/MSVC headers.
#if !defined(__CYGWIN__) // Cygwin provides its own full POSIX environment
#include <stddef.h> // For ptrdiff_t
#ifndef ssize_t
infix/src/common/infix_config.h view on Meta::CPAN
*/
#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
* would not reflect the target environment.
*
* 2. **Automatic Detection:** If no ABI is forced, it uses the `INFIX_ARCH_*` and
* `INFIX_OS_*` macros to deduce the correct ABI for the current build target.
infix/src/common/infix_internals.h view on Meta::CPAN
*/
#pragma once
#include "common/infix_config.h"
#include "common/platform.h"
#include <infix/infix.h>
/**
* @struct infix_executable_t
* @brief Internal representation of an executable memory block for JIT code.
*
* @details This struct encapsulates the platform-specific details of allocating and
* managing executable memory in a way that is compliant with modern OS security
* features like W^X (Write XOR Execute). It supports two primary strategies:
*
* 1. **Single-Mapping W^X (Windows/macOS/Android):** A single memory region is
* allocated as Read-Write (`rw_ptr`). After the JIT compiler writes the
* machine code to this region, its permissions are changed to Read-Execute.
* In this model, `rx_ptr` and `rw_ptr` point to the same address.
*
* 2. **Dual-Mapping W^X (Linux/BSD):** A single underlying shared memory object
* is mapped into the process's address space twice: once as Read-Write
* (`rw_ptr`) and once as Read-Execute (`rx_ptr`). The pointers have different
infix/src/common/infix_internals.h view on Meta::CPAN
typedef enum {
/** @brief Argument is passed in a general-purpose integer register (e.g., `RCX`, `RDI`, `X0`). */
ARG_LOCATION_GPR,
#if defined(INFIX_ABI_AAPCS64)
/** @brief (AArch64) Argument is passed in a vector/floating-point register (e.g., `V0`). */
ARG_LOCATION_VPR,
/** @brief (AArch64) A struct <= 16 bytes passed in a pair of GPRs (e.g., `X0`, `X1`). */
ARG_LOCATION_GPR_PAIR,
/** @brief (AArch64) A large struct (> 16 bytes) passed by reference; the pointer is in a GPR. */
ARG_LOCATION_GPR_REFERENCE,
/** @brief (AArch64) A Homogeneous Floating-point Aggregate passed in consecutive VPRs. */
ARG_LOCATION_VPR_HFA,
#else // x64 ABIs
/** @brief (x64) Argument is passed in an SSE/XMM register (e.g., `XMM0`). */
ARG_LOCATION_XMM,
/** @brief (SysV x64) A struct passed in two GPRs (e.g., `RDI`, `RSI`). */
ARG_LOCATION_GPR_PAIR,
/** @brief (SysV x64) A struct passed in two SSE registers (e.g., `XMM0`, `XMM1`). */
ARG_LOCATION_SSE_SSE_PAIR,
/** @brief (SysV x64) A struct split between a GPR and an SSE register. */
ARG_LOCATION_INTEGER_SSE_PAIR,
infix/src/common/infix_internals.h view on Meta::CPAN
/**
* @struct infix_arg_location
* @brief Detailed location information for a single function argument.
* @details This struct is the result of the ABI classification process for one
* argument. It provides all the information the code emitters need to generate
* the correct move/load/store instructions.
*/
typedef struct {
infix_arg_location_type type; /**< The classification of the argument's location. */
uint8_t reg_index; /**< The index of the primary register used. */
uint8_t reg_index2; /**< The index of the second register (for pairs). */
uint32_t num_regs; /**< Number of regs OR scratch buffer offset. */
uint32_t stack_offset; /**< The byte offset from the stack pointer. */
} infix_arg_location;
/**
* @struct infix_call_frame_layout
* @brief A complete layout blueprint for a forward call frame.
* @details This structure is the primary output of `prepare_forward_call_frame`. It serves
* as a complete plan for the JIT engine, detailing every register and stack slot
* that needs to be populated before making the `call` instruction.
*/
infix/src/common/infix_internals.h view on Meta::CPAN
* @return An `infix_protected_t` handle.
*/
INFIX_INTERNAL c23_nodiscard infix_protected_t infix_protected_alloc(size_t size);
/**
* @brief Frees a block of protected memory.
* @details Located in `src/jit/executor.c`.
* @param prot The memory block to free.
*/
INFIX_INTERNAL void infix_protected_free(infix_protected_t prot);
/**
* @brief Makes a block of memory read-only for security hardening.
* @details Located in `src/jit/executor.c`. This is called on the `infix_reverse_t`
* context after it has been fully initialized.
* @param prot The memory block to make read-only.
* @return `true` on success, `false` on failure.
*/
INFIX_INTERNAL c23_nodiscard bool infix_protected_make_readonly(infix_protected_t prot);
/**
* @brief The universal C entry point for all reverse call trampolines.
* @details Located in `src/jit/executor.c`, this function is called by the JIT-compiled
* stub. It receives the marshalled arguments and dispatches the call to either
infix/src/core/type_registry.c view on Meta::CPAN
* reducing the likelihood of hash collisions.
*/
#define INITIAL_REGISTRY_BUCKETS 61
/**
* @internal
* @struct resolve_memo_node_t
* @brief A node for a "visited set" used during type resolution to handle cycles.
* @details During the recursive traversal of a type graph in `_resolve_type_graph_inplace_recursive`,
* this temporary, stack-allocated linked list tracks `infix_type` nodes that have
* already been visited in the current recursion path. If a node is encountered a
* second time, it indicates a cycle (e.g., `struct Node { struct Node* next; };`),
* and the recursion must stop to prevent a stack overflow.
*/
typedef struct resolve_memo_node_t {
infix_type * src; /**< The `infix_type` object that has been visited. */
struct resolve_memo_node_t * next; /**< The next node in the visited list. */
} resolve_memo_node_t;
// Hash Table Implementation
/**
* @internal
* @brief Computes a hash for a string using the djb2 algorithm.
infix/src/infix.c view on Meta::CPAN
/**
* @file infix.c
* @brief The unity build source file for the infix library.
* @ingroup internal_core
*
* @internal
* This file acts as the single translation unit for the entire infix library. It
* includes all other necessary C source files in a specific order to resolve
* dependencies and create the final library object.
*
* @section build_strategy Build Strategy
*
* Using a unity build (also known as a jumbo build) offers several advantages for
* a library of this nature:
* - **Simplified Build Process:** It eliminates the need for a complex build system
* to manage dependencies between multiple object files. The entire library can
* be compiled with a single command (e.g., `cc -o libinfix.so infix.c ...`).
* - **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
infix/src/jit/executor.c view on Meta::CPAN
*/
/**
* @file executor.c
* @brief Implements platform-specific memory management for JIT code and execution.
* @ingroup internal_jit
*
* @details This module serves as the critical OS abstraction layer for the JIT engine.
* Its primary responsibilities are:
*
* 1. **Executable Memory Management:** It allocates, protects, and frees executable
* memory in a way that is secure and compliant with modern OS security features
* like **W^X (Write XOR Execute)**. It implements different strategies (single-
* vs. dual-mapping) depending on the platform's capabilities and security model.
*
* 2. **Security Hardening:** It provides mechanisms to make memory regions read-only,
* which is used to protect the `infix_reverse_t` context from runtime memory
* corruption. It also implements "guard pages" on freed memory to immediately
* catch use-after-free bugs.
*
* 3. **Universal Dispatch:** It contains the `infix_internal_dispatch_callback_fn_impl`,
* the universal C entry point that is the final target of all reverse trampoline
* stubs. This function is the bridge between the low-level JIT code and the
* high-level user-provided C handlers.
infix/src/jit/executor.c view on Meta::CPAN
// 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;
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);
infix/src/jit/executor.c view on Meta::CPAN
/**
* @internal
* @brief One-time initialization to dynamically load macOS framework functions.
* @details Uses `dlopen` and `dlsym` to find the necessary CoreFoundation and Security
* framework functions at runtime. This avoids a hard link-time dependency,
* making the library more portable and resilient if these frameworks change.
*/
static void initialize_macos_apis(void) {
// We don't need to link against these frameworks, which makes building simpler.
void * cf = dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", RTLD_LAZY);
void * sec = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY);
// Hardened Runtime helpers found in libSystem/libpthread
g_macos_apis.pthread_jit_write_protect_np = dlsym(RTLD_DEFAULT, "pthread_jit_write_protect_np");
g_macos_apis.sys_icache_invalidate = dlsym(RTLD_DEFAULT, "sys_icache_invalidate");
if (!cf || !sec) {
INFIX_DEBUG_PRINTF("Warning: Could not dlopen macOS frameworks. JIT security features will be degraded.");
if (cf)
dlclose(cf);
if (sec)
dlclose(sec);
return;
}
g_macos_apis.CFRelease = dlsym(cf, "CFRelease");
g_macos_apis.CFBooleanGetValue = dlsym(cf, "CFBooleanGetValue");
g_macos_apis.CFStringCreateWithCString = dlsym(cf, "CFStringCreateWithCString");
void ** pAlloc = (void **)dlsym(cf, "kCFAllocatorDefault");
if (pAlloc)
g_macos_apis.kCFAllocatorDefault = *pAlloc;
g_macos_apis.SecTaskCreateFromSelf = dlsym(sec, "SecTaskCreateFromSelf");
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);
CFTypeRef value = nullptr;
if (key) {
// This is the core check: ask the system for the value of the entitlement.
value = g_macos_apis.SecTaskCopyValueForEntitlement(task, key, nullptr);
g_macos_apis.CFRelease(key);
}
g_macos_apis.CFRelease(task);
if (value) {
// The value of the entitlement is a CFBoolean, so we must extract its value.
if (g_macos_apis.CFBooleanGetValue && g_macos_apis.CFBooleanGetValue(value))
infix/src/jit/executor.c view on Meta::CPAN
#include <stdint.h>
#if defined(INFIX_OS_LINUX) && defined(_GNU_SOURCE)
#include <sys/syscall.h>
#endif
/**
* @internal
* @brief Creates an anonymous file descriptor suitable for dual-mapping.
*
* @details Attempts multiple strategies in order of preference:
* 1. `memfd_create`: Modern Linux (kernel 3.17+). Best for security (no filesystem path).
* 2. `shm_open(SHM_ANON)`: FreeBSD/DragonFly. Automatic anonymity.
* 3. `shm_open(random_name)`: Fallback for older Linux/POSIX. Manually unlinked immediately.
*/
static int create_anonymous_file(void) {
#if defined(INFIX_OS_LINUX) && defined(MFD_CLOEXEC)
// Strategy 1: memfd_create (Linux 3.17+)
// MFD_CLOEXEC ensures the FD isn't leaked to child processes.
int linux_fd = memfd_create("infix_jit", MFD_CLOEXEC);
if (linux_fd >= 0)
return linux_fd;
infix/src/jit/executor.c view on Meta::CPAN
shm_unlink(shm_name);
return fd;
}
return -1;
}
#endif
// Public API: Executable Memory Management
/**
* @internal
* @brief Allocates a block of memory suitable for holding JIT-compiled code,
* respecting platform-specific W^X (Write XOR Execute) security policies.
* @param size The number of bytes to allocate. Must be a multiple of the system page size.
* @return An `infix_executable_t` structure. On failure, its pointers will be `nullptr`.
*/
c23_nodiscard infix_executable_t infix_executable_alloc(size_t size) {
#if defined(INFIX_OS_WINDOWS)
infix_executable_t exec = {
.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .handle = nullptr, .seh_registration = nullptr};
#else
infix_executable_t exec = {.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .shm_fd = -1, .eh_frame_ptr = nullptr};
#endif
infix/src/jit/executor.c view on Meta::CPAN
}
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;
#if defined(INFIX_OS_MACOS)
// On macOS, we perform a one-time check for JIT support.
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();
INFIX_DEBUG_PRINTF("macOS JIT check: Entitlement found = %s. Using %s API.",
g_use_secure_jit_path ? "yes" : "no",
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
_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)
infix/src/jit/executor.c view on Meta::CPAN
INFIX_DEBUG_PRINTF("Memory at %p is now executable.", exec->rx_ptr);
return result;
}
// Public API: Protected (Read-Only) Memory
/**
* @internal
* @brief Allocates a block of standard read-write memory for a context object.
*
* @details This is used to allocate the memory for an `infix_reverse_t` context. The
* memory is allocated as standard RW memory, populated, and then made read-only
* via `infix_protected_make_readonly` for security hardening.
*
* @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);
infix/src/jit/executor.c view on Meta::CPAN
if (prot.size == 0)
return;
#if defined(INFIX_OS_WINDOWS)
VirtualFree(prot.rw_ptr, 0, MEM_RELEASE);
#else
munmap(prot.rw_ptr, prot.size);
#endif
}
/**
* @internal
* @brief Makes a block of memory read-only for security hardening.
*
* @details This function is called on the `infix_reverse_t` context after it has been
* fully initialized. By making the context read-only, it helps prevent bugs or
* security vulnerabilities from corrupting critical state like function pointers.
*
* @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});
infix/src/jit/trampoline.c view on Meta::CPAN
*
* - **Type-safe Callback (`is_callback = true`):** In this model, the user provides a
* standard C function pointer with a matching signature. This function internally
* creates a *forward* trampoline (`cached_forward_trampoline`) that is used by the
* universal C dispatcher to call the user's handler in a type-safe way.
*
* - **Generic Closure (`is_callback = false`):** The user provides a generic handler of
* type `infix_closure_handler_fn`. The universal dispatcher calls this handler
* directly, without needing a cached forward trampoline.
*
* For security, the entire `infix_reverse_t` context struct is allocated in a
* special page-aligned memory region that is made read-only after initialization.
*
* @param is_callback `true` to create a type-safe callback, `false` for a generic closure.
* @return `INFIX_SUCCESS` on success.
*/
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,
infix/src/jit/trampoline.c view on Meta::CPAN
}
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;
}
// ============================================================================
// UNITY BUILD INCLUDES
// This section includes the actual ABI implementations at the end of the file.
// Because `trampoline.c` is the central translation unit, including the
// correct ABI-specific .c file here makes its functions (`g_win_x64_spec`, etc.)
// available without needing to add platform-specific logic to the build system.
// The `infix_config.h` header ensures only one of these #if blocks is active.
// ============================================================================
#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"
lib/Affix/Build.pm view on Meta::CPAN
else {
my $lib = $build_dir->child( $self->_base($file) . $Config{_a} );
my @cmd = ( $dmd, '-lib', "$file", "-of=$lib" );
push @cmd, '-fPIC' unless $os eq 'MSWin32';
$self->_run(@cmd);
return { file => $lib };
}
}
method _build_csharp ( $file, $out, $mode ) { $self->_build_dotnet( $file, $out, $mode, 'cs' ); }
#~ https://github.com/secana/Native-FSharp-Library
#~ https://secanablog.wordpress.com/2020/02/01/writing-a-native-library-in-f-which-can-be-called-from-c/
method _build_fsharp ( $file, $out, $mode ) { $self->_build_dotnet( $file, $out, $mode, 'fs' ); }
method _build_dotnet ( $src, $out, $mode, $lang ) {
my $file = $src->{path};
my $dotnet = $self->_can_run('dotnet') // croak "Dotnet not found";
my $proj_dir = $build_dir->child( "dotnet_${lang}_" . $self->_base($file) );
$proj_dir->mkpath;
$file->copy( $proj_dir->child( $file->basename ) );
my $ext = $lang eq 'fs' ? 'fsproj' : 'csproj';
my $proj = $proj_dir->child("Build.$ext");
lib/Affix/Build.pod view on Meta::CPAN
If you provide source files from B<multiple> languages (e.g., C code calling into a Rust library), the builder switches
to an aggregation strategy:
=over
=item 1. It instructs each language's compiler to output a B<Static Library> (C<.a> / C<.lib>) or B<Object File> (C<.o> / C<.obj>).
=item 2. It aggregates these intermediate artifacts into a unified build context.
=item 3. It invokes the system C/C++ linker (usually C<cc> or C<g++>) to securely link them into a final shared library, bridging their differing ABIs.
=back
=back
=head1 CONSTRUCTOR
my $builder = Affix::Build->new( %params );
Creates a new compiler instance.
t/017_affix_build.t view on Meta::CPAN
; ARM64: add w0, w0, w1
.global add_asm
.text
.align 2
add_asm:
add w0, w0, w1
ret
; Win64 x86_64: RCX + RDX -> RAX
global add_asm
section .text
add_asm:
mov eax, ecx
add eax, edx
ret
; SysV x86_64: RDI + RSI -> RAX
global add_asm
section .text
add_asm:
mov eax, edi
add eax, esi
ret
run_test( 'cobol', 'Cobol', <<~'', 'add_cob', 'cobc' );
IDENTIFICATION DIVISION.
PROGRAM-ID. add_cob.
DATA DIVISION.
LINKAGE SECTION.
t/017_affix_build.t view on Meta::CPAN
.align 2
asm_inc:
add w0, w0, #1
ret
elsif ( $^O eq 'MSWin32' ) {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, ecx
inc eax
ret
else {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, edi
inc eax
ret
skip_all "Missing Assembler ($asm_bin)" unless bin_path($asm_bin);
my $asm_file = $TMP_DIR->child($asm_file_name);
$asm_file->spew_utf8($asm_src);
#
my $compiler = Affix::Build->new( name => 'number_cruncher', build_dir => $TMP_DIR );
t/017_affix_build.t view on Meta::CPAN
func_f=6
end function
$c->add($f6);
#
my $asm_ext = ( $Config{archname} =~ /arm64/ ) ? 's' : 'asm';
my $f7 = $TMP_DIR->child("f7.$asm_ext");
$f7->spew_utf8( ( $^O eq 'MSWin32' || $Config{archname} !~ /arm64/ ) ? <<~'' : <<~'' );
; x86/x64
global func_asm
section .text
func_asm:
mov eax, 7
ret
; ARM64
.global func_asm
.text
func_asm:
mov w0, #7
ret
t/019_fileio.t view on Meta::CPAN
unlink $filename;
};
subtest 'Reading from a Perl filehandle in C' => sub {
my ( $fh, $filename ) = tempfile();
syswrite $fh, 'ABC';
close $fh;
open my $read_fh, '<', $filename or die $!;
my $char_code = c_read_char($read_fh);
is chr($char_code), 'A', 'C function read first character correctly';
$char_code = c_read_char($read_fh);
is chr($char_code), 'B', 'C function read second character correctly';
close $read_fh;
unlink $filename;
};
subtest 'Returning a FILE* from C to Perl' => sub {
my $fh = c_create_tmpfile();
ok $fh, 'Received a filehandle from C';
# Affix returns a Glob reference for files
is ref($fh), 'GLOB', 'Returned handle is a Glob reference';
my $line = <$fh>;
t/050_affix_build.t view on Meta::CPAN
; ARM64: add w0, w0, w1
.global add_asm
.text
.align 2
add_asm:
add w0, w0, w1
ret
; Win64 x86_64: RCX + RDX -> RAX
global add_asm
section .text
add_asm:
mov eax, ecx
add eax, edx
ret
; SysV x86_64: RDI + RSI -> RAX
global add_asm
section .text
add_asm:
mov eax, edi
add eax, esi
ret
run_test( 'cobol', 'Cobol', <<~'', 'add_cob', 'cobc' );
IDENTIFICATION DIVISION.
PROGRAM-ID. add_cob.
DATA DIVISION.
LINKAGE SECTION.
t/050_affix_build.t view on Meta::CPAN
.align 2
asm_inc:
add w0, w0, #1
ret
elsif ( $^O eq 'MSWin32' ) {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, ecx
inc eax
ret
else {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, edi
inc eax
ret
skip_all "Missing Assembler ($asm_bin)" unless bin_path($asm_bin);
my $asm_file = $TMP_DIR->child($asm_file_name);
$asm_file->spew_utf8($asm_src);
#
my $compiler = Affix::Build->new( name => 'number_cruncher', build_dir => $TMP_DIR );
t/050_affix_build.t view on Meta::CPAN
func_f=6
end function
$c->add($f6);
#
my $asm_ext = ( $Config{archname} =~ /arm64/ ) ? 's' : 'asm';
my $f7 = $TMP_DIR->child("f7.$asm_ext");
$f7->spew_utf8( ( $^O eq 'MSWin32' || $Config{archname} !~ /arm64/ ) ? <<~'' : <<~'' );
; x86/x64
global func_asm
section .text
func_asm:
mov eax, 7
ret
; ARM64
.global func_asm
.text
func_asm:
mov w0, #7
ret