Affix
view release on metacpan or search on metacpan
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
infix/src/jit/executor.c view on Meta::CPAN
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`.
infix/src/jit/executor.c view on Meta::CPAN
infix_executable_t exec = {
.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .handle = nullptr, .seh_registration = nullptr};
#else
infix_executable_t exec = {.rx_ptr = nullptr, .rw_ptr = nullptr, .size = 0, .shm_fd = -1, .eh_frame_ptr = nullptr};
#endif
if (size == 0)
return exec;
#if defined(INFIX_OS_WINDOWS)
// Add headroom for SEH metadata on Windows.
size_t total_size = size + INFIX_SEH_METADATA_SIZE;
// Windows: Single-mapping W^X. Allocate as RW, later change to RX via VirtualProtect.
void * code = VirtualAlloc(nullptr, total_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (code == nullptr) {
_infix_set_system_error(
INFIX_CATEGORY_ALLOCATION, INFIX_CODE_EXECUTABLE_MEMORY_FAILURE, GetLastError(), nullptr);
return exec;
}
exec.rw_ptr = code;
exec.rx_ptr = code;
infix/src/jit/executor.c view on Meta::CPAN
#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;
infix/src/jit/executor.c view on Meta::CPAN
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);
infix/src/jit/executor.c view on Meta::CPAN
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);
infix/src/jit/executor.c view on Meta::CPAN
rf[0].BeginAddress = rva_offset;
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());
}
}
#endif
#endif
#if defined(INFIX_OS_LINUX) && defined(INFIX_ARCH_X64)
/**
infix/src/jit/executor.c view on Meta::CPAN
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) {
g_use_secure_jit_path = has_jit_entitlement();
g_checked_jit_support = true;
}
lib/Test2/Tools/Affix.pm view on Meta::CPAN
my $key = $block;
$key =~ s/(\A\{[^\n]*\n)\s*[^\n]*\n/$1/;
my $sum = Digest::MD5::md5_hex($key);
$dups++ if exists $known->{$sum};
$known->{$sum} = $block;
}
return ( $known, $dups );
}
sub dec_ent {
return $1 if $_[0] =~ m/^<!\[CDATA\[\{(.*)}]]>$/smg;
$_[0] =~ s[<][<]g;
$_[0] =~ s[>][>]g;
$_[0] =~ s[&][&]g;
shift;
}
sub stacktrace($blah) {
use Test2::Util::Table qw[table];
$blah ?
join "\n", table(
t/017_affix_build.t view on Meta::CPAN
global add_asm
section .text
add_asm:
mov eax, edi
add eax, esi
ret
run_test( 'cobol', 'Cobol', <<~'', 'add_cob', 'cobc' );
IDENTIFICATION DIVISION.
PROGRAM-ID. add_cob.
DATA DIVISION.
LINKAGE SECTION.
01 A PIC 9(9) USAGE COMP-5.
01 B PIC 9(9) USAGE COMP-5.
01 R PIC 9(9) USAGE COMP-5.
PROCEDURE DIVISION USING A, B, R.
ADD A TO B GIVING R.
GOBACK.
END PROGRAM add_cob.
subtest 'Polyglot: Number Cruncher (C + Fortran + ASM)' => sub {
t/050_affix_build.t view on Meta::CPAN
global add_asm
section .text
add_asm:
mov eax, edi
add eax, esi
ret
run_test( 'cobol', 'Cobol', <<~'', 'add_cob', 'cobc' );
IDENTIFICATION DIVISION.
PROGRAM-ID. add_cob.
DATA DIVISION.
LINKAGE SECTION.
01 A PIC 9(9) USAGE COMP-5.
01 B PIC 9(9) USAGE COMP-5.
01 R PIC 9(9) USAGE COMP-5.
PROCEDURE DIVISION USING A, B, R.
ADD A TO B GIVING R.
GOBACK.
END PROGRAM add_cob.
subtest 'Polyglot: Number Cruncher (C + Fortran + ASM)' => sub {
( run in 0.457 second using v1.01-cache-2.11-cpan-140bd7fdf52 )