Affix

 view release on metacpan or  search on metacpan

CODE_OF_CONDUCT.md  view on Meta::CPAN

diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
  advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment

CODE_OF_CONDUCT.md  view on Meta::CPAN

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
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels

CONTRIBUTING.md  view on Meta::CPAN

2.  **Fork the Repository**: Start by forking the main repository to your own GitHub account.
3.  **Create a Branch**: Create a new branch for your feature or bugfix from the `main` branch. Please use a descriptive name.

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

Changes.md  view on Meta::CPAN

# Changelog

All notable changes to Affix.pm will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v1.0.9] - 2026-03-05

This release focuses on refining the "Live" zero-copy system (ugh) and fixing bitfield write-back support (yay).

### Breaking Changes
- Replaced `LiveStruct`, `LiveArray`, and `LiveUnion` with a single `Live` wrapper. It now accepts any type object or signature (`Live[Struct[...]]`, `Live[Array[Int, 10]]`) and returns a live, zero-copy view. I hate this too but I'm workin' on it.

### Added

Changes.md  view on Meta::CPAN


## [v1.0.7] - 2026-02-15

Valgrind directed the work in Affix itself but infix got a lot of platform stability fixes which found their way into Affix by way of new Float16 support, bitfield width support, and SIMD improvements.

### Fixed

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

### Added

- Float16 support
 - Added the Float16 keyword to Affix.pm.
 - Implemented float_to_half and half_to_float conversion logic in Affix.c (IEEE 754).
 - Added optimized opcodes (OP_PUSH_FLOAT16, OP_RET_FLOAT16) to the internal VM dispatcher for high-performance marshalling.

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

- SIMD Vector Improvements:
 - Added M512, M512d, and M512i type helpers.
 - Ensured compatibility with infix's refined vector alignment and passing rules.
- [infix] Added support for half-precision floating-point (`float16`).

Changes.md  view on Meta::CPAN


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

LICENSE  view on Meta::CPAN

modifying or distributing the Package, you accept this license. Do not
use, modify, or distribute the Package, if you do not accept this
license.

(11)  If your Modified Version has been derived from a Modified
Version made by someone other than you, you are nevertheless required
to ensure that your Modified Version complies with the requirements of
this license.

(12)  This license does not grant you the right to use any trademark,
service mark, tradename, or logo of the Copyright Holder.

(13)  This license includes the non-exclusive, worldwide,
free-of-charge patent license to make, have made, use, offer to sell,
sell, import and otherwise transfer the Package with respect to any
patent claims licensable by the Copyright Holder that are necessarily
infringed by the Package. If you institute patent litigation
(including a cross-claim or counterclaim) against any party alleging
that the Package constitutes direct or contributory patent
infringement, then this Artistic License to you shall terminate on the
date that such litigation is filed.

README.md  view on Meta::CPAN


### Explicit Type Control with `coerce()`

In variadic functions, C relies on the caller to pass data in the exact format the function expects. While Affix
attempts to guess the correct C type for Perl scalars, these guesses might not always match the library's expectations
like passing a 64-bit integer where a 32-bit one is expected, or a float instead of a double.

Use `coerce( $type, $value )` to explicitly tell Affix how to marshal a variadic argument.

```perl
# Suppose we have a variadic log function that expects specific bit-widths
# C: void custom_log(int level, ...);
affix $lib, 'custom_log', [ Int, VarArgs ] => Void;

custom_log(
    1,
    coerce(Short, 10),    # Explicitly pass as a 16-bit signed int
    coerce(Float, 1.5),    # Explicitly pass as a 32-bit float
    coerce(ULong, 1000)    # Explicitly pass as a platform-native unsigned long
);
```

Note: Standard C default argument promotions still apply. For example, passing a `Float` to a variadic function will
typically be promoted to a `Double` by the C runtime unless the receiving function specifically handles raw floats.

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

        if    ( $cc_cmd =~ /cl(\.exe)?$/i ) { $cc_type = 'msvc'; }
        elsif ( $cc_cmd =~ /clang/i )       { $cc_type = 'clang'; }
        elsif ( $cc_cmd =~ /gcc/i )         { $cc_type = 'gcc'; }
        elsif ( $cc_cmd =~ /egcc/i )        { $cc_type = 'gcc'; }

        # Setup Flags
        my ( $ar_cmd, @cflags, @arflags, $out_flag_cc, $out_flag_ar );
        my @includes = map { ( $cc_type eq 'msvc' ? '/I' : '-I' ) . $_ } @include_dirs;
        if ( $cc_type eq 'msvc' ) {
            $ar_cmd      = 'lib';
            @cflags      = ( '/nologo', '/c', '/std:c11', '/W3', '/GS', '/MD', '/O2', @includes );
            @cflags      = ( @cflags, '/DINFIX_DEBUG_ENABLED=1' ) if $verbose;
            @arflags     = ('/nologo');
            $out_flag_cc = '/Fo';
            $out_flag_ar = '/OUT:';
        }
        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';

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

                $obj;
        }

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

infix/LICENSE-A2  view on Meta::CPAN

modifying or distributing the Package, you accept this license. Do not
use, modify, or distribute the Package, if you do not accept this
license.

(11)  If your Modified Version has been derived from a Modified
Version made by someone other than you, you are nevertheless required
to ensure that your Modified Version complies with the requirements of
this license.

(12)  This license does not grant you the right to use any trademark,
service mark, tradename, or logo of the Copyright Holder.

(13)  This license includes the non-exclusive, worldwide,
free-of-charge patent license to make, have made, use, offer to sell,
sell, import and otherwise transfer the Package with respect to any
patent claims licensable by the Copyright Holder that are necessarily
infringed by the Package. If you institute patent litigation
(including a cross-claim or counterclaim) against any party alleging
that the Package constitutes direct or contributory patent
infringement, then this Artistic License to you shall terminate on the
date that such litigation is filed.

infix/LICENSE-CC  view on Meta::CPAN

     accordance with the terms and conditions of this Public License.

  c. Copyright and Similar Rights means copyright and/or similar rights
     closely related to copyright including, without limitation,
     performance, broadcast, sound recording, and Sui Generis Database
     Rights, without regard to how the rights are labeled or
     categorized. For purposes of this Public License, the rights
     specified in Section 2(b)(1)-(2) are not Copyright and Similar
     Rights.

  d. Effective Technological Measures means those measures that, in the
     absence of proper authority, may not be circumvented under laws
     fulfilling obligations under Article 11 of the WIPO Copyright
     Treaty adopted on December 20, 1996, and/or similar international
     agreements.

  e. Exceptions and Limitations means fair use, fair dealing, and/or
     any other exception or limitation to Copyright and Similar Rights
     that applies to Your use of the Licensed Material.

  f. Licensed Material means the artistic or literary work, database,

infix/LICENSE-CC  view on Meta::CPAN

          6(a).

       4. Media and formats; technical modifications allowed. The
          Licensor authorizes You to exercise the Licensed Rights in
          all media and formats whether now known or hereafter created,
          and to make technical modifications necessary to do so. The
          Licensor waives and/or agrees not to assert any right or
          authority to forbid You from making technical modifications
          necessary to exercise the Licensed Rights, including
          technical modifications necessary to circumvent Effective
          Technological Measures. For purposes of this Public License,
          simply making modifications authorized by this Section 2(a)
          (4) never produces Adapted Material.

       5. Downstream recipients.

            a. Offer from the Licensor -- Licensed Material. Every
               recipient of the Licensed Material automatically
               receives an offer from the Licensor to exercise the
               Licensed Rights under the terms and conditions of this
               Public License.

            b. No downstream restrictions. You may not offer or impose
               any additional or different terms or conditions on, or
               apply any Effective Technological Measures to, the
               Licensed Material if doing so restricts exercise of the
               Licensed Rights by any recipient of the Licensed
               Material.

       6. No endorsement. Nothing in this Public License constitutes or
          may be construed as permission to assert or imply that You
          are, or that Your use of the Licensed Material is, connected
          with, or sponsored, endorsed, or granted official status by,
          the Licensor or others designated to receive attribution as
          provided in Section 3(a)(1)(A)(i).

infix/LICENSE-CC  view on Meta::CPAN


Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.

Creative Commons may be contacted at creativecommons.org.

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

 */
typedef struct infix_registry_iterator_t {
    const infix_registry_t * registry; /**< The registry being iterated. */
    size_t _bucket_index;              /**< Internal: current hash bucket. */
    void * _current_entry;             /**< Internal: opaque pointer to current entry. */
} infix_registry_iterator_t;
/**
 * @brief Serializes all defined types within a registry into a single, human-readable string.
 *
 * The output format is a sequence of definitions (e.g., `@Name = { ... };`) separated
 * by newlines, suitable for logging, debugging, or saving to a file. This function
 * will not print forward declarations that have not been fully defined.
 *
 * @param[out] buffer The output buffer to write the string into.
 * @param[in] buffer_size The size of the output buffer.
 * @param[in] registry The registry to serialize.
 * @return `INFIX_SUCCESS` on success, or `INFIX_ERROR_INVALID_ARGUMENT` if the buffer is too small.
 */
INFIX_API INFIX_NODISCARD infix_status infix_registry_print(char *, size_t, const infix_registry_t *);
/**
 * @brief Initializes an iterator for traversing the types in a registry.

infix/src/arch/aarch64/abi_arm64.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 abi_arm64.c
 * @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

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

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

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

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

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

/**
 * @internal
 * @brief Recursively finds the first primitive floating-point type in a potential HFA.
 * @details This function performs a depth-first search to find the very first `float`
 *          or `double` primitive within an aggregate. This becomes the candidate
 *          "base type" that all other members of the aggregate will be compared against.
 * @param type The type to search within.
 * @return A pointer to the `infix_type` of the base element, or `nullptr` if not found.
 */

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

    if (!is_hfa_recursive_check(type, base, &field_count))
        return false;
    if (out_base_type)
        *out_base_type = base;
    return true;
}
/**
 * @internal
 * @brief Stage 1 (Forward): Analyzes a signature and creates a call frame layout for AAPCS64.
 * @details This function assigns each argument to a location (GPR, VPR, or Stack) according
 *          to the AAPCS64 rules. It contains extensive conditional logic to handle ABI
 *          deviations on Apple and Windows platforms, especially for variadic arguments
 *          and 16-byte aggregate alignment.
 *
 * @param arena The temporary arena for allocations.
 * @param out_layout Receives the created layout blueprint.
 * @param ret_type The function's return type.
 * @param arg_types Array of argument types.
 * @param num_args Total number of arguments.
 * @param num_fixed_args Number of non-variadic arguments.
 * @param target_fn The target function address.

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

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

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

    // Move the trampoline's own arguments into these now-safe callee-saved registers.
    if (layout->target_fn == nullptr) {  // Unbound trampoline args: (target_fn, ret_ptr, args_ptr) in X0, X1, X2.
        emit_arm64_mov_reg(buf, true, X19_REG, X0_REG);  // mov x19, x0 (x19 will hold target_fn)
        emit_arm64_mov_reg(buf, true, X20_REG, X1_REG);  // mov x20, x1 (x20 will hold ret_ptr)
        emit_arm64_mov_reg(buf, true, X21_REG, X2_REG);  // mov x21, x2 (x21 will hold args_ptr)
    }
    else {                                               // Bound trampoline args: (ret_ptr, args_ptr) in X0, X1.
        emit_arm64_mov_reg(buf, true, X20_REG, X0_REG);  // mov x20, x0 (x20 = ret_ptr)
        emit_arm64_mov_reg(buf, true, X21_REG, X1_REG);  // mov x21, x1 (x21 = args_ptr)

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

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

        // The order of these checks is critical. Handle the most specific cases first.
        // On Apple Silicon, long double is 8 bytes. Only emit 128-bit store if size is actually 16.
        if ((is_long_double(ret_type) && ret_type->size == 16) ||
            (ret_type->category == INFIX_TYPE_VECTOR && ret_type->size == 16))
            emit_arm64_str_q_imm(buf, V0_REG, X20_REG, 0);  // str q0, [x20]

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

        return INFIX_ERROR_LAYOUT_FAILED;
    }
    size_t total_local_space = return_size + args_array_size + saved_args_data_size;
    // The total stack allocation for the frame must be 16-byte aligned.
    if (total_local_space > INFIX_MAX_STACK_ALLOC) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    layout->total_stack_alloc = (total_local_space + 15) & ~15;
    // Local variables are accessed via positive offsets from the stack pointer (SP)
    // after the initial `sub sp, sp, #alloc` in the prologue.
    // The layout on our local stack will be: [ return_buffer | args_array | saved_args_data ]
    layout->return_buffer_offset = 0;
    layout->args_array_offset = layout->return_buffer_offset + (int32_t)return_size;
    layout->saved_args_offset = layout->args_array_offset + (int32_t)args_array_size;
    *out_layout = layout;
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 2 (Reverse): Generates the prologue for the reverse trampoline stub.
 * @details This function emits the standard AArch64 function entry code. It saves the
 *          caller's frame pointer (X29) and the link register (X30, the return address)
 *          to the stack, establishes a new frame by pointing X29 to the current stack
 *          pointer, and allocates the pre-calculated stack space for local variables.
 *
 * @param buf The code buffer to write to.
 * @param layout The blueprint containing the total stack space to allocate.
 * @return `INFIX_SUCCESS` on success.
 */
static infix_status generate_reverse_prologue_arm64(code_buffer * buf, infix_reverse_call_frame_layout * layout) {
    // `stp x29, x30, [sp, #-16]!` : Save Frame Pointer and Link Register, pre-decrementing SP.
    emit_arm64_stp_pre_index(buf, true, X29_FP_REG, X30_LR_REG, SP_REG, -16);
    // `mov x29, sp` : Establish the new frame pointer.
    emit_arm64_mov_reg(buf, true, X29_FP_REG, SP_REG);
    // `sub sp, sp, #total_stack_alloc` : Allocate space for our local variables.
    if (layout->total_stack_alloc > 0)
        emit_arm64_sub_imm(buf, true, false, SP_REG, SP_REG, (uint32_t)layout->total_stack_alloc);
    return INFIX_SUCCESS;
}
/**

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

            else {
                emit_arm64_add_imm(buf, true, false, X10_REG, SP_REG, dest_offset);
                emit_arm64_str_imm(buf, true, X9_REG, X10_REG, 0);
            }

            current_saved_data_offset += (type->size + 15) & ~15;
            continue;  // Argument handled, move to next
        }
#endif

        // Standard AAPCS64 logic
        bool is_pass_by_ref = (type->size > 16) && !is_variadic_arg;
        bool is_from_stack = false;

        bool expect_in_vpr = is_float16(type) || is_float(type) || is_double(type) || is_long_double(type) ||
            type->category == INFIX_TYPE_VECTOR;
#if defined(INFIX_OS_WINDOWS)
        // Windows on ARM ABI disables HFA rules for variadic functions; floats go to GPRs.
        if (context->is_variadic)
            expect_in_vpr = false;
#endif

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

                    // Use STR Qn for 128-bit types
                    if (arg_save_loc >= 0 && ((unsigned)arg_save_loc / 16) <= 0xFFF && (arg_save_loc % 16 == 0))
                        emit_arm64_str_q_imm(buf, VPR_ARGS[vpr_idx++], SP_REG, arg_save_loc);
                    else {
                        emit_arm64_add_imm(buf, true, false, X10_REG, SP_REG, arg_save_loc);
                        emit_arm64_str_q_imm(buf, VPR_ARGS[vpr_idx++], X10_REG, 0);
                    }
                }
                else {
                    // Use STR Hn (16-bit), STR Sn (32-bit), or STR Dn (64-bit)
                    // Note: macOS long double (8 bytes) falls into path here via size check/alias logic
                    const int scale = (int)type->size;

                    if (arg_save_loc >= 0 && ((unsigned)arg_save_loc / scale) <= 0xFFF && (arg_save_loc % scale == 0)) {
                        emit_arm64_str_vpr(buf, type->size, VPR_ARGS[vpr_idx++], SP_REG, arg_save_loc);
                    }
                    else {
                        emit_arm64_add_imm(buf, true, false, X10_REG, SP_REG, arg_save_loc);
                        emit_arm64_str_vpr(buf, type->size, VPR_ARGS[vpr_idx++], X10_REG, 0);
                    }
                }

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

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

        // Explicitly check for 128-bit types.

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

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

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

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


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

    *out_layout = layout;
    return INFIX_SUCCESS;
}

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

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

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

                                                                   infix_direct_call_frame_layout * layout) {
    emit_arm64_load_u64_immediate(buf, X19_REG, (uint64_t)layout->target_fn);
    emit_arm64_cbnz(buf, true, X19_REG, 8);
    emit_arm64_brk(buf, 0);
    emit_arm64_blr_reg(buf, X19_REG);
    return INFIX_SUCCESS;
}

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

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

                size_t s = layout->args[i].type->size;
                size_t end = layout->args[i].location.stack_offset + ((s + 7) & ~7);
                if (end > stack_offset)
                    stack_offset = end;
            }
        }
        standard_alloc_size = (stack_offset + 15) & ~15;
    }

    // Call Write-Back Handlers
    size_t epilogue_scratch_offset = 0;  // Track offset locally to ensure consistency

    for (size_t i = 0; i < layout->num_args; ++i) {
        const infix_direct_arg_layout * arg_layout = &layout->args[i];

        // Re-calculate offset for this arg (Must match Phase 1 & 2 logic exactly)
        int32_t my_scratch_offset = -1;
        bool needs_scratch = false;
        size_t size = 0;
        size_t align = 0;

        if (arg_layout->handler->aggregate_marshaller) {
            size = arg_layout->type->size;
            align = arg_layout->type->alignment;
            needs_scratch = true;
        }

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

        else if (arg_layout->handler->writeback_handler) {
            const infix_type * pointee = (arg_layout->type->category == INFIX_TYPE_POINTER)
                ? arg_layout->type->meta.pointer_info.pointee_type
                : arg_layout->type;
            size = pointee->size;
            align = pointee->alignment;
            needs_scratch = true;
        }

        if (needs_scratch) {
            epilogue_scratch_offset = _infix_align_up(epilogue_scratch_offset, align);
            my_scratch_offset = (int32_t)(standard_alloc_size + epilogue_scratch_offset);
            epilogue_scratch_offset += size;
        }

        if (arg_layout->handler->writeback_handler) {
            // Save C return value (in X0/V0) before calling out.
            // Note: Technically should save more registers for HFA returns, but this matches basic needs.
            emit_arm64_sub_imm(buf, true, false, SP_REG, SP_REG, 32);
            emit_arm64_str_imm(buf, true, X0_REG, SP_REG, 0);
            emit_arm64_str_imm(buf, true, X1_REG, SP_REG, 8);
            emit_arm64_str_q_imm(buf, V0_REG, SP_REG, 16);  // Save V0 (covers float/double/vector)

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

            emit_arm64_blr_reg(buf, X10_REG);

            // Restore C return value.
            emit_arm64_ldr_q_imm(buf, V0_REG, SP_REG, 16);
            emit_arm64_ldr_imm(buf, true, X1_REG, SP_REG, 8);
            emit_arm64_ldr_imm(buf, true, X0_REG, SP_REG, 0);
            emit_arm64_add_imm(buf, true, false, SP_REG, SP_REG, 32);
        }
    }

    // Standard Epilogue
    // Restore stack pointer to the saved registers area.
    // X29 was set to SP after all pushes.
    // mov sp, x29
    emit_arm64_mov_reg(buf, true, SP_REG, X29_FP_REG);

    emit_arm64_ldp_post_index(buf, true, X21_REG, X22_REG, SP_REG, 16);
    emit_arm64_ldp_post_index(buf, true, X19_REG, X20_REG, SP_REG, 16);
    emit_arm64_ldp_post_index(buf, true, X29_FP_REG, X30_LR_REG, SP_REG, 16);
    emit_arm64_ret(buf, X30_LR_REG);

infix/src/arch/aarch64/abi_arm64_common.h  view on Meta::CPAN

 * @file abi_arm64_common.h
 * @brief Common register definitions and instruction encodings for the AArch64 (ARM64) architecture.
 * @ingroup internal_abi_aarch64
 *
 * @internal
 * This header serves two primary purposes for the AArch64 backend:
 *
 * 1.  **Register Enumerations:** It defines enums for the general-purpose registers (GPRs) and
 *     the floating-point/SIMD registers (VPRs). These enums provide a clear, type-safe,
 *     and self-documenting way to refer to specific registers when emitting machine
 *     code or implementing the ABI logic. The comments on each register describe its
 *     role according to the standard AAPCS64 calling convention.
 *
 * 2.  **Instruction Encoding Constants:** It contains preprocessor definitions for the
 *     fixed bitfields of various AArch64 instructions. This abstracts away the
 *     "magic numbers" of machine code generation, making the emitter code in
 *     `abi_arm64_emitters.c` more readable and easier to verify against the official
 *     ARM Architecture Reference Manual.
 *
 * By centralizing these definitions, this header provides a single source of truth for
 * the low-level architectural details, separating them from the higher-level ABI logic.
 * @endinternal
 */
#include <stdint.h>
/**
 * @internal
 * @enum arm64_gpr
 * @brief Enumerates the ARM64 General-Purpose Registers (GPRs), X0-X30 and SP.
 *
 * @details The enum values (0-31) correspond directly to the 5-bit register numbers
 * used in the encoding of machine code instructions. The comments on each register

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

 * @file abi_arm64_emitters.c
 * @brief Implements internal helper functions for emitting AArch64 machine code.
 * @ingroup internal_abi_aarch64
 *
 * @internal
 * This file provides the concrete implementations for the low-level AArch64
 * instruction emitters. Each function constructs a single, valid 32-bit AArch64
 * instruction word from its component parts (registers, immediates, etc.) and
 * appends it to a `code_buffer`.
 *
 * This module encapsulates the bitwise logic for encoding ARM64 instructions,
 * keeping the main `abi_arm64.c` file focused on the higher-level logic of
 * applying the AAPCS64 ABI rules.
 * @endinternal
 */
#include "arch/aarch64/abi_arm64_emitters.h"
#include "common/utility.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
// GPR <-> Immediate Value Emitters
/*

infix/src/arch/aarch64/abi_arm64_emitters.h  view on Meta::CPAN

 * @file abi_arm64_emitters.h
 * @brief Declares internal helper functions for emitting AArch64 machine code.
 * @ingroup internal_abi_aarch64
 *
 * @internal
 * This header provides the function prototypes for all low-level AArch64 instruction
 * emitters. These functions are the fundamental building blocks used by `abi_arm64.c`
 * to generate the machine code for both forward and reverse trampolines.
 *
 * This module was created to cleanly separate the low-level, bit-twiddling details
 * of AArch64 instruction set encoding from the higher-level logic of applying the
 * AAPCS64 ABI rules (like argument classification and stack layout).
 * @endinternal
 */
#include "arch/aarch64/abi_arm64_common.h"
#include "common/infix_internals.h"
// GPR <-> Immediate Value Emitters
/** @internal @brief Emits a MOVZ/MOVK sequence to load an arbitrary 64-bit immediate into a GPR. */
INFIX_INTERNAL void emit_arm64_load_u64_immediate(code_buffer * buf, arm64_gpr dest, uint64_t value);
// GPR <-> GPR Move Emitters
/** @internal @brief Emits `MOV <Xd|Wd>, <Xn|Wn>` for a register-to-register move. */

infix/src/arch/x64/abi_sysv_x64.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 abi_sysv_x64.c
 * @brief Implements the FFI logic for the System V AMD64 ABI.
 * @ingroup internal_abi_x64
 *
 * @internal
 * This file provides the concrete implementation of the ABI spec for the System V
 * x86-64 ABI, the standard calling convention for Linux, macOS, BSD, and other
 * UNIX-like operating systems on this architecture.
 *
 * Key features of the System V ABI implemented here:
 *
 * - **Register Usage:**

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

} arg_class_t;

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

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

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

/**
 * @internal
 * @brief Recursively classifies the eightbytes of an aggregate type.
 * @details This is the core of the complex System V classification algorithm. It traverses
 * the fields of a struct/array, examining each 8-byte chunk ("eightbyte") and assigning it a
 * class (INTEGER, SSE, MEMORY). The classification is "merged" according to ABI rules
 * (e.g., if an eightbyte contains both INTEGER and SSE parts, it becomes INTEGER).
 *
 * @param type The type of the current member/element being examined.

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

    // Main Argument Classification Loop
    for (size_t i = 0; i < num_args; ++i) {
        infix_type * type = arg_types[i];
        // Security: Reject excessively large types before they reach the code generator.
        if (type->size > INFIX_MAX_ARG_SIZE) {
            *out_layout = nullptr;
            return INFIX_ERROR_LAYOUT_FAILED;
        }
        // An array passed as a function parameter decays to a pointer.
        // We must treat it as a pointer (INTEGER class) for classification,
        // bypassing the aggregate classification logic which would incorrectly
        // treat it as a by-value struct.
        if (type->category == INFIX_TYPE_ARRAY) {
            if (gpr_count < NUM_GPR_ARGS) {
                layout->arg_locations[i].type = ARG_LOCATION_GPR;
                layout->arg_locations[i].reg_index = gpr_count++;
            }
            else {
                layout->arg_locations[i].type = ARG_LOCATION_STACK;
                layout->arg_locations[i].stack_offset = current_stack_offset;
                current_stack_offset += 8;  // Pointers are 8 bytes on the stack

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

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

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

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

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

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

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

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

    emit_sub_reg_imm32(buf, RSP_REG, layout->total_stack_alloc);  // Allocate our calculated space.
    return INFIX_SUCCESS;
}

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

    // If the return value is passed by reference, save the pointer from RDI.
    if (return_in_memory)
        emit_mov_mem_reg(buf, RBP_REG, layout->return_buffer_offset, GPR_ARGS[gpr_idx++]);  // mov [rbp + offset], rdi
    // Stack arguments passed by the caller start at [rbp + 16].
    size_t stack_arg_offset = 16;
    for (size_t i = 0; i < context->num_args; i++) {
        infix_type * current_type = context->arg_types[i];
        current_saved_data_offset = _infix_align_up(current_saved_data_offset, current_type->alignment);
        int32_t arg_save_loc = layout->saved_args_offset + current_saved_data_offset;

        // Correct classification logic for vectors/primitives vs aggregates
        arg_class_t classes[2] = {NO_CLASS, NO_CLASS};
        size_t num_classes = 0;
        bool is_aggregate =
            (current_type->category == INFIX_TYPE_STRUCT || current_type->category == INFIX_TYPE_UNION ||
             current_type->category == INFIX_TYPE_ARRAY || current_type->category == INFIX_TYPE_COMPLEX);

        if (is_aggregate) {
            classify_aggregate_sysv(current_type, classes, &num_classes);
        }
        else if (is_float16(current_type) || is_float(current_type) || is_double(current_type) ||

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

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

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

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

/**
 * @internal
 * @brief Stage 1 (Direct): Analyzes a signature and creates a call frame layout for System V.
 * @details This is the direct-marshalling equivalent of the standard `prepare` function.

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

    if (layout->total_stack_alloc > INFIX_MAX_STACK_ALLOC) {
        *out_layout = nullptr;
        return INFIX_ERROR_LAYOUT_FAILED;
    }
    *out_layout = layout;
    return INFIX_SUCCESS;
}

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

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

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

    emit_mov_reg_imm64(buf, R12_REG, (uint64_t)layout->target_fn);
    emit_test_reg_reg(buf, R12_REG, R12_REG);
    emit_jnz_short(buf, 2);
    emit_ud2(buf);
    emit_call_reg(buf, R12_REG);
    return INFIX_SUCCESS;
}

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

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

            emit_call_reg(buf, R10_REG);

            // Restore return registers
            emit_movsd_xmm_mem(buf, XMM0_REG, RSP_REG, 0);
            emit_add_reg_imm32(buf, RSP_REG, 32);
            emit_pop_reg(buf, RDX_REG);
            emit_pop_reg(buf, RAX_REG);
        }
    }

    // Standard Epilogue
    if (layout->total_stack_alloc > 0)
        emit_add_reg_imm32(buf, RSP_REG, (int32_t)layout->total_stack_alloc);

    emit_pop_reg(buf, R15_REG);
    emit_pop_reg(buf, R14_REG);
    emit_pop_reg(buf, R13_REG);
    emit_pop_reg(buf, R12_REG);

    emit_pop_reg(buf, RBP_REG);
    emit_ret(buf);

infix/src/arch/x64/abi_win_x64.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 abi_win_x64.c
 * @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.

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

#define SHADOW_SPACE 32

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

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

/**
 * @internal
 * @brief Determines if a type is returned by value in RAX/XMM0 or via a hidden pointer.
 * @details On Windows x64:
 * - Scalars and __m128 are returned in registers (RAX or XMM0).
 * - **__m256 and __m512 are returned in registers (YMM0 or ZMM0).**
 * - Aggregates > 8 bytes (excluding vectors) are returned via a hidden pointer.
 */
static bool return_value_is_by_reference(const infix_type * type) {

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

    size_t current_stack_offset = SHADOW_SPACE;
    size_t max_align = 16;
    layout->num_stack_args = 0;
    for (size_t i = 0; i < num_args; ++i) {
        infix_type * current_type = arg_types[i];
        if (current_type->alignment > max_align)
            max_align = current_type->alignment;
        // Detect vectors as FP so they get XMM slots if passed by value (<=16 bytes).
        bool is_fp = is_float16(current_type) || is_float(current_type) || is_double(current_type) ||
            (current_type->category == INFIX_TYPE_VECTOR);
        // as FP register args in the slot assignment logic
        // (they go to GPR slots as pointers).
        bool is_ref = is_passed_by_reference(current_type);
        bool is_variadic_arg = (i >= num_fixed_args);

        if (arg_position < 4) {
            if (is_fp && !is_ref && !is_variadic_arg) {
                layout->arg_locations[i].type = ARG_LOCATION_XMM;
                layout->arg_locations[i].reg_index = (uint8_t)arg_position++;
            }
            else {

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

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

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

    // FORCE 16-BYTE ALIGNMENT.
    // AND RSP, -16
    emit_and_reg_imm8(buf, RSP_REG, -16);

    // Move incoming trampoline arguments to non-volatile registers.
    if (layout->target_fn == nullptr) {           // Unbound: (target_fn, ret_ptr, args_ptr) in RCX, RDX, R8
        emit_mov_reg_reg(buf, R12_REG, RCX_REG);  // R12 = target function
        emit_mov_reg_reg(buf, R13_REG, RDX_REG);  // R13 = return value buffer
        emit_mov_reg_reg(buf, R14_REG, R8_REG);   // R14 = argument values array

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

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

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

    layout->saved_args_offset =
        (int32_t)_infix_align_up((size_t)(layout->args_array_offset + args_array_size), max_align);

    layout->max_align = (uint32_t)max_align;

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

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

    // FORCE STACK ALIGNMENT.
    // Use the maximum alignment required by the signature (16, 32, or 64).
    emit_and_reg_imm8(buf, RSP_REG, -(int8_t)layout->max_align);

    // Allocate all local stack space calculated in the prepare stage. This includes
    // space for register save areas, the return buffer, args_array, and shadow space.
    if (layout->total_stack_alloc > 0)
        emit_sub_reg_imm32(buf, RSP_REG, (int32_t)layout->total_stack_alloc);

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

    if (layout->max_align >= 32)
        emit_vzeroupper(buf);

    // Load the C dispatcher's address into a scratch register (R9) and call it.
    emit_mov_reg_imm64(buf, R9_REG, (uint64_t)context->internal_dispatcher);
    emit_call_reg(buf, R9_REG);
    return INFIX_SUCCESS;
}
/**
 * @internal
 * @brief Stage 5 (Reverse): Generates the epilogue for the reverse trampoline stub.
 * @details After the C dispatcher returns, this code is responsible for the final steps
 *          of the reverse trampoline. It retrieves the return value from the buffer on
 *          the stub's local stack and places it into the correct native return register
 *          (`RAX` or `XMM0`) as required by the Windows x64 ABI.
 *
 *          It then restores the stack pointer using `LEA RSP, [RBP - 16]` to undo the
 *          dynamic alignment performed in the prologue, restores saved registers, and returns.
 *
 * @param buf The code buffer.
 * @param layout The blueprint containing stack offsets.
 * @param context The context containing the return type information.
 * @return `INFIX_SUCCESS`.
 */
static infix_status generate_reverse_epilogue_win_x64(code_buffer * buf,
                                                      infix_reverse_call_frame_layout * layout,
                                                      infix_reverse_t * context) {
    if (layout->max_align >= 32) {
        // Only call VZEROUPPER if we aren't returning a value in YMM/ZMM registers,
        // as VZEROUPPER would zero the upper half of the result.
        bool returning_large_vector =
            (context->return_type->category == INFIX_TYPE_VECTOR && context->return_type->size >= 32 &&
             !return_value_is_by_reference(context->return_type));
        if (!returning_large_vector)
            emit_vzeroupper(buf);

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

            const infix_type * type = arg_types[i];
            bool by_ref = is_passed_by_reference(type) ||
                (type->category == INFIX_TYPE_POINTER && handlers[i].aggregate_marshaller);
            // Overwrite the temp offset with the final outgoing offset.
            layout->args[i].location.stack_offset = (uint32_t)outgoing_stack_offset;
            size_t size_on_stack = by_ref ? 8 : ((type->size + 7) & ~7);
            outgoing_stack_offset += size_on_stack;
        }
    }

    // Ensure the base of the scratch area is 16-byte aligned, matching Generate phase logic.
    size_t scratch_base_offset = (outgoing_stack_offset + 15) & ~15;

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

    // Final pass: Adjust temp/scratch offsets to be relative to RSP after allocation.
    size_t temp_base_offset = scratch_base_offset;
    for (size_t i = 0; i < num_args; ++i) {
        if (layout->args[i].handler->aggregate_marshaller || layout->args[i].handler->scalar_marshaller ||
            layout->args[i].handler->writeback_handler) {

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


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

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

    // FORCE 64-BYTE ALIGNMENT.
    // AND RSP, -64 (48 83 E4 C0)
    emit_and_reg_imm8(buf, RSP_REG, -64);

    // The direct CIF is called with (ret_ptr, lang_args) in RCX, RDX.
    emit_mov_reg_reg(buf, R13_REG, RCX_REG);  // r13 = ret_ptr
    emit_mov_reg_reg(buf, R14_REG, RDX_REG);  // r14 = lang_objects array

    // Allocate all stack space.

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

    emit_mov_reg_imm64(buf, R15_REG, (uint64_t)layout->target_fn);  // Use R15 for target function
    emit_test_reg_reg(buf, R15_REG, R15_REG);
    emit_jnz_short(buf, 2);
    emit_ud2(buf);
    emit_call_reg(buf, R15_REG);
    return INFIX_SUCCESS;
}

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

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


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

            emit_mov_reg_mem(buf, RAX_REG, RSP_REG, 32);
            emit_movsd_xmm_mem(buf, XMM0_REG, RSP_REG, 40);
            emit_add_reg_imm32(buf, RSP_REG, 48);
        }
    }

    // Safe Epilogue
    // If AVX was potentially used, clear the upper bits of YMM registers.
    // Note: We'll add max_align to direct layout in the next step.
    // For now, let's assume if any arg was a vector, we might have used AVX.
    bool maybe_avx = false;
    for (size_t i = 0; i < layout->num_args; i++) {
        if (layout->args[i].type->category == INFIX_TYPE_VECTOR && layout->args[i].type->size >= 32) {
            maybe_avx = true;
            break;
        }
    }

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

    if (dest_base % 8 == RSP_REG)
        emit_byte(buf, 0x24);
    if (mod == 0x40)
        emit_byte(buf, (uint8_t)offset);
    else if (mod == 0x80)
        emit_int32(buf, offset);
}
/**
 * @internal
 * @brief Emits a VEX prefix for an AVX instruction.
 * @details This helper centralizes the logic of choosing between 2-byte (C5) and 3-byte (C4) VEX encodings.
 */
INFIX_INTERNAL void emit_vex_prefix(
    code_buffer * buf, bool r, bool x, bool b, uint8_t m, bool w, uint8_t v, bool l, uint8_t p) {
    // VEX encoding inverts R, X, B bits.
    // The 2-byte VEX prefix cannot encode the L bit for 256-bit operations.
    // The condition must ensure we only use it for 128-bit operations (l=0).
    if (!b && !x && m == 1 && w == 0 && !l) {
        // Use 2-byte VEX prefix (C5) when possible.
        emit_byte(buf, 0xC5);
        uint8_t byte2 = ((!r) << 7) | ((~v & 0xF) << 3) | ((l & 1) << 2) | (p & 3);

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

 * @file double_tap.h
 * @brief A lightweight, single-header TAP (Test Anything Protocol) library.
 * @ingroup internal_test_harness
 *
 * @details This file provides a simple, self-contained testing harness that produces
 * TAP-compliant output, which is ideal for integration with CI/CD systems and other
 * 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,

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

#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
// Define ssize_t as ptrdiff_t, the standard signed counterpart to size_t.
typedef ptrdiff_t ssize_t;

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

#else
    uint8_t num_xmm_args; /**< The number of XMMs used for arguments. */
#endif
    infix_arg_location * arg_locations; /**< An array of location info for each argument. */
    bool return_value_in_memory; /**< `true` if the return value uses a hidden pointer argument (struct return). */
    bool is_variadic;            /**< `true` if the function is variadic. */
    size_t num_stack_args;       /**< The number of arguments passed on the stack. */
    size_t num_args;             /**< The total number of arguments. */
    void * target_fn;            /**< The target function address. */
    uint32_t max_align;          /**< Maximum required alignment for any argument or the stack. */
    uint32_t prologue_size;      /**< Size of the generated prologue in bytes. */
    uint32_t epilogue_offset;    /**< Offset from the start of the JIT block to the epilogue. */
} infix_call_frame_layout;
/**
 * @struct infix_reverse_call_frame_layout
 * @brief A complete layout blueprint for a reverse call frame.
 * @details This structure serves as a plan for the JIT-compiled reverse call stub.
 * It contains the offsets for all data structures that the stub needs to create
 * on its stack frame before calling the universal C dispatcher.
 */
typedef struct {
    size_t total_stack_alloc;     /**< Total bytes of local stack space needed. */
    int32_t return_buffer_offset; /**< Stack offset for the buffer to store the return value. */
    int32_t args_array_offset;    /**< Stack offset for the `void**` array passed to the C dispatcher. */
    int32_t saved_args_offset;    /**< Stack offset for the area where argument data is stored/marshalled. */
    int32_t gpr_save_area_offset; /**< (Win x64) Stack offset for saving non-volatile GPRs. */
    int32_t xmm_save_area_offset; /**< (Win x64) Stack offset for saving non-volatile XMMs. */
    uint32_t max_align;           /**< Maximum required alignment for any argument or the stack. */
    uint32_t prologue_size;       /**< Size of the generated prologue in bytes. */
} infix_reverse_call_frame_layout;
/**
 * @brief Defines the ABI-specific implementation interface for forward trampolines.
 *
 * @details This structure is a virtual function table (v-table) that decouples the
 * platform-agnostic JIT engine (`trampoline.c`) from the platform-specific
 * code generation logic (`arch/...`). Each supported ABI (e.g., SysV x64,
 * Win x64, AArch64) provides a concrete implementation of this interface.
 *
 * The JIT pipeline for a forward call proceeds in a well-defined order:
 * 1. `prepare_forward_call_frame` is called first to analyze the function
 *    signature and produce a complete `infix_call_frame_layout` blueprint.
 * 2. The `generate_*` functions are then called in sequence, consuming the layout
 *    blueprint to emit the corresponding machine code into a `code_buffer`.
 */
typedef struct {
    /**

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

     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*prepare_forward_call_frame)(infix_arena_t * arena,
                                               infix_call_frame_layout ** out_layout,
                                               infix_type * ret_type,
                                               infix_type ** arg_types,
                                               size_t num_args,
                                               size_t num_fixed_args,
                                               void * target_fn);
    /**
     * @brief Generates the function prologue (stack setup, saving registers).
     * @param[in,out] buf The code buffer to append machine code to.
     * @param[in] layout The layout blueprint from the previous step.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_forward_prologue)(code_buffer * buf, infix_call_frame_layout * layout);
    /**
     * @brief Generates code to move arguments from the `void**` array into registers and/or the stack.
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] arg_types The array of argument types.
     * @param[in] num_args Total number of arguments.
     * @param[in] num_fixed_args Number of fixed arguments.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_forward_argument_moves)(code_buffer * buf,

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

                                                    size_t num_args,
                                                    size_t num_fixed_args);
    /**
     * @brief Generates the `call` instruction to the target function.
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_forward_call_instruction)(code_buffer * buf, infix_call_frame_layout * layout);
    /**
     * @brief Generates the function epilogue (handling return value, restoring stack, returning).
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] ret_type The function's return type.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_forward_epilogue)(code_buffer * buf,
                                              infix_call_frame_layout * layout,
                                              infix_type * ret_type);
} infix_forward_abi_spec;
/**
 * @brief Defines the ABI-specific implementation interface for reverse trampolines.
 * @details This v-table defines the contract for generating the JIT stub for a
 * reverse call (callback). The stub's primary job is to receive arguments in
 * native ABI format, marshal them into a generic `void**` array, and call the
 * universal C dispatcher.
 */

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

     * @brief Analyzes a function signature to create a layout for the reverse call stub's stack frame.
     * @param[in] arena The temporary arena for allocations.
     * @param[out] out_layout Receives the newly created layout blueprint.
     * @param[in] context The reverse trampoline context, containing all type info.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*prepare_reverse_call_frame)(infix_arena_t * arena,
                                               infix_reverse_call_frame_layout ** out_layout,
                                               infix_reverse_t * context);
    /**
     * @brief Generates the reverse stub's prologue (stack setup).
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_reverse_prologue)(code_buffer * buf, infix_reverse_call_frame_layout * layout);
    /**
     * @brief Generates code to marshal arguments from their native locations (registers/stack) into a `void**` array.
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] context The reverse context.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_reverse_argument_marshalling)(code_buffer * buf,
                                                          infix_reverse_call_frame_layout * layout,
                                                          infix_reverse_t * context);

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

     * @brief Generates the call to the universal C dispatcher (`infix_internal_dispatch_callback_fn_impl`).
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] context The reverse context.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_reverse_dispatcher_call)(code_buffer * buf,
                                                     infix_reverse_call_frame_layout * layout,
                                                     infix_reverse_t * context);
    /**
     * @brief Generates the reverse stub's epilogue (handling return value, restoring stack, returning).
     * @param[in,out] buf The code buffer.
     * @param[in] layout The layout blueprint.
     * @param[in] context The reverse context.
     * @return `INFIX_SUCCESS` on success.
     */
    infix_status (*generate_reverse_epilogue)(code_buffer * buf,
                                              infix_reverse_call_frame_layout * layout,
                                              infix_reverse_t * context);
} infix_reverse_abi_spec;

/**
 * @struct infix_direct_arg_layout
 * @brief Internal layout information for a single argument in a direct marshalling trampoline.
 *
 * This struct combines the ABI location information with pointers to the type and
 * handler information needed by the JIT emitters.

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

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

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

} infix_direct_forward_abi_spec;

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

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

    INFIX_EXECUTABLE_REVERSE,
    INFIX_EXECUTABLE_DIRECT
} infix_executable_category_t;

/**
 * @brief Makes a block of JIT memory executable, completing the W^X process.
 * @details Located in `src/jit/executor.c`. For single-map platforms, this calls
 * `VirtualProtect` or `mprotect`. For dual-map platforms, this is a no-op. It
 * also handles instruction cache flushing on relevant architectures like AArch64.
 * @param exec The handle to the memory block to make executable.
 * @param prologue_size The size of the generated prologue in bytes.
 * @param category The type of trampoline being finalized.
 * @return `true` on success, `false` on failure.
 */
INFIX_INTERNAL c23_nodiscard bool infix_executable_make_executable(infix_executable_t * exec,
                                                                   infix_executable_category_t category,
                                                                   uint32_t prologue_size,
                                                                   uint32_t epilogue_offset);
/**
 * @brief Allocates a block of standard memory for later protection.
 * @details Located in `src/jit/executor.c`. This is used to allocate the memory
 * for an `infix_reverse_t` context before it is made read-only.
 * @param size The number of bytes to allocate.
 * @return An `infix_protected_t` handle.
 */
INFIX_INTERNAL c23_nodiscard infix_protected_t infix_protected_alloc(size_t size);
/**
 * @brief Frees a block of protected memory.

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

 * @internal
 * This header is the central point for the library's internal debugging infrastructure.
 * Its primary feature is that it behaves differently based on the `INFIX_DEBUG_ENABLED`
 * preprocessor macro. This allows debugging code to be seamlessly integrated
 * during development without affecting the performance or size of the final
 * production binary.
 *
 * - **When `INFIX_DEBUG_ENABLED` is defined and non-zero (Debug Mode):**
 *   - It declares the `infix_dump_hex` function for detailed memory inspection.
 *   - It defines the `INFIX_DEBUG_PRINTF` macro, which integrates with the `double_tap`
 *     test harness's logging system (`note()`) if available, or falls back to a
 *     standard `printf`. This allows debug messages from the core library to appear
 *     cleanly within the test output.
 *
 * - **When `INFIX_DEBUG_ENABLED` is not defined or is zero (Release Mode):**
 *   - All debugging macros are defined as no-ops (`((void)0)`).
 *   - The `infix_dump_hex` function is defined as an empty `static inline` function.
 *   - This design ensures that all debugging code and calls are completely compiled
 *     out by the optimizer, resulting in zero overhead in release builds.
 * @endinternal
 */

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

    if (status != INFIX_SUCCESS)
        return status;
    // Safely copy the data using the parsed size.
    infix_memcpy(buffer, symbol_addr, type->size);
    infix_arena_destroy(arena);
    return INFIX_SUCCESS;
}
/**
 * @brief Writes data from a buffer into an exported global variable in a library.
 *
 * @details This function is analogous to `infix_read_global`. It finds the symbol's
 * address and uses the signature string to determine the correct number of bytes
 * to copy from the source buffer to the library's memory.
 *
 * @note This operation assumes that the memory page containing the global variable
 *       is writable. This is typical for `.data` or `.bss` segments but may fail
 *       if the variable is in a read-only segment (e.g., a `const` global). The
 *       function does not attempt to change memory permissions.
 *
 * @param[in] lib The library handle.
 * @param[in] symbol_name The name of the global variable.

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

    *out_args = args;
    *out_num_args = num_args;
    return INFIX_SUCCESS;
}
// High-Level API Implementation
/**
 * @internal
 * @brief The internal entry point for the signature parser (the "Parse" stage).
 *
 * This function takes a signature string and produces a raw, unresolved type
 * graph in a new, temporary arena. It is the core parsing logic, separated from the
 * higher-level functions that manage the full data pipeline. It is careful not to
 * modify the global error context string (`g_infix_last_signature_context`), which
 * is the responsibility of its public API callers.
 *
 * @param[out] out_type On success, receives the parsed type graph.
 * @param[out] out_arena On success, receives the temporary arena holding the graph. The caller is responsible for
 * destroying it.
 * @param[in] signature The signature string to parse.
 * @return `INFIX_SUCCESS` on success.
 */

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

                _print(state, ",");
            const infix_struct_member * member = &type->meta.aggregate_info.members[i];
            if (member->name)
                _print(state, "%s:", member->name);
            _infix_type_print_signature_recursive(state, member->type);
            if (member->bit_width > 0)
                _print(state, ":%u", member->bit_width);
        }
        _print(state, ">");
        break;
    // For all other types, we replicate the printing logic from the main printer
    // to ensure we print the structure, not a potential top-level alias name.
    case INFIX_TYPE_VOID:
        _print(state, "void");
        break;
    case INFIX_TYPE_POINTER:
        _print(state, "*");
        if (type->meta.pointer_info.pointee_type == type || type->meta.pointer_info.pointee_type == nullptr ||
            type->meta.pointer_info.pointee_type->category == INFIX_TYPE_VOID)
            _print(state, "void");
        else

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

        }
    }
    else if (buffer_size > 0)
        buffer[buffer_size - 1] = '\0';
    return state.status;
}
/**
 * @brief Serializes all defined types within a registry into a single, human-readable string.
 *
 * @details The output format is a sequence of definitions (e.g., `@Name = { ... };`) separated
 * by newlines, suitable for logging, debugging, or saving to a file. This function
 * will not print forward declarations that have not been fully defined. The order of
 * definitions in the output string is not guaranteed.
 *
 * @param[out] buffer The output buffer to write the string into.
 * @param[in] buffer_size The size of the output buffer.
 * @param[in] registry The registry to serialize.
 * @return `INFIX_SUCCESS` on success, or `INFIX_ERROR_INVALID_ARGUMENT` if the buffer is too small
 *         or another error occurs.
 */
c23_nodiscard infix_status infix_registry_print(char * buffer, size_t buffer_size, const infix_registry_t * registry) {

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

            final_status = INFIX_ERROR_ALLOCATION_FAILED;
            infix_arena_destroy(parser_arena);
            goto cleanup;
        }

        // Update the entry. If a placeholder already exists (from forward decl), we MUST update it in-place
        // to preserve pointers from other types that have already resolved to it.
        if (entry->type) {
            // Struct-copy the new definition into the existing placeholder memory.
            *entry->type = *new_def;
            // Restore the self-reference if the copy logic pointed the arena elsewhere.
            entry->type->arena = registry->arena;
            // The new definition is complete, so ensure the flag is cleared.
            entry->type->is_incomplete = false;
        }
        else {
            entry->type = new_def;
            entry->type->is_incomplete = false;
        }

        // Ensure the name is attached and the flag is cleared.

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

 */
/**
 * @file types.c
 * @brief Implements the public API for creating and managing type descriptions.
 * @ingroup internal_core
 *
 * @details This module serves two primary functions:
 * 1.  It provides the public functions for programmatically constructing `infix_type`
 *     objects (the "Manual API"). These functions are the building blocks for
 *     users who need to create type information dynamically without parsing strings.
 * 2.  It contains the crucial internal logic for two core stages of the data pipeline:
 *     - **Copying (`_copy_type_graph_to_arena`):** Deep-copies a type graph to a
 *       new memory arena, which is fundamental to creating self-contained trampoline
 *       objects and ensuring memory safety.
 *     - **Layout (`_infix_type_recalculate_layout`):** Traverses a fully resolved
 *       type graph to compute the final memory layout (size, alignment, and offsets)
 *       of all structures and unions.
 *
 * The creation functions (`infix_type_create_*`) perform an initial, preliminary layout
 * calculation. This layout is considered unresolved until a final pass with
 * `_infix_type_recalculate_layout` is performed after all named types have been

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

 */
INFIX_API infix_struct_member infix_type_create_bitfield_member(const char * name,
                                                                infix_type * type,
                                                                size_t offset,
                                                                uint8_t bit_width) {
    return (infix_struct_member){name, type, offset, bit_width, 0, true};
}

/**
 * @internal
 * @brief Shared logic to calculate struct layout, including bitfields and FAMs.
 * @return `true` on success, `false` if an integer overflow occurred.
 */
static bool _layout_struct(infix_type * type) {
    size_t current_byte_offset = 0;
    size_t current_unit_offset = 0;
    size_t current_unit_size = 0;
    uint32_t current_unit_bits_used = 0;
    size_t max_alignment = 1;
    bool in_bitfield = false;

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

    // If it is packed, the alignment is explicitly determined by the user.
    if (type->meta.aggregate_info.is_packed)
        max_alignment = type->alignment;

    type->alignment = max_alignment;
    type->size = _infix_align_up(current_byte_offset, max_alignment);
    return true;
}
/**
 * @internal
 * @brief Common setup logic for creating aggregate types (structs and unions).
 *
 * @details This helper function reduces code duplication by handling the common tasks of:
 * 1. Validating that member types are not null.
 * 2. Allocating the main `infix_type` object from the arena.
 * 3. Allocating a new array for the members within the arena and copying the
 *    user-provided member data into it.
 *
 * @param[in] arena The arena to allocate from.
 * @param[out] out_type The output pointer for the new `infix_type`.
 * @param[out] out_arena_members The output pointer for the newly copied members array.

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

    type->category = INFIX_TYPE_STRUCT;
    type->meta.aggregate_info.members = arena_members;
    type->meta.aggregate_info.num_members = num_members;
    type->meta.aggregate_info.is_packed = false;

    // This performs a preliminary layout calculation.
    // Note: This layout may be incomplete if it contains unresolved named references or flexible arrays.
    // The final, correct layout will be computed by `_infix_type_recalculate_layout`.
    // However, we must set a preliminary size/alignment here.

    // We use the recalculate logic to do the heavy lifting, assuming a temporary arena can be made.
    // But we can't create an arena here easily if we are in a strict context.
    // So we do a simplified pass just like the old logic, ignoring complex bitfield rules for now.
    // The proper bitfield layout happens in `_infix_type_recalculate_layout`.

    for (size_t i = 0; i < num_members; ++i) {
        infix_struct_member * member = &arena_members[i];
        if (member->type->alignment == 0 && member->type->category != INFIX_TYPE_NAMED_REFERENCE &&
            !(member->type->category == INFIX_TYPE_ARRAY && member->type->meta.array_info.is_flexible)) {
            if (member->type->category != INFIX_TYPE_ARRAY) {
                *out_type = nullptr;
                _infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INVALID_MEMBER_TYPE, 0);
                return INFIX_ERROR_INVALID_ARGUMENT;

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

 * is defined and set to a non-zero value.
 *
 * In release builds, this entire translation unit is effectively empty, ensuring
 * that debugging code has no impact on the final binary's size or performance.
 * The corresponding function declarations in `utility.h` become empty inline
 * stubs, which are optimized away by the compiler.
 */
// This file is only compiled if debugging is enabled.
#if defined(INFIX_DEBUG_ENABLED) && INFIX_DEBUG_ENABLED
// Use the double-tap test harness's `note` macro for debug printing if available.
// This integrates the debug output seamlessly into the TAP test logs.
#if defined(DBLTAP_ENABLE) && defined(DBLTAP_IMPLEMENTATION)
#include "common/double_tap.h"
#else
// If not building as part of a test, fall back to a standard printf implementation.
#include <stdio.h>
#ifndef note
#define note(...)                 \
    do {                          \
        printf("# " __VA_ARGS__); \
        printf("\n");             \

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

#include "core/type_registry.c"
// 5. Signature Parser: Implements the high-level string-based API.
//    (Depends on types, arena, and registry).
#include "core/signature.c"
// 6. Dynamic Library Loader: Implements cross-platform `dlopen`/`dlsym`.
//    (Depends on error handling, types, and arena).
#include "core/loader.c"
// 7. Type System: Defines and manages `infix_type` objects and graph algorithms.
//    (Depends on the arena and error handling).
#include "core/types.c"
// 8. Debugging Utilities: Low-level helpers for logging and inspection.
//    (No dependencies).
#include "core/utility.c"
// 9. Platform and processor feature detection.
//    (No dependencies).
#include "core/platform.c"
// 10. Trampoline Cache: Deduplication logic.
#include "core/cache.c"
// 11. Trampoline Engine: The central JIT compiler.
//    This must be last, as it depends on all other components and includes the
//    final ABI- and architecture-specific C files itself.
#include "jit/trampoline.c"

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

#pragma pack(push, 1)
typedef struct _UNWIND_CODE {
    uint8_t CodeOffset;
    uint8_t UnwindOp : 4;
    uint8_t OpInfo : 4;
} UNWIND_CODE;

typedef struct _UNWIND_INFO {
    uint8_t Version : 3;
    uint8_t Flags : 5;
    uint8_t SizeOfPrologue;
    uint8_t CountOfCodes;
    uint8_t FrameRegister : 4;
    uint8_t FrameOffset : 4;
    UNWIND_CODE UnwindCode[1];  // Variable length array
} UNWIND_INFO;

// We reserve 512 bytes at the end of every JIT block for SEH metadata.
#define INFIX_SEH_METADATA_SIZE 256
#elif defined(INFIX_OS_WINDOWS) && defined(INFIX_ARCH_AARCH64)
#pragma pack(push, 1)
typedef struct _UNWIND_INFO_ARM64 {
    uint32_t FunctionLength : 18;
    uint32_t Version : 2;
    uint32_t X : 1;
    uint32_t E : 1;
    uint32_t EpilogueCount : 5;
    uint32_t CodeWords : 5;
} UNWIND_INFO_ARM64;
#pragma pack(pop)
#define INFIX_SEH_METADATA_SIZE 256
#else
#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;
typedef struct __SecTask * SecTaskRef;
typedef struct __CFError * CFErrorRef;
#define kCFStringEncodingUTF8 0x08000100
// A struct to hold dynamically loaded function pointers from macOS frameworks.

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

}

#if defined(INFIX_OS_WINDOWS)
/**
 * @internal
 * @brief The personality routine for safe trampolines on Windows.
 *
 * @details This function is called by the Windows unwinder when an exception
 * occurs within a safe trampoline or its callees. It catches the exception,
 * sets the `INFIX_CODE_NATIVE_EXCEPTION` error, and redirects execution to
 * the trampoline's epilogue by modifying the instruction pointer in the
 * current context record and continuing execution.
 */
static EXCEPTION_DISPOSITION _infix_seh_personality_routine(PEXCEPTION_RECORD ExceptionRecord,
                                                            void * EstablisherFrame,
                                                            c23_maybe_unused PCONTEXT ContextRecord,
                                                            void * DispatcherContext) {
    PDISPATCHER_CONTEXT dc = (PDISPATCHER_CONTEXT)DispatcherContext;

    // If we are already unwinding, don't do anything.
    if (ExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))
        return ExceptionContinueSearch;

    // Set the thread-local error.
    _infix_set_error(INFIX_CATEGORY_ABI, INFIX_CODE_NATIVE_EXCEPTION, 0);

    // Retrieve the target epilogue IP from our HandlerData.
    // The HandlerData points to the 4-byte epilogue offset we stored in UNWIND_INFO.
    uint32_t epilogue_offset = *(uint32_t *)dc->HandlerData;
    void * target_ip = (void *)(dc->ImageBase + epilogue_offset);

    // Perform a non-local unwind to the epilogue.
    RtlUnwind(EstablisherFrame, target_ip, ExceptionRecord, nullptr);

    return ExceptionContinueSearch;  // Unreachable
}

#if defined(INFIX_ARCH_X64)
// Internal: Populates and registers SEH metadata for a Windows x64 JIT block.
static void _infix_register_seh_windows_x64(infix_executable_t * exec,
                                            infix_executable_category_t category,
                                            uint32_t prologue_size,
                                            uint32_t epilogue_offset) {
    // metadata_ptr starts after the machine code.
    uint8_t * metadata_base = (uint8_t *)exec->rw_ptr + exec->size;

    // RUNTIME_FUNCTION (PDATA) - Must be 4-byte aligned.
    RUNTIME_FUNCTION * rf = (RUNTIME_FUNCTION *)_infix_align_up((size_t)metadata_base, 4);

    // UNWIND_INFO (XDATA) - Follows PDATA.
    UNWIND_INFO * ui = (UNWIND_INFO *)_infix_align_up((size_t)(rf + 1), 2);

    ui->Version = 1;
    ui->Flags = 0;
    if (category == INFIX_EXECUTABLE_SAFE_FORWARD)
        ui->Flags |= UNW_FLAG_EHANDLER;
    ui->FrameRegister = 5;  // RBP
    ui->FrameOffset = 0;
    ui->SizeOfPrologue = (uint8_t)prologue_size;

    if (category == INFIX_EXECUTABLE_REVERSE) {
        // Reverse Trampoline: push rbp, push rsi, push rdi, mov rbp, rsp, and rsp -mask, [sub rsp, alloc]
        ui->CountOfCodes = 4;
        ui->UnwindCode[0].CodeOffset = 6;  // After mov rbp, rsp
        ui->UnwindCode[0].UnwindOp = UWOP_SET_FPREG;
        ui->UnwindCode[0].OpInfo = 0;

        ui->UnwindCode[1].CodeOffset = 3;  // After push rdi
        ui->UnwindCode[1].UnwindOp = UWOP_PUSH_NONVOL;

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

    DWORD rva_offset = (DWORD)((uint8_t *)exec->rx_ptr - (uint8_t *)base_address);

    rf->BeginAddress = rva_offset;  // Relative to BaseAddress
    // EndAddress covers the entire code block.
    rf->EndAddress = rva_offset + (DWORD)exec->size;
    rf->UnwindData = rva_offset + (DWORD)((uint8_t *)ui - (uint8_t *)exec->rx_ptr);

    if (ui->Flags & UNW_FLAG_EHANDLER) {
        // ExceptionHandler RVA points to our absolute jump stub.
        eh_field_ptr[0] = rva_offset + (uint32_t)(stub - (uint8_t *)exec->rx_ptr);
        // HandlerData field stores our target epilogue offset.
        eh_field_ptr[1] = epilogue_offset;
    }

    if (RtlAddFunctionTable(rf, 1, base_address)) {
        exec->seh_registration = rf;
        INFIX_DEBUG_PRINTF(
            "Registered SEH PDATA at %p (XDATA at %p, Stub at %p) for JIT code at %p", rf, ui, stub, exec->rx_ptr);
    }
    else {
        fprintf(stderr, "infix: RtlAddFunctionTable failed! GetLastError=%lu\n", GetLastError());
    }
}
#elif defined(INFIX_ARCH_AARCH64)
// Internal: Populates and registers SEH metadata for a Windows ARM64 JIT block.
static void _infix_register_seh_windows_arm64(infix_executable_t * exec,
                                              infix_executable_category_t category,
                                              uint32_t prologue_size,
                                              uint32_t epilogue_offset) {
    uint8_t * metadata_base = (uint8_t *)exec->rw_ptr + exec->size;

    // RUNTIME_FUNCTION (PDATA) - Must be 4-byte aligned.
    // On ARM64, we use two entries: one for the function and a sentinel for the end.
    RUNTIME_FUNCTION * rf = (RUNTIME_FUNCTION *)_infix_align_up((size_t)metadata_base, 4);

    // UNWIND_INFO (XDATA) - Follows PDATA.
    UNWIND_INFO_ARM64 * ui = (UNWIND_INFO_ARM64 *)_infix_align_up((size_t)(rf + 2), 4);
    infix_memset(ui, 0, sizeof(UNWIND_INFO_ARM64));

    ui->FunctionLength = (uint32_t)(exec->size / 4);
    ui->Version = 0;
    ui->X = (category == INFIX_EXECUTABLE_SAFE_FORWARD);
    ui->E = 0;
    ui->EpilogueCount = 1;

    uint8_t * unwind_codes = (uint8_t *)(ui + 1);
    uint32_t code_idx = 0;

    if (category == INFIX_EXECUTABLE_REVERSE) {
        // Reverse Prologue: stp x29, x30, [sp, #-16]!; mov x29, sp; sub sp, sp, #alloc
        // Opcodes in REVERSE order:
        unwind_codes[code_idx++] = 0xE1;  // mov x29, sp
        unwind_codes[code_idx++] = 0xC8;  // stp x29, x30, [sp, #-16]!
        unwind_codes[code_idx++] = 0xE4;  // end
    }
    else {
        // Forward or Direct Prologue: stp x29, x30, [sp, #-16]!; stp x19, x20, ...; stp x21, x22, ...; mov x29, sp; sub
        // sp, sp, #alloc
        unwind_codes[code_idx++] = 0xE1;  // mov x29, sp
        unwind_codes[code_idx++] = 0xD4;  // stp x21, x22, [sp, #-16]!
        unwind_codes[code_idx++] = 0xD2;  // stp x19, x20, [sp, #-16]!
        unwind_codes[code_idx++] = 0xC8;  // stp x29, x30, [sp, #-16]!
        unwind_codes[code_idx++] = 0xE4;  // end
    }

    ui->CodeWords = (code_idx + 3) / 4;

    // On ARM64, if X=1, the Exception Handler RVA and Handler Data follow the epilogue scopes
    // and unwind codes.
    // XDATA layout: [Header] [Epilogue Scopes] [Unwind Codes] [Padding] [Handler RVA] [Handler Data]

    uint32_t * epilogue_scopes = (uint32_t *)(ui + 1);
    // Each epilogue scope is 4 bytes. We have ui->EpilogueCount of them.
    epilogue_scopes[0] = (epilogue_offset / 4);  // Epilogue Start Index (instructions)

    uint8_t * unwind_codes_ptr = (uint8_t *)(epilogue_scopes + ui->EpilogueCount);
    // Clear and then copy the codes
    infix_memset(unwind_codes_ptr, 0, ui->CodeWords * 4);
    infix_memcpy(unwind_codes_ptr, unwind_codes, code_idx);

    // Handler info must follow unwind codes (which are already padded to 4 bytes by ui->CodeWords).
    uint32_t * handler_info_ptr = (uint32_t *)(unwind_codes_ptr + ui->CodeWords * 4);

    uint8_t * stub = (uint8_t *)_infix_align_up((size_t)(handler_info_ptr + 2), 16);

    // stub:

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

    rf[0].UnwindData = rva_offset + (DWORD)((uint8_t *)ui - (uint8_t *)exec->rx_ptr);

    // Sentinel entry defines the end of the previous function
    rf[1].BeginAddress = rva_offset + (DWORD)exec->size;
    rf[1].UnwindData = 0;

    if (ui->X) {
        // According to the spec, the Exception Handler RVA and Handler Data
        // are located at the end of the XDATA, which is 4-byte aligned.
        handler_info_ptr[0] = rva_offset + (uint32_t)(stub - (uint8_t *)exec->rx_ptr);
        handler_info_ptr[1] = epilogue_offset;
    }

    if (RtlAddFunctionTable(rf, 2, base_address)) {
        exec->seh_registration = rf;
        INFIX_DEBUG_PRINTF(
            "Registered SEH PDATA at %p (XDATA at %p, Stub at %p) for JIT code at %p", rf, ui, stub, exec->rx_ptr);
    }
    else {
        fprintf(stderr, "infix: RtlAddFunctionTable failed! GetLastError=%lu\n", GetLastError());
    }

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

    p += 4;  // length
    *(uint32_t *)p = (uint32_t)(p - eh);
    p += 4;  // cie_pointer (back-offset)

    *(void **)p = exec->rx_ptr;
    p += 8;  // pc_begin (absolute)
    *(uint64_t *)p = (uint64_t)exec->size;
    p += 8;    // pc_range (absolute)
    *p++ = 0;  // aug data len

    // Instructions: match our trampoline prologue
    if (category == INFIX_EXECUTABLE_REVERSE) {
        // stp x29, x30, [sp, #-16]!; mov x29, sp
        *p++ = 0x41;  // loc +1 (4 bytes, after stp)
        *p++ = 0x0e;
        *p++ = 16;  // def_cfa_offset 16
        *p++ = 0x9d;
        *p++ = 2;  // offset r29 (x29), 2 (CFA - 16)
        *p++ = 0x9e;
        *p++ = 1;     // offset r30 (x30/lr), 1 (CFA - 8)
        *p++ = 0x41;  // loc +1 (4 bytes, after mov)

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

 * - On single-mapping platforms, it changes the memory protection from RW to RX.
 * - On dual-mapping platforms, this is a no-op as the RX mapping already exists.
 *
 * Crucially, it also handles flushing the CPU's instruction cache on architectures
 * that require it (like AArch64). This is necessary because the CPU may have cached
 * old (zeroed) data from the memory location, and it must be explicitly told to
 * re-read the newly written machine code instructions.
 *
 * @param exec The executable memory block.
 * @param category The category of the trampoline.
 * @param prologue_size The size of the prologue.
 * @return `true` on success, `false` on failure.
 */
c23_nodiscard bool infix_executable_make_executable(infix_executable_t * exec,
                                                    c23_maybe_unused infix_executable_category_t category,
                                                    c23_maybe_unused uint32_t prologue_size,
                                                    c23_maybe_unused uint32_t epilogue_offset) {
    if (exec->rw_ptr == nullptr || exec->size == 0)
        return false;

    // On AArch64 (and other RISC architectures), the instruction and data caches can be
    // separate. We must explicitly flush the D-cache (where the JIT wrote the code)
    // and invalidate the I-cache so the CPU fetches the new instructions.
    // We might as well do it on x64 too.
#if defined(INFIX_COMPILER_MSVC)
    // Use the Windows-specific API.
    FlushInstructionCache(GetCurrentProcess(), exec->rw_ptr, exec->size);

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

    else
        __builtin___clear_cache((char *)exec->rw_ptr, (char *)exec->rw_ptr + exec->size);
#elif defined(INFIX_ARCH_AARCH64)
    // Robust manual cache clearing for AArch64 Linux/BSD.
    // We clean the D-cache to point of unification and invalidate the I-cache.
    uintptr_t start = (uintptr_t)exec->rw_ptr;
    uintptr_t end = start + exec->size;
    uintptr_t ctr_el0;
    __asm__ __volatile__("mrs %0, ctr_el0" : "=r"(ctr_el0));

    // D-cache line size is in bits [19:16] as log2 of number of words.
    uintptr_t d_line_size = 4 << ((ctr_el0 >> 16) & 0xf);
    for (uintptr_t addr = start & ~(d_line_size - 1); addr < end; addr += d_line_size)
        __asm__ __volatile__("dc cvau, %0" ::"r"(addr) : "memory");
    __asm__ __volatile__("dsb ish" ::: "memory");

    // I-cache line size is in bits [3:0] as log2 of number of words.
    uintptr_t i_line_size = 4 << (ctr_el0 & 0xf);
    for (uintptr_t addr = start & ~(i_line_size - 1); addr < end; addr += i_line_size)
        __asm__ __volatile__("ic ivau, %0" ::"r"(addr) : "memory");
    __asm__ __volatile__("dsb ish\n\tisb" ::: "memory");
#else
    // Use the GCC/Clang built-in for other platforms.
    __builtin___clear_cache((char *)exec->rw_ptr, (char *)exec->rw_ptr + exec->size);
#endif

    bool result = false;
#if defined(INFIX_OS_WINDOWS)
    // On Windows, we register SEH unwind info before making the memory executable.
#if defined(INFIX_ARCH_X64)
    _infix_register_seh_windows_x64(exec, category, prologue_size, epilogue_offset);
#elif defined(INFIX_ARCH_AARCH64)
    _infix_register_seh_windows_arm64(exec, category, prologue_size, epilogue_offset);
#endif
    // Finalize permissions to Read+Execute.
    // We include the SEH metadata in the protected region.
    result = VirtualProtect(exec->rw_ptr, exec->size + INFIX_SEH_METADATA_SIZE, PAGE_EXECUTE_READ, &(DWORD){0});
    if (!result)
        _infix_set_system_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_PROTECTION_FAILURE, GetLastError(), nullptr);
#elif defined(INFIX_OS_MACOS)
    static bool g_use_secure_jit_path = false;
    static bool g_checked_jit_support = false;
    if (!g_checked_jit_support) {

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

 * Creative Commons Attribution 4.0 International License (CC BY 4.0).
 *
 * SPDX-License-Identifier: CC-BY-4.0
 */
/**
 * @file trampoline.c
 * @brief The core JIT engine for generating forward and reverse trampolines.
 * @ingroup internal_jit
 *
 * @details This module is the central orchestrator of the `infix` library. It brings
 * together the type system, memory management, and ABI-specific logic to generate
 * executable machine code at runtime.
 *
 * It implements both the high-level Signature API (e.g., `infix_forward_create`)
 * and the low-level Manual API (e.g., `infix_forward_create_manual`). The high-level
 * functions are convenient wrappers that use the signature parser to create the
 * necessary `infix_type` objects before calling the core internal implementation.
 *
 * The core logic is encapsulated in `_infix_forward_create_internal` and
 * `_infix_reverse_create_internal`. These functions follow a clear pipeline:
 * 1.  **Prepare:** Analyze the function signature with the appropriate ABI-specific
 *     `prepare_*_call_frame` function to create a layout blueprint.
 * 2.  **Generate:** Use the layout blueprint to call a sequence of ABI-specific
 *     `generate_*` functions, which emit machine code into a temporary `code_buffer`.
 * 3.  **Finalize:** Allocate executable memory, copy the generated code into it,
 *     create the final self-contained trampoline handle (deep-copying all type
 *     metadata), and make the code executable.
 */
#include "common/infix_internals.h"

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


    code_buffer buf;
    code_buffer_init(&buf, temp_arena);
    // JIT Compilation Pipeline
    // Prepare: Classify arguments and create the layout blueprint.
    status = spec->prepare_forward_call_frame(
        temp_arena, &layout, return_type, arg_types, num_args, num_fixed_args, target_fn);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    // 2. Generate: Emit machine code based on the layout.
    status = spec->generate_forward_prologue(&buf, layout);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_forward_argument_moves(&buf, layout, arg_types, num_args, num_fixed_args);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_forward_call_instruction(&buf, layout);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_forward_epilogue(&buf, layout, return_type);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    if (buf.error || temp_arena->error) {
        status = INFIX_ERROR_ALLOCATION_FAILED;
        goto cleanup;
    }
    // Finalize Handle
    handle = infix_calloc(1, sizeof(infix_forward_t));
    if (handle == nullptr) {
        status = INFIX_ERROR_ALLOCATION_FAILED;

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


    // Allocate and finalize executable memory.
    handle->exec = infix_executable_alloc(buf.size);
    if (handle->exec.rw_ptr == nullptr) {
        status = INFIX_ERROR_ALLOCATION_FAILED;
        goto cleanup;
    }
    infix_memcpy(handle->exec.rw_ptr, buf.code, buf.size);
    if (!infix_executable_make_executable(&handle->exec,
                                          is_safe ? INFIX_EXECUTABLE_SAFE_FORWARD : INFIX_EXECUTABLE_FORWARD,
                                          layout->prologue_size,
                                          layout->epilogue_offset)) {
        status = INFIX_ERROR_PROTECTION_FAILED;
        goto cleanup;
    }
    infix_dump_hex(handle->exec.rx_ptr, handle->exec.size, "Forward Trampoline Machine Code");
    *out_trampoline = handle;

    // Cache the newly created trampoline.
    // We ONLY cache if we own the arena. If the user provided an external arena,
    // they control the lifetime, and we can't guarantee the signature string
    // (which is in that arena) will remain valid as long as the cache entry exists.

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

    }
    code_buffer buf;
    code_buffer_init(&buf, temp_arena);

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

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

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

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

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

    if (buf.error || temp_arena->error) {
        status = INFIX_ERROR_ALLOCATION_FAILED;
        goto cleanup;
    }

    // 3. Finalize Handle
    handle = infix_calloc(1, sizeof(infix_forward_t));

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

    handle->ref_count = 1;

    // 4. Allocate and Finalize Executable Memory
    handle->exec = infix_executable_alloc(buf.size);
    if (handle->exec.rw_ptr == nullptr) {
        status = INFIX_ERROR_ALLOCATION_FAILED;
        goto cleanup;
    }
    infix_memcpy(handle->exec.rw_ptr, buf.code, buf.size);
    if (!infix_executable_make_executable(
            &handle->exec, INFIX_EXECUTABLE_DIRECT, layout->prologue_size, layout->epilogue_offset)) {
        status = INFIX_ERROR_PROTECTION_FAILED;
        goto cleanup;
    }

    infix_dump_hex(handle->exec.rx_ptr, handle->exec.size, "Direct-Marshalling Forward Trampoline Machine Code");
    *out_trampoline = handle;

cleanup:
    if (status != INFIX_SUCCESS && handle != nullptr)
        infix_forward_destroy(handle);

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

                                             context->num_args,
                                             context->num_fixed_args,
                                             user_callback_fn);
        if (status != INFIX_SUCCESS)
            goto cleanup;
    }
    // JIT Compilation Pipeline for Reverse Stub
    status = spec->prepare_reverse_call_frame(temp_arena, &layout, context);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_reverse_prologue(&buf, layout);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_reverse_argument_marshalling(&buf, layout, context);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_reverse_dispatcher_call(&buf, layout, context);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    status = spec->generate_reverse_epilogue(&buf, layout, context);
    if (status != INFIX_SUCCESS)
        goto cleanup;
    // End of Pipeline
    if (buf.error || temp_arena->error) {
        status = INFIX_ERROR_ALLOCATION_FAILED;
        goto cleanup;
    }
    context->exec = infix_executable_alloc(buf.size);
    if (context->exec.rw_ptr == nullptr) {
        status = INFIX_ERROR_ALLOCATION_FAILED;
        goto cleanup;
    }
    infix_memcpy(context->exec.rw_ptr, buf.code, buf.size);
    if (!infix_executable_make_executable(&context->exec, INFIX_EXECUTABLE_REVERSE, layout->prologue_size, 0)) {
        status = INFIX_ERROR_PROTECTION_FAILED;
        goto cleanup;
    }
    // Security Hardening: Make the context memory read-only to prevent runtime corruption.
    if (!infix_protected_make_readonly(context->protected_ctx)) {
        status = INFIX_ERROR_PROTECTION_FAILED;
        goto cleanup;
    }
    infix_dump_hex(context->exec.rx_ptr, buf.size, "Reverse Trampoline Machine Code");
    *out_context = context;

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

    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"
#include "../arch/x64/abi_x64_emitters.c"
#elif defined(INFIX_ABI_AAPCS64)
#include "../arch/aarch64/abi_arm64.c"

lib/Affix.c  view on Meta::CPAN

    break;             \
    }

// No table needed
#define DEFINE_DISPATCH_TABLE()
#endif

// Forward declaration for the lazy rebuilder
static void rebuild_affix_data(pTHX_ Affix * affix);

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

lib/Affix.c  view on Meta::CPAN

    Newxz(affix->plan, affix->plan_length + 1, Affix_Plan_Step);

    size_t out_param_count = 0;
    OutParamInfo * temp_out_info = safemalloc(sizeof(OutParamInfo) * (affix->num_args > 0 ? affix->num_args : 1));
    size_t current_offset = 0;

    for (size_t i = 0; i < affix->num_args; ++i) {
        // Deep copy types from parse_arena to persistent args_arena
        const infix_type * original_type = _copy_type_graph_to_arena(affix->args_arena, args[i].type);

        // Recalculate offsets (logic duplication from Affix_affix, but necessary)
        size_t alignment, size;
        if (original_type->category == INFIX_TYPE_ARRAY) {
            alignment = _Alignof(void *);
            size = sizeof(void *);
        }
        else {
            alignment = infix_type_get_alignment(original_type);
            size = infix_type_get_size(original_type);
        }
        if (alignment == 0)

lib/Affix.c  view on Meta::CPAN

    const char * full_sig = SvPV_nolen(sig_sv);

    // Check Cache
    infix_forward_t * trampoline = NULL;
    SV ** cache_entry = hv_fetch(affix->variadic_cache, full_sig, strlen(full_sig), 0);

    if (cache_entry)
        trampoline = INT2PTR(infix_forward_t *, SvIV(*cache_entry));
    else {
        // Cache Miss: Compile new trampoline
        // We use the parsing logic to get types
        infix_arena_t * temp_arena = NULL;
        infix_type * ret_type = NULL;
        infix_function_argument * args = NULL;
        size_t num_args = 0, num_fixed = 0;

        infix_status status =
            infix_signature_parse(full_sig, &temp_arena, &ret_type, &args, &num_args, &num_fixed, MY_CXT.registry);

        if (status != INFIX_SUCCESS) {
            if (temp_arena)

lib/Affix.c  view on Meta::CPAN

    }

    void * ptr = safemalloc(size);
    Affix_Pin * pin;
    Newxz(pin, 1, Affix_Pin);
    pin->size = size;
    pin->pointer = ptr;
    pin->managed = true;
    pin->type_arena = infix_arena_create(1024);

    // We unwrap the pointer type logic here similar to cast.
    // If the user passed "Int", we want the pin to be typed as "Int" (so $$pin reads an int).
    // _unwrap_pin_type handles the logic of "don't unwrap *void or *char".
    pin->type = _copy_type_graph_to_arena(pin->type_arena, _unwrap_pin_type(type));

    infix_arena_destroy(parse_arena);
    ST(0) = sv_2mortal(_new_pointer_obj(aTHX_ pin));
    XSRETURN(1);
}

XS_INTERNAL(Affix_calloc) {
    dXSARGS;
    dMY_CXT;

lib/Affix.c  view on Meta::CPAN

    new_pin->managed = false;  // Aliases are never managed
    new_pin->type_arena = infix_arena_create(256);

    if (pin) {
        new_pin->owner_sv = pin->owner_sv ? pin->owner_sv : ST(0);
        SvREFCNT_inc(new_pin->owner_sv);
    }

    if (type) {
        if (type->category == INFIX_TYPE_ARRAY) {
            // Decay Array[T] -> Pointer[T] logic.
            // When adding an offset to an array, the result is a pointer to the element type,
            // not a new array. This allows dereferencing assignment ($$ptr = val) to work correctly.
            const infix_type * elem_src = type->meta.array_info.element_type;
            const infix_type * elem_copy = _copy_type_graph_to_arena(new_pin->type_arena, elem_src);

            // Cast to (infix_type*) to satisfy signature; safe because we just allocated it in our arena.
            if (infix_type_create_pointer_to(
                    new_pin->type_arena, (infix_type **)&new_pin->type, (infix_type *)elem_copy) != INFIX_SUCCESS) {
                infix_arena_destroy(new_pin->type_arena);
                safefree(new_pin);

lib/Affix.c  view on Meta::CPAN

        (void)newXSproto_portable("Affix::_attach_destructor", Affix_attach_destructor, __FILE__, "$$;$");
    }

    XSUB_EXPORT(coerce, "$$", "core");

    XSUB_EXPORT(errno, "", "core");
    (void)newXSproto_portable("Affix::set_destruct_level", Affix_set_destruct_level, __FILE__, "$");

#undef XSUB_EXPORT

    Perl_xs_boot_epilog(aTHX_ ax);
}

lib/Affix.h  view on Meta::CPAN

                                    SV ** perl_stack_frame,
                                    void * args_buffer,
                                    void ** c_args,
                                    void * ret_buffer);
/// Function pointer type for a "pull" operation: marshalling from C (void*) to Perl (SV).
typedef void (*Affix_Pull)(pTHX_ Affix * affix, SV *, const infix_type *, void *);
/// Function pointer type for a "push" operation: marshalling from Perl (SV) to C (void*).
typedef void (*Affix_Push_Handler)(pTHX_ Affix * affix, SV *, void *);
/**
 * Function pointer for a specialized out-parameter write-back handler.
 * By pre-resolving this function, we avoid conditional logic in the hot path.
 * @param pTHX_ The Perl interpreter context.
 * @param affix The main Affix context object.
 * @param info A pointer to the OutParamInfo struct for this parameter.
 * @param perl_sv The referenced SV* to be modified (e.g., the scalar backing `$$foo`).
 * @param c_arg_ptr The pointer from the c_args array (e.g., `T**` for a `T*` out-param).
 */
typedef void (*Affix_Out_Param_Writer)(pTHX_ Affix * affix, const OutParamInfo * info, SV * perl_sv, void * c_arg_ptr);
/// Stores the pre-calculated information needed to write back an "out" parameter.
struct OutParamInfo {
    size_t perl_stack_index;          // Index of the SV* in the perl_stack_frame



( run in 1.462 second using v1.01-cache-2.11-cpan-5735350b133 )