view release on metacpan or search on metacpan
CONTRIBUTING.md view on Meta::CPAN
```bash
# For a new feature:
git checkout -b feature/add-riscv-support
# For a bug fix:
git checkout -b fix/struct-classification-bug
```
4. **Make Your Changes**: Write your code. Please adhere to the existing coding style and add comments to new or complex logic.
5. **Test Your Changes**: A pull request is far more likely to be accepted if it includes tests. If you add new functionality, please add a corresponding test case.
6. **Update the Changelog**: Add an entry to the `[Unreleased]` section of `CHANGELOG.md` describing your change.
7. **Submit a Pull Request**: Push your branch to your fork and open a pull request against the `main` branch of the original repository. Please provide a clear description of your changes and link to the relevant issue (e.g., `Fixes #123`).
## [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: `t...
### Added
- Added support for native Perl array indexing on pointers and arrays. You can now use `$ptr->[$i]` to read or write memory at an offset without manual casting.
- New `Affix::Pointer` objects for structs and unions now allow direct field access like `$ptr->{field}` without explicit casting to `LiveStruct`.
- Recursive Liveness: Unified access and `LiveStruct` now work recursively. Accessing a nested struct member returns a live view or pointer tied to the original memory block.
- All pointers returned by `malloc`, `calloc`, `cast`, etc., are now blessed into the `Affix::Pointer` class, which provides several new methods:
- `address()`: Returns the virtual memory address.
- `type()`: Returns the L<infix> signature of the pointer.
- `element_type()`: Returns the signature of the pointed-to elements.
- `count()`: Returns the element count for Arrays, or byte size for Void pointers.
- `size()`: Returns the total allocated size for managed pointers.
- `cast($type)`: Reinterprets the pointer.
- `attach_destructor($destructor, [$lib])`: Attaches a custom C cleanup routine to the pointer.
- Added `attach_destructor( $pin, $destructor, [$lib] )` to allow attaching custom C cleanup routines to managed pointers.
- Improved `VarArgs` support to automatically promote Perl strings to `char*`.
(1) You are permitted to use the Standard Version and create and use
Modified Versions for any purpose without restriction, provided that
you do not Distribute the Modified Version.
Permissions for Redistribution of the Standard Version
(2) You may Distribute verbatim copies of the Source form of the
Standard Version of this Package in any medium without restriction,
either gratis or for a Distributor Fee, provided that you duplicate
all of the original copyright notices and associated disclaimers. At
your discretion, such verbatim copies may or may not include a
Compiled form of the Package.
(3) You may apply any bug fixes, portability changes, and other
modifications made available from the Copyright Holder. The resulting
Package will still be considered the Standard Version, and as such
will be subject to the Original License.
Distribution of Modified Versions of the Package as Source
Allocates memory for an array of `$count` elements of the given `$type`, and zero-initializes the memory. Returns a
managed pin typed as an Array.
```perl
my $arr = calloc( 10, Int );
$arr->[0] = 42; # Write directly to the first element
```
### `realloc( $ptr, $new_size )`
Resizes the memory area pointed to by `$ptr` to `$new_size` bytes. The original pin is updated automatically
in-place.
```
$ptr = realloc( $ptr, 2048 );
```
### `strdup( $string )`
Allocates managed memory and copies the Perl string (along with a `NULL` terminator) into it. Returns a managed
`Pointer[Char]` pin.
infix/LICENSE-A2 view on Meta::CPAN
(1) You are permitted to use the Standard Version and create and use
Modified Versions for any purpose without restriction, provided that
you do not Distribute the Modified Version.
Permissions for Redistribution of the Standard Version
(2) You may Distribute verbatim copies of the Source form of the
Standard Version of this Package in any medium without restriction,
either gratis or for a Distributor Fee, provided that you duplicate
all of the original copyright notices and associated disclaimers. At
your discretion, such verbatim copies may or may not include a
Compiled form of the Package.
(3) You may apply any bug fixes, portability changes, and other
modifications made available from the Copyright Holder. The resulting
Package will still be considered the Standard Version, and as such
will be subject to the Original License.
Distribution of Modified Versions of the Package as Source
infix/LICENSE-CC view on Meta::CPAN
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
infix/include/infix/infix.h view on Meta::CPAN
*/
/**
* @brief A function pointer for a direct marshalling forward trampoline.
*
* This is the callable code generated by `infix_forward_create_direct`. It takes
* an array of `void*` pointers that point directly to the language-specific
* objects (e.g., `SV*` in Perl, `PyObject*` in Python).
*
* @param return_value_ptr A pointer to a buffer to receive the C function's return value.
* @param lang_objects_array An array of `void*` pointers to the original language objects.
*/
typedef void (*infix_direct_cif_func)(void *, void **);
/**
* @brief A union to hold any primitive value returned by a scalar marshaller.
*
* Since a C function can only have one return type, a marshaller for primitive
* types (`infix_marshaller_fn`) returns this union. The JIT-compiled code will
* know which member of the union to access based on the argument's C type.
*/
infix/include/infix/infix.h view on Meta::CPAN
* @param type A pointer to the `infix_type` object describing the C aggregate. The
* marshaller can introspect this type to determine field names, offsets,
* and member types.
*/
typedef void (*infix_aggregate_marshaller_fn)(void * source_object, void * dest_buffer, const infix_type * type);
/**
* @brief A function pointer for a "write-back" handler for out/in-out parameters.
*
* This function is called by the JIT trampoline *after* the native C function has
* returned. Its purpose is to update the original language object with any changes
* made to the C data by the function.
*
* @param source_object A `void*` pointer to the original language object that was passed in.
* @param c_data_ptr A pointer to the (potentially modified) C data buffer that was passed
* to the C function.
* @param type A pointer to the `infix_type` object describing the C data.
*/
typedef void (*infix_writeback_fn)(void * source_object, void * c_data_ptr, const infix_type * type);
/**
* @brief A struct containing all the necessary handlers for a single function argument.
*
* For each argument, a language binding provides an instance of this struct. Based on
infix/src/arch/x64/abi_sysv_x64.c view on Meta::CPAN
* @param buf The code buffer.
* @param layout The call frame layout.
* @return `INFIX_SUCCESS`.
*/
static infix_status generate_forward_prologue_sysv_x64(code_buffer * buf, infix_call_frame_layout * layout) {
// Standard Function Prologue
emit_push_reg(buf, RBP_REG); // push rbp
emit_mov_reg_reg(buf, RBP_REG, RSP_REG); // mov rbp, rsp
// Save Callee-Saved Registers
// We will use these registers to store our context (target_fn, ret_ptr, args_ptr)
// across the native function call, so we must save their original values first.
emit_push_reg(buf, R12_REG); // push r12
emit_push_reg(buf, R13_REG); // push r13
emit_push_reg(buf, R14_REG); // push r14
emit_push_reg(buf, R15_REG); // push r15
// Move Trampoline Arguments to Persistent Registers
if (layout->target_fn == nullptr) { // Unbound trampoline
// The trampoline is called with (target_fn, ret_ptr, args_ptr) in RDI, RSI, RDX.
// We move these into our saved callee-saved registers to protect them.
emit_mov_reg_reg(buf, R12_REG, RDI_REG); // r12 = target_fn
emit_mov_reg_reg(buf, R13_REG, RSI_REG); // r13 = ret_ptr
infix/src/arch/x64/abi_sysv_x64.c view on Meta::CPAN
* @brief Stage 4 (Reverse): Generates the code to call the high-level C dispatcher function.
* @details Emits code to load the dispatcher's arguments into the correct registers
* according to the System V ABI, then calls the dispatcher.
*
* The C dispatcher's signature is:
* `void fn(infix_reverse_t* context, void* return_value_ptr, void** args_array)`
*
* The generated code performs the following argument setup:
* 1. `RDI` (Arg 1): The `context` pointer (a 64-bit immediate).
* 2. `RSI` (Arg 2): The pointer to the return value buffer. This is either a
* pointer to local stack space, or the original pointer passed by the
* caller in RDI if the function returns a large struct by reference.
* 3. `RDX` (Arg 3): The pointer to the `args_array` on the local stack.
* 4. The address of the dispatcher function itself is loaded into a scratch
* register (`RAX`), which is then called.
* @param buf The code buffer.
* @param layout The layout blueprint.
* @param context The reverse context.
* @return `INFIX_SUCCESS`.
*/
static infix_status generate_reverse_dispatcher_call_sysv_x64(code_buffer * buf,
infix/src/arch/x64/abi_sysv_x64.c view on Meta::CPAN
emit_push_reg(buf, RDX_REG); // +8
emit_sub_reg_imm32(buf, RSP_REG, 32); // +32 (space for XMM0/XMM1)
// Total stack shift: +48 bytes
emit_movsd_mem_xmm(buf, RSP_REG, 0, XMM0_REG);
// Set up args for write-back call
emit_mov_reg_mem(buf, RDI_REG, R14_REG, i * sizeof(void *));
// Arg 2 (RSI): Pointer to the C data (in our scratch space)
// Offsets are relative to the *original* RSP of the body.
// Since we just pushed/subbed 48 bytes, we must add 48 to reach the original frame.
int32_t temp_offset = (int32_t)arg->location.num_regs;
emit_lea_reg_mem(buf, RSI_REG, RSP_REG, temp_offset + 48);
emit_mov_reg_imm64(buf, RDX_REG, (uint64_t)arg->type);
emit_mov_reg_imm64(buf, R10_REG, (uint64_t)arg->handler->writeback_handler);
emit_call_reg(buf, R10_REG);
// Restore return registers
emit_movsd_xmm_mem(buf, XMM0_REG, RSP_REG, 0);
infix/src/arch/x64/abi_win_x64.c view on Meta::CPAN
* @details This function emits the instructions to load the three arguments for the C
* dispatcher into the correct registers according to the Windows x64 ABI,
* then calls the dispatcher.
*
* The C dispatcher's signature is:
* `void fn(infix_reverse_t* context, void* return_value_ptr, void** args_array)`
*
* The generated code performs the following argument setup:
* 1. `RCX` (Arg 1): The `context` pointer (a 64-bit immediate).
* 2. `RDX` (Arg 2): The pointer to the return value buffer. This is either a
* pointer to local stack space, or the original pointer passed by the
* caller in RCX if the function returns a large struct by reference.
* 3. `R8` (Arg 3): The pointer to the `args_array` on the local stack.
* 4. The address of the dispatcher function itself is loaded into `R9`,
* which is then called.
* @param buf The code buffer.
* @param layout The blueprint containing stack offsets.
* @param context The context, containing the dispatcher's address.
* @return `INFIX_SUCCESS`.
*/
static infix_status generate_reverse_dispatcher_call_win_x64(code_buffer * buf,
infix_reverse_call_frame_layout * layout,
infix_reverse_t * context) {
// Arg 1 (RCX): Load the `context` pointer.
emit_mov_reg_imm64(buf, RCX_REG, (uint64_t)context);
// Arg 2 (RDX): Load the pointer to the return value buffer.
if (return_value_is_by_reference(context->return_type))
// If the return is by reference, the original caller passed the destination
// pointer in RCX. We saved it in our GPR save area (Step 1 of marshalling).
emit_mov_reg_mem(buf, RDX_REG, RSP_REG, layout->gpr_save_area_offset + 0 * 8);
else
// Otherwise, the return buffer is on our local stack. Load its address.
emit_lea_reg_mem(buf, RDX_REG, RSP_REG, layout->return_buffer_offset);
// Arg 3 (R8): Load the address of the `args_array` on our local stack.
emit_lea_reg_mem(buf, R8_REG, RSP_REG, layout->args_array_offset);
if (layout->max_align >= 32)
infix/src/arch/x64/abi_win_x64.c view on Meta::CPAN
(context->return_type->category == INFIX_TYPE_VECTOR && context->return_type->size >= 32 &&
!return_value_is_by_reference(context->return_type));
if (!returning_large_vector)
emit_vzeroupper(buf);
}
// Handle the return value after the dispatcher returns.
if (context->return_type->category != INFIX_TYPE_VOID) {
if (return_value_is_by_reference(context->return_type))
// The return value was written directly via the hidden pointer.
// The ABI requires this original pointer (which was in RCX) to be returned in RAX.
emit_mov_reg_mem(buf, RAX_REG, RSP_REG, layout->gpr_save_area_offset + 0 * 8);
else {
// The return value is in our local buffer. Load it into the correct return register.
#if !defined(INFIX_COMPILER_MSVC)
if (context->return_type->size == 16 && context->return_type->category == INFIX_TYPE_PRIMITIVE)
// GCC/Clang on Windows returns 128-bit integers and long double in XMM0.
emit_movups_xmm_mem(buf, XMM0_REG, RSP_REG, layout->return_buffer_offset);
else
#endif
if (context->return_type->category == INFIX_TYPE_VECTOR) {
infix/src/common/infix_internals.h view on Meta::CPAN
* for reporting errors from within the library. It populates the thread-local
* `g_infix_last_error` struct. For parser errors, it generates a rich diagnostic
* message with a code snippet.
* @param category The general category of the error.
* @param code The specific error code.
* @param position For parser errors, the byte offset into the signature string where the error occurred.
*/
INFIX_INTERNAL void _infix_set_error(infix_error_category_t category, infix_error_code_t code, size_t position);
/**
* @brief Sets the thread-local error state for a system-level error.
* @details Located in `src/core/error.c`, this is used for errors originating from
* the operating system, such as `dlopen` or `mmap` failures.
* @param category The general category of the error.
* @param code The `infix` error code that corresponds to the failure.
* @param system_code The OS-specific error code (e.g., from `errno` or `GetLastError`).
* @param msg An optional custom message from the OS (e.g., from `dlerror`).
*/
INFIX_INTERNAL void _infix_set_system_error(infix_error_category_t category,
infix_error_code_t code,
long system_code,
const char * msg);
infix/src/core/error.c view on Meta::CPAN
// For non-parser errors, just copy the standard message.
const char * msg = _get_error_message_for_code(code);
_INFIX_SAFE_STRNCPY(g_infix_last_error.message, msg, sizeof(g_infix_last_error.message) - 1);
}
}
/**
* @internal
* @brief Sets a detailed system error with a platform-specific error code and message.
*
* @details This is used for errors originating from OS-level functions like `dlopen`,
* `mmap`, or `VirtualAlloc`. It records both the `infix` error code and the
* underlying system error code (`errno` or `GetLastError`).
*
* @param category The category of the error.
* @param code The `infix` error code that corresponds to the failure.
* @param system_code The OS-specific error code (e.g., from `errno` or `GetLastError`).
* @param msg An optional custom message from the OS (e.g., from `dlerror`). If `nullptr`, the default message for
* `code` is used.
*/
void _infix_set_system_error(infix_error_category_t category,
infix/src/core/signature.c view on Meta::CPAN
skip_whitespace(state);
return true;
}
return false;
}
/**
* @internal
* @brief Parses an optional named prefix, like `name: type`.
* @details If a valid identifier is found followed by a colon, the name is returned
* and the parser's cursor is advanced past the colon. If not, the parser state is
* rewound to its original position (backtracking) and `nullptr` is returned.
* @param[in,out] state The parser state.
* @return An arena-allocated string for the name, or `nullptr` if no `name:` prefix is present.
*/
static const char * parse_optional_name_prefix(parser_state * state) {
skip_whitespace(state);
// Save the current position in case we need to backtrack.
const char * p_before = state->p;
const char * name = parse_identifier(state);
if (name) {
skip_whitespace(state);
if (*state->p == ':') { // Found "identifier:", so consume the colon and return the name.
state->p++;
return name;
}
}
// If it wasn't a `name:`, backtrack to the original position.
state->p = p_before;
return nullptr;
}
/**
* @internal
* @brief A lookahead function to disambiguate a grouped type `(type)` from a
* function signature `(...) -> type`.
*
* @details This is a classic parser "lookahead". When the parser encounters an opening
* parenthesis `(`, it calls this function to peek ahead in the string without
infix/src/core/types.c view on Meta::CPAN
}
/**
* @internal
* @struct memo_node_t
* @brief A memoization node for the deep copy algorithm.
* @details This temporary structure maps a source `infix_type` address to its
* newly copied destination address. It is used to prevent re-copying the same
* object and to correctly reconstruct cyclic type graphs.
*/
typedef struct memo_node_t {
const infix_type * src; /**< The original type object's address. */
infix_type * dest; /**< The copied type object's address. */
struct memo_node_t * next; /**< The next node in the memoization list. */
} memo_node_t;
/**
* @internal
* @brief Recursively performs a deep copy of a type graph into a destination arena.
*
* @details This function is the implementation of the **"Copy"** stage of the data pipeline.
* It is essential for creating self-contained trampoline objects and for safely
* managing type lifecycles. It uses memoization to correctly handle cycles and shared
lib/Affix.c view on Meta::CPAN
// Rebuild plan
Newxz(affix->plan, affix->plan_length + 1, Affix_Plan_Step);
size_t out_param_count = 0;
OutParamInfo * temp_out_info = safemalloc(sizeof(OutParamInfo) * (affix->num_args > 0 ? affix->num_args : 1));
size_t current_offset = 0;
for (size_t i = 0; i < affix->num_args; ++i) {
// Deep copy types from parse_arena to persistent args_arena
const infix_type * original_type = _copy_type_graph_to_arena(affix->args_arena, args[i].type);
// Recalculate offsets (logic duplication from Affix_affix, but necessary)
size_t alignment, size;
if (original_type->category == INFIX_TYPE_ARRAY) {
alignment = _Alignof(void *);
size = sizeof(void *);
}
else {
alignment = infix_type_get_alignment(original_type);
size = infix_type_get_size(original_type);
}
if (alignment == 0)
alignment = 1;
current_offset = (current_offset + alignment - 1) & ~(alignment - 1);
affix->plan[i].data.c_arg_offset = current_offset;
current_offset += size;
affix->plan[i].executor = get_plan_step_executor(original_type);
affix->plan[i].opcode = get_opcode_for_type(original_type);
affix->plan[i].data.type = original_type;
affix->plan[i].data.index = i;
// Re-detect out params
if (original_type->category == INFIX_TYPE_POINTER) {
const infix_type * pointee = original_type->meta.pointer_info.pointee_type;
const char * pointee_name = infix_type_get_name(pointee);
bool is_sv_pointer = pointee_name && (strEQ(pointee_name, "SV") || strEQ(pointee_name, "@SV"));
if (!is_sv_pointer && pointee->category != INFIX_TYPE_REVERSE_TRAMPOLINE &&
pointee->category != INFIX_TYPE_VOID) {
temp_out_info[out_param_count].perl_stack_index = i;
temp_out_info[out_param_count].pointee_type = pointee;
temp_out_info[out_param_count].writer = get_out_param_writer(pointee);
out_param_count++;
}
}
else if (original_type->category == INFIX_TYPE_ARRAY) {
temp_out_info[out_param_count].perl_stack_index = i;
temp_out_info[out_param_count].pointee_type = original_type;
temp_out_info[out_param_count].writer = affix_array_writeback;
out_param_count++;
}
}
affix->plan[affix->num_args].opcode = OP_DONE;
// Setup OUT params
if (out_param_count > 0) {
affix->out_param_info = safemalloc(sizeof(OutParamInfo) * out_param_count);
memcpy(affix->out_param_info, temp_out_info, sizeof(OutParamInfo) * out_param_count);
lib/Affix.c view on Meta::CPAN
size_t items = SP - MARK;
if (items < affix->num_fixed_args)
croak(
"Not enough arguments for variadic function. Expected at least %zu, got %zu", affix->num_fixed_args, items);
// Construct the complete signature string dynamically
SV * sig_sv = sv_2mortal(newSVpv("", 0));
// Reconstruct fixed part from the cached sig_str (which ends in '; ...' or similar)
// We need to parse the original signature string to get the fixed part cleanly,
// OR we can reconstruct it from the plan.
// Simplest: The affix->sig_str contains the fixed part and the ';'.
// We assume affix->sig_str is like "(*char; ...)->int"
char * semi_ptr = strchr(affix->sig_str, ';');
if (!semi_ptr)
croak("Internal error: Variadic function missing semicolon in signature");
// Append fixed part up to and including ';'
sv_catpvn(sig_sv, affix->sig_str, (semi_ptr - affix->sig_str) + 1);
lib/Affix.c view on Meta::CPAN
else if (SvIOK(arg))
sv_catpvs(sig_sv, "sint64"); // Default integer promotion
else if (SvNOK(arg))
sv_catpvs(sig_sv, "double"); // Default float promotion
else if (SvPOK(arg))
sv_catpvs(sig_sv, "*char"); // Default string promotion
else // Fallback/Unknown
sv_catpvs(sig_sv, "sint64");
}
// Append return type part (find ')' in original sig)
char * close_paren = strrchr(affix->sig_str, ')');
if (close_paren)
sv_catpv(sig_sv, close_paren);
else
croak("Malformed signature string in affix");
const char * full_sig = SvPV_nolen(sig_sv);
// Check Cache
infix_forward_t * trampoline = NULL;
lib/Affix.c view on Meta::CPAN
if (status != INFIX_SUCCESS) {
infix_error_details_t err = infix_get_last_error();
warn("Failed to parse signature: %s", err.message);
if (parse_arena)
infix_arena_destroy(parse_arena);
XSRETURN_UNDEF;
}
// JIT Type substitution (array decay)
// We create a separate list of types for JIT compilation where Arrays are replaced by Pointers.
// The original Array types are kept for the marshalling plan.
infix_type ** jit_arg_types = NULL;
if (num_args > 0) {
jit_arg_types = safemalloc(sizeof(infix_type *) * num_args);
for (size_t i = 0; i < num_args; ++i) {
infix_type * t = args[i].type;
if (t->category == INFIX_TYPE_ARRAY) {
// Arrays passed as arguments decay to pointers.
// We create a Pointer[Element] type in the temp arena for JIT creation.
infix_type * ptr_type = NULL;
// FIX: Check return value to satisfy nodiscard warning
lib/Affix.c view on Meta::CPAN
affix->plan_length = affix->num_args;
Newxz(affix->plan, affix->plan_length + 1, Affix_Plan_Step);
size_t current_offset = 0;
size_t out_param_count = 0;
OutParamInfo * temp_out_info = safemalloc(sizeof(OutParamInfo) * (affix->num_args > 0 ? affix->num_args : 1));
for (size_t i = 0; i < affix->num_args; ++i) {
// Deep copy from temporary parse_arena to persistent args_arena.
// We use the ORIGINAL types (args[i].type) so marshalling knows it's an Array.
const infix_type * original_type = _copy_type_graph_to_arena(affix->args_arena, args[i].type);
// Calculate offset based on JIT expectation (Array Decay -> Pointer size)
size_t alignment, size;
if (original_type->category == INFIX_TYPE_ARRAY) {
alignment = _Alignof(void *);
size = sizeof(void *);
}
else {
alignment = infix_type_get_alignment(original_type);
size = infix_type_get_size(original_type);
}
if (alignment == 0)
alignment = 1;
current_offset = (current_offset + alignment - 1) & ~(alignment - 1);
affix->plan[i].data.c_arg_offset = current_offset;
current_offset += size;
affix->plan[i].executor = get_plan_step_executor(original_type);
affix->plan[i].opcode = get_opcode_for_type(original_type);
affix->plan[i].data.type = original_type; // Now points to persistent memory
affix->plan[i].data.index = i;
if (original_type->category == INFIX_TYPE_POINTER) {
const infix_type * pointee = original_type->meta.pointer_info.pointee_type;
const char * pointee_name = infix_type_get_name(pointee);
// Skip writeback for Pointer[@SV] to avoid corrupting Perl variables with void return values
// We assume SV* passed to C is owned by C for the duration and shouldn't be auto-updated
// (since the SV* itself is the value, not a pointer to a value we want copied back).
bool is_sv_pointer = pointee_name && (strEQ(pointee_name, "SV") || strEQ(pointee_name, "@SV"));
if (!is_sv_pointer && pointee->category != INFIX_TYPE_REVERSE_TRAMPOLINE &&
pointee->category != INFIX_TYPE_VOID) {
temp_out_info[out_param_count].perl_stack_index = i;
temp_out_info[out_param_count].pointee_type = pointee;
temp_out_info[out_param_count].writer = get_out_param_writer(pointee);
out_param_count++;
}
}
else if (original_type->category == INFIX_TYPE_ARRAY) {
// Register write-back handler for Arrays
temp_out_info[out_param_count].perl_stack_index = i;
temp_out_info[out_param_count].pointee_type = original_type;
temp_out_info[out_param_count].writer = affix_array_writeback;
out_param_count++;
}
}
affix->plan[affix->num_args].opcode = OP_DONE;
affix->total_args_size = current_offset;
affix->num_out_params = out_param_count;
if (out_param_count > 0) {
affix->out_param_info = safemalloc(sizeof(OutParamInfo) * out_param_count);
lib/Affix.pod view on Meta::CPAN
=head3 C<calloc( $count, $type )>
Allocates memory for an array of C<$count> elements of the given C<$type>, and zero-initializes the memory. Returns a
managed pin typed as an Array.
my $arr = calloc( 10, Int );
$arr->[0] = 42; # Write directly to the first element
=head3 C<realloc( $ptr, $new_size )>
Resizes the memory area pointed to by C<$ptr> to C<$new_size> bytes. The original pin is updated automatically
in-place.
$ptr = realloc( $ptr, 2048 );
=head3 C<strdup( $string )>
Allocates managed memory and copies the Perl string (along with a C<NULL> terminator) into it. Returns a managed
C<Pointer[Char]> pin.
my $str_ptr = strdup("Hello C!");
lib/Affix/Build.pod view on Meta::CPAN
$mix->add('src/wrapper.c', flags => '-DDEBUG');
$mix->add('src/core.go');
my $poly_dll = $mix->link();
=head1 DESCRIPTION
C<Affix::Build> is a cross-platform compilation utility designed to generate shared libraries (C<.dll>, C<.so>,
C<.dylib>) from source code in over 20 different programming languages.
While originally developed to compile test fixtures for the Affix suite, it has evolved into a robust tool for creating
polyglot extensions. It abstracts away the complexity of invoking various compilers, normalizing object file
extensions, handling platform-specific linker flags (such as MinGW vs. MSVC on Windows), and ensuring that language
runtimes (like the Go GC or .NET Runtime) are correctly initialized.
It serves as a powerful, polyglot alternative to C<Inline::*> modulesâallowing you to write native code in your
language of choice and instantly bind to it from Perl using L<Affix>.
=head2 Build Strategies
The builder automatically selects the optimal strategy based on the input sources:
t/007_pointers.t view on Meta::CPAN
# Cast returns a new pin. We must assign it or use the returned object.
# Also, we keep $mem alive to ensure the memory isn't freed if $int_ptr assumes
# $mem owns it (though cast usually creates unmanaged aliases, so we need $mem to stay alive).
my $int_ptr = Affix::cast( $mem, Pointer [Int] );
# Test magical 'set' via dereferencing
# $$int_ptr is a scalar magic that writes to the address
$$int_ptr = 42;
# Use the original $mem pointer for reading (verifying they point to the same place)
is( read_int_from_void_ptr($mem), 42, 'Magical set via deref wrote to C memory' );
# Test cast again
my $long_ptr = Affix::cast( $mem, Pointer [LongLong] );
$$long_ptr = 1234567890123;
is $$long_ptr, 1234567890123, 'Magical get after casting to a new type works';
# Test realloc
my $r_ptr = calloc( 2, Int );
t/007_pointers.t view on Meta::CPAN
subtest 'Pointer Arithmetic and String Utils' => sub {
imported_ok qw[ptr_add ptr_diff strdup strnlen is_null];
subtest 'ptr_add and ptr_diff' => sub {
my $buf = calloc( 10, Int ); # 40 bytes
ok !is_null($buf), 'buffer is not null';
my $p2 = ptr_add( $buf, 8 ); # width of 2 ints
is ptr_diff( $p2, $buf ), 8, 'ptr_diff calculates 8 bytes';
is ptr_diff( $buf, $p2 ), -8, 'ptr_diff calculates -8 bytes';
$$p2 = 999; # Write to offset
# Verify via original array pointer
my $arr = cast( $buf, Array [ Int, 10 ] );
is $$arr->[2], 999, 'ptr_add moved to index 2 correctly';
free($buf);
};
subtest 'strdup and strnlen' => sub {
my $str = "Hello World";
my $dup = strdup($str);
ok !is_null($dup), 'strdup returned non-null';
is cast( $dup, String ), $str, 'strdup content matches';
is strnlen( $dup, 5 ), 5, 'strnlen capped at max';
t/007_pointers.t view on Meta::CPAN
# Correct cleanup: Use the allocator that created it.
c_free($string);
pass('freed via c_free');
};
subtest 'deep pointers' => sub {
# Deep Indirection (***int)
isa_ok my $set_deep = wrap( $lib_path, 'set_int_deep', [ Pointer [ Pointer [ Pointer [Int] ] ], Int ] => Void ), ['Affix'];
# Manually construct the pointer chain with correct types
# Keep original 'malloc' pointers alive (managed) while using 'cast' aliases
# Layer 1: The int value (int*)
my $p_mem = malloc(8);
my $p_val = Affix::cast( $p_mem, Pointer [Int] );
# Assigning directly ($p_val = 0) would overwrite the magic scalar with a normal SV*
$$p_val = 0;
# Layer 2: Pointer to Layer 1 (int**)
my $pp_mem = malloc(8);
my $pp_val = Affix::cast( $pp_mem, Pointer [ Pointer [Int] ] );
t/007_pointers.t view on Meta::CPAN
# Dereference to invoke SET magic, writing the pointer address to memory.
$$ppp_val = $pp_val;
# Call Function
$set_deep->( $ppp_val, 12345 );
# Verification
is $$p_val, 12345, '***int deep write successful via Pins';
# Cleanup (Freeing the originals clears the memory)
Affix::free($p_mem);
Affix::free($pp_mem);
Affix::free($ppp_mem);
# Manual Memory Management (malloc/free/cast)
isa_ok my $get_heap = wrap( $lib_path, 'get_heap_int', [Int] => Pointer [Int] ), ['Affix'];
# Alias libc free to avoid conflict with Affix::free
diag affix( $lib_path, 'libc_free', [ Pointer [Void] ] => Void );
my $heap_ptr = $get_heap->(99);
t/032_recursive_liveness.t view on Meta::CPAN
my $live_arr = cast( $ptr, Live [ Array [ $Point, 2 ] ] );
# Set values
$live_arr->[0]{x} = 10;
$live_arr->[0]{y} = 20;
# Accessing $live_arr->[0] returns an Affix::Live blessed hash
my $p0 = $live_arr->[0];
isa_ok $p0, ['Affix::Live'], 'Element of live array should be Affix::Live';
$p0->{x} = 30;
is $live_arr->[0]{x}, 30, 'Changes to live element reflect in original memory';
free($ptr);
};
subtest 'Recursive Liveness: Live struct with Array' => sub {
# Using typedef to ensure member names are preserved in the infix registry
typedef ListStruct => Struct [ items => Array [ Int, 3 ] ];
my $ptr = malloc( sizeof( ListStruct() ) );
my $live_struct = cast( $ptr, Live [ ListStruct() ] );
# Accessing $live_struct->{items} returns an Affix::Pointer object