Affix

 view release on metacpan or  search on metacpan

Changes.md  view on Meta::CPAN

Plugging leaks...

### Fixed

- Use `SAVEVPTR` and `SAVEDESTRUCTOR_X` to swap out arenas to fix leaky allocator in situations where tons of structs are passed in a list and need to be marshalled in only one direction
- Casting or binding an aggregate (`Affix::cast`, member pins) no longer leaks: member pins borrowed the freshly created parent hash/array as their lifeline, forming a strong reference cycle that Perl's refcounting cannot collect, so the whole pin tr...
- Passing a union to a wrapped call no longer segfaults: the argument sync read back *every* union member, and reading an inactive pointer/string member dereferenced the active member's float bytes as a C string pointer. Deep writes now skip members ...
- The library probe in `Affix::Platform::Unix` (`_findLib_gcc`) no longer prints linker errors (`undefined reference to WinMain`/`main`) while searching: it probes with `-shared`, which needs no entry point.
- Bitfields inside `Struct[...]` are no longer read or written out of bounds: `member->offset` now points at the storage unit base (with `bit_offset` relative to the unit) instead of the bitfield's own byte, so the unit-sized load/store in `push_stru...
- Reading and writing packed struct members (and pinned primitives) no longer uses unaligned native loads/stores: the dispatch vtables, bitfield vtables, pull handlers, and push handlers now round-trip through `memcpy`, which is safe on strict-alignm...
- Passing a wide string (`WString()`, i.e. `*wchar_t`) to a wrapped function now works on all platforms instead of croaking `Don't know how to handle this type of scalar as a pointer argument yet` on non-Windows systems, where the wide-string push op...
- Returning a `WString` no longer crashes: the wide-string pull handler called `SvGROW` on an uninitialized target SV, faulting before any buffer was allocated.
- [infix] Passing a 5-7 byte `Struct[...]` by value to a wrapped function no longer drops the trailing members on ARM64. The forward trampoline emitted a 32-bit register load unless the struct was exactly 8 bytes, so a `Struct[ arr => Array[2, UInt16...

## [v1.2.3] - 2026-08-08

### Fixed

- `Struct[...]` fields of type `WString` can now be assigned to (or as?) Perl strings (`SvPV`) rather than falling through and becoming null pointers

## [v1.2.2] - 2026-08-05

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

int main(void) { shm_open("/test", O_RDONLY, 0); return 0; }
END_C
        my ( $fh, $src ) = tempfile( SUFFIX => '.c', UNLINK => 1 );
        print $fh $test_code;
        close $fh;
        my ( $ofh, $out ) = tempfile( UNLINK => 1 );
        close $ofh;

        # Try without -lrt (list-form system to avoid shell injection)
        open( my $devnull, '>', '/dev/null' ) if $^O ne 'MSWin32';
        my $old_stdout = select $devnull      if $devnull;
        system( $cc, '-o', $out, $src );
        select $old_stdout if $old_stdout;
        close $devnull     if $devnull;
        return ''          if $? == 0;

        # Try with -lrt
        open( $devnull, '>', '/dev/null' ) if $^O ne 'MSWin32';
        $old_stdout = select $devnull      if $devnull;
        system( $cc, '-o', $out, $src, '-lrt' );
        select $old_stdout if $old_stdout;
        close $devnull     if $devnull;
        return '-lrt'      if $? == 0;
        return '';
    }

    sub command_exists {
        my ($cmd) = @_;
        if ( $Config{osname} eq 'MSWin32' ) {
            return system( 'where', $cmd ) == 0;
        }

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

INFIX_INTERNAL void emit_arm64_arith_imm(
    code_buffer * buf, bool is_sub, bool is64, bool set_flags, arm64_gpr dest, arm64_gpr base, uint32_t imm) {
    uint32_t instr = is_sub ? 0x51000000 : 0x11000000;
    if (is64)
        instr |= (1u << 31);
    if (set_flags)
        instr |= (1u << 29);
    if (imm <= 0xFFF)  // Check for un-shifted 12-bit immediate.
        instr |= (imm & 0xFFF) << 10;
    else if ((imm & 0xFFF) == 0 && (imm >> 12) <= 0xFFF && (imm >> 12) > 0) {  // Check for shifted 12-bit immediate.
        instr |= (1u << 22);                                                   // 'sh' bit selects LSL #12 shift.
        instr |= ((imm >> 12) & 0xFFF) << 10;
    }
    else {
        // Immediate is too large. Load it into a scratch register (X15) and do a register-based operation.
        arm64_gpr scratch_reg = X15_REG;
        emit_arm64_load_u64_immediate(buf, scratch_reg, imm);
        uint32_t reg_instr = is_sub ? 0x4B000000 : 0x0B000000;
        if (is64)
            reg_instr |= (1u << 31);
        if (set_flags)

infix/src/arch/riscv/abi_riscv64_common.h  view on Meta::CPAN

// funct7 fields (bits 31:25) for R-type integer ops
#define RV_F7_ADD 0x00U
#define RV_F7_SUB 0x20U
#define RV_F7_SLL 0x00U
#define RV_F7_SRL 0x00U
#define RV_F7_SRA 0x20U
// Shift immediate funct6 (bits 31:26)
#define RV_F6_SLLI 0x00U
#define RV_F6_SRLI 0x00U
#define RV_F6_SRAI 0x10U
// Floating-point move instructions (funct7 selects the operation).
// The RV64 W-level moves transfer between GPRs and single-precision FP registers.
#define RV_F7_FMV_X_S 0x70U  // FMV.X.W: FP(single) -> GPR
#define RV_F7_FMV_S_X 0x78U  // FMV.W.X: GPR -> FP(single)
#define RV_F7_FMV_X_D 0x71U  // FMV.X.D: FP(double) -> GPR
#define RV_F7_FMV_D_X 0x79U  // FMV.D.X: GPR -> FP(double)
#define RV_F7_FMV_X_H 0x72U  // FMV.X.H (Zfh)
#define RV_F7_FMV_H_X 0x7AU  // FMV.H.X (Zfh)
// FP-to-FP moves are the FSGNJ pseudo-instructions (fmv.s rd,rs1 == fsgnj.s rd,rs1,rs1).
#define RV_F7_FSGNJ_S 0x10U  // FSGNJ.S
#define RV_F7_FSGNJ_D 0x11U  // FSGNJ.D
// The 'imm' (rs2) encodings used by the fcvt family to select source/target precision.
// The source precision selects funct7 bit 0: 0 = 32-bit source (FCVT.S.D), 1 = 64-bit source (FCVT.D.S).
#define RV_FCVT_RM_RNE 0x0U    // Round to nearest, ties to even
#define RV_F7_FCVT_S_D 0x20U   // FCVT.S.D: source is double-precision, dest is single
#define RV_F7_FCVT_D_S 0x21U   // FCVT.D.S: source is single-precision, dest is double
#define RV_RS2_FCVT_D_S 0x00U  // rs2 field for FCVT.D.S
#define RV_RS2_FCVT_S_D 0x01U  // rs2 field for FCVT.S.D
// Instruction bit-field helper macros
#define RV_RS1_SHIFT 15U
#define RV_RS2_SHIFT 20U
#define RV_RD_SHIFT 7U
#define RV_FUNCT3_SHIFT 12U

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

                                     bool b,
                                     uint8_t aaa)  // Masking/control bits
{
    emit_byte(buf, 0x62);
    // Byte 2: P0 - R, X, B, R' bits are inverted. 0 means 1, 1 means 0.
    uint8_t p0 = 0;
    p0 |= (R ? 0 : 1) << 7;        // Inverted R bit
    p0 |= (X ? 0 : 1) << 6;        // Inverted X bit
    p0 |= (B ? 0 : 1) << 5;        // Inverted B bit
    p0 |= (R_prime ? 0 : 1) << 4;  // Inverted R' bit
    p0 |= (map & 0x0F);            // Low 4 bits select the opcode map (0F, 0F38, 0F3A)
    emit_byte(buf, p0);
    // Byte 3: P1
    uint8_t p1 = 0;
    p1 |= (pp & 0b11);
    p1 |= (1 << 2);              // ' (marks EVEX), must be 1
    p1 |= ((~vvvv & 0xF) << 3);  // vvvv field is inverted
    p1 |= W ? (1 << 7) : 0;
    emit_byte(buf, p1);
    // Byte 4: P2
    uint8_t p2 = 0;

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

/**
 * @file infix_config.h
 * @brief Platform, architecture, and ABI detection macros.
 * @ingroup internal_common
 *
 * @details This header is the first to be included by `infix_internals.h` and is
 * responsible for defining a consistent set of `INFIX_*` macros that describe the
 * build environment. It is the central point of configuration for the entire library,
 * adapting the build to different operating systems, compilers, and CPU architectures.
 *
 * Its most critical function is to select the correct **Application Binary Interface (ABI)**
 * implementation to use for JIT code generation. This is achieved through a cascade
 * of preprocessor checks that can be overridden by the user for cross-compilation.
 * By the end of this file, exactly one `INFIX_ABI_*` macro must be defined, which
 * determines which `abi_*.c` file is included in the unity build.
 *
 * @internal
 */
#pragma once
// System Feature Test Macros
/**

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

extern const infix_direct_forward_abi_spec g_arm64_direct_forward_spec;
#elif defined(INFIX_ABI_RISCV64)
extern const infix_forward_abi_spec g_riscv64_forward_spec;
extern const infix_reverse_abi_spec g_riscv64_reverse_spec;
extern const infix_direct_forward_abi_spec g_riscv64_direct_forward_spec;
#endif
/**
 * @internal
 * @brief Retrieves a pointer to the ABI specification v-table for forward calls.
 * @details This function is the entry point to the ABI abstraction layer. It uses
 * compile-time preprocessor macros (defined in `infix_config.h`) to select and
 * return the correct v-table for the target platform.
 * @return A pointer to the active `infix_forward_abi_spec`, or `nullptr` if the
 *         platform is unsupported.
 */
const infix_forward_abi_spec * get_current_forward_abi_spec() {
#if defined(INFIX_ABI_WINDOWS_X64)
    return &g_win_x64_forward_spec;
#elif defined(INFIX_ABI_SYSV_X64)
    return &g_sysv_x64_forward_spec;
#elif defined(INFIX_ABI_AAPCS64)

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

#elif defined(INFIX_ABI_SYSV_X64)
#include "../arch/x64/abi_sysv_x64.c"
#include "../arch/x64/abi_x64_emitters.c"
#elif defined(INFIX_ABI_AAPCS64)
#include "../arch/aarch64/abi_arm64.c"
#include "../arch/aarch64/abi_arm64_emitters.c"
#elif defined(INFIX_ABI_RISCV64)
#include "../arch/riscv/abi_riscv64.c"
#include "../arch/riscv/abi_riscv64_emitters.c"
#else
#error "No supported ABI was selected for the unity build in trampoline.c."
#endif

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

Optional. An array reference of paths to search for C<#include "..."> directives. The directory of every file listed in
C<project_files> is automatically added to this list.

=item C<types>

Optional. A hash reference for manually mapping type names to L<Affix> type objects or definition strings. Very useful
for masking complex internal library structures behind Pointer[Void] handles.

=item C<driver>

Optional. Explicitly select the parser driver. Values are C<'Clang'> or C<'Regex'>. If omitted, C<Affix::Wrap> attempts
to find the C<clang> executable and falls back to Regex if unavailable.

=back

=head1 METHODS

Parse headers, inject bindings at runtime, or generate standalone .pm files:

=head2 wrap( $lib, [$target_package] )

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

        affix $lib, 'c_create_tmpfile', []                         => Pointer [File];
        affix $lib, 'c_is_null_file',   [ Pointer [File] ]         => Int;
        #
        subtest 'Writing to a Perl filehandle from C' => sub {
            my $file = tempfile( { realpath => 1 } );
            my $fh   = $file->filehandle('>');

            # Note: We use a real file because PerlIO_findFILE (used internally)
            # requires a valid C-level FILE* which scalar handles (\$) might not provide.
            # Turn off buffering to ensure C sees the file state immediately
            my $old_fh = select($fh);
            $| = 1;
            select($old_fh);
            my $bytes = c_write_to_file( $fh, 'Hello from C' );
            ok $bytes > 0, 'C function returned success count';
            close $fh;

            # Verify content
            my $check   = $file->filehandle('<');
            my $content = <$check>;
            is $content, 'Hello from C', 'Data written by C appears in file';
        };
        subtest 'Reading from a Perl filehandle in C' => sub {

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

    typedef Logger => Struct [ log_file => Pointer [File], counter => Int ];

    # Bind functions
    affix $lib, 'init_logger',   [ Pointer [ Logger() ], Pointer [File] ] => Void;
    affix $lib, 'log_message',   [ Pointer [ Logger() ], String ]         => Void;
    affix $lib, 'create_logger', [ Pointer [File] ] => Logger();
    subtest 'File inside Struct (Pointer)' => sub {
        my $file     = tempfile( { realpath => 1 } );
        my $filename = $file->stringify;
        my $fh       = $file->filehandle('+>');
        my $old_fh   = select($fh);
        $| = 1;
        select($old_fh);

        # Allocate struct memory
        my $logger = malloc( sizeof( Logger() ) );

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

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

        like $lines[0], qr/\[1\] First message/,  'Line 1 matches';
        like $lines[1], qr/\[2\] Second message/, 'Line 2 matches';

        # Keep $fh alive until test end to avoid closing underneath C
        close $fh;
    };
    subtest 'File inside Struct (Value Return)' => sub {
        my $file     = tempfile( { realpath => 1 } );
        my $filename = $file->stringify;
        my $fh       = $file->filehandle('+>');
        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" ), 'syswrite to the handle from Perl';

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

        my $f1    = $file1->stringify;
        my $fh1   = $file1->filehandle('+>');
        my $file2 = tempfile( { realpath => 1 } );
        my $f2    = $file2->stringify;
        my $fh2   = $file2->filehandle('+>');
        my $file3 = tempfile( { realpath => 1 } );
        my $f3    = $file3->stringify;
        my $fh3   = $file3->filehandle('+>');

        # Flush buffers
        for my $h ( $fh1, $fh2, $fh3 ) { my $o = select($h); $| = 1; select($o); }

        # Pass array of handles
        write_all( [ $fh1, $fh2, $fh3 ], 'Broadcast' );
        close $_ for ( $fh1, $fh2, $fh3 );

        # Verify
        for my $f ( $f1, $f2, $f3 ) {
            open my $in, '<', $f;
            is <$in>, 'Broadcast', "File $f written to";
            close $in;



( run in 1.577 second using v1.01-cache-2.11-cpan-8dfa8b56332 )