Affix

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN


Allocates zero-initialized memory for `$count` elements of `$size`. Returns a `Pointer[Void]` pin.

```perl
my $ptr = calloc( 10, sizeof(Int) );
my $arr = cast( $ptr, Array[Int, 10] );
```

### `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/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

/**
 * @internal
 * @brief Recursively classifies the eightbytes of an aggregate type.
 * @details This is the core of the complex System V classification algorithm. It traverses
 * the fields of a struct/array, examining each 8-byte chunk ("eightbyte") and assigning it a
 * class (INTEGER, SSE, MEMORY). The classification is "merged" according to ABI rules
 * (e.g., if an eightbyte contains both INTEGER and SSE parts, it becomes INTEGER).
 *
 * @param type The type of the current member/element being examined.
 * @param offset The byte offset of this member from the start of the aggregate.
 * @param[in,out] classes An array of two `arg_class_t` that is updated during classification.
 * @param depth The current recursion depth (to prevent stack overflow on malicious input).
 * @param field_count A counter to prevent DoS from excessively complex types.
 * @param is_bitfield True if the current member is a bitfield.
 * @return `true` if a condition forcing MEMORY classification is found, `false` otherwise.
 */
static bool classify_recursive(
    const infix_type * type, size_t offset, arg_class_t classes[2], int depth, size_t * field_count, bool is_bitfield) {
    // A recursive call can be made with a NULL type (e.g., from a malformed array from fuzzer).
    if (type == nullptr)
        return false;  // Terminate recusion path.

infix/src/core/signature.c  view on Meta::CPAN


/**
 * @internal
 * @brief The internal implementation of the type-to-string printer for standard signatures.
 *
 * This function recursively walks a type graph and prints its signature representation.
 * The key feature is the initial check for `type->name`. If a semantic alias exists,
 * it is always preferred, ensuring that introspection and serialization produce
 * canonical, readable output (e.g., printing "@MyHandle" instead of "*void").
 *
 * @param[in,out] state The printer state, which is updated as the string is built.
 * @param[in] type The `infix_type` to print.
 */
static void _infix_type_print_signature_recursive(printer_state * state, const infix_type * type) {
    if (state->status != INFIX_SUCCESS || !type) {
        if (state->status == INFIX_SUCCESS)
            state->status = INFIX_ERROR_INVALID_ARGUMENT;
        return;
    }
    // If the type has a semantic name, always prefer printing it.
    if (type->name) {

infix/src/core/types.c  view on Meta::CPAN

    if (!_layout_struct(type)) {
        *out_type = nullptr;
        return INFIX_ERROR_INVALID_ARGUMENT;
    }
    *out_type = type;
    return INFIX_SUCCESS;
}
/**
 * @brief Creates a placeholder for a named type that will be resolved later by a type registry.
 * @details This is a key component for defining recursive or mutually-dependent types.
 * The created type has a size and alignment of 0/1, which are updated during the
 * "Resolve" and "Layout" stages of the pipeline.
 * @param[in] arena The arena for allocation.
 * @param[out] out_type A pointer to receive the new `infix_type`.
 * @param[in] name The name of the type (e.g., "MyStruct").
 * @param[in] agg_cat The expected category of the aggregate (struct or union).
 * @return `INFIX_SUCCESS` on success.
 */
INFIX_API c23_nodiscard infix_status infix_type_create_named_reference(infix_arena_t * arena,
                                                                       infix_type ** out_type,
                                                                       const char * name,

infix/src/core/types.c  view on Meta::CPAN

    case INFIX_TYPE_STRUCT:
    case INFIX_TYPE_UNION:
        for (size_t i = 0; i < type->meta.aggregate_info.num_members; ++i) {
            _infix_type_recalculate_layout_recursive(
                temp_arena, type->meta.aggregate_info.members[i].type, visited_head);
        }
        break;
    default:
        break;  // Other types have no child types to recurse into.
    }
    // After children are updated, recalculate this type's layout.
    if (type->category == INFIX_TYPE_STRUCT)
        _layout_struct(type);
    else if (type->category == INFIX_TYPE_UNION) {
        size_t max_size = 0;
        size_t max_alignment = 1;
        for (size_t i = 0; i < type->meta.aggregate_info.num_members; ++i) {
            infix_type * member_type = type->meta.aggregate_info.members[i].type;
            if (member_type->size > max_size)
                max_size = member_type->size;
            if (member_type->alignment > max_alignment)

lib/Affix.c  view on Meta::CPAN

        affix->plan[i].opcode = get_opcode_for_type(aTHX_ 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);
            if (!pointee_name && pointee->category == INFIX_TYPE_NAMED_REFERENCE)
                pointee_name = pointee->meta.named_reference.name;
            // 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++;
            }

lib/Affix.pod  view on Meta::CPAN


=head3 C<calloc( $count, $size )>

Allocates zero-initialized memory for C<$count> elements of C<$size>. Returns a C<Pointer[Void]> pin.

    my $ptr = calloc( 10, sizeof(Int) );
    my $arr = cast( $ptr, Array[Int, 10] );

=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!");

t/007_pointers.t  view on Meta::CPAN

# But $r_ptr still thinks it's [2:int]. We must cast to update the type view.
my $arr_ptr = cast( $r_ptr, Array [ Int, 8 ] );

# Initialize new memory to zero
memset( $arr_ptr, 0, 32 );

# Modify perl's copy (writes directly to C memory)
$arr_ptr->[0] = 10;
$arr_ptr->[7] = 80;

# Visual evidence that the memory has actually been updated
#~ Affix::dump( $arr_ptr, 32 );
# sum_int_array takes *int, so passing [8:int] (array ref) works as pointer
ok affix( $lib_path, 'sum_int_array', [ Pointer [Int], Int ], Int ), 'affix ... "sum_int_array", ...';
is sum_int_array( $arr_ptr, 8 ), 90, 'realloc successfully resized memory';
#
isa_ok my $check_is_null = wrap( $lib_path, 'check_is_null', '(*void)->bool' ), ['Affix'];
ok $check_is_null->(undef), 'Passing undef to a *void argument is received as NULL';
subtest 'char*' => sub {
    isa_ok my $get_string = wrap( $lib_path, 'get_hello_string', '()->*char' ), ['Affix'];
    is $get_string->(), 'Hello from C', 'Correctly returned a C string';

t/026_context.t  view on Meta::CPAN

#
my $context = { id => 1, count => 0 };

# This verifies that spawn correctly accepts Perl SVs as "SVPtr" (aliased Pointer[SV])
spawn( $context, $step1 );

# This verifies that run_scheduler correctly calls the callback,
# and the callback correctly receives SVs back from C.
run_scheduler($wrapper);
#
is $context->{count}, 3, 'All steps executed and context updated';
#
done_testing();

t/035_magic_struct.t  view on Meta::CPAN

    # BIND the hash to the pointer (The new system)
    my $struct = cast $ptr, Point();

    # ACT: Modify the hash
    $struct->{x} = 123;
    $struct->{y} = 456;

    # ASSERT: Read raw memory back using cast (bypassing the hash magic)
    my $raw_x = cast $ptr, Int;
    my $raw_y = cast ptr_add( $ptr, 4 ), Int;    # offset of 'y' is 4
    is $raw_x, 123, 'C memory for x updated immediately';
    is $raw_y, 456, 'C memory for y updated immediately';
};
subtest 'Reference counting and persistence' => sub {
    my $sub_hash;
    {
        my $root_ptr = Affix::malloc( sizeof( Transform() ) );
        my $tx       = cast $root_ptr, Transform();

        # Grab a reference to a nested struct
        $sub_hash = $tx->{origin};
    }

t/081_packed.t  view on Meta::CPAN

    is $p->{b}, 54321, 'Write and read packed field b';
};
#
subtest 'Packed char + uint64_t -- C-level roundtrip' => sub {
    my $mem = calloc( 1, sizeof_packed_char_u64() );
    fill_packed_u64($mem);
    is get_packed_u64_char($mem), 88,                 'C reads packed char as 88 (ASCII "X")';
    is get_packed_u64_val($mem),  0xDEADBEEFCAFEBABE, 'C reads packed uint64_t correctly';
    my $p = cast( $mem, Packed( Struct [ a => Char, b => UInt64 ] ) );
    $p->{a} = 90;
    is get_packed_u64_char($mem), 90, 'C sees char updated via magic';
    $p->{b} = 0x1122334455667788;
    is get_packed_u64_val($mem), 0x1122334455667788, 'C sees u64 updated via magic';
};
#
subtest 'Packed struct -- by-value return' => sub {
    my $ret = make_packed_abc( 42, -66, 99 );
    is ref($ret), 'HASH', 'Return value is a hashref';
    is $ret->{a},  42,    'Field a returned correctly';
    is $ret->{b}, -66,    'Field b returned correctly';
    is $ret->{c},  99,    'Field c returned correctly';
};
#

t/083_pin_conventions.t  view on Meta::CPAN


    # Write through clone, read through original
    $cloned = 77;
    is $original, 77, 'Original sees writes through clone';

    # Write through original, read through clone
    $original = 88;
    is $cloned, 88, 'Clone sees writes through original';

    # Both update C
    is $get->(), 88, 'C global updated through either pin';
    ok unpin($cloned),   'Unpinned clone';
    ok unpin($original), 'Unpinned original';
};
#
subtest 'unpin() returns false for non-pinned scalar' => sub {
    ok !unpin(42),      'unpin returns false for plain integer';
    ok !unpin("hello"), 'unpin returns false for string';
};
#
done_testing;



( run in 0.974 second using v1.01-cache-2.11-cpan-bbc515a03b3 )