view release on metacpan or search on metacpan
- 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...
- 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
- 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).
- [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/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 ...
- \[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.
- \[infix] Fixed a logic error in the System V reverse argument classifier where vectors were defaulting to `INTEGER` class, causing the trampoline to look in `RDI`/`RSI` instead of `XMM` registers.
Explicitly hints types for [Variadic Functions](#variadic-functions-varargs).
```
# Hint that we are passing a Float, not a Double
coerce( Float, 1.5 );
```
# VARIABLES & PINNING
Affix allows you to link Perl scalars directly to global or external variables exported by C libraries.
## `pin( $var, $lib, $symbol, $type )`
Binds a scalar to a C variable. Reading the scalar reads C memory; writing to it updates C memory immediately.
```perl
# C: extern int errno;
my $errno;
pin $errno, libc(), 'errno', Int;
existence.
```perl
# Find libssl.so.1.1 or libssl.1.1.dylib
my $path = locate_lib('ssl', '1.1');
say "Found SSL at: $path" if $path;
```
### `find_symbol( $lib_handle, $symbol_name )`
Looks up an exported symbol (function or global variable) inside an already-loaded `Affix::Lib` handle. Returns an
unmanaged `Affix::Pointer` (Pin) of type `Pointer[Void]` pointing to the memory address of the symbol.
```perl
my $lib = load_library('m');
# Get the raw memory address of the 'pow' function
my $pow_ptr = find_symbol($lib, 'pow');
if ($pow_ptr) {
say sprintf("pow() is located at: 0x%X", address($pow_ptr));
```perl
typedef Rect => Struct[ x => Int, y => Int, w => Int, h => Int ];
# C: offsetof(Rect, w);
say offsetof( Rect(), 'w' ); # 8 (skips x and y, 4 bytes each)
```
### `types()`
Returns a list of all custom type names currently registered in Affix's global type registry via `typedef`. In scalar
context, returns the total number of registered types.
```perl
my @known_types = types();
say "Registered types: " . join(', ', @known_types);
```
# INTERFACING WITH OTHER LANGUAGES
Because Affix dynamically loads symbols according to the C Application Binary Interface (C ABI), it can interact with
# Exposes IV flags, memory addresses of the SV head, etc.
```
## Advanced Debugging
### `set_destruct_level( $level )`
Sets the internal `PL_perl_destruct_level` variable.
When testing XS/FFI code for memory leaks using tools like Valgrind or AddressSanitizer, you often want Perl to
meticulously clean up all global memory during its destruction phase (otherwise the leak checker will be flooded with
false-positive "leaks" that are actually just memory Perl intentionally leaves to the OS to reclaim).
```
# Call this at the start of your script when running under Valgrind
set_destruct_level(2);
```
# COMPANION MODULES
Affix ships with two powerful companion modules to streamline your FFI development:
- [**Affix::Wrap**](https://metacpan.org/pod/Affix%3A%3AWrap): Parses C/C++ headers using the Clang AST to automatically generate Affix bindings for entire libraries.
- [**Affix::Build**](https://metacpan.org/pod/Affix%3A%3ABuild): A polyglot builder that compiles inline C, C++, Rust, Zig, Go, and 15+ other languages into dynamic libraries you can bind instantly.
# THREAD SAFETY & CONCURRENCY
Affix bridges Perl (a single-threaded interpreter, generally) with libraries that may be multi-threaded. This creates
potential hazards that you must manage.
## 1. Initialization Phase vs. Execution Phase
Functions that modify Affix's global state are **not thread-safe**. You must perform all definitions in the main thread
before starting any background threads or loops in the library.
Unsafe operations that you should never call from Callbacks or in a threaded context:
- `affix( ... )` - Binding new functions.
- `typedef( ... )` - Registering new types.
## 2. Callbacks
When passing a Perl subroutine as a `Callback`, avoid performing complex Perl operations like loading modules or
infix/include/infix/infix.h view on Meta::CPAN
*/
INFIX_API void infix_library_close(infix_library_t *);
/**
* @brief Retrieves the address of a symbol (function or variable) from a loaded library.
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the symbol to look up.
* @return A pointer to the symbol's address, or `nullptr` if not found.
*/
INFIX_API INFIX_NODISCARD void * infix_library_get_symbol(infix_library_t *, const char *);
/**
* @brief Reads the value of a global variable from a library into a buffer.
*
* Uses the signature parser to determine the size of the variable to ensure the
* correct number of bytes are copied.
*
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the global variable.
* @param[in] type_signature The `infix` signature string describing the variable's type.
* @param[out] buffer A pointer to the destination buffer to receive the data.
* @param[in] registry An optional registry for resolving named types in the signature.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
INFIX_API INFIX_NODISCARD infix_status
infix_read_global(infix_library_t *, const char *, const char *, void *, infix_registry_t *);
/**
* @brief Writes data from a buffer into a global variable in a library.
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the global variable.
* @param[in] type_signature The `infix` signature string describing the variable's type.
* @param[in] buffer A pointer to the source buffer containing the data to write.
* @param[in] registry An optional registry for resolving named types in the signature.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
INFIX_API INFIX_NODISCARD infix_status
infix_write_global(infix_library_t *, const char *, const char *, void *, infix_registry_t *);
/** @} */ // end of exports_api group
/**
* @defgroup manual_api Manual API
* @brief A lower-level, programmatic API for creating trampolines from `infix_type` objects.
*
* This API is intended for performance-critical applications or language bindings that
* need to construct type information dynamically without the overhead of string parsing.
* All `infix_type` objects passed to these functions must be allocated from an `infix_arena_t`.
* @{
*/
infix/src/common/double_tap.h view on Meta::CPAN
#elif defined(__GNUC__) || defined(__clang__)
#define TAP_ATOMIC_SIZE_T size_t
#define TAP_ATOMIC_FETCH_ADD(ptr, val) __atomic_fetch_add(ptr, (size_t)(val), __ATOMIC_SEQ_CST)
#define TAP_ATOMIC_INIT(val) = val
#else
// Fallback for older compilers without atomics support. This is not thread-safe.
#define TAP_ATOMIC_SIZE_T size_t
#define TAP_ATOMIC_FETCH_ADD(ptr, val) ((*ptr) += (val))
#define TAP_ATOMIC_INIT(val) = val
#if !defined(_MSC_VER)
#warning "Compiler does not support C11 atomics or GCC builtins; global counters will not be thread-safe."
#endif
#endif
#if defined(__OpenBSD__)
// OpenBSD has known issues with TLS cleanup in some linking scenarios.
// Disable TLS to prevent segfaults at exit.
#define TAP_THREAD_LOCAL
#elif defined(__cplusplus)
#define TAP_THREAD_LOCAL thread_local
#elif defined(_MSC_VER)
infix/src/common/double_tap.h view on Meta::CPAN
char skip_reason[256]; /**< The reason for the current `skip_all`. */
} tap_state_t;
#define MAX_DEPTH 16 /**< Maximum nesting depth for subtests. */
#define NO_PLAN ((size_t)-1) /**< Sentinel value for an undeclared plan. */
/** @internal The thread-local stack of test states. */
static TAP_THREAD_LOCAL tap_state_t state_stack[MAX_DEPTH];
/** @internal A pointer to the current test state on the thread-local stack. */
static TAP_THREAD_LOCAL tap_state_t * current_state = NULL;
/** @internal A global, thread-safe counter for the total number of failed tests across all threads. */
static TAP_ATOMIC_SIZE_T g_total_failed TAP_ATOMIC_INIT(0);
// One-Time Initialization for TAP Header
#if defined(_WIN32) || defined(__CYGWIN__)
static INIT_ONCE g_tap_init_once = INIT_ONCE_STATIC_INIT;
static BOOL CALLBACK _tap_init_routine(PINIT_ONCE initOnce, PVOID param, PVOID * context) {
(void)initOnce;
(void)param;
(void)context;
printf("TAP version %d\n", TAP_VERSION);
infix/src/common/double_tap.h view on Meta::CPAN
va_start(args, name);
vsnprintf(name_buffer, sizeof(name_buffer), name, args);
va_end(args);
}
current_state->count++;
if (!condition) {
if (current_state->todo)
current_state->failed_todo++;
else {
current_state->failed++;
if (current_state == &state_stack[0]) // Only increment global fail count for top-level tests
TAP_ATOMIC_FETCH_ADD(&g_total_failed, 1);
}
}
print_indent(stdout);
printf("%s %llu", condition ? "ok" : "not ok", (unsigned long long)current_state->count);
if (name_buffer[0] != '\0')
printf(" - %s", name_buffer);
if (current_state->todo)
printf(" # TODO %s", current_state->todo_reason);
printf("\n");
infix/src/core/cache.c view on Meta::CPAN
*/
/**
* @file cache.c
* @brief Implements trampoline deduplication and caching.
* @ingroup internal_core
*/
#include "common/infix_internals.h"
#include <stdlib.h>
#include <string.h>
/** @internal Initial number of buckets for the global trampoline cache. */
#define CACHE_BUCKETS 1021
/** @internal A single entry in the global trampoline cache. */
typedef struct _cache_entry_t {
infix_forward_t * trampoline; /**< The cached trampoline handle. */
struct _cache_entry_t * next; /**< Next entry in the hash bucket chain. */
} _cache_entry_t;
/** @internal The global hash table for forward trampolines. */
static _cache_entry_t * g_trampoline_cache[CACHE_BUCKETS];
/** @internal Mutex to protect the global cache. */
static infix_mutex_t g_cache_mutex = INFIX_MUTEX_INITIALIZER;
/**
* @internal
* @brief Computes a hash for a cache lookup.
*/
static uint64_t _cache_hash(const char * sig, void * target_fn, bool is_safe) {
uint64_t h = 5381;
int c;
while ((c = *sig++))
h = ((h << 5) + h) + c;
h ^= (uint64_t)(uintptr_t)target_fn;
if (is_safe)
h ^= 0x123456789ABCDEF0ULL;
return h;
}
/**
* @internal
* @brief Searches the global cache for an existing trampoline.
* @return The cached trampoline with its ref_count incremented, or NULL if not found.
*/
infix_forward_t * _infix_cache_lookup(const char * signature, void * target_fn, bool is_safe) {
uint64_t h = _cache_hash(signature, target_fn, is_safe);
size_t index = h % CACHE_BUCKETS;
INFIX_MUTEX_LOCK(&g_cache_mutex);
for (_cache_entry_t * entry = g_trampoline_cache[index]; entry; entry = entry->next) {
if (entry->trampoline->target_fn == target_fn && entry->trampoline->is_safe == is_safe &&
strcmp(entry->trampoline->signature, signature) == 0) {
infix/src/core/cache.c view on Meta::CPAN
INFIX_MUTEX_UNLOCK(&g_cache_mutex);
return entry->trampoline;
}
}
INFIX_MUTEX_UNLOCK(&g_cache_mutex);
return NULL;
}
/**
* @internal
* @brief Inserts a trampoline into the global cache.
*/
void _infix_cache_insert(infix_forward_t * trampoline) {
uint64_t h = _cache_hash(trampoline->signature, trampoline->target_fn, trampoline->is_safe);
size_t index = h % CACHE_BUCKETS;
INFIX_MUTEX_LOCK(&g_cache_mutex);
// Double check it's not already there
for (_cache_entry_t * entry = g_trampoline_cache[index]; entry; entry = entry->next) {
if (entry->trampoline->target_fn == trampoline->target_fn &&
entry->trampoline->is_safe == trampoline->is_safe &&
infix/src/core/cache.c view on Meta::CPAN
entry->trampoline = trampoline;
trampoline->ref_count++; // Cache reference
entry->next = g_trampoline_cache[index];
g_trampoline_cache[index] = entry;
INFIX_MUTEX_UNLOCK(&g_cache_mutex);
}
/**
* @internal
* @brief Clears all entries from the global cache.
*/
void _infix_cache_clear(void) {
INFIX_MUTEX_LOCK(&g_cache_mutex);
for (size_t i = 0; i < CACHE_BUCKETS; ++i) {
_cache_entry_t * entry = g_trampoline_cache[i];
while (entry) {
_cache_entry_t * next = entry->next;
if (--entry->trampoline->ref_count == 0)
_infix_forward_destroy_internal(entry->trampoline);
infix_free(entry);
infix/src/core/cache.c view on Meta::CPAN
trampoline->ref_count--; // Decrement since cache no longer holds it
return true;
}
p = &((*p)->next);
}
return false;
}
/**
* @internal
* @brief Removes a trampoline from the global cache.
* @return True if found and removed.
*/
bool _infix_cache_remove(infix_forward_t * trampoline) {
INFIX_MUTEX_LOCK(&g_cache_mutex);
bool result = _cache_remove_no_lock(trampoline);
INFIX_MUTEX_UNLOCK(&g_cache_mutex);
return result;
}
/**
infix/src/core/loader.c view on Meta::CPAN
*
* SPDX-License-Identifier: CC-BY-4.0
*/
/**
* @file loader.c
* @brief Implements cross-platform dynamic library loading.
* @ingroup internal_core
*
* This module provides a platform-agnostic API for opening shared libraries
* (`.dll`, `.so`, `.dylib`), looking up symbols within them, and reading or
* writing to exported global variables. It abstracts away the differences
* between the Windows API (`LoadLibrary`, `GetProcAddress`) and the POSIX API
* (`dlopen`, `dlsym`).
*
* The functions `infix_read_global` and `infix_write_global` combine this dynamic
* loading capability with the `infix` type system to safely interact with global
* variables of any type described by a signature string.
*/
#include "common/infix_internals.h"
#if defined(INFIX_OS_WINDOWS)
#include <windows.h>
#else
#include <dlfcn.h>
#endif
/**
* @brief Opens a dynamic library and returns a handle to it.
infix/src/core/loader.c view on Meta::CPAN
}
/**
* @brief Retrieves the address of a symbol (function or variable) from a loaded library.
*
* This is a cross-platform wrapper around `GetProcAddress` (Windows) and `dlsym` (POSIX).
*
* @note On POSIX, `dlsym` returning `NULL` is not a definitive error condition, as a
* symbol's address could itself be `NULL`. The official way to check for an
* error is to call `dlerror()` afterwards. This function does not perform
* that check and does not set the `infix` error state, as its primary callers
* (`infix_read_global`, etc.) will set a more specific `INFIX_CODE_SYMBOL_NOT_FOUND`
* error if the lookup fails.
*
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the symbol to look up (e.g., `"my_function"`).
* @return A `void*` pointer to the symbol's address, or `nullptr` if not found.
*/
INFIX_API INFIX_NODISCARD void * infix_library_get_symbol(infix_library_t * lib, const char * symbol_name) {
if (lib == nullptr || lib->handle == nullptr || symbol_name == nullptr)
return nullptr;
#if defined(INFIX_OS_WINDOWS)
return (void *)GetProcAddress((HMODULE)lib->handle, symbol_name);
#else
return dlsym(lib->handle, symbol_name);
#endif
}
/**
* @brief Reads the value of an exported global variable from a library into a buffer.
*
* This function first looks up the symbol's address. It then uses the `infix`
* signature parser (`infix_type_from_signature`) to determine the size of the
* variable. This ensures that the correct number of bytes are copied from the
* library's data segment into the user's buffer, preventing buffer overflows.
*
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the global variable.
* @param[in] type_signature The `infix` signature string describing the variable's type (e.g., `"int32"`,
* `"{double,double}"`).
* @param[out] buffer A pointer to the destination buffer to receive the data. This buffer must be large enough to hold
* the type described by the signature.
* @param[in] registry An optional registry for resolving named types in the signature.
* @return `INFIX_SUCCESS` on success, or an error code on failure (e.g., symbol not found, invalid signature).
*/
INFIX_API INFIX_NODISCARD infix_status infix_read_global(infix_library_t * lib,
const char * symbol_name,
const char * type_signature,
void * buffer,
infix_registry_t * registry) {
if (buffer == nullptr)
return INFIX_ERROR_INVALID_ARGUMENT;
void * symbol_addr = infix_library_get_symbol(lib, symbol_name);
if (symbol_addr == nullptr) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_SYMBOL_NOT_FOUND, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
infix/src/core/loader.c view on Meta::CPAN
infix_arena_t * arena = nullptr;
infix_status status = infix_type_from_signature(&type, &arena, type_signature, registry);
if (status != INFIX_SUCCESS)
return status;
// Safely copy the data using the parsed size.
infix_memcpy(buffer, symbol_addr, type->size);
infix_arena_destroy(arena);
return INFIX_SUCCESS;
}
/**
* @brief Writes data from a buffer into an exported global variable in a library.
*
* @details This function is analogous to `infix_read_global`. It finds the symbol's
* address and uses the signature string to determine the correct number of bytes
* to copy from the source buffer to the library's memory.
*
* @note This operation assumes that the memory page containing the global variable
* is writable. This is typical for `.data` or `.bss` segments but may fail
* if the variable is in a read-only segment (e.g., a `const` global). The
* function does not attempt to change memory permissions.
*
* @param[in] lib The library handle.
* @param[in] symbol_name The name of the global variable.
* @param[in] type_signature The `infix` signature string describing the variable's type.
* @param[in] buffer A pointer to the source buffer containing the data to write.
* @param[in] registry An optional registry for resolving named types in the signature.
* @return `INFIX_SUCCESS` on success, or an error code on failure.
*/
INFIX_API INFIX_NODISCARD infix_status infix_write_global(infix_library_t * lib,
const char * symbol_name,
const char * type_signature,
void * buffer,
infix_registry_t * registry) {
if (buffer == nullptr)
return INFIX_ERROR_INVALID_ARGUMENT;
void * symbol_addr = infix_library_get_symbol(lib, symbol_name);
if (symbol_addr == nullptr) {
_infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_SYMBOL_NOT_FOUND, 0);
return INFIX_ERROR_INVALID_ARGUMENT;
}
infix_type * type = nullptr;
infix_arena_t * arena = nullptr;
infix_status status = infix_type_from_signature(&type, &arena, type_signature, registry);
if (status != INFIX_SUCCESS)
return status;
// Note: This assumes the memory page containing the global is writable.
// This is standard for data segments but could fail in unusual cases.
infix_memcpy(symbol_addr, buffer, type->size);
infix_arena_destroy(arena);
return INFIX_SUCCESS;
}
infix/src/core/signature.c view on Meta::CPAN
return INFIX_SUCCESS;
}
// High-Level API Implementation
/**
* @internal
* @brief The internal entry point for the signature parser (the "Parse" stage).
*
* This function takes a signature string and produces a raw, unresolved type
* graph in a new, temporary arena. It is the core parsing logic, separated from the
* higher-level functions that manage the full data pipeline. It is careful not to
* modify the global error context string (`g_infix_last_signature_context`), which
* is the responsibility of its public API callers.
*
* @param[out] out_type On success, receives the parsed type graph.
* @param[out] out_arena On success, receives the temporary arena holding the graph. The caller is responsible for
* destroying it.
* @param[in] signature The signature string to parse.
* @return `INFIX_SUCCESS` on success.
*/
c23_nodiscard infix_status _infix_parse_type_internal(infix_type ** out_type,
infix_arena_t ** out_arena,
infix/src/core/type_registry.c view on Meta::CPAN
final_status = INFIX_ERROR_INVALID_ARGUMENT;
infix_arena_destroy(parser_arena);
goto cleanup;
}
}
// Prepare the new definition.
infix_type * new_def = nullptr;
if (!type_to_alias->is_arena_allocated) {
// This is a static type (e.g., primitive). We MUST create a mutable
// copy in the registry's arena before we can attach a name to it.
// This prevents corrupting the global static singletons.
new_def = infix_arena_alloc(registry->arena, sizeof(infix_type), _Alignof(infix_type));
if (new_def) {
*new_def = *type_to_alias;
new_def->is_arena_allocated = true;
new_def->arena = registry->arena;
}
}
else // Dynamic type: deep copy.
new_def = _copy_type_graph_to_arena(registry->arena, type_to_alias);
lib/Affix.c view on Meta::CPAN
if (entry->lib == affix->lib_handle) {
entry->ref_count--;
if (entry->ref_count == 0) {
STRLEN klen;
const char * kstr = HePV(he, klen);
SV * key_sv = newSVpvn(kstr, klen);
if (HeKUTF8(he))
SvUTF8_on(key_sv);
// On Linux, dlclose() is notoriously dangerous for libraries that
// spawn background threads or register global handlers (Go, .NET, Audio, etc.)
// unmapping the code while these threads are active causes a SEGV.
#if defined(__linux__) || defined(__linux)
// Leak the library handle but free our wrapper
infix_free(entry->lib);
#else
infix_library_close(entry->lib);
#endif
safefree(entry);
hv_delete_ent(MY_CXT.lib_registry, key_sv, G_DISCARD, 0);
SvREFCNT_dec(key_sv);
lib/Affix.c view on Meta::CPAN
// Deep copy the type registry.
// This ensures typedefs and structs defined in the parent thread exist in the child thread,
// but the child owns its own memory arena, making it thread-safe.
if (parent_registry)
MY_CXT.registry = infix_registry_clone(parent_registry);
else
MY_CXT.registry = infix_registry_create();
if (!MY_CXT.registry)
warn("Failed to initialize the global type registry in new thread");
// Don't ccall _register_core_types here if we cloned, because the clone already contains @SV, @File, etc.
if (!parent_registry)
_register_core_types(MY_CXT.registry);
XSRETURN_EMPTY;
}
void boot_Affix(pTHX_ CV * cv) {
dVAR;
lib/Affix.c view on Meta::CPAN
my_perl = (PerlInterpreter *)PERL_GET_CONTEXT;
#endif
MY_CXT_INIT;
MY_CXT.lib_registry = newHV();
MY_CXT.callback_registry = newHV();
MY_CXT.enum_registry = newHV();
MY_CXT.coercion_cache = newHV();
MY_CXT.stash_pointer = nullptr;
MY_CXT.registry = infix_registry_create();
if (!MY_CXT.registry)
croak("Failed to initialize the global type registry");
_register_core_types(MY_CXT.registry);
// Helper macro to define and export an XSUB in one line.
// Assumes C function is Affix_name and Perl sub is Affix::name.
#define XSUB_EXPORT(name, proto, tag) \
(void)newXSproto_portable("Affix::" #name, Affix_##name, __FILE__, proto); \
export_function("Affix", #name, tag)
{
lib/Affix.pod view on Meta::CPAN
=head2 C<coerce( $type, $value )>
Explicitly hints types for L<Variadic Functions|/Variadic Functions (VarArgs)>.
# Hint that we are passing a Float, not a Double
coerce( Float, 1.5 );
=head1 VARIABLES & PINNING
Affix allows you to link Perl scalars directly to global or external variables exported by C libraries.
=head2 C<pin( $var, $lib, $symbol, $type )>
Binds a scalar to a C variable. Reading the scalar reads C memory; writing to it updates C memory immediately.
# C: extern int errno;
my $errno;
pin $errno, libc(), 'errno', Int;
$errno = 0; # Writes directly to C memory
lib/Affix.pod view on Meta::CPAN
Searches for a library using Affix's discovery engine and returns its absolute file path as a string. It B<does not>
load the library into memory. This is useful if you need to pass the library path to another tool or check for its
existence.
# Find libssl.so.1.1 or libssl.1.1.dylib
my $path = locate_lib('ssl', '1.1');
say "Found SSL at: $path" if $path;
=head3 C<find_symbol( $lib_handle, $symbol_name )>
Looks up an exported symbol (function or global variable) inside an already-loaded C<Affix::Lib> handle. Returns an
unmanaged C<Affix::Pointer> (Pin) of type C<Pointer[Void]> pointing to the memory address of the symbol.
my $lib = load_library('m');
# Get the raw memory address of the 'pow' function
my $pow_ptr = find_symbol($lib, 'pow');
if ($pow_ptr) {
say sprintf("pow() is located at: 0x%X", address($pow_ptr));
}
lib/Affix.pod view on Meta::CPAN
Returns the byte offset of a named field within an Aggregate type (Struct or Union). This is incredibly useful for
manual pointer arithmetic.
typedef Rect => Struct[ x => Int, y => Int, w => Int, h => Int ];
# C: offsetof(Rect, w);
say offsetof( Rect(), 'w' ); # 8 (skips x and y, 4 bytes each)
=head3 C<types()>
Returns a list of all custom type names currently registered in Affix's global type registry via C<typedef>. In scalar
context, returns the total number of registered types.
my @known_types = types();
say "Registered types: " . join(', ', @known_types);
=head1 INTERFACING WITH OTHER LANGUAGES
Because Affix dynamically loads symbols according to the C Application Binary Interface (C ABI), it can interact with
libraries written in almost any language, provided they expose their functions correctly. Companion modules like
L<Affix::Build> make compiling these languages seamless.
lib/Affix.pod view on Meta::CPAN
sv_dump($val);
# Exposes IV flags, memory addresses of the SV head, etc.
=head2 Advanced Debugging
=head3 C<set_destruct_level( $level )>
Sets the internal C<PL_perl_destruct_level> variable.
When testing XS/FFI code for memory leaks using tools like Valgrind or AddressSanitizer, you often want Perl to
meticulously clean up all global memory during its destruction phase (otherwise the leak checker will be flooded with
false-positive "leaks" that are actually just memory Perl intentionally leaves to the OS to reclaim).
# Call this at the start of your script when running under Valgrind
set_destruct_level(2);
=head1 COMPANION MODULES
Affix ships with two powerful companion modules to streamline your FFI development:
=over
lib/Affix.pod view on Meta::CPAN
=back
=head1 THREAD SAFETY & CONCURRENCY
Affix bridges Perl (a single-threaded interpreter, generally) with libraries that may be multi-threaded. This creates
potential hazards that you must manage.
=head2 1. Initialization Phase vs. Execution Phase
Functions that modify Affix's global state are B<not thread-safe>. You must perform all definitions in the main thread
before starting any background threads or loops in the library.
Unsafe operations that you should never call from Callbacks or in a threaded context:
=over
=item * C<affix( ... )> - Binding new functions.
=item * C<typedef( ... )> - Registering new types.
lib/Affix/Build.pm view on Meta::CPAN
$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" : '';
$libname = $build_dir->child("$prefix$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++';
# Parse global flags...
@cflags = map { chomp; $_ } grep { defined && length } Text::ParseWords::parse_line( q/ /, 1, $flags->{cflags} // '' );
@cxxflags = map { chomp; $_ } grep { defined && length } Text::ParseWords::parse_line( q/ /, 1, $flags->{cxxflags} // '' );
@ldflags = map { chomp; $_ } grep { defined && length } Text::ParseWords::parse_line( q/ /, 1, $flags->{ldflags} // '' );
}
method add ( $input, %args ) {
$_lib = (); # Reset cached library handle
my ( $path, $lang );
if ( ref $input eq 'SCALAR' ) { # Inline source code
$args{lang} // croak q[Parameter 'lang' (extension) is required for inline source];
lib/Affix/Build.pod view on Meta::CPAN
=item * B<C<name>>: The base name of the resulting library (default: C<'affix_lib'>). The compiler will automatically append the OS-specific extension (C<.dll> on Windows, C<.dylib> on macOS, C<.so> on Linux).
=item * B<C<version>>: Optional version string to append to the library name.
=item * B<C<build_dir>>: The directory where intermediate artifacts and the final library will be written. If not provided, a temporary directory is created via L<Path::Tiny>.
=item * B<C<clean>>: If true, the generated C<build_dir> and all its contents will be deleted when the object is destroyed (default: C<0>).
=item * B<C<debug>>: If true, the exact system commands executed by the compiler will be printed to C<STDERR> (default: C<0>).
=item * B<C<flags>>: A HashRef of global flags applied across all files. Keys can be C<cflags>, C<cxxflags>, and C<ldflags>.
=item * B<C<os>>: Override the detected operating system (defaults to C<$^O>).
=back
=head1 METHODS
=head2 C<add( $input, %args )>
Adds a source file to the build manifest.
lib/Affix/Build.pod view on Meta::CPAN
=item * B<Compiler:> C<go> or C<gccgo>
=back
B<Caveats:>
=over
=item * In Polyglot mode, requires C<gccgo> or standard C<go> with C<c-archive> support.
=item * The Go runtime spins up background threads (for GC and scheduling) that do not shut down cleanly when a shared library is unloaded. This can cause access violations on Windows during program exit. If you encounter segmentation faults during g...
=back
=head3 Zig
=over
=item * B<Extensions:> C<.zig>
=item * B<Compiler:> C<zig>
lib/Affix/Wrap.pod view on Meta::CPAN
# You may call functions exported by the lib immediately
$wrapper->wrap('libsqlite3.so', __PACKAGE__);
# Option 2: Generate a standalone Perl module file to disk
# This should get you started on a library wrapper you'll eventually put on CPAN
$wrapper->generate('libsqlite3.so', 'My::SQLite', 'lib/My/SQLite.pm');
=head1 DESCRIPTION
C<Affix::Wrap> is a frictionless binding generator that bridges C/C++ header files and L<Affix>. It parses headers to
extract functions, structs, enums, typedefs, macros, and global variables, automatically converting this information
into L<Affix> definitions.
It is designed to facilitate two primary developer workflows:
=over
=item 1. B<Rapid Prototyping (Runtime Wrapping)>
Parse headers on the fly and inject bindings directly into your running Perl environment via C<wrap()>. This is perfect
for private tooling, experimental scripts, or whenever you want to avoid the boilerplate of a dedicated FFI module.
lib/Affix/Wrap.pod view on Meta::CPAN
An enumeration.
=over
=item * C<affix_type>: Returns signature string C<Enum[ Name =E<gt> Val, ... ]>. String values/expressions in enums are quoted automatically to prevent eval errors.
=back
=head2 Affix::Wrap::Variable
A global C<extern> variable.
=over
=item * C<affix_type>: Returns string C<pin my $var, $lib, name =E<gt> Type>.
=item * C<affix( $lib, $pkg )>: Installs the variable accessor into C<$pkg>.
=back
=head2 Affix::Wrap::Macro
t/003_pin.t view on Meta::CPAN
$|++;
my $C_CODE = <<'END_C';
#include "std.h"
//ext: .c
#include <stdint.h>
#include <stdbool.h>
#include <string.h> // For strcmp
#include <stdlib.h> // For malloc
DLLEXPORT int global_counter = 100;
DLLEXPORT char global_buffer[64] = "Initial";
DLLEXPORT void set_global_counter(int value) { global_counter = value;}
DLLEXPORT int get_global_counter(void) { return global_counter;}
DLLEXPORT void set_int_deep(int*** ptr, int val) {
if (ptr && *ptr && **ptr) {
***ptr = val;
}
}
END_C
#
my $lib_path = compile_ok($C_CODE);
ok( $lib_path && -e $lib_path, 'Compiled a test shared library successfully' );
#
isa_ok my $get = wrap( $lib_path, 'get_global_counter', [] => Int ), ['Affix'];
isa_ok my $set = wrap( $lib_path, 'set_global_counter', [Int] => Void ), ['Affix'];
is $get->(), 100, 'Initial global value read correctly via function';
$set->(500);
is $get->(), 500, 'Global value modified via function';
#
my $pinned_int;
ok pin( $pinned_int, $lib_path, 'global_counter', Int ), 'Pin global_counter';
is $pinned_int, 500, 'Pinned scalar matches current global value';
$pinned_int = 999; # Write via magic
is $get->(), 999, 'Writing to pinned scalar updates C global';
$set->(42); # Use the wrapped function, not direct call
is $pinned_int, 42, 'Modifying C global updates pinned scalar';
ok unpin($pinned_int), 'Unpin variable';
$pinned_int = 0;
is $get->(), 42, 'Unpinned variable detached from C global';
#
my $pinned_buf;
ok pin( $pinned_buf, $lib_path, 'global_buffer', Array [ Char, 64 ] ), 'Pin char array';
is $pinned_buf, "Initial", 'Read C string from pinned array';
#
my $sym = find_symbol( load_library($lib_path), 'global_buffer' );
#
my $sym_arr = Affix::cast( $sym, Array [ Char, 64 ] );
$$sym_arr = "Perl was here";
is $pinned_buf, "Perl was here", 'Writing string to pinned array persisted in C memory';
done_testing;
t/017_affix_build.t view on Meta::CPAN
end
run_test( 'swift', 'Swift', <<~'', 'add_swift', 'swiftc' );
@_cdecl("add_swift")
public func add_swift(a: Int32, b: Int32) -> Int32 {
return a + b
}
run_test( 'assembly', 'Assembly', $Config{archname} =~ /arm64|aarch64/ ? <<~'' : $^O eq 'MSWin32' ? <<~'': <<~'', 'add_asm', 'nasm' );
; ARM64: add w0, w0, w1
.global add_asm
.text
.align 2
add_asm:
add w0, w0, w1
ret
; Win64 x86_64: RCX + RDX -> RAX
global add_asm
section .text
add_asm:
mov eax, ecx
add eax, edx
ret
; SysV x86_64: RDI + RSI -> RAX
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.
t/017_affix_build.t view on Meta::CPAN
F90
# Assembly for optimization
my $asm_bin;
my $asm_src;
my $asm_file_name;
if ( $Config{archname} =~ /arm64|aarch64/ ) {
$asm_bin = $Config{cc};
$asm_file_name = 'fast.s';
$asm_src = <<~'' }
.global asm_inc
.text
.align 2
asm_inc:
add w0, w0, #1
ret
elsif ( $^O eq 'MSWin32' ) {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, ecx
inc eax
ret
else {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, edi
inc eax
ret
skip_all "Missing Assembler ($asm_bin)" unless bin_path($asm_bin);
my $asm_file = $TMP_DIR->child($asm_file_name);
$asm_file->spew_utf8($asm_src);
#
t/017_affix_build.t view on Meta::CPAN
integer(c_int) :: func_f
func_f=6
end function
$c->add($f6);
#
my $asm_ext = ( $Config{archname} =~ /arm64/ ) ? 's' : 'asm';
my $f7 = $TMP_DIR->child("f7.$asm_ext");
$f7->spew_utf8( ( $^O eq 'MSWin32' || $Config{archname} !~ /arm64/ ) ? <<~'' : <<~'' );
; x86/x64
global func_asm
section .text
func_asm:
mov eax, 7
ret
; ARM64
.global func_asm
.text
func_asm:
mov w0, #7
ret
$c->add($f7);
#
ok( lives { $c->link() }, 'Linked Kitchen Sink' ) or note $@;
#
enjoin( $c->libname, 'func_c', 'func_cpp', 'func_rs', 'func_zig', 'func_d', 'func_f', 'func_asm' );
t/019_fileio.t view on Meta::CPAN
# Verify via C function
log_message( $logger, 'First message' );
log_message( $logger, 'Second message' );
# Verify Perl side struct access
# Note: Pulling a File handle usually creates a new GLOB wrapper around the FILE*
# Since we own $fh, let's verify checking against undef works
my $logger_struct = cast( $logger, Logger() ); # View as struct
my $retrieved_fh = $logger_struct->{log_file};
ok $retrieved_fh, 'Retrieved filehandle from struct';
is ref($retrieved_fh), 'GLOB', 'It is a glob';
# Write from Perl using retrieved handle
# print {$retrieved_fh} "From Perl\n"; # Careful, might double-close if not careful
# Check file content
open my $check, '<', $filename;
my @lines = <$check>;
close $check;
is scalar(@lines), 2, 'File has 2 lines';
like $lines[0], qr/\[1\] First message/, 'Line 1 matches';
like $lines[1], qr/\[2\] Second message/, 'Line 2 matches';
t/019_fileio.t view on Meta::CPAN
subtest 'File inside Struct (Value Return)' => sub {
my ( $fh, $filename ) = tempfile();
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\n" ), 'syswrite to the handle from Perl';
# To avoid double-close warnings, we let Perl handle cleanup of the glob
# but be careful about explicit closes.
undef $logger_hash;
# Check
open my $check, '<', $filename;
my $content = <$check>;
close $check;
is $content, "Direct write from Perl\n", 'Handle returned in struct is usable';
close $fh;
};
t/019_fileio.t view on Meta::CPAN
# Allocate struct memory
my $logger = malloc( sizeof( Logger2() ) );
# Pass Perl filehandle to C. C stores the PerlIO* address.
init_logger2( $logger, $fh );
# Verify we can retrieve it back as a Glob
my $logger_struct = cast( $logger, Logger2() );
my $retrieved_fh = $logger_struct->{handle};
ok $retrieved_fh, 'Retrieved filehandle from struct';
is ref($retrieved_fh), 'GLOB', 'It is a glob';
# Verify it points to the same stream by writing to it
syswrite $retrieved_fh, "Appended via Struct\n";
close $fh;
# Check file content
open my $check, '<', $filename;
my @lines = <$check>;
close $check;
is scalar(@lines), 2, 'File has 2 lines';
t/019_fileio.t view on Meta::CPAN
like $lines[1], qr/Appended via Struct/, 'Line 2 matches';
free($logger);
};
subtest 'PerlIO inside Struct (Value Return)' => sub {
my ( $fh, $filename ) = tempfile();
# Call C function returning a struct by value
my $logger_hash = create_logger2($fh);
is $logger_hash->{counter}, 100, 'Counter is correct';
ok $logger_hash->{handle}, 'Got filehandle back';
is ref( $logger_hash->{handle} ), 'GLOB', 'It is a glob';
# Write using the returned handle
syswrite $logger_hash->{handle}, "Write via Value Return\n";
close $fh;
# Verify content
open my $check, '<', $filename;
my $content = <$check>;
close $check;
is $content, "Write via Value Return\n", 'Handle returned in struct is usable';
t/021_shakedown.t view on Meta::CPAN
#include "std.h"
//ext: .c
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
// Primitives & Globals for Pinning
DLLEXPORT int32_t global_counter = 100;
DLLEXPORT double global_pi = 3.14159;
DLLEXPORT char global_buffer[64] = "Initial";
DLLEXPORT int32_t get_counter() { return global_counter; }
DLLEXPORT void set_counter(int32_t v) { global_counter = v; }
// Structs & Nested Types
typedef struct {
int x;
int y;
} Point;
typedef struct {
Point top_left;
Point bottom_right;
t/025_affix_wrap.t view on Meta::CPAN
like( $sig, qr/IDLE/, 'IDLE in sig' );
like( $sig, qr/RUNNING => 5/, 'RUNNING in sig' );
};
subtest 'Functions & Variables' => sub {
my $dir = Path::Tiny->tempdir;
spew_files(
$dir,
'funcs.h' => <<'EOF',
int calc(int a);
int ptr_calc(int *p);
extern double global_val;
void cb_test(void (*callback)(int));
EOF
'main.c' => '#include "funcs.h"'
);
my $parser = $driver_class->new( project_files => [ $dir->child('funcs.h')->stringify ] );
my @objs = $parser->parse( $dir->child('main.c')->stringify, [] );
# Function
my ($f1) = grep { $_->name eq 'calc' } @objs;
ok( $f1, 'Found function calc' );
is( $f1->ret->affix_type, 'Int', 'Ret Int' );
is( $f1->args->[0]->name, 'a', 'Arg name a' );
my ($f2) = grep { $_->name eq 'ptr_calc' } @objs;
ok( $f2, 'Found function ptr_calc' );
is( $f2->args->[0]->type->affix_type, 'Pointer[Int]', 'Arg 0 is Pointer[Int]' );
# Variable
my ($var) = grep { $_->name eq 'global_val' } @objs;
ok( $var, 'Found global_val' );
isa_ok( $var, ['Affix::Wrap::Variable'] );
is( $var->type->affix_type, 'Double', 'Variable is Double' );
# CodeRef / Callback
my ($cb_func) = grep { $_->name eq 'cb_test' } @objs;
ok( $cb_func, 'Found cb_test' );
my $arg0 = $cb_func->args->[0];
isa_ok( $arg0->type, ['Affix::Wrap::Type::CodeRef'], 'Arg is CodeRef' );
if ( $arg0->type->isa('Affix::Wrap::Type::CodeRef') ) {
is( $arg0->type->ret->affix_type, 'Void', 'Callback returns Void' );
t/050_affix_build.t view on Meta::CPAN
end
run_test( 'swift', 'Swift', <<~'', 'add_swift', 'swiftc' );
@_cdecl("add_swift")
public func add_swift(a: Int32, b: Int32) -> Int32 {
return a + b
}
run_test( 'assembly', 'Assembly', $Config{archname} =~ /arm64|aarch64/ ? <<~'' : $^O eq 'MSWin32' ? <<~'': <<~'', 'add_asm', 'nasm' );
; ARM64: add w0, w0, w1
.global add_asm
.text
.align 2
add_asm:
add w0, w0, w1
ret
; Win64 x86_64: RCX + RDX -> RAX
global add_asm
section .text
add_asm:
mov eax, ecx
add eax, edx
ret
; SysV x86_64: RDI + RSI -> RAX
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.
t/050_affix_build.t view on Meta::CPAN
F90
# Assembly for optimization
my $asm_bin;
my $asm_src;
my $asm_file_name;
if ( $Config{archname} =~ /arm64|aarch64/ ) {
$asm_bin = $Config{cc};
$asm_file_name = 'fast.s';
$asm_src = <<~'' }
.global asm_inc
.text
.align 2
asm_inc:
add w0, w0, #1
ret
elsif ( $^O eq 'MSWin32' ) {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, ecx
inc eax
ret
else {
$asm_bin = 'nasm';
$asm_file_name = 'fast.asm';
$asm_src = <<~'' }
global asm_inc
section .text
asm_inc:
mov eax, edi
inc eax
ret
skip_all "Missing Assembler ($asm_bin)" unless bin_path($asm_bin);
my $asm_file = $TMP_DIR->child($asm_file_name);
$asm_file->spew_utf8($asm_src);
#
t/050_affix_build.t view on Meta::CPAN
integer(c_int) :: func_f
func_f=6
end function
$c->add($f6);
#
my $asm_ext = ( $Config{archname} =~ /arm64/ ) ? 's' : 'asm';
my $f7 = $TMP_DIR->child("f7.$asm_ext");
$f7->spew_utf8( ( $^O eq 'MSWin32' || $Config{archname} !~ /arm64/ ) ? <<~'' : <<~'' );
; x86/x64
global func_asm
section .text
func_asm:
mov eax, 7
ret
; ARM64
.global func_asm
.text
func_asm:
mov w0, #7
ret
$c->add($f7);
#
ok( lives { $c->link() }, 'Linked Kitchen Sink' ) or note $@;
#
enjoin( $c->libname, 'func_c', 'func_cpp', 'func_rs', 'func_zig', 'func_d', 'func_f', 'func_asm' );