Changelog
All notable changes to Affix.pm will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
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, andLiveUnionwith a singleLivewrapper. 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
- Fully implemented write support for bitfields in live views. Modifying a bitfield member in an
Affix::Livehash now correctly performs bit-masked writes to the underlying C memory. - Added
LiveUnionas a deprecated alias for the unifiedLive()classifier.
Fixed
- Fixed an inconsistency where
Affix::Type::Arrayobjects were stringifying in a format incompatible withcast. They now correctly use the$type[$count]syntax. - Updated the internal type stringifier to correctly resolve and prefer the
signaturemethod overstringify, ensuring consistent behavior across allAffix::Typeobjects. - Fixed a bug in
Affix_castwhere thelive_hint(+) was ignored for Arrays and Unions.
v1.0.8 - 2026-03-02
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: typedef States => Enum['SOLID', 'LIQUID', 'GAS', 'PLASMA'].
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::Pointerobjects for structs and unions now allow direct field access like$ptr->{field}without explicit casting toLiveStruct. - Recursive Liveness: Unified access and
LiveStructnow 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 theAffix::Pointerclass, which provides several new methods:address(): Returns the virtual memory address.type(): Returns the L 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
VarArgssupport to automatically promote Perl strings tochar*. - 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 liveAffix::Pointerobjects instead of deeply copied array references.- Implemented the
TIEHASHinterface forAffix::Liveso perl can treat them as standard Perl hashes (keys %$live,each %$live, etc.).
- Fully implemented marshalling for
Int128andUInt128(sint128/uint128) primitive types. - Added
Affix::Wrap->generate( $lib, $pkg, $file )for static binding generation. This emits standalone Perl modules that depend only onAffix, eliminating the need forClangor header files at runtime. - Recursive macro resolution support in
Affix::Wrapfor bitwise OR expressions like(FLAG_A | FLAG_B). - Support for passing string names of enum constants directly to functions.
- Added
params()method toAffix::Type::Callbackto allow inspecting and modifying callback parameters. - Added string-to-integer conversion when passing Perl strings to C functions expecting enums.
Fixed
- Optimized
Pointerreturns in the XSUB dispatcher for performance by inlining the marshalling path and caching the stash. - Fixed several issues in
CLONEwhere metadata, managed memory, and enum registries were not correctly duplicated across perl's ithreads. - Improved
_get_pin_from_svandis_pinto safely handle both references to pins and direct magical scalars like those found in Unions. - Fixed potential double-frees and leaks in
Affix_Lib_DESTROYandAffix_free_pinby improving reference counting and ownership tracking. - Symbols found via
find_symbolnow correctly track the parentAffix::Libobject to prevent the library from being unloaded while symbols are still in use. - Corrected a memory corruption bug in
Affix_mallocandAffix_strdupcaused by uninitialized internalAffix_Pinstructures. - Fixed
dualvarbehavior for enums returned from C, ensuring they correctly function as both strings and integers in Perl. - Fixed the
cleanaction inAffix::Builderwhich was failing due to an undefinedrmtreecall. - Fixed an issue where blessing a return value could prematurely trigger 'set' magic on the underlying SV.
- Fixed
typedefparsing: Named types now return properAffix::Type::Referenceobjects instead of strings, ensuring they are correctly resolved when nested in other aggregates. - Fixed
castto correctly return blessedAffix::Liveobjects 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 onArraytypes orVoid*(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) from being destroyed until global destruction.
- 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 leading to premature closing or double-decrements.
- 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_vprandemit_arm64_str_vprwhere 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_framegeneration for ARM64 LinuxFORWARDtrampolines, correcting the instruction sequence and offsets to enable reliable C++ exception propagation. - [infix] Corrected a performance issue on x64 by adding
vzerouppercalls 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_structinsrc/core/types.cto correctly handle bitfields larger than 8 bits and ensuresbit_offsetis 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). -
[infix] Implemented C++ exception propagation through JIT frames on Linux (x86-64 and ARM64) using manual DWARF
.eh_framegeneration 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_safeAPI 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_vectorto 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
XMM0. - [infix] Move to a pre-calculated hash field in
_infix_registry_entry_t. Lookups and rehashing now use this stored hash, significantly reducing string hashing overhead during type resolution and registry scaling. - [infix] Optimized Type Registry memory management: Internal hash table buckets are now heap-allocated and freed during rehashes, preventing memory "leaks" within the registry's arena.
v1.0.6 - 2026-01-22
Most of this version's work went into threading stability, ABI correctness, and security within the JIT engine.
Changed
- [[infix]] The JIT memory allocator on Linux now uses
memfd_create(on kernels 3.17+) to create anonymous file descriptors for dual-mapped W^X memory. This avoids creating visible temporary files in/dev/shmand improves hygiene and security. On FreeBSD,SHM_ANONis now used. - [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 executable code by finding the stale RW pointer.
- [infix]
infix_library_opennow usesRTLD_LOCALinstead ofRTLD_GLOBALon 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
CLONEto 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 iftypedef'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 shutdown. 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 doubleon this platform is 8 bytes (an alias fordouble), unlike standard AAPCS64 where it is 16 bytes. The JIT previously emitted 16-byte stores (STR Qn) for these types, overwriting adjacent stack memory. - [infix] Fixed
long doublehandling 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.hheader included an internal file (common/compat_c23.h). The header is now fully self-contained and definesINFIX_NODISCARDfor 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 useMOVUPS. - [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
INTEGERclass, causing the trampoline to look inRDI/RSIinstead ofXMMregisters. - [infix] Fixed potential cache coherency issues on Windows x64. The library now unconditionally calls
FlushInstructionCacheafter JIT compilation. - [infix] Capped the maximum alignment in
infix_type_create_packed_structto 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. ImplementedLDRSHandLDRSB. - [infix] Added native support for Apple's Hardened Runtime security policy.
- The JIT engine now utilizes
MAP_JITwhen thecom.apple.security.cs.allow-jitentitlement is detected. - Implemented thread-local permission toggling via
pthread_jit_write_protect_npto maintain W^X compliance.
- The JIT engine now utilizes
v1.0.5 - 2026-01-11
Changed
- Affix::Wrap allows you to define your own types just in case the headers fail to parse completely.
v1.0.4 - 2026-01-10
This should just be a documentation cleanup cycle.
v1.0.3 - 2026-01-07
Based on infix v0.1.3
Added
- Support for Variadic Functions (varargs):
- Implemented dynamic JIT compilation for C functions with variable arguments (e.g.,
printf). - Added
variadic_cacheto cache trampolines for repeated calls, ensuring high performance. - Implemented runtime type inference: Perl integers promote to
sint64, floats todouble, and strings to*char.
- Implemented dynamic JIT compilation for C functions with variable arguments (e.g.,
- Added
Affix::coerce($type, $value)to explicitly hint types for variadic arguments. This allows passing structs by value or forcing specific integer widths where inference is insufficient. - Cookbook: I'm putting together chapters on a wide range of topics at https://github.com/sanko/Affix.pm/discussions/categories/recipes
affixandwrapfunctions now accept an address to bind to. This expects the library to beundefand jumps past the lib location and loading steps.- Added
FileandPerlIOtypes.- Allows passing Perl filehandles to C functions expecting standard C streams (
PerlIO*=>Pointer[PerlIO]). - Allows receiving
FILE*from C and using them as standard Perl filehandles (FILE*=>Pointer[File]).
- Allows passing Perl filehandles to C functions expecting standard C streams (
- A few new specialized pointer types:
StringList: Automatically marshals an array ref of strings to a null-terminatedchar**array (and back). This is useful in instances whereargvor a similar list is expected.Buffer: Allows passing a pre-allocated scalar as a mutablechar*buffer to C (zero-copy write).SockAddr: Safe marshalling of Perl packed socket addresses tostruct sockaddr*.
- Affix::Build: A polyglot shared library builder. Currently supports Ada, Assembly, C, C#, C++, Cobol, Crystal, Dlang, Eiffel, F#, Fortran, Futhark, Go, Haskell, Nim, OCaml, Odin, Pascal, Rust, Swift, Vlang, and Zig.
- Affix::Wrap: An experimental tool to introspect C header files and generate Affix bindings and documentation.
- Dual-Driver Architecture:
Affix::Wrap::Driver::Clang: Uses the systemclangexecutable to parse the AST for high-fidelity extraction of types, macros, and comments.Affix::Wrap::Driver::Regex: A zero-dependency fallback driver that parses headers using heuristics.
- Dual-Driver Architecture:
Changed
Array[Char]function arguments now accept Perl strings directly, copying the string data into the temporary C array.Affix::errno()now returns a dualvar containing both the numeric error code (errno/GetLastError) and the system error string (strerror/FormatMessage).
Fixed
- Correctly implemented array decay for function arguments on ARM and Win64.
Array[...]types are now marshalled into temporary C arrays and passed as pointers, matching standard C behavior. Previously, they were incorrectly passed by value, causing stack corruption. - Fixed binary safety for
Array[Char/UChar]. Reading these arrays now respects the explicit length rather than stopping at the first null byte. - The write-back mechanism no longer attempts to overwrite the read-only ArrayRef scalar with the pointer address.
Pointer[SV]is now handled properly as args, return values, and in callbacks. Reference counting is automatic to prevent premature garbage collection of passed scalars.- Shared libs written in Go spin up background threads (for GC and scheduling) that do not shut down cleanly when a shared library is unloaded. This often causes access violations on Windows during program exit. We attempt to work around this by detecting libs with the Go runtime and just... not unloading them.
v1.0.2 - 2025-12-14
Changed
- In an attempt to debug mystery failures in SDL3.pm, Affix.pm will warn and return
undefinstead ofcroaking. - Improved error reporting: if the internal error message is empty, the numeric error code is now included in the warning.
Fixed
- [[infix]] Fixed a critical file descriptor leak on POSIX platforms (Linux/FreeBSD) where the file descriptor returned by
shm_openwas kept open for the lifetime of the trampoline, eventually hitting the process file descriptor limit (EMFILE). The descriptor is now closed immediately after mapping, as intended. - Fixed memory leaks that occurred when trampoline creation failed midway (cleaning up partial arenas, strings, and backend structures).
v1.0.1 - 2025-12-13
Changed
- Improved Union marshalling: Union members are now exposed as pins within the hash. This allows clean syntax (like
$u->{member} = 5) without needing to dereference a reference, while maintaining C-memory aliasing.
Fixed
- Fixed
writeback_pointer_genericto support writing back to scalar output parameters (pointers-to-pointers). This resolves issues where C functions returning handles via arguments would fail to populate the SV*/pin.
v1.0.0 - 2025-12-13
- Stable? Stable. Stable enough.
v0.12.0 - 2025-12-12
Changed
- Affix is reborn! This is a complete rewrite
- Replaced dyncall with a JIT and introspection engine I've called infix
0.11 - 2023-03-30
Added
- Support for WChar
- Rough, basic support for mangled symbols:
- Itanium C++ ABI
- Rust (legacy)
- Expose dcNewCallVM( ... ) size variable
0.10 - 2023-03-11
Changed
- Support for ArrayRef[] with dynamic size
- Support for empty Stuct[]
- Coerce Enum[] types with sv2ptr(...)
- Explicit undef values are turned into NULL in Pointer[], ArrayRef[], etc.
- Provide default values in Struct[]
- Ignore perl's PTRSIZE which might be different than the system's actual pointer size
- Cleanup VM on Affix::END()
- Simplify API around named subs
- Support for WStr (wchar_t *, PWSTR, etc.)
0.09 - 2023-01-26
Added
- Structs may now contain a CodeRef
- CodeRef, Any, etc. are now properly handled as aggregate members
- Nesting CodeRefs used as callbacks work now
- Bind to exported values with pin()
- Expose aggregate by value and syscall support in Affix::Feature
- Survive callbacks with unexpectedly empty return values
- Delayed type resolution with InstanceOf
0.08 - 2022-12-19
Fixed
- Correct struct alignment for perls with quadmath and/or longdouble enabled
0.07 - 2022-12-17
Changed
- Pull upstream changes to dyncall 1.5 (unstable)
0.06 - 2022-12-16
Changed
- Allow calling convention to be changed in param lists
- Fix quadmath tests (I hope)
- Attempt to build with nmake on Win32 smokers that have gcc but not GNU make (how? why?)
- Fix default struct padding when passing around by value
0.05 - 2022-12-14
Changed
- Expose offsetof( ... )
- Pull upstream changes to dyncall 1.4 (stable)
0.04 - 2022-12-07
Changed
- Affix.pm is born