Affix

 view release on metacpan or  search on metacpan

Changes.md  view on Meta::CPAN

- SIMD Vector Improvements:
 - Added M512, M512d, and M512i type helpers.
 - Ensured compatibility with infix's refined vector alignment and passing rules.
- [infix] Added support for half-precision floating-point (`float16`).
- [infix] Implemented C++ exception propagation through JIT frames on Linux (x86-64 and ARM64) using manual DWARF `.eh_frame` generation and `__register_frame`.
- [infix] Implemented Structured Exception Handling (SEH) for Windows x64 and ARM64 for C++ exception propagation through trampolines.
- [infix] Added `infix_forward_create_safe` API to establish an exception boundary that catches native exceptions and returns a dedicated error code (`INFIX_CODE_NATIVE_EXCEPTION`).
- [infix] Added support for 256-bit (AVX) and 512-bit (AVX-512) vectors in the System V ABI.
- [infix] Added support for receiving bitfield structs in reverse call trampolines.
- [infix] Added trampoline caching. Identical signatures and targets now share the same JIT-compiled code and metadata via internal reference counting, significantly reducing memory overhead and initialization time.
- [infix] Added a new opt-in build mode (`--sanity`) that emits extra JIT instructions to verify stack pointer consistency around user-provided marshaller calls, making it easier to debug corrupting language bindings.

### Changed

- Pull infix v0.1.6.
- [infix] Explicitly enabled 16-byte stack alignment in Windows x64 trampolines to ensure SIMD compatibility.
- [infix] Updated `infix_type_create_vector` to use the vector's full size for its natural alignment (e.g., 32-byte alignment for `__m256`).
- [infix] Refined the Windows x64 ABI to pass all vector types by reference (pointer in GPR). This ensures compatibility with MSVC which expects even 128-bit vectors to be passed via pointer in many scenarios, while still returning them by value in `...
- [infix] Move to a pre-calculated hash field in `_infix_registry_entry_t`. Lookups and rehashing now use this stored hash, significantly reducing string hashing overhead during type resolution and registry scaling.
- [infix] Optimized Type Registry memory management: Internal hash table buckets are now heap-allocated and freed during rehashes, preventing memory "leaks" within the registry's arena.

README.md  view on Meta::CPAN

```

**Note:** You must call `errno()` immediately after the C function invokes, as subsequent Perl operations (like
printing to STDOUT) might overwrite the system's error register.

## Memory Inspection

### `dump( $pin, $length_in_bytes )`

Prints a formatted hex dump of the memory pointed to by a Pin directly to `STDOUT`. This is an invaluable tool for
verifying that C structs or buffers contain the data you expect.

```perl
my $ptr = strdup("Affix Debugging");
dump($ptr, 16);

# Output:
# Dumping 16 bytes from 0x55E9A8A5 at script.pl line 42
#  000  41 66 66 69 78 20 44 65 62 75 67 67 69 6e 67 00 | Affix Debugging.
```

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

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif
/**
 * @brief Retrieves the version of the infix library linked at runtime.
 *
 * @details This function allows applications to verify that the version of the
 *          library they are linked against matches the headers they were compiled with.
 *          This is particularly useful when loading `infix` as a shared library/DLL
 *          to detect version mismatches.
 *
 * @return An `infix_version_t` structure containing the major, minor, and patch numbers.
 */
INFIX_API INFIX_NODISCARD infix_version_t infix_get_version(void);

/**
 * @defgroup high_level_api High-Level Signature API

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

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

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

    A64_COND_LE = 0xD,  ///< Less Than or Equal (Signed)
    A64_COND_AL = 0xE,  ///< Always
} arm64_cond;
/**
 * @internal
 * @defgroup aarch64_opcodes AArch64 Instruction Opcodes and Bitfields
 * @brief Defines for the bit-level encoding of AArch64 instructions.
 * @details These constants represent the fixed bit patterns for various instruction
 *          classes as specified in the ARM Architecture Reference Manual. Using these
 *          defines instead of raw hex literals makes the emitter code more readable
 *          and easier to verify. The `U` suffix is critical to prevent signed
 *          integer overflow during bit-shifting operations at compile time.
 * @{
 */
// Common bitfields
#define A64_SF_64BIT (1U << 31)  // 'sf' (size field) bit for 64-bit operations
#define A64_SF_32BIT (0U << 31)  // 'sf' bit for 32-bit operations
#define A64_V_VECTOR (1U << 26)  // Vector bit for SIMD/FP instructions
// Data Processing -- Immediate (e.g., ADD, SUB)
#define A64_OPC_ADD (0b00U << 29)
#define A64_OPC_ADDS (0b01U << 29)

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

 * from a source (like the parser's temporary arena) into its own private arena.
 * The size of the source arena is used as a hint for the new arena's size, but the
 * copy process itself requires a small amount of extra memory for its own bookkeeping
 * (e.g., the memoization list in `_copy_type_graph_to_arena_recursive`). This
 * headroom provides that extra space to prevent allocation failures during the copy.
 */
#define INFIX_TRAMPOLINE_HEADROOM 128

/**
 * @def INFIX_SANITY_CHECK_ENABLE
 * @brief If defined and non-zero, the JIT will emit extra instructions to verify
 *        stack consistency around user-provided marshaller calls.
 */
#ifndef INFIX_SANITY_CHECK_ENABLE
#define INFIX_SANITY_CHECK_ENABLE 0
#endif

/**
 * @def INFIX_INTERNAL
 * @brief When compiling with -fvisibility=hidden, we use this to explicitly mark internal-but-shared functions as
 * hidden.

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

 * @details This module is responsible for two key functionalities that form the
 * user-facing API of the library:
 *
 * 1.  **Parsing:** It contains a hand-written recursive descent parser that transforms a
 *     human-readable signature string (e.g., `"({int, *char}) -> void"`) into an
 *     unresolved `infix_type` object graph. This is the **"Parse"** stage of the core
 *     data pipeline. The internal entry point for the "Parse" stage is `_infix_parse_type_internal`.
 *
 * 2.  **Printing:** It provides functions to serialize a fully resolved `infix_type`
 *     graph back into a canonical signature string. This is crucial for introspection,
 *     debugging, and verifying the library's understanding of a type.
 *
 * The public functions `infix_type_from_signature` and `infix_signature_parse`
 * are high-level orchestrators. They manage the entire **"Parse -> Copy -> Resolve -> Layout"**
 * pipeline, providing the user with a fully validated, self-contained, and ready-to-use
 * type object that is safe to use for the lifetime of its returned arena.
 */
#include "common/infix_internals.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>

lib/Affix.pod  view on Meta::CPAN

    }

B<Note:> You must call C<errno()> immediately after the C function invokes, as subsequent Perl operations (like
printing to STDOUT) might overwrite the system's error register.

=head2 Memory Inspection

=head3 C<dump( $pin, $length_in_bytes )>

Prints a formatted hex dump of the memory pointed to by a Pin directly to C<STDOUT>. This is an invaluable tool for
verifying that C structs or buffers contain the data you expect.

    my $ptr = strdup("Affix Debugging");
    dump($ptr, 16);

    # Output:
    # Dumping 16 bytes from 0x55E9A8A5 at script.pl line 42
    #  000  41 66 66 69 78 20 44 65 62 75 67 67 69 6e 67 00 | Affix Debugging.

=head3 C<sv_dump( $scalar )>

t/007_pointers.t  view on Meta::CPAN


# Cast returns a new pin. We must assign it or use the returned object.
# Also, we keep $mem alive to ensure the memory isn't freed if $int_ptr assumes
# $mem owns it (though cast usually creates unmanaged aliases, so we need $mem to stay alive).
my $int_ptr = Affix::cast( $mem, Pointer [Int] );

# Test magical 'set' via dereferencing
# $$int_ptr is a scalar magic that writes to the address
$$int_ptr = 42;

# Use the original $mem pointer for reading (verifying they point to the same place)
is( read_int_from_void_ptr($mem), 42, 'Magical set via deref wrote to C memory' );

# Test cast again
my $long_ptr = Affix::cast( $mem, Pointer [LongLong] );
$$long_ptr = 1234567890123;
is $$long_ptr, 1234567890123, 'Magical get after casting to a new type works';

# Test realloc
my $r_ptr = calloc( 2, Int );

t/009_128bit.t  view on Meta::CPAN

    DLLEXPORT int has_int128() { return 1; }

    DLLEXPORT int128 add_i128(int128 a, int128 b) {
        return a + b;
    }

    DLLEXPORT uint128 add_u128(uint128 a, uint128 b) {
        return a + b;
    }

    // Helper to verify value passed correctly (returns high 64 bits cast to 64)
    DLLEXPORT int64_t high_bits_i128(int128 v) {
        return (int64_t)(v >> 64);
    }
#else
    DLLEXPORT int has_int128() { return 0; }
#endif
END_C

# Compile the library
my $lib = compile_ok($c_source);

t/014_array.t  view on Meta::CPAN

ok affix( $lib_path, 'sum_float_array', '(*float, int)->float' ), 'affix sum_float_array';
my $floats = [ 1.1, 2.2, 3.3 ];
is( sum_float_array( $floats, 3 ), float( 6.6, tolerance => 0.01 ), 'Correctly summed an array of floats' );
#
isa_ok my $sum_arr = wrap( $lib_path, 'sum_array_static', [ Array [ Int, 5 ] ] => Int ), ['Affix'];
is $sum_arr->( [ 1, 2, 3, 4, 5 ] ), 15, 'Fixed size array passed by value';
#
subtest 'Fixed Array Binary Safety' => sub {
    my $ptr = calloc( 5, Int8 );                   # Allocate 5 bytes
    my $mem = cast( $ptr, Array [ Int8, 5 ] );     # Write: "A \0 B \0 C"
    $$mem = [ 65, 0, 66, 0, 67 ];                  # We can write using an array ref (slow path, verify write works)
    my $view = cast( $ptr, Array [ Char, 5 ] );    # Now read it back as a char array (fast path)
    my $data = $$view;
    is length($data), 5,         'Binary string has correct length (5)';
    is $data,         "A\0B\0C", 'Binary content preserved (nulls included)';
    free($ptr);
};
done_testing;

t/019_fileio.t  view on Meta::CPAN

    }
    return fp;
}

// Identity function to test round-tripping a PerlIO pointer.
// Since we don't link against libperl here, we treat PerlIO* as void*.
DLLEXPORT void* c_perlio_identity(void* p) {
    return p;
}

// Check if FILE* is NULL (to verify failure cases)
DLLEXPORT int c_is_null_file(FILE* fp) {
    return fp == NULL;
}
END_C
    subtest 'Standard C FILE* (Affix::File)' => sub {

        # File represents the FILE struct, so Pointer[File] is FILE*
        affix $lib, 'c_write_to_file',  [ Pointer [File], String ] => Int;
        affix $lib, 'c_read_char',      [ Pointer [File] ]         => Int;
        affix $lib, 'c_create_tmpfile', []                         => Pointer [File];

t/019_fileio.t  view on Meta::CPAN


        # Pass filehandle to C to store in struct
        init_logger( $logger, $fh );

        # Verify via C function
        log_message( $logger, 'First message' );
        log_message( $logger, 'Second message' );

        # Verify Perl side struct access
        # Note: Pulling a File handle usually creates a new GLOB wrapper around the FILE*
        # Since we own $fh, let's verify checking against undef works
        my $logger_struct = cast( $logger, Logger() );    # View as struct
        my $retrieved_fh  = $logger_struct->{log_file};
        ok $retrieved_fh, 'Retrieved filehandle from struct';
        is ref($retrieved_fh), 'GLOB', 'It is a glob';

        # Write from Perl using retrieved handle
        # print {$retrieved_fh} "From Perl\n"; # Careful, might double-close if not careful
        # Check file content
        open my $check, '<', $filename;
        my @lines = <$check>;

t/019_fileio.t  view on Meta::CPAN

        my $old_fh = select($fh);
        $| = 1;
        select($old_fh);

        # Call C function returning a struct by value
        my $logger_hash = create_logger($fh);
        is $logger_hash->{counter}, 100, 'Counter is correct';
        ok $logger_hash->{log_file}, 'Got filehandle back';
        is ref( $logger_hash->{log_file} ), 'GLOB', 'It is a glob';

        # Write using the returned handle to verify it works
        # Note: $logger_hash->{log_file} wraps the same FILE* as $fh.
        ok syswrite( $logger_hash->{log_file}, "Direct write from Perl\n" ), 'syswrite to the handle from Perl';

        # To avoid double-close warnings, we let Perl handle cleanup of the glob
        # but be careful about explicit closes.
        undef $logger_hash;

        # Check
        open my $check, '<', $filename;
        my $content = <$check>;

t/020_deep_types.t  view on Meta::CPAN

    ok $ptr, 'Got a pointer';

    # Dereference to read (Deep copy from C -> Perl)
    my $val = $$ptr;
    is $val, { top_left => { x => 0, y => 0 }, bottom_right => { x => 10, y => 20 } }, 'Dereferenced nested struct pointer correctly';

    # Modify via pointer using pinning syntax logic (write to C)
    # Since $ptr is magic, assigning to $$ptr should marshal data back to C memory
    $$ptr = { top_left => { x => 99, y => 99 }, bottom_right => { x => 100, y => 100 } };

    # Read again to verify round-trip
    my $val_new = $$ptr;
    is $val_new->{top_left}{x}, 99, 'Write-back to static C struct via magic successful';

    # Verify it persists (call the C accessor again)
    my $ptr2 = $get_static_rect->();
    is $$ptr2->{top_left}{x}, 99, 'Changes persisted in C memory';
};
#
done_testing;

t/023_sockaddr.t  view on Meta::CPAN

    #include "std.h"
    //ext: .c

    // Minimal definition to ensure struct layout matches system
    #if defined(_WIN32)
      #include <winsock2.h>
    #else
      #include <netinet/in.h>
    #endif

    // We implement a manual byte swap to verify the data arrived correctly
    // without needing to link against system network libraries (ws2_32.dll etc)
    // which simplifies the test build process.
    DLLEXPORT int get_port_raw(struct sockaddr_in* sa) {
        if (!sa) return -1;
        // Return raw network-byte-order value
        return sa->sin_port;
    }

    DLLEXPORT unsigned long get_addr(struct sockaddr_in* sa) {
        if (!sa) return 0;

t/023_sockaddr.t  view on Meta::CPAN


# Verify Port
# C returns raw network short (Big Endian).
# unpack('n') converts "Network to Native".
my $raw_port_from_c = get_port_raw($sa);
my $port_back       = unpack 'n', pack 'S', $raw_port_from_c;

# On Little Endian systems (x86), pack('S') puts the bytes in LE.
# But wait, C returned a UShort (number).
# If C read 0x1F90 (8080) from memory as a short on LE, it saw 0x901F (36895).
# Let's just verify round-trip logic via 'n' (Network order).
# Simpler check: Just pack the Perl port into network order and compare values
my $expected_raw_port = unpack 'S', pack( 'n', $port );
is $raw_port_from_c, $expected_raw_port, 'Port passed correctly (Network Byte Order preserved)';

# Verify IP
# IP is just a 32-bit int, raw
my $raw_addr      = get_addr($sa);
my $expected_addr = unpack 'L', inet_aton($ip);
is $raw_addr, $expected_addr, 'IP address passed correctly';
done_testing;



( run in 1.362 second using v1.01-cache-2.11-cpan-fe3c2283af0 )