Affix

 view release on metacpan or  search on metacpan

Changes.md  view on Meta::CPAN


This release introduces a modernization of pointer handling, turning pins into first-class objects with native indexing support. In other words, you can now use `$ptr->[$n]` to access the nth element.

Support for passing 128bit integers around is now complete. Additionally, functions expecting an enumeration now accept the string name of a constant; `state('PLASMA')` is the same as `state(PLASMA)` where `state` expects values defined like this: `t...

### Added

- Added support for native Perl array indexing on pointers and arrays. You can now use `$ptr->[$i]` to read or write memory at an offset without manual casting.
- New `Affix::Pointer` objects for structs and unions now allow direct field access like `$ptr->{field}` without explicit casting to `LiveStruct`.
- Recursive Liveness: Unified access and `LiveStruct` now work recursively. Accessing a nested struct member returns a live view or pointer tied to the original memory block.
- All pointers returned by `malloc`, `calloc`, `cast`, etc., are now blessed into the `Affix::Pointer` class, which provides several new methods:
    - `address()`: Returns the virtual memory address.
    - `type()`: Returns the L<infix> signature of the pointer.
    - `element_type()`: Returns the signature of the pointed-to elements.
    - `count()`: Returns the element count for Arrays, or byte size for Void pointers.
    - `size()`: Returns the total allocated size for managed pointers.
    - `cast($type)`: Reinterprets the pointer.
    - `attach_destructor($destructor, [$lib])`: Attaches a custom C cleanup routine to the pointer.
- Added `attach_destructor( $pin, $destructor, [$lib] )` to allow attaching custom C cleanup routines to managed pointers.
- Improved `VarArgs` support to automatically promote Perl strings to `char*`.
- Experimental Zero-Copy 'Live' Aggregates:
    - `LiveStruct`: A new helper to return zero-copy, live views of C structs. Modifications to the returned blessed hash reflect immediately in C memory.
    - `LiveArray`: A new helper to return live `Affix::Pointer` objects instead of deeply copied array references.
    - Implemented the `TIEHASH` interface for `Affix::Live` so perl can treat them as standard Perl hashes (`keys %$live`, `each %$live`, etc.).
- Fully implemented marshalling for `Int128` and `UInt128` (sint128/uint128) primitive types.
- Added `Affix::Wrap->generate( $lib, $pkg, $file )` for static binding generation. This emits standalone Perl modules that depend only on `Affix`, eliminating the need for `Clang` or header files at runtime.
- Recursive macro resolution support in `Affix::Wrap` for bitwise OR expressions like `(FLAG_A | FLAG_B)`.
- Support for passing string names of enum constants directly to functions.
- Added `params()` method to `Affix::Type::Callback` to allow inspecting and modifying callback parameters.
- Added string-to-integer conversion when passing Perl strings to C functions expecting enums.

### Fixed

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

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

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

README.md  view on Meta::CPAN

```perl
# C: typedef struct { uint32_t a : 1; uint32_t b : 3; } Config;
typedef Config => Struct[ a => UInt32 | 1, b => UInt32 | 3 ];
```

## Live Views (Zero-Copy Aggregates)

In Affix, memory structures are live by design. When C returns a pointer to an aggregate (Struct, Union, or Array), or
when you use `cast()` to overlay a type onto a memory address, Affix does not copy the data.

Instead, it returns a magical Perl reference (blessed into `Affix::Pointer`) mapped directly to the C memory via Perl
VTables. This means zero-copy performance without the overhead of `tie`.

Modifying keys or elements in these structures updates C memory immediately, and reading them reads directly from the C
heap.

```perl
# Example: Live view of a struct
my $live = cast( $ptr, Struct[ x => Int, y => Int ] );
$live->{x} = 42; # Updates C memory
```

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

            require CPAN::Requirements::Dynamic;
            my $dynamic_parser = CPAN::Requirements::Dynamic->new();
            my $prereq         = $dynamic_parser->evaluate($dynamic);
            $meta{prereqs} = $meta->effective_prereqs->with_merged_prereqs($prereq)->as_string_hash;
            $meta = CPAN::Meta->new( \%meta );
        }
        $meta->save(@$_) for ['MYMETA.json'];
    }

    sub find ( $pattern, $base ) {
        $base = path($base) unless builtin::blessed $base;
        my $blah = $base->visit(
            sub ( $path, $state ) {
                $state->{$path} = $path if $path =~ $pattern;
            },
            { recurse => 1 }
        );
        values %$blah;
    }

    # infix builder

lib/Affix.c  view on Meta::CPAN

        }

        backend->lib_handle = created_implicit_handle ? lib_handle_for_symbol : nullptr;

        CV * cv_new =
            newXSproto_portable((ix == 0 || ix == 2) ? rename_str : nullptr, Affix_trigger_backend, __FILE__, nullptr);

        CvXSUBANY(cv_new).any_ptr = (void *)backend;

        SV * obj = (ix == 1 || ix == 3) ? newRV_noinc(MUTABLE_SV(cv_new)) : newRV_inc(MUTABLE_SV(cv_new));
        sv_bless(obj, gv_stashpv("Affix::Bundled", GV_ADD));
        ST(0) = sv_2mortal(obj);
        XSRETURN(1);
    }

    // Standard path (parse & prepare types)
    infix_arena_t * parse_arena = nullptr;
    infix_type * ret_type = nullptr;
    infix_function_argument * args = nullptr;
    size_t num_args = 0, num_fixed = 0;

lib/Affix.c  view on Meta::CPAN

    // We MUST use nullptr/0 here and assign mg_ptr manually, otherwise sv_magicext treats 'affix' as a string and
    // copies truncated garbage.
    MAGIC * mg = sv_magicext((SV *)cv_new, nullptr, PERL_MAGIC_ext, &Affix_cv_vtbl, nullptr, 0);
    mg->mg_ptr = (char *)affix;

    // Set optimization pointer
    CvXSUBANY(cv_new).any_ptr = (void *)affix;

    //
    SV * obj = (ix == 1 || ix == 3) ? newRV_noinc(MUTABLE_SV(cv_new)) : newRV_inc(MUTABLE_SV(cv_new));
    sv_bless(obj, gv_stashpv("Affix", GV_ADD));
    ST(0) = sv_2mortal(obj);

    infix_arena_destroy(parse_arena);  // Now safe to destroy as we deep-copied everything
    XSRETURN(1);
}
XS_INTERNAL(Affix_Bundled_DESTROY) {
    dXSARGS;
    dMY_CXT;
    PERL_UNUSED_VAR(items);
    Affix_Backend * backend;

lib/Affix.c  view on Meta::CPAN

#if 0
static void pull_struct_as_live(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * p, bool readonly) {
    void * c_ptr = *(void **)p;
    if (c_ptr == nullptr) {
        sv_setsv(sv, &PL_sv_undef);
        return;
    }
    const infix_type * pointee_type = type->meta.pointer_info.pointee_type;
    HV * hv = newHV();
    SV * rv = newRV_noinc(MUTABLE_SV(hv));
    //~ sv_bless(rv, gv_stashpv("Affix::Live", GV_ADD));
    _populate_hv_from_c_struct(aTHX_ affix, hv, pointee_type, c_ptr, true, nullptr, readonly);
    sv_setsv(sv, rv);
    SvREFCNT_dec(rv);
}
#endif
static void pull_pointer_as_array(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * p, bool readonly) {
    void * c_ptr = *(void **)p;
    if (c_ptr == nullptr)
        sv_setsv(sv, &PL_sv_undef);
    else {

lib/Affix.c  view on Meta::CPAN

    dMY_CXT;
    if (items != 1)
        croak_xs_usage(cv, "library_path");
    const char * path = SvPV_nolen(ST(0));
    SV ** entry_sv_ptr = hv_fetch(MY_CXT.lib_registry, path, strlen(path), 0);
    if (entry_sv_ptr) {
        LibRegistryEntry * entry = INT2PTR(LibRegistryEntry *, SvIV(*entry_sv_ptr));
        entry->ref_count++;
        SV * obj_data = newSV(0);
        sv_setiv(obj_data, PTR2IV(entry->lib));
        ST(0) = sv_2mortal(sv_bless(newRV_inc(obj_data), gv_stashpv("Affix::Lib", GV_ADD)));
        XSRETURN(1);
    }
    infix_library_t * lib = infix_library_open(path);
    if (lib) {
        LibRegistryEntry * new_entry;
        Newxz(new_entry, 1, LibRegistryEntry);
        new_entry->lib = lib;
        new_entry->ref_count = 1;
        hv_store(MY_CXT.lib_registry, path, strlen(path), newSViv(PTR2IV(new_entry)), 0);
        SV * obj_data = newSV(0);
        sv_setiv(obj_data, PTR2IV(lib));
        ST(0) = sv_2mortal(sv_bless(newRV_inc(obj_data), gv_stashpv("Affix::Lib", GV_ADD)));
        XSRETURN(1);
    }
    XSRETURN_UNDEF;
}
XS_INTERNAL(Affix_get_last_error_message) {
    dXSARGS;
    PERL_UNUSED_VAR(items);
    infix_error_details_t err = infix_get_last_error();
    if (err.message[0] != '\0')
        ST(0) = sv_2mortal(newSVpv(err.message, 0));

lib/Affix.c  view on Meta::CPAN


    // 3. Update the local Pin metadata if the argument is a Pin
    Affix_Pin_2_Point_Oh * pin = get_pin_v2(aTHX_ arg);
    if (pin)
        pin->ptr = new_ptr;

    // 4. Update the Root Lifeline (the Affix::Memory internal container)
    // We trace back to the SV/AV that actually holds the address for the GC.
    SV * owner = _borrow_lifeline(aTHX_ arg);
    if (owner) {
        /* owner may be the blessed Affix::Memory RV (whose referent holds the
           address) or a raw storage SV. Unwrap the RV so we never write into
           the reference header itself. */
        SV * storage = SvROK(owner) ? SvRV(owner) : owner;
        if (SvTYPE(storage) == SVt_PVAV) {
            SV ** p = av_fetch((AV *)storage, 0, 0);
            if (p && *p)
                sv_setuv(*p, PTR2UV(new_ptr));
        }
        else {
            sv_setuv(storage, PTR2UV(new_ptr));

lib/Affix.c  view on Meta::CPAN

    const char * str = SvPV(ST(0), len);

    // Allocate managed memory
    char * dup = safemalloc(len + 1);
    memcpy(dup, str, len);
    dup[len] = '\0';

    /* Wrap in an Affix::Memory to ensure it gets freed */
    SV * mem_obj = newSVuv(PTR2UV(dup));
    SV * mem_rv = sv_2mortal(newRV_noinc(mem_obj));
    sv_bless(mem_rv, gv_stashpv("Affix::Memory", GV_ADD));

    /* Cast to *void to return a persistent pin instead of a copied string */
    ST(0) = sv_2mortal(cast(aTHX_ mem_rv, "*void"));

    XSRETURN(1);
}

XS_INTERNAL(Affix_strnlen) {
    dXSARGS;
    if (items != 2)

lib/Affix.pm  view on Meta::CPAN

        #~ ddx \@_;
        #~ ddx $cache;
        if (@libs) {
            ( $cache->{$name}{ $version // '' } ) = @libs;
            return $cache->{$name}{ $version // '' }->{path};
        }
        ();
    }

    sub _is_type ($thing) {
        return 1 if builtin::blessed($thing) && $thing->isa('Affix::Type');
        return 1 if !ref($thing) && defined $thing && index( '*@({;<', substr( $thing, 0, 1 ) ) >= 0;
        return 0;
    }

    # Abstract
    CORE::state $wchar_size = $Config{wcharsize} // ( $^O eq 'MSWin32' ? 2 : 4 );
    sub Void () { Affix::Type::Primitive->new( name => 'void' ) }
    sub Bool () { Affix::Type::Primitive->new( name => 'bool' ) }
    sub Char () { Affix::Type::Primitive->new( name => 'char' ) }
    sub UChar() { Affix::Type::Primitive->new( name => 'uchar' ) }

lib/Affix.pm  view on Meta::CPAN

    }

    # Vector[ 4, Float ] -> v[4:float]
    sub Vector : prototype($) {
        my ( $size, $type ) = @{ $_[0] };
        return "v[$size:$type]";
    }

    sub ThisCall : prototype($) {
        my $cb = $_[0];
        if ( builtin::blessed($cb) && $cb->isa('Affix::Type::Callback') ) {

            # Prepend 'this' pointer
            unshift @{ $cb->params }, Pointer [Void];
            return $cb;
        }
        elsif ( !ref $cb && $cb =~ /^\*\(\((.*)\)->(.*)\)$/ ) {
            my ( $args, $ret ) = ( $1, $2 );
            $args = $args ? "*void,$args" : "*void";
            return "*(($args)->$ret)";
        }

lib/Affix.pm  view on Meta::CPAN

        my $content = join( ',', @parts );
        return sprintf( $wrapper, $content );
    }

    sub typedef ( $name, $type //= () ) {
        ( my $clean_name = $name ) =~ s/^@//;
        if ( !defined $type ) {
            Affix::_typedef($clean_name);
        }
        else {
            if ( builtin::blessed($type) && $type->isa('Affix::Type::Enum') ) {
                my ( $const_map, $val_map ) = $type->resolve();
                my $pkg = caller;
                no strict 'refs';
                while ( my ( $const_name, $val ) = each %$const_map ) {
                    *{"${pkg}::${const_name}"} = sub () {$val};
                }
                &Affix::_register_enum_values( $clean_name, $val_map, $const_map );
            }
            if ( builtin::blessed($type) && $type->isa('Affix::Type') ) {
                Affix::_typedef("$clean_name = $type");
            }
            else {
                if ( $type =~ /^@/ ) {
                    Affix::_typedef($type);
                }
                else {
                    Affix::_typedef("$clean_name = $type");
                }
            }

lib/Affix.pm  view on Meta::CPAN

                };
            }
        }
        return 1;
    }

    # Update @EXPORT_OK or %EXPORT_TAGS to include 'cast' if not already there
    package    #
        Affix::Type {
        use overload '""' => sub { shift->signature() }, fallback => 1;
        sub new       { my ( $class, %args ) = @_; bless \%args, $class }
        sub signature {...}
    }
    package    #
        Affix::Type::Const {
        our @ISA = qw[Affix::Type];
        sub new { my ( $class, %args ) = @_; bless \%args, $class }

        sub signature {
            my $self = shift;
            return builtin::blessed( $self->{inner} ) ? $self->{inner}->signature : $self->{inner};
        }
    }
    package    #
        Affix::Type::Reference {
        our @ISA = qw[Affix::Type];
        sub signature { '@' . shift->{name} }
    }
    package    #
        Affix::Type::Primitive {
        our @ISA = qw[Affix::Type];

lib/Affix.pm  view on Meta::CPAN

                    else                                                       { push @parts, "$name:$type"; }
                }
                else { push @parts, "$curr"; }
            }
            return sprintf( $kind, join( ',', @parts ) );
        }
    }
    package    #
        Affix::Type::Struct {
        our @ISA = qw[Affix::Type::Aggregate];
        sub new { my $class = shift; my %args = @_; $args{kind} = '{%s}'; bless \%args, $class }
    }
    package    #
        Affix::Type::Union {
        our @ISA = qw[Affix::Type::Aggregate];
        sub new { my $class = shift; my %args = @_; $args{kind} = '<%s>'; bless \%args, $class }
    }
    package    #
        Affix::Type::Array {
        our @ISA = qw[Affix::Type];
        sub signature { my $self = shift; my $c = $self->{count} // '?'; return "[$c:" . $self->{type} . "]"; }
    }
    package    #
        Affix::Type::Pointer {
        our @ISA = qw[Affix::Type];
        sub signature { '*' . ( shift->{subtype} // 'void' ) }
    }
    package    #
        Affix::Type::Callback {
        our @ISA = qw[Affix::Type];
        sub params { shift->{params} }

        sub signature {
            my $self = shift;
            my @args = map { builtin::blessed($_) ? $_->signature : $_ } @{ $self->{params} };
            my $args = join( ',', @args );
            $args =~ s/,\;,/;/g;
            $args =~ s/,\;$/;/;
            my $r = builtin::blessed( $self->{ret} ) ? $self->{ret}->signature : $self->{ret};
            return "*(($args)->$r)";
        }
    }
};
1;
__END__
Copyright (C) Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under
the terms found in the Artistic License 2. Other copyrights, terms, and

lib/Affix.pod  view on Meta::CPAN

Specify bit widths using the pipe (C<|>) operator within Structs/Unions. Affix handles all masking and shifting.

    # C: typedef struct { uint32_t a : 1; uint32_t b : 3; } Config;
    typedef Config => Struct[ a => UInt32 | 1, b => UInt32 | 3 ];

=head2 Live Views (Zero-Copy Aggregates)

In Affix, memory structures are live by design. When C returns a pointer to an aggregate (Struct, Union, or Array), or
when you use C<cast()> to overlay a type onto a memory address, Affix does not copy the data.

Instead, it returns a magical Perl reference (blessed into C<Affix::Pointer>) mapped directly to the C memory via Perl
VTables. This means zero-copy performance without the overhead of C<tie>.

Modifying keys or elements in these structures updates C memory immediately, and reading them reads directly from the C
heap.

    # Example: Live view of a struct
    my $live = cast( $ptr, Struct[ x => Int, y => Int ] );
    $live->{x} = 42; # Updates C memory

=head3 Unified Access

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

        field $linker  : reader;

        # Cached Flag Arrays
        field @cflags;
        field @cxxflags;
        field @ldflags;
        field $_lib;
        #
        ADJUST {
            my $so_ext = $Config{so} // 'so';
            $build_dir = Path::Tiny->new($build_dir) unless builtin::blessed $build_dir;

            # Standard convention: Windows DLLs don't need 'lib' prefix, Unix SOs do.
            my $prefix    = ( $os eq 'MSWin32' || $name =~ /^lib/ ) ? ''          : 'lib';
            my $suffix    = defined $version                        ? ".$version" : '';
            my $safe_name = $name;
            $safe_name =~ s/[^\w.-]/_/g;
            $libname = $build_dir->child("$prefix$safe_name.$so_ext$suffix")->absolute;

            # We prefer C++ drivers (g++, clang++) to handle standard libraries for mixed code (C+Rust, C+C++)
            $linker = $self->_can_run(qw[g++ clang++ c++ icpx]) || $self->_can_run(qw[cc gcc clang icx cl]) || 'c++';

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

        field $doc        : reader : param //= ();
        field $definition : reader : param //= ();

        method affix_type {
            return $definition->affix_type if defined $definition;
            return $type->affix_type;
        }

        method affix {
            return $definition->affix if defined $definition;
            return $type->affix       if builtin::blessed($type);

            # Fallback: if it's just a string, wrap it in a Reference object
            return Affix::Type::Reference->new( name => $type =~ s/^@//r ) if defined $type;
            return Affix::Void();
        }
    }
    class    #
        Affix::Wrap::Macro : isa(Affix::Wrap::Entity) {
        field $value : reader : param //= ();
        method set_value ($v) { $value = $v }

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

        }
    }

    class Affix::Wrap {
        field $driver        : param //= ();
        field $project_files : param //= $driver->project_files;
        field $include_dirs  : param //= [];
        field $types         : param //= {};
        #
        ADJUST {
            if ( defined $driver && !builtin::blessed($driver) ) {
                if    ( $driver eq 'Clang' ) { $driver = Affix::Wrap::Driver::Clang->new( project_files => $project_files ); }
                elsif ( $driver eq 'Regex' ) { $driver = Affix::Wrap::Driver::Regex->new( project_files => $project_files ); }
                else                         { die "Unknown driver '$driver'"; }
            }
            elsif ( !defined $driver ) {

                # Wrap in a localized warn-handler to suppress the internal "Can't spawn" error
                my $exit = do {
                    local $SIG{__WARN__} = sub { };
                    system( 'clang', '--version', '>', File::Spec->devnull, '2>&1' );

lib/Affix/marshal.c  view on Meta::CPAN

 * @param in The input SV (integer address or Affix::Memory managed object).
 * @param name The struct or primitive type name to cast the memory into.
 * @return A magic-bound SV tracking the memory block natively.
 */
SV * cast(pTHX_ SV * in, const char * name) {
    dMY_CXT;
    void * addr = get_address_v2(aTHX_ in);
    if (!addr)
        return &PL_sv_undef;

    /* Keep the blessed Affix::Memory object itself as the lifeline so the pin
       holds a strong reference to it. This keeps the memory alive for as long as
       any derived pin exists and lets free()/DESTROY locate the owner. */
    SV * owner = (SvROK(in) && sv_derived_from(in, "Affix::Memory")) ? in : nullptr;
    infix_type * new_type = nullptr;
    infix_arena_t * local_arena = nullptr;

    if (infix_type_from_signature(&new_type, &local_arena, name, MY_CXT.registry) != INFIX_SUCCESS)
        croak("Type not found: %s", name);

    const infix_type * resolved = resolve_type(aTHX_ new_type);

lib/Affix/marshal.c  view on Meta::CPAN

 * @brief Wraps an existing C pointer with a custom destructor callback into an object payload.
 * @param ptr_iv The raw pointer (as integer).
 * @param dtor_iv The destructor callback pointer (as integer).
 * @return An Affix::Memory object representing the mapped block.
 */
SV * wrap_owned(pTHX_ UV ptr_uv, UV dtor_uv) {
    AV * av = newAV();
    av_push(av, newSVuv(ptr_uv));  /* Use UV */
    av_push(av, newSVuv(dtor_uv)); /* Use UV */
    SV * rv = newRV_noinc((SV *)av);
    sv_bless(rv, gv_stashpv("Affix::Memory", GV_ADD));
    return rv;
}
/**
 * @brief Allocates zeroed C memory and wraps it into a Perl Affix::Memory object.
 * @param size Memory allocation size in bytes.
 * @return An Affix::Memory object representation.
 */
SV * alloc_owned(pTHX_ UV size) {
    void * ptr = safecalloc(1, size);
    SV * sv = newSVuv(PTR2UV(ptr)); /* Use UV */
    SV * rv = newRV_noinc(sv);
    sv_bless(rv, gv_stashpv("Affix::Memory", GV_ADD));
    return rv;
}

/**
 * @brief Garbage Collector Hook: Frees C memory owned by an Affix::Memory object.
 * @details Can fall back to standard `safefree` or use a custom C++ destructor mapping if passed via `wrap_owned`.
 * @param rv The Affix::Memory reference triggered by DESTROY.
 */
void free_owned(pTHX_ SV * rv) {
    if (!rv || !SvROK(rv))

lib/Affix/marshal.c  view on Meta::CPAN

 */
void * _extract_pointer_value(pTHX_ SV * sv, MAGIC * ignore_mg) {
    if (!sv)
        return nullptr;

    // Use a secondary pointer for unwrapping to preserve the original SV (the potential object)
    SV * target = sv;
    if (SvROK(sv))
        target = SvRV(sv);

    /* Handle Magic Pins (even inside blessed objects) */
    if (SvMAGICAL(target)) {
        MAGIC * mg = mg_find(target, PERL_MAGIC_ext);
        while (mg) {
            if (is_v2_vtable(mg->mg_virtual) && mg != ignore_mg) {
                Affix_Pin_2_Point_Oh * im = (Affix_Pin_2_Point_Oh *)mg->mg_ptr;
                if (mg->mg_virtual == &vtbl_pointer)
                    return im->absolute ? im->ptr : (im->ptr ? *(void **)im->ptr : nullptr);
                return im->ptr;
            }
            mg = mg->mg_moremagic;

t/032_recursive_liveness.t  view on Meta::CPAN

};
subtest 'Recursive Liveness: Live array of Structs' => sub {
    my $Point    = Struct [ x => Int, y => Int ];
    my $ptr      = malloc( sizeof($Point) * 2 );
    my $live_arr = cast( $ptr, Array [ $Point, 2 ] );

    # Set values
    $live_arr->[0]{x} = 10;
    $live_arr->[0]{y} = 20;

    # Accessing $live_arr->[0] returns an Affix::Live blessed hash
    my $p0 = $live_arr->[0];
    $p0->{x} = 30;
    is $live_arr->[0]{x}, 30, 'Changes to live element reflect in original memory';
};
subtest 'Recursive Liveness: Live struct with Array' => sub {

    # Using typedef to ensure member names are preserved in the infix registry
    typedef ListStruct => Struct [ items => Array [ Int, 3 ] ];
    my $ptr         = malloc( sizeof( ListStruct() ) );
    my $live_struct = cast( $ptr, ListStruct() );



( run in 1.629 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )