view release on metacpan or search on metacpan
All notable changes to Affix.pm will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v1.0.2] - 2025-12-14
### Changed
- In an attempt to debug mystery failures in SDL3.pm, Affix.pm will warn and return `undef` instead of `croak`ing.
- 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_open` was kept open for the lifetime of the trampoline, eventually hitting the process file descriptor limit (EMFILE). T...
- 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
Standard C memory operations are available for high-performance manipulation of Pins.
- `memchr( $ptr, $ch, $count )`
- `memcmp( $lhs, $rhs, $count )`
- `memset( $dest, $ch, $count )`
- `memcpy( $dest, $src, $count )`
- `memmove( $dest, $src, $count )`
## `dump( $ptr, $length )`
Dumps `$length` bytes of raw data from a given point in memory to STDOUT in a hex editor style. Useful for debugging
layout issues.
# INTROSPECTION
## `sizeof( ... )`
```perl
my $size = sizeof( Int );
my $size_rect = sizeof( Struct[ x => Int, y => Int ] );
```
# UTILITIES
## `get_system_error()`
Returns the `errno` (Linux/Unix) or `GetLastError` (Windows) from the most recent FFI call. This must be called
immediately after the function invokes to ensure accuracy.
## `sv_dump( $sv )`
Dumps the internal structure of a Perl scalar to STDERR. Useful for debugging Perl internals.
# COMPILER WRAPPER
Affix includes a lightweight, cross-platform C compiler wrapper `Affix::Compiler`. This is useful for compiling small
C stubs or "glue" code at runtime to bridge complex macros or inline functions that cannot be bound directly.
```perl
use Affix;
my $compiler = Affix::Compiler->new(
name => 'my_wrapper',
builder/Affix/Builder.pm view on Meta::CPAN
use JSON::PP 2 qw[encode_json decode_json];
use File::Temp qw[tempfile];
# Not in CORE
use Path::Tiny qw[path cwd];
use ExtUtils::Helpers 0.028 qw[make_executable split_like_shell detildefy];
# infix and Affix stuff
use Config qw[%Config];
field $force : param //= 0;
field $debug : param = 0;
field $libver;
field $cflags;
field $ldflags;
field $cppver = 'c++17'; # https://en.wikipedia.org/wiki/C%2B%2B20#Compiler_support
field $cver = 'c17'; # https://en.wikipedia.org/wiki/C17_(C_standard_revision)
field $make : param //= $Config{make};
#
field $action : param //= 'build';
field $meta : reader = CPAN::Meta->load_file('META.json');
builder/Affix/Builder.pm view on Meta::CPAN
field $prefix : param //= '';
#
ADJUST {
-e 'META.json' or die "No META information provided\n";
# Configure Flags
my $is_bsd = $^O =~ /bsd/i;
my $is_win = $^O =~ /MSWin32/i;
$cflags = $is_bsd ? '' : '-fPIC ';
$ldflags = $is_bsd ? '' : ' -flto=auto ';
if ( $debug > 0 ) {
$cflags
.= '-DDEBUG=' .
$debug .
' -g3 -gdwarf-4 ' .
' -Wno-deprecated -pipe ' .
' -Wall -Wextra -Wpedantic -Wvla -Wnull-dereference ' .
' -Wswitch-enum -Wduplicated-cond ' .
' -Wduplicated-branches';
$cflags .= ' -fvar-tracking-assignments' unless $Config{osname} eq 'darwin';
}
elsif ( !$is_win ) {
$cflags
.= ' -DNDEBUG -DBOOST_DISABLE_ASSERTS -Ofast -ftree-vectorize -ffast-math -fno-align-functions -fno-align-loops -fno-omit-frame-pointer -flto=auto';
builder/Affix/Builder.pm view on Meta::CPAN
quiet => 0,
'C++' => $cxx,
source => $source->stringify,
defines => { VERSION => qq/"$version"/, XS_VERSION => qq/"$version"/ },
include_dirs => [
cwd->stringify, cwd->child('infix')->realpath->stringify,
cwd->child('infix')->child('include')->realpath->stringify, cwd->child('infix')->child('src')->realpath->stringify,
$source->dirname, $pre->child( $meta->name, 'include' )->stringify
],
extra_compiler_flags =>
( '-fPIC -std=' . ( $cxx ? $cppver : $cver ) . ' ' . $cflags . ( $debug ? ' -ggdb3 -g -Wall -Wextra -pedantic' : '' ) )
) :
$obj;
}
# Point to the Architecture-specific build lib
my $infix_build_lib = cwd->absolute->child('infix')->child( 'build_lib', $Config{archname} )->stringify;
# Check for -lrt requirement
my $lrt_flag = $self->check_for_lrt();
my $data = {
infix/include/infix/infix.h view on Meta::CPAN
* @brief APIs for inspecting and serializing the contents of a named type registry.
* @ingroup high_level_api
* @{
*/
/** @brief An opaque handle to a registry iterator. Created by `infix_registry_iterator_begin`. */
typedef struct infix_registry_iterator_t infix_registry_iterator_t;
/**
* @brief Serializes all defined types within a registry into a single, human-readable string.
*
* The output format is a sequence of definitions (e.g., `@Name = { ... };`) separated
* by newlines, suitable for logging, debugging, or saving to a file. This function
* will not print forward declarations that have not been fully defined.
*
* @param[out] buffer The output buffer to write the string into.
* @param[in] buffer_size The size of the output buffer.
* @param[in] registry The registry to serialize.
* @return `INFIX_SUCCESS` on success, or `INFIX_ERROR_INVALID_ARGUMENT` if the buffer is too small.
*/
c23_nodiscard infix_status infix_registry_print(char *, size_t, const infix_registry_t *);
/**
* @brief Initializes an iterator for traversing the types in a registry.
infix/src/common/utility.h view on Meta::CPAN
*
* SPDX-License-Identifier: (Artistic-2.0 OR MIT)
*
* The documentation blocks within this file are licensed under the
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file utility.h
* @brief A header for conditionally compiled debugging utilities.
* @ingroup internal_utils
*
* @internal
* This header is the central point for the library's internal debugging infrastructure.
* Its primary feature is that it behaves differently based on the `INFIX_DEBUG_ENABLED`
* preprocessor macro. This allows debugging code to be seamlessly integrated
* during development without affecting the performance or size of the final
* production binary.
*
* - **When `INFIX_DEBUG_ENABLED` is defined and non-zero (Debug Mode):**
* - It declares the `infix_dump_hex` function for detailed memory inspection.
* - It defines the `INFIX_DEBUG_PRINTF` macro, which integrates with the `double_tap`
* test harness's logging system (`note()`) if available, or falls back to a
* standard `printf`. This allows debug messages from the core library to appear
* cleanly within the test output.
*
* - **When `INFIX_DEBUG_ENABLED` is not defined or is zero (Release Mode):**
* - All debugging macros are defined as no-ops (`((void)0)`).
* - The `infix_dump_hex` function is defined as an empty `static inline` function.
* - This design ensures that all debugging code and calls are completely compiled
* out by the optimizer, resulting in zero overhead in release builds.
* @endinternal
*/
#include "common/compat_c23.h"
#include <stddef.h>
// Check if INFIX_DEBUG_ENABLED is defined and set to a non-zero value.
#if defined(INFIX_DEBUG_ENABLED) && INFIX_DEBUG_ENABLED
// The double_tap framework is only included if both debug mode AND the main
// test harness toggle are enabled. This allows for debug builds of non-test executables.
#if defined(DBLTAP_ENABLE)
#include "common/double_tap.h"
/**
* @internal
* @def INFIX_DEBUG_PRINTF(...)
* @brief A macro for printing formatted debug messages during a debug build with the test harness.
* @details In debug builds where `double_tap.h` is active, this macro wraps the `note()`
* macro, integrating debug output from the library's internals cleanly into the
* TAP-formatted test logs.
* @example
* ```c
* INFIX_DEBUG_PRINTF("Processing type %d with size %zu", type->id, type->size);
* ```
*/
#define INFIX_DEBUG_PRINTF(...) note("INFIX_DEBUG: " __VA_ARGS__)
#else
#include <stdio.h>
/**
* @internal
* @def INFIX_DEBUG_PRINTF(...)
* @brief A macro for printing formatted debug messages (printf fallback).
* @details In debug builds where the `double_tap.h` harness is *not* active (e.g., when
* building a standalone example program), this macro falls back to a standard
* `printf`, ensuring that debug messages are still visible.
*/
#define INFIX_DEBUG_PRINTF(...) \
do { \
printf("# INFIX_DEBUG: " __VA_ARGS__); \
printf("\n"); \
} while (0)
#endif // DBLTAP_ENABLE
/**
* @internal
* @brief Declares the function prototype for `infix_dump_hex` for use in debug builds.
* @details This function is an invaluable tool for inspecting the raw machine code generated
* by the JIT compiler or examining the memory layout of complex structs. It prints
* a detailed hexadecimal and ASCII dump of a memory region to the standard
* output, formatted for readability.
*
* @param data A pointer to the start of the memory block to dump.
* @param size The number of bytes to dump.
* @param title A descriptive title to print before and after the hex dump.
*/
void infix_dump_hex(const void * data, size_t size, const char * title);
#else // INFIX_DEBUG_ENABLED is NOT defined or is zero (Release Mode)
/**
* @internal
* @def INFIX_DEBUG_PRINTF(...)
* @brief A no-op macro for printing debug messages in release builds.
* @details In release builds, this macro is defined as `((void)0)`, a standard C idiom
* for creating a statement that does nothing and has no side effects. The
* compiler will completely remove any calls to it, ensuring zero performance impact.
*/
#define INFIX_DEBUG_PRINTF(...) ((void)0)
/**
* @internal
* @brief A no-op version of `infix_dump_hex` for use in release builds.
* @details This function is defined as an empty `static inline` function.
* - `static`: Prevents linker errors if this header is included in multiple files.
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>
infix/src/core/signature.c view on Meta::CPAN
}
}
else if (buffer_size > 0)
buffer[buffer_size - 1] = '\0';
return state.status;
}
/**
* @brief Serializes all defined types within a registry into a single, human-readable string.
*
* @details The output format is a sequence of definitions (e.g., `@Name = { ... };`) separated
* by newlines, suitable for logging, debugging, or saving to a file. This function
* will not print forward declarations that have not been fully defined. The order of
* definitions in the output string is not guaranteed.
*
* @param[out] buffer The output buffer to write the string into.
* @param[in] buffer_size The size of the output buffer.
* @param[in] registry The registry to serialize.
* @return `INFIX_SUCCESS` on success, or `INFIX_ERROR_INVALID_ARGUMENT` if the buffer is too small
* or another error occurs.
*/
c23_nodiscard infix_status infix_registry_print(char * buffer, size_t buffer_size, const infix_registry_t * registry) {
infix/src/core/utility.c view on Meta::CPAN
*
* SPDX-License-Identifier: (Artistic-2.0 OR MIT)
*
* The documentation blocks within this file are licensed under the
* Creative Commons Attribution 4.0 International License (CC BY 4.0).
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file utility.c
* @brief Implements internal debugging utilities.
* @ingroup internal_core
*
* @details This file provides the implementation for debugging functions that are
* conditionally compiled only when the `INFIX_DEBUG_ENABLED` preprocessor macro
* is defined and set to a non-zero value.
*
* In release builds, this entire translation unit is effectively empty, ensuring
* that debugging code has no impact on the final binary's size or performance.
* The corresponding function declarations in `utility.h` become empty inline
* stubs, which are optimized away by the compiler.
*/
// This file is only compiled if debugging is enabled.
#if defined(INFIX_DEBUG_ENABLED) && INFIX_DEBUG_ENABLED
// Use the double-tap test harness's `note` macro for debug printing if available.
// This integrates the debug output seamlessly into the TAP test logs.
#if defined(DBLTAP_ENABLE) && defined(DBLTAP_IMPLEMENTATION)
#include "common/double_tap.h"
#else
// If not building as part of a test, fall back to a standard printf implementation.
#include <stdio.h>
#ifndef note
#define note(...) \
do { \
printf("# " __VA_ARGS__); \
printf("\n"); \
} while (0)
#endif
#endif // DBLTAP_ENABLE
#include "common/utility.h"
#include <inttypes.h>
/**
* @internal
* @brief Dumps a block of memory to standard output in a standard hexadecimal format.
*
* @details This is an essential debugging tool for inspecting the machine code generated
* by the JIT engine or for examining the memory layout of complex data structures.
* The output is formatted similarly to common hex dump utilities, with a 16-byte
* width, address offsets, hexadecimal bytes, and an ASCII representation.
*
* This function is only compiled in debug builds.
*
* @param data A pointer to the memory to dump.
* @param size The number of bytes to dump.
* @param title A descriptive title to print before the hex dump.
*/
void infix_dump_hex(const void * data, size_t size, const char * title) {
const uint8_t * byte = (const uint8_t *)data;
char line_buf[256];
char * buf_ptr;
size_t remaining_len;
infix/src/infix.c view on Meta::CPAN
* rest of the source tree. It is the entry point for the build system.
* @endinternal
*/
// 1. Error Handling: Provides the thread-local error reporting system.
// (No dependencies on other infix modules).
#include "core/error.c"
// 2. Arena Allocator: The fundamental memory management component.
// (Depends only on malloc/free).
#include "core/arena.c"
// 3. OS Executor: Handles OS-level memory management for executable code.
// (Depends on error handling, debugging utilities).
#include "jit/executor.c"
// 4. Type Registry: Manages named types.
// (Depends on arena for storage and signature parser for definitions).
#include "core/type_registry.c"
// 5. Signature Parser: Implements the high-level string-based API.
// (Depends on types, arena, and registry).
#include "core/signature.c"
// 6. Dynamic Library Loader: Implements cross-platform `dlopen`/`dlsym`.
// (Depends on error handling, types, and arena).
#include "core/loader.c"
infix/src/jit/executor.c view on Meta::CPAN
return exec;
}
/**
* @internal
* @brief Frees a block of executable memory with use-after-free hardening.
*
* @details Before freeing the memory, this function first attempts to change the
* memory protection to be inaccessible (`PROT_NONE` or `PAGE_NOACCESS`). This
* creates a "guard page" that will cause an immediate, safe crash if a dangling
* pointer to the freed trampoline is ever used, making use-after-free bugs
* much easier to detect and debug.
*
* @param exec The executable memory block to free.
*/
void infix_executable_free(infix_executable_t exec) {
if (exec.size == 0)
return;
#if defined(INFIX_OS_WINDOWS)
if (exec.rw_ptr) {
// Change protection to NOACCESS to catch use-after-free bugs immediately.
if (!VirtualProtect(exec.rw_ptr, exec.size, PAGE_NOACCESS, &(DWORD){0}))
lib/Affix.pm view on Meta::CPAN
use Config qw[%Config];
use File::Spec::Functions qw[rel2abs canonpath curdir path catdir];
use File::Basename qw[basename dirname];
use File::Find qw[find];
use File::Temp qw[tempdir];
#
my $okay = 0;
#
BEGIN {
use XSLoader;
$DynaLoad::dl_debug = 1;
$okay = XSLoader::load();
my $platform
= 'Affix::Platform::' .
( ( $^O eq 'MSWin32' ) ? 'Windows' :
$^O eq 'darwin' ? 'MacOS' :
( $^O eq 'freebsd' || $^O eq 'openbsd' || $^O eq 'netbsd' || $^O eq 'dragonfly' ) ? 'BSD' :
'Unix' );
#~ warn $platform;
#~ use base $platform;
lib/Affix.pod view on Meta::CPAN
=item C<memset( $dest, $ch, $count )>
=item C<memcpy( $dest, $src, $count )>
=item C<memmove( $dest, $src, $count )>
=back
=head2 C<dump( $ptr, $length )>
Dumps C<$length> bytes of raw data from a given point in memory to STDOUT in a hex editor style. Useful for debugging
layout issues.
=head1 INTROSPECTION
=head2 C<sizeof( ... )>
my $size = sizeof( Int );
my $size_rect = sizeof( Struct[ x => Int, y => Int ] );
Returns the size, in bytes, of the Type passed to it.
lib/Affix.pod view on Meta::CPAN
=head1 UTILITIES
=head2 C<get_system_error()>
Returns the C<errno> (Linux/Unix) or C<GetLastError> (Windows) from the most recent FFI call. This must be called
immediately after the function invokes to ensure accuracy.
=head2 C<sv_dump( $sv )>
Dumps the internal structure of a Perl scalar to STDERR. Useful for debugging Perl internals.
=head1 COMPILER WRAPPER
Affix includes a lightweight, cross-platform C compiler wrapper C<Affix::Compiler>. This is useful for compiling small
C stubs or "glue" code at runtime to bridge complex macros or inline functions that cannot be bound directly.
use Affix;
my $compiler = Affix::Compiler->new(
name => 'my_wrapper',
source => [ 'wrapper.c' ]
lib/Affix/Platform/Windows.pm view on Meta::CPAN
$clibname = 'msvcrt';
}
elsif ( $version <= 13 ) {
$clibname = sprintf( 'msvcr%d', $version * 10 );
}
else {
# CRT not directly loadable (see python/cpython#23606)
return undef;
}
# Check for debug build
my $debug_suffix = '_d'; # Assuming debug suffix is '_d'
my $suffixes = join '|', map quotemeta, @DynaLoader::dl_extensions;
if ( $debug_suffix =~ /$suffixes/ ) {
$clibname .= $debug_suffix;
}
return "$clibname.dll";
}
sub get_msvcrt_version() {
open( my $pipe, '-|', 'dumpbin /headers msvcrt.dll', 'r' ) or return;
my $dumpbin_output;
$dumpbin_output .= $_ while <$pipe>;
close $pipe;
return $1 if $dumpbin_output && $dumpbin_output =~ /FileVersion\s+(\d+\.\d+\.\d+\.\d+)/;