Affix

 view release on metacpan or  search on metacpan

lib/Affix.c  view on Meta::CPAN

        case OP_RET_BOOL:                                                                                    \
            sv_setbool(TARG, *(bool *)ret_buffer);                                                           \
            break;                                                                                           \
        case OP_RET_SINT8:                                                                                   \
            sv_setiv(TARG, *(int8_t *)ret_buffer);                                                           \
            break;                                                                                           \
        case OP_RET_UINT8:                                                                                   \
            sv_setuv(TARG, *(uint8_t *)ret_buffer);                                                          \
            break;                                                                                           \
        case OP_RET_SINT16:                                                                                  \
            sv_setiv(TARG, *(int16_t *)ret_buffer);                                                          \
            break;                                                                                           \
        case OP_RET_UINT16:                                                                                  \
            sv_setuv(TARG, *(uint16_t *)ret_buffer);                                                         \
            break;                                                                                           \
        case OP_RET_SINT32:                                                                                  \
            sv_setiv(TARG, *(int32_t *)ret_buffer);                                                          \
            break;                                                                                           \
        case OP_RET_UINT32:                                                                                  \
            sv_setuv(TARG, *(uint32_t *)ret_buffer);                                                         \
            break;                                                                                           \
        case OP_RET_SINT64:                                                                                  \
            sv_setiv(TARG, *(int64_t *)ret_buffer);                                                          \
            break;                                                                                           \
        case OP_RET_UINT64:                                                                                  \
            sv_setuv(TARG, *(uint64_t *)ret_buffer);                                                         \
            break;                                                                                           \
        case OP_RET_SINT128:                                                                                 \
            sv_from_int128_safe(TARG, ret_buffer);                                                           \
            break;                                                                                           \
        case OP_RET_UINT128:                                                                                 \
            sv_from_uint128_safe(TARG, ret_buffer);                                                          \
            break;                                                                                           \
        case OP_RET_FLOAT:                                                                                   \
            sv_setnv(TARG, (double)*(float *)ret_buffer);                                                    \
            break;                                                                                           \
        case OP_RET_FLOAT16:                                                                                 \
            sv_setnv(TARG, (double)half_to_float(*(infix_float16_t *)ret_buffer));                           \
            break;                                                                                           \
        case OP_RET_DOUBLE:                                                                                  \
            sv_setnv(TARG, *(double *)ret_buffer);                                                           \
            break;                                                                                           \
        case OP_RET_PTR:                                                                                     \
            {                                                                                                \
                void * c_ptr = *(void **)ret_buffer;                                                         \
                if (c_ptr == nullptr) {                                                                      \
                    sv_setsv(TARG, &PL_sv_undef);                                                            \
                }                                                                                            \
                else {                                                                                       \
                    Affix_Pin * pin;                                                                         \
                    Newxz(pin, 1, Affix_Pin);                                                                \
                    pin->pointer = c_ptr;                                                                    \
                    pin->type = affix->unwrapped_ret_type;                                                   \
                                                                                                             \
                    SV * obj_data = newSViv(PTR2IV(pin));                                                    \
                    SV * rv = newRV_noinc(obj_data);                                                         \
                                                                                                             \
                    dMY_CXT;                                                                                 \
                    if (UNLIKELY(MY_CXT.stash_pointer == nullptr))                                           \
                        MY_CXT.stash_pointer = gv_stashpv("Affix::Pointer", GV_ADD);                         \
                    (void)sv_bless(rv, MY_CXT.stash_pointer);                                                \
                                                                                                             \
                    (void)sv_magicext(obj_data, nullptr, PERL_MAGIC_ext, &Affix_pin_vtbl, (char *)pin, 0);   \
                    sv_setsv(TARG, sv_2mortal(rv));                                                          \
                }                                                                                            \
                break;                                                                                       \
            }                                                                                                \
        case OP_RET_PTR_CHAR:                                                                                \
            {                                                                                                \
                char * p = *(char **)ret_buffer;                                                             \
                if (p)                                                                                       \
                    sv_setpv(TARG, p);                                                                       \
                else                                                                                         \
                    sv_setsv(TARG, &PL_sv_undef);                                                            \
                break;                                                                                       \
            }                                                                                                \
        case OP_RET_PTR_WCHAR:                                                                               \
            pull_pointer_as_wstring(aTHX_ affix, TARG, affix->ret_type, ret_buffer);                         \
            break;                                                                                           \
        case OP_RET_SV:                                                                                      \
            {                                                                                                \
                SV * s = *(SV **)ret_buffer;                                                                 \
                if (s)                                                                                       \
                    sv_setsv(TARG, s);                                                                       \
                else                                                                                         \
                    sv_setsv(TARG, &PL_sv_undef);                                                            \
                break;                                                                                       \
            }                                                                                                \
        case OP_RET_CUSTOM:                                                                                  \
        default:                                                                                             \
            if (affix->ret_pull_handler)                                                                     \
                affix->ret_pull_handler(aTHX_ affix, TARG, affix->ret_type, ret_buffer);                     \
            break;                                                                                           \
        }                                                                                                    \
        if (UNLIKELY(affix->num_out_params > 0)) {                                                           \
            for (size_t i = 0; i < affix->num_out_params; ++i) {                                             \
                const OutParamInfo * info = &affix->out_param_info[i];                                       \
                SV * arg_sv = ST(info->perl_stack_index);                                                    \
                if (SvROK(arg_sv) && !is_pin(aTHX_ arg_sv)) {                                                \
                    SV * rsv = SvRV(arg_sv);                                                                 \
                    info->writer(aTHX_ affix, info, rsv, c_args[info->perl_stack_index]);                    \
                }                                                                                            \
                else if (!SvOK(arg_sv) && !SvREADONLY(arg_sv))                                               \
                    info->writer(aTHX_ affix, info, arg_sv, c_args[info->perl_stack_index]);                 \
            }                                                                                                \
        }                                                                                                    \
                                                                                                             \
        affix->args_arena->current_offset = arena_mark;                                                      \
        affix->ret_arena->current_offset = 0;                                                                \
                                                                                                             \
        ST(0) = TARG;                                                                                        \
        XSRETURN(1);                                                                                         \
    }

// Generate the two XSUBs
GENERATE_TRIGGER_XSUB(Affix_trigger_stack, 1)
GENERATE_TRIGGER_XSUB(Affix_trigger_arena, 0)

static void _lib_registry_inc_ref(pTHX_ infix_library_t * lib) {
    dMY_CXT;
    if (MY_CXT.lib_registry == nullptr)

lib/Affix.c  view on Meta::CPAN

        }

        infix_status status =
            infix_signature_parse(sig_to_parse, &parse_arena, &ret_type, &args, &num_args, &num_fixed, MY_CXT.registry);

        if (clean_sig)
            safefree(clean_sig);

        if (status != INFIX_SUCCESS) {
            safefree(backend);
            if (parse_arena)
                infix_arena_destroy(parse_arena);
            infix_error_details_t err = infix_get_last_error();
            if (err.message[0] != '\0')
                warn("Failed to parse signature for affix_bundle: %s", err.message);
            else
                warn("Failed to parse signature for affix_bundle (Error Code: %d)", status);
            XSRETURN_UNDEF;
        }

        infix_direct_arg_handler_t * handlers =
            (infix_direct_arg_handler_t *)safecalloc(num_args, sizeof(infix_direct_arg_handler_t));

        for (size_t i = 0; i < num_args; ++i)
            handlers[i] = get_direct_handler_for_type(args[i].type);

        status = infix_forward_create_direct(&backend->infix, signature, symbol, handlers, MY_CXT.registry);

        safefree(handlers);
        infix_arena_destroy(parse_arena);

        if (status != INFIX_SUCCESS) {
            safefree(backend);
            infix_error_details_t err = infix_get_last_error();
            warn("Failed to create direct trampoline: %s", err.message[0] ? err.message : "Unknown Error");
            XSRETURN_UNDEF;
        }

        backend->cif = infix_forward_get_direct_code(backend->infix);
        backend->num_args = num_args;
        backend->ret_type = infix_forward_get_return_type(backend->infix);

        backend->pull_handler = get_pull_handler(aTHX_ backend->ret_type);
        backend->ret_opcode = get_ret_opcode_for_type(backend->ret_type);

        if (!backend->pull_handler) {
            infix_forward_destroy(backend->infix);
            safefree(backend);
            warn("Unsupported return type for affix_bundle");
            XSRETURN_UNDEF;
        }

        backend->lib_handle = created_implicit_handle ? lib_handle_for_symbol : nullptr;

        CV * cv_new =
            newXSproto_portable((ix == 0 || ix == 2) ? rename_str : nullptr, Affix_trigger_backend, __FILE__, nullptr);

        CvXSUBANY(cv_new).any_ptr = (void *)backend;

        SV * obj = (ix == 1 || ix == 3) ? newRV_noinc(MUTABLE_SV(cv_new)) : newRV_inc(MUTABLE_SV(cv_new));
        sv_bless(obj, gv_stashpv("Affix::Bundled", GV_ADD));
        ST(0) = sv_2mortal(obj);
        XSRETURN(1);
    }

    // Standard path (parse & prepare types)
    infix_arena_t * parse_arena = NULL;
    infix_type * ret_type = NULL;
    infix_function_argument * args = NULL;
    size_t num_args = 0, num_fixed = 0;

    const char * sig_to_parse = signature;
    char * clean_sig = nullptr;
    if (strstr(signature, "+")) {
        clean_sig = savepv(signature);
        const char * p = signature;
        char * d = clean_sig;
        while (*p) {
            // Strip '+' only if it precedes a signature character: * [ { ! < ( @
            if (*p == '+' &&
                (p[1] == '*' || p[1] == '[' || p[1] == '{' || p[1] == '!' || p[1] == '<' || p[1] == '(' || p[1] == '@'))
                p++;
            else
                *d++ = *p++;
        }
        *d = '\0';
        sig_to_parse = clean_sig;
    }

    infix_status status =
        infix_signature_parse(sig_to_parse, &parse_arena, &ret_type, &args, &num_args, &num_fixed, MY_CXT.registry);

    if (clean_sig)
        safefree(clean_sig);

    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
                if (infix_type_create_pointer_to(parse_arena, &ptr_type, t->meta.array_info.element_type) !=
                    INFIX_SUCCESS) {
                    safefree(jit_arg_types);
                    infix_arena_destroy(parse_arena);
                    croak("Failed to create pointer type for array decay");

lib/Affix.c  view on Meta::CPAN

            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);
        memcpy(affix->out_param_info, temp_out_info, sizeof(OutParamInfo) * out_param_count);
    }
    else
        affix->out_param_info = nullptr;

    safefree(temp_out_info);

    char prototype_buf[256] = {0};
    for (size_t i = 0; i < affix->num_args; ++i)
        strcat(prototype_buf, "$");

    // Install XSUB
    XSUBADDR_t trigger;
    if (is_variadic)
        trigger = Affix_trigger_variadic;
    else
        trigger = (affix->total_args_size <= 512) ? Affix_trigger_stack : Affix_trigger_arena;

    CV * cv_new = newXSproto_portable(ix == 0 ? rename_str : nullptr, trigger, __FILE__, nullptr);
    if (UNLIKELY(cv_new == nullptr)) {
        infix_forward_destroy(affix->infix);
        SvREFCNT_dec(affix->return_sv);
        infix_arena_destroy(affix->args_arena);
        infix_arena_destroy(affix->ret_arena);
        safefree(affix->plan);
        if (affix->out_param_info)
            safefree(affix->out_param_info);
        if (affix->c_args)
            safefree(affix->c_args);
        safefree(affix->sig_str);
        if (affix->sym_name)
            safefree(affix->sym_name);
        SvREFCNT_dec(affix->variadic_cache);
        safefree(affix);
        warn("Failed to install new XSUB");
        infix_arena_destroy(parse_arena);
        XSRETURN_UNDEF;
    }

    // Attach magic for lifecycle management
    // We MUST use nullptr/0 here and assign mg_ptr manually, otherwise sv_magicext treats 'affix' as a string and
    // copies truncated garbage.
    MAGIC * mg = sv_magicext((SV *)cv_new, nullptr, PERL_MAGIC_ext, &Affix_cv_vtbl, nullptr, 0);
    mg->mg_ptr = (char *)affix;

    // Set optimization pointer
    CvXSUBANY(cv_new).any_ptr = (void *)affix;

    //
    SV * obj = (ix == 1 || ix == 3) ? newRV_noinc(MUTABLE_SV(cv_new)) : newRV_inc(MUTABLE_SV(cv_new));
    sv_bless(obj, gv_stashpv("Affix", GV_ADD));
    ST(0) = sv_2mortal(obj);

    infix_arena_destroy(parse_arena);  // Now safe to destroy as we deep-copied everything
    XSRETURN(1);
}
XS_INTERNAL(Affix_Bundled_DESTROY) {
    dXSARGS;
    dMY_CXT;
    PERL_UNUSED_VAR(items);
    Affix_Backend * backend;
    STMT_START {
        HV * st;
        GV * gvp;
        SV * const xsub_tmp_sv = ST(0);
        SvGETMAGIC(xsub_tmp_sv);
        CV * cv_ptr = sv_2cv(xsub_tmp_sv, &st, &gvp, 0);
        backend = (Affix_Backend *)CvXSUBANY(cv_ptr).any_ptr;
    }
    STMT_END;

    if (backend) {
        if (backend->lib_handle != nullptr && MY_CXT.lib_registry != nullptr) {
            hv_iterinit(MY_CXT.lib_registry);
            HE * he;
            while ((he = hv_iternext(MY_CXT.lib_registry))) {
                SV * entry_sv = HeVAL(he);
                LibRegistryEntry * entry = INT2PTR(LibRegistryEntry *, SvIV(entry_sv));
                if (entry->lib == backend->lib_handle) {
                    entry->ref_count--;
                    if (entry->ref_count == 0) {
                        infix_library_close(entry->lib);
                        safefree(entry);
                        hv_delete_ent(MY_CXT.lib_registry, HeKEY_sv(he), G_DISCARD, 0);
                    }
                    break;
                }
            }
        }
        if (backend->infix)
            infix_forward_destroy(backend->infix);
        safefree(backend);
    }
    XSRETURN_EMPTY;
}

XS_INTERNAL(Affix_DESTROY) {
    dXSARGS;
    dMY_CXT;
    PERL_UNUSED_VAR(items);
    Affix * affix;
    STMT_START {
        HV * st;
        GV * gvp;
        SV * const xsub_tmp_sv = ST(0);
        SvGETMAGIC(xsub_tmp_sv);
        CV * cv_ptr = sv_2cv(xsub_tmp_sv, &st, &gvp, 0);
        affix = (Affix *)CvXSUBANY(cv_ptr).any_ptr;
    }
    STMT_END;
    if (affix != nullptr)
        _affix_destroy(aTHX_ affix);
    XSRETURN_EMPTY;
}

static void pull_sint8(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setiv(sv, *(int8_t *)p); }
static void pull_uint8(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setuv(sv, *(uint8_t *)p); }
static void pull_sint16(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setiv(sv, *(int16_t *)p); }
static void pull_uint16(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setuv(sv, *(uint16_t *)p); }
static void pull_sint32(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setiv(sv, *(int32_t *)p); }
static void pull_uint32(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setuv(sv, *(uint32_t *)p); }
static void pull_sint64(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setiv(sv, *(int64_t *)p); }
static void pull_uint64(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setuv(sv, *(uint64_t *)p); }
static void pull_float16(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) {
    sv_setnv(sv, (double)half_to_float(*(infix_float16_t *)p));
}
static void pull_float(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setnv(sv, *(float *)p); }
static void pull_double(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setnv(sv, *(double *)p); }
static void pull_long_double(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) {
    sv_setnv(sv, *(long double *)p);
}
static void pull_bool(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setbool(sv, *(bool *)p); }
static void pull_void(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_setsv(sv, &PL_sv_undef); }

#if !defined(INFIX_COMPILER_MSVC)
static void pull_sint128(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_from_int128_safe(sv, p); }
static void pull_uint128(pTHX_ Affix * affix, SV * sv, const infix_type * t, void * p) { sv_from_uint128_safe(sv, p); }
#endif

static void pull_struct(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * p) {
    HV * hv;
    bool live = (sv_isobject(sv) && sv_derived_from(sv, "Affix::Live")) ||
        (SvROK(sv) && sv_isobject(SvRV(sv)) && sv_derived_from(SvRV(sv), "Affix::Live"));

    if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV)
        hv = (HV *)SvRV(sv);
    else {
        hv = newHV();
        sv_setsv(sv, sv_2mortal(newRV_noinc(MUTABLE_SV(hv))));
    }
    _populate_hv_from_c_struct(aTHX_ affix, hv, type, p, live, nullptr);
}

static void pull_union(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * p) {
    HV * hv;
    if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV) {
        hv = (HV *)SvRV(sv);
        hv_clear(hv);
    }
    else {
        hv = newHV();
        SV * rv = newRV_noinc(MUTABLE_SV(hv));
        sv_bless(rv, gv_stashpv("Affix::Live", GV_ADD));
        sv_setsv(sv, sv_2mortal(rv));
    }
    _populate_hv_from_c_struct(aTHX_ affix, hv, type, p, true, nullptr);
}

// Helper for portability if strnlen isn't available
static size_t _safe_strnlen(const char * s, size_t maxlen) {
    size_t len;
    for (len = 0; len < maxlen; len++, s++)
        if (!*s)
            break;
    return len;
}

static void pull_array(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * p) {
    const infix_type * element_type = type->meta.array_info.element_type;

    if (element_type->category == INFIX_TYPE_PRIMITIVE) {
        if (element_type->meta.primitive_id == INFIX_PRIMITIVE_SINT8) {
            // char[] / int8[]: Treat as fixed buffer but strip trailing nulls.
            // This satisfies typical C-string in struct usage (padded with nulls)
            // AND binary usage where nulls are embedded (as long as not trailing).
            size_t len = type->meta.array_info.num_elements;
            const char * ptr = (const char *)p;
            while (len > 0 && ptr[len - 1] == '\0')
                len--;
            sv_setpvn(sv, ptr, len);
            return;
        }
        if (element_type->meta.primitive_id == INFIX_PRIMITIVE_UINT8) {
            // uchar[] / uint8[]: Treat as raw binary blob, read full length.
            sv_setpvn(sv, (const char *)p, type->meta.array_info.num_elements);
            return;
        }
    }

    // Standard array handling (ArrayRef of values)
    AV * av;
    if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
        av = (AV *)SvRV(sv);
        av_clear(av);
    }
    else {
        av = newAV();
        sv_setsv(sv, sv_2mortal(newRV_noinc(MUTABLE_SV(av))));
    }
    size_t num_elements = type->meta.array_info.num_elements;
    size_t element_size = infix_type_get_size(element_type);
    av_extend(av, num_elements);
    for (size_t i = 0; i < num_elements; ++i) {
        void * element_ptr = (char *)p + (i * element_size);
        SV * element_sv = newSV(0);
        ptr2sv(aTHX_ affix, element_ptr, element_sv, element_type);
        av_push(av, element_sv);
    }
}

static void pull_reverse_trampoline(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * p) {
    sv_setiv(sv, PTR2IV(*(void **)p));
}

lib/Affix.c  view on Meta::CPAN

    }
    const infix_type * base_type = type->meta.complex_info.base_type;
    size_t base_size = infix_type_get_size(base_type);
    SV * real_sv = newSV(0);
    ptr2sv(aTHX_ affix, p, real_sv, base_type);
    av_push(av, real_sv);
    SV * imag_sv = newSV(0);
    ptr2sv(aTHX_ affix, (char *)p + base_size, imag_sv, base_type);
    av_push(av, imag_sv);
}

static void pull_vector(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * p) {
    AV * av;
    if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
        av = (AV *)SvRV(sv);
        av_clear(av);
    }
    else {
        av = newAV();
        sv_setsv(sv, sv_2mortal(newRV_noinc(MUTABLE_SV(av))));
    }
    const infix_type * element_type = type->meta.vector_info.element_type;
    size_t num_elements = type->meta.vector_info.num_elements;
    size_t element_size = infix_type_get_size(element_type);
    av_extend(av, num_elements);
    for (size_t i = 0; i < num_elements; ++i) {
        void * element_ptr = (char *)p + (i * element_size);
        SV * element_sv = newSV(0);
        ptr2sv(aTHX_ affix, element_ptr, element_sv, element_type);
        av_push(av, element_sv);
    }
}

static void pull_pointer_as_string(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * ptr) {
    void * c_ptr = *(void **)ptr;
    if (c_ptr == nullptr)
        sv_setsv(sv, &PL_sv_undef);
    else
        sv_setpv(sv, (const char *)c_ptr);
}

static void pull_pointer_as_struct(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * ptr) {
    void * c_ptr = *(void **)ptr;
    if (c_ptr == nullptr)
        sv_setsv(sv, &PL_sv_undef);
    else {
        const infix_type * pointee_type = type->meta.pointer_info.pointee_type;
        pull_struct(aTHX_ affix, sv, pointee_type, c_ptr);
    }
}

static void pull_struct_as_live(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * ptr) {
    void * c_ptr = *(void **)ptr;
    if (c_ptr == nullptr) {
        sv_setsv(sv, &PL_sv_undef);
        return;
    }
    const infix_type * pointee_type = type->meta.pointer_info.pointee_type;
    HV * hv = newHV();
    SV * rv = newRV_noinc(MUTABLE_SV(hv));
    sv_bless(rv, gv_stashpv("Affix::Live", GV_ADD));
    _populate_hv_from_c_struct(aTHX_ affix, hv, pointee_type, c_ptr, true, nullptr);
    sv_setsv(sv, rv);
    SvREFCNT_dec(rv);
}

static void pull_pointer_as_array(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * ptr) {
    void * c_ptr = *(void **)ptr;
    if (c_ptr == nullptr)
        sv_setsv(sv, &PL_sv_undef);
    else {
        const infix_type * pointee_type = type->meta.pointer_info.pointee_type;
        pull_array(aTHX_ affix, sv, pointee_type, c_ptr);
    }
}

static void pull_pointer_as_pin(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * ptr) {
    void * c_ptr = *(void **)ptr;

    if (c_ptr == nullptr) {
        sv_setsv(sv, &PL_sv_undef);
        return;
    }

    Affix_Pin * pin;
    Newxz(pin, 1, Affix_Pin);
    pin->pointer = c_ptr;

    // Ensure we point to the content type, not Pointer[Content]
    pin->type = _unwrap_pin_type(type);
    pin->managed = false;

    SV * obj_data = newSV(0);
    sv_setiv(obj_data, PTR2IV(pin));

    // Create the Reference
    SV * rv = sv_2mortal(newRV_noinc(obj_data));

    // Bless into Affix::Pointer BEFORE attaching magic to avoid triggering 'set' during blessing
    (void)sv_bless(rv, gv_stashpv("Affix::Pointer", GV_ADD));

    MAGIC * mg = sv_magicext(obj_data, nullptr, PERL_MAGIC_ext, &Affix_pin_vtbl, nullptr, 0);
    mg->mg_ptr = (char *)pin;

    // Update the target SV
    sv_setsv(sv, rv);
}

static void pull_sv(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * ptr) {
    void * c_ptr = *(void **)ptr;
    if (c_ptr == nullptr)
        sv_setsv(sv, &PL_sv_undef);
    else
        sv_setsv(sv, (SV *)c_ptr);
}

static void pull_file(pTHX_ Affix * affix, SV * sv, const infix_type * type, void * ptr) {
    PERL_UNUSED_VAR(affix);
    PERL_UNUSED_VAR(type);
    FILE * fp = *(FILE **)ptr;
    if (!fp) {
        sv_setsv(sv, &PL_sv_undef);
        return;
    }

    // Duplicate FD to avoid double-close issues
    int fd =
#ifdef _WIN32
        _fileno
#else
        fileno
#endif
        (fp);
    if (fd < 0) {
        sv_setsv(sv, &PL_sv_undef);
        return;
    }

    int new_fd = PerlLIO_dup(fd);
    if (new_fd < 0) {
        sv_setsv(sv, &PL_sv_undef);
        return;
    }

    PerlIO * new_pio = PerlIO_fdopen(new_fd, "r+");  // Assuming R/W safe
    if (!new_pio) {
        PerlLIO_close(new_fd);
        sv_setsv(sv, &PL_sv_undef);
        return;
    }

    GV * gv = newGVgen("Affix::FileHandle");
    if (do_open(gv, "+<&", 3, FALSE, 0, 0, new_pio))
        sv_setsv(sv, sv_2mortal(newRV((SV *)gv)));
    else {
        PerlIO_close(new_pio);
        sv_setsv(sv, &PL_sv_undef);
    }
}

lib/Affix.c  view on Meta::CPAN

                               snippet,
                               pointer,
                               err.message,
                               err.position));
}
XS_INTERNAL(Affix_Lib_as_string) {
    dVAR;
    dXSARGS;
    if (items < 1)
        croak_xs_usage(cv, "$lib");
    IV RETVAL;
    {
        infix_library_t * lib;
        IV tmp = SvIV((SV *)SvRV(ST(0)));
        lib = INT2PTR(infix_library_t *, tmp);
        RETVAL = PTR2IV(lib->handle);
    }
    XSRETURN_IV(RETVAL);
};
XS_INTERNAL(Affix_Lib_DESTROY) {
    dXSARGS;
    dMY_CXT;
    if (items != 1)
        croak_xs_usage(cv, "$lib");
    IV tmp = SvIV((SV *)SvRV(ST(0)));
    infix_library_t * lib = INT2PTR(infix_library_t *, tmp);
    if (MY_CXT.lib_registry) {
        hv_iterinit(MY_CXT.lib_registry);
        HE * he;
        SV * key_to_delete = nullptr;
        while ((he = hv_iternext(MY_CXT.lib_registry))) {
            SV * entry_sv = HeVAL(he);
            LibRegistryEntry * entry = INT2PTR(LibRegistryEntry *, SvIV(entry_sv));
            if (entry->lib == lib) {
                entry->ref_count--;
                if (entry->ref_count == 0) {
                    key_to_delete = sv_2mortal(newSVsv(HeKEY_sv(he)));
                    infix_library_close(entry->lib);
                    safefree(entry);
                }
                break;
            }
        }
        if (key_to_delete)
            hv_delete_ent(MY_CXT.lib_registry, key_to_delete, G_DISCARD, 0);
    }
    XSRETURN_EMPTY;
}
XS_INTERNAL(Affix_load_library) {
    dXSARGS;
    dMY_CXT;
    if (items != 1)
        croak_xs_usage(cv, "library_path");
    const char * path = SvPV_nolen(ST(0));
    SV ** entry_sv_ptr = hv_fetch(MY_CXT.lib_registry, path, strlen(path), 0);
    if (entry_sv_ptr) {
        LibRegistryEntry * entry = INT2PTR(LibRegistryEntry *, SvIV(*entry_sv_ptr));
        entry->ref_count++;
        SV * obj_data = newSV(0);
        sv_setiv(obj_data, PTR2IV(entry->lib));
        ST(0) = sv_2mortal(sv_bless(newRV_inc(obj_data), gv_stashpv("Affix::Lib", GV_ADD)));
        XSRETURN(1);
    }
    infix_library_t * lib = infix_library_open(path);
    if (lib) {
        LibRegistryEntry * new_entry;
        Newxz(new_entry, 1, LibRegistryEntry);
        new_entry->lib = lib;
        new_entry->ref_count = 1;
        hv_store(MY_CXT.lib_registry, path, strlen(path), newSViv(PTR2IV(new_entry)), 0);
        SV * obj_data = newSV(0);
        sv_setiv(obj_data, PTR2IV(lib));
        ST(0) = sv_2mortal(sv_bless(newRV_inc(obj_data), gv_stashpv("Affix::Lib", GV_ADD)));
        XSRETURN(1);
    }
    XSRETURN_UNDEF;
}
XS_INTERNAL(Affix_get_last_error_message) {
    dXSARGS;
    PERL_UNUSED_VAR(items);
    infix_error_details_t err = infix_get_last_error();
    if (err.message[0] != '\0')
        ST(0) = sv_2mortal(newSVpv(err.message, 0));
#if defined(INFIX_OS_WINDOWS)
    else if (err.system_error_code != 0) {
        char buf[256];
        FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                       nullptr,
                       err.system_error_code,
                       0,
                       buf,
                       sizeof(buf),
                       nullptr);
        ST(0) = sv_2mortal(newSVpvf("System error: %s (code %ld)", buf, err.system_error_code));
    }
#endif
    else
        ST(0) = sv_2mortal(newSVpvf("Infix error code %d at position %zu", (int)err.code, err.position));
    XSRETURN(1);
}

Affix_Pin * _get_pin_from_sv(pTHX_ SV * sv) {
    if (!sv || !SvOK(sv) || !SvROK(sv) || !SvMAGICAL(SvRV(sv)))
        return nullptr;
    MAGIC * mg = mg_findext(SvRV(sv), PERL_MAGIC_ext, &Affix_pin_vtbl);
    if (mg)
        return (Affix_Pin *)mg->mg_ptr;
    return nullptr;
}
static int Affix_set_pin(pTHX_ SV * sv, MAGIC * mg) {
    Affix_Pin * pin = (Affix_Pin *)mg->mg_ptr;
    if (!pin || !pin->pointer || !pin->type)
        return 0;

    if (pin->bit_width > 0) {
        size_t sz = infix_type_get_size(pin->type);
        uint64_t val = (uint64_t)SvUV(sv);
        uint64_t mask = ((uint64_t)1 << pin->bit_width) - 1;
        val &= mask;
        uint64_t current = 0;
        memcpy(&current, pin->pointer, sz);
        current &= ~(mask << pin->bit_offset);
        current |= (val << pin->bit_offset);
        memcpy(pin->pointer, &current, sz);
        return 0;
    }

    const infix_type * type_to_marshal = pin->type;

    if (pin->type->category == INFIX_TYPE_POINTER) {
        const infix_type * pointee = pin->type->meta.pointer_info.pointee_type;
        if (pointee->category == INFIX_TYPE_VOID) {
            if (pin->size > 0) {

lib/Affix.c  view on Meta::CPAN

        pin->destructor(pin->pointer);
    else if (pin->managed && pin->pointer)
        safefree(pin->pointer);

    if (pin->destructor_lib_sv) {
        if (!PL_dirty)
            SvREFCNT_dec(pin->destructor_lib_sv);
    }

    if (pin->owner_sv) {
        if (!PL_dirty)
            SvREFCNT_dec(pin->owner_sv);
    }

    if (pin->type_arena != nullptr)
        infix_arena_destroy(pin->type_arena);
    safefree(pin);
    mg->mg_ptr = nullptr;
    return 0;
}
static int Affix_get_pin(pTHX_ SV * sv, MAGIC * mg) {
    Affix_Pin * pin = (Affix_Pin *)mg->mg_ptr;
    if (!pin || !pin->pointer) {
        sv_setsv_mg(sv, &PL_sv_undef);
        return 0;
    }

    if (pin->bit_width > 0) {
        size_t sz = infix_type_get_size(pin->type);
        uint64_t raw = 0;
        memcpy(&raw, pin->pointer, sz);
        uint64_t val = (raw >> pin->bit_offset) & (((uint64_t)1 << pin->bit_width) - 1);
        sv_setuv(sv, val);
        return 0;
    }

    if (pin->type && pin->type->category == INFIX_TYPE_POINTER) {
        const infix_type * pointee = pin->type->meta.pointer_info.pointee_type;

        if (pointee->category == INFIX_TYPE_PRIMITIVE &&
            (pointee->meta.primitive_id == INFIX_PRIMITIVE_SINT8 ||
             pointee->meta.primitive_id == INFIX_PRIMITIVE_UINT8)) {

            const char * str = (const char *)pin->pointer;
            if (str)
                sv_setpv(sv, str);
            else
                sv_setsv(sv, &PL_sv_undef);

            return 0;
        }

        if (pointee->category == INFIX_TYPE_VOID) {
            sv_setuv(sv, PTR2UV(pin->pointer));
            return 0;
        }

        if (pointee->category == INFIX_TYPE_STRUCT || pointee->category == INFIX_TYPE_UNION) {
            HV * hv = newHV();
            SV * rv = newRV_noinc(MUTABLE_SV(hv));
            sv_bless(rv, gv_stashpv("Affix::Live", GV_ADD));
            _populate_hv_from_c_struct(
                aTHX_ nullptr, hv, pointee, pin->pointer, true, pin->owner_sv ? pin->owner_sv : sv);
            sv_setsv(sv, rv);
            SvREFCNT_dec(rv);
            return 0;
        }

        if (pointee->category == INFIX_TYPE_ARRAY) {
            Affix_Pin * new_pin;
            Newxz(new_pin, 1, Affix_Pin);
            new_pin->pointer = pin->pointer;
            new_pin->managed = false;
            new_pin->owner_sv = pin->owner_sv ? pin->owner_sv : sv;
            SvREFCNT_inc(new_pin->owner_sv);
            new_pin->type_arena = infix_arena_create(256);
            new_pin->type = _copy_type_graph_to_arena(new_pin->type_arena, pointee);

            sv_setsv(sv, sv_2mortal(_new_pointer_obj(aTHX_ new_pin)));
            return 0;
        }
    }

    if (pin->type)
        ptr2sv(aTHX_ nullptr, pin->pointer, sv, pin->type);
    return 0;
}
bool is_pin(pTHX_ SV * sv) {
    if (!sv || !SvOK(sv) || !SvROK(sv) || !SvMAGICAL(SvRV(sv)))
        return false;
    return mg_findext(SvRV(sv), PERL_MAGIC_ext, &Affix_pin_vtbl) != nullptr;
}
void _pin_sv(pTHX_ SV * sv,
             const infix_type * type,
             void * pointer,
             bool managed,
             SV * owner_sv,
             size_t bit_offset,
             size_t bit_width) {
    if (SvREADONLY(sv))
        return;
    SvUPGRADE(sv, SVt_PVMG);
    MAGIC * mg = mg_findext(sv, PERL_MAGIC_ext, &Affix_pin_vtbl);
    Affix_Pin * pin;
    if (mg) {
        pin = (Affix_Pin *)mg->mg_ptr;
        if (pin && pin->managed && pin->pointer)
            safefree(pin->pointer);
        if (pin && pin->type_arena) {
            infix_arena_destroy(pin->type_arena);
            pin->type_arena = nullptr;
        }
        if (pin && pin->owner_sv) {
            SvREFCNT_dec(pin->owner_sv);
            pin->owner_sv = nullptr;
        }
    }
    else {
        Newxz(pin, 1, Affix_Pin);
        mg = sv_magicext(sv, nullptr, PERL_MAGIC_ext, &Affix_pin_vtbl, nullptr, 0);
    }
    // Re-assign mg_ptr because sv_magicext(..., 0) likely corrupted it by treating pin as a string
    mg->mg_ptr = (char *)pin;

    pin->pointer = pointer;
    pin->managed = managed;
    pin->bit_offset = bit_offset;
    pin->bit_width = bit_width;

    if (owner_sv) {
        pin->owner_sv = owner_sv;
        SvREFCNT_inc(pin->owner_sv);
    }

    pin->type_arena = infix_arena_create(2048);
    if (!pin->type_arena) {
        safefree(pin);
        mg->mg_ptr = nullptr;
        croak("Failed to create memory arenas for pin's type information");
    }
    pin->type = _copy_type_graph_to_arena(pin->type_arena, type);
    if (!pin->type) {
        infix_arena_destroy(pin->type_arena);
        safefree(pin);
        mg->mg_ptr = nullptr;
        croak("Failed to copy type information into pin");
    }
}
XS_INTERNAL(Affix_find_symbol) {
    dXSARGS;
    dMY_CXT;  // Require the thread-local context
    if (items != 2 || !sv_isobject(ST(0)) || !sv_derived_from(ST(0), "Affix::Lib"))
        croak_xs_usage(cv, "Affix_Lib_object, symbol_name");
    IV tmp = SvIV((SV *)SvRV(ST(0)));
    infix_library_t * lib = INT2PTR(infix_library_t *, tmp);
    const char * name = SvPV_nolen(ST(1));
    void * symbol = infix_library_get_symbol(lib, name);
    if (symbol) {
        Affix_Pin * pin;
        Newxz(pin, 1, Affix_Pin);
        pin->pointer = symbol;
        pin->managed = false;
        pin->owner_sv = ST(0);
        SvREFCNT_inc(pin->owner_sv);
        pin->type_arena = infix_arena_create(256);
        infix_type * void_ptr_type = nullptr;
        if (infix_type_create_pointer_to(pin->type_arena, &void_ptr_type, infix_type_create_void()) != INFIX_SUCCESS) {
            safefree(pin);
            infix_arena_destroy(pin->type_arena);
            croak("Internal error: Failed to create pointer type for pin");
        }
        pin->type = void_ptr_type;
        SV * obj_data = newSV(0);
        sv_setiv(obj_data, PTR2IV(pin));
        SV * rv = newRV_inc(obj_data);

        // Bless into Affix::Pointer BEFORE attaching magic to avoid triggering 'set' during blessing
        (void)sv_bless(rv, gv_stashpv("Affix::Pointer", GV_ADD));

        MAGIC * mg = sv_magicext(obj_data, nullptr, PERL_MAGIC_ext, &Affix_pin_vtbl, nullptr, 0);
        mg->mg_ptr = (char *)pin;
        ST(0) = sv_2mortal(rv);
        XSRETURN(1);
    }
    XSRETURN_UNDEF;
}

XS_INTERNAL(Affix_pin) {
    dXSARGS;
    dMY_CXT;
    if (items != 4)
        croak_xs_usage(cv, "var, lib, symbol, type");
    SV * target_sv = ST(0);
    const char * lib_path_or_name = SvPV_nolen(ST(1));
    const char * symbol_name = SvPV_nolen(ST(2));
    const char * signature = SvPV_nolen(ST(3));
    infix_library_t * lib = infix_library_open(lib_path_or_name);
    if (lib == nullptr) {
        warn("Failed to load library from path '%s' for pinning: %s", lib_path_or_name, infix_get_last_error().message);
        XSRETURN_UNDEF;
    }
    void * ptr = infix_library_get_symbol(lib, symbol_name);
    infix_library_close(lib);
    if (ptr == nullptr) {
        warn("Failed to locate symbol '%s' in library '%s'", symbol_name, lib_path_or_name);
        XSRETURN_UNDEF;
    }
    infix_type * type = nullptr;
    infix_arena_t * arena = nullptr;
    const char * sig_to_parse = signature;
    char * clean_sig = nullptr;
    if (strstr(signature, "+")) {
        clean_sig = savepv(signature);
        const char * p = signature;
        char * d = clean_sig;
        while (*p) {
            // Strip '+' only if it precedes a signature character: * [ { ! < ( @
            if (*p == '+' &&
                (p[1] == '*' || p[1] == '[' || p[1] == '{' || p[1] == '!' || p[1] == '<' || p[1] == '(' || p[1] == '@'))
                p++;
            else
                *d++ = *p++;
        }
        *d = '\0';
        sig_to_parse = clean_sig;
    }

    if (infix_type_from_signature(&type, &arena, sig_to_parse, MY_CXT.registry) != INFIX_SUCCESS) {
        SV * err_sv = _format_parse_error(aTHX_ "for pin", sig_to_parse, infix_get_last_error());
        warn_sv(err_sv);
        if (arena)
            infix_arena_destroy(arena);
        if (clean_sig)
            safefree(clean_sig);
        XSRETURN_UNDEF;
    }
    _pin_sv(aTHX_ target_sv, type, ptr, false, nullptr, 0, 0);
    infix_arena_destroy(arena);

lib/Affix.c  view on Meta::CPAN

        printf(" %02x", pc[i]);
        if ((pc[i] < 0x20) || (pc[i] > 0x7e))
            buff[i % perLine] = '.';
        else
            buff[i % perLine] = pc[i];
        buff[(i % perLine) + 1] = '\0';
    }
    while ((i % perLine) != 0) {
        printf("   ");
        i++;
    }
    printf(" | %s\n", buff);
    safefree(buff);
    fflush(stdout);
}

void _DD(pTHX_ SV * scalar, const char * file, int line) {
    Perl_load_module(aTHX_ PERL_LOADMOD_NOIMPORT, newSVpvs("Data::Printer"), nullptr, nullptr, nullptr);
    if (!get_cvs("Data::Printer::p", GV_NOADD_NOINIT | GV_NO_SVGMAGIC))
        return;
    fflush(stdout);
    dSP;
    int count;
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    EXTEND(SP, 1);
    PUSHs(scalar);
    PUTBACK;
    count = call_pv("Data::Printer::p", G_SCALAR);
    SPAGAIN;
    if (count != 1) {
        warn("Big trouble\n");
        return;
    }
    STRLEN len;
    const char * s = SvPVx(POPs, len);
    printf("%s at %s line %d\n", s, file, line);
    fflush(stdout);
    PUTBACK;
    FREETMPS;
    LEAVE;
}

XS_INTERNAL(Affix_sv_dump) {
    dXSARGS;
    if (items != 1)
        croak_xs_usage(cv, "sv");
    sv_dump(ST(0));
    XSRETURN_EMPTY;
}

SV * _new_pointer_obj(pTHX_ Affix_Pin * pin) {
    SV * data_sv = newSV(0);
    sv_setiv(data_sv, PTR2IV(pin));
    SvUPGRADE(data_sv, SVt_PVMG);

    SV * rv = newRV_noinc(data_sv);

    // Bless into Affix::Pointer
    (void)sv_bless(rv, gv_stashpv("Affix::Pointer", GV_ADD));

    MAGIC * mg = sv_magicext(data_sv, nullptr, PERL_MAGIC_ext, &Affix_pin_vtbl, nullptr, 0);
    mg->mg_ptr = (char *)pin;

    return rv;
}

XS_INTERNAL(Affix_malloc) {
    dXSARGS;
    dMY_CXT;

    if (items < 1)
        croak_xs_usage(cv, "size");

    UV size = SvUV(ST(0));
    infix_type * type = nullptr;
    infix_arena_t * parse_arena = nullptr;

    const char * sig = "*void";

    if (infix_type_from_signature(&type, &parse_arena, sig, MY_CXT.registry) != INFIX_SUCCESS) {
        SV * err_sv = _format_parse_error(aTHX_ "for malloc", sig, infix_get_last_error());
        warn_sv(err_sv);
        if (parse_arena)
            infix_arena_destroy(parse_arena);
        XSRETURN_UNDEF;
    }

    if (size == 0) {
        infix_arena_destroy(parse_arena);
        warn("Cannot malloc a zero-sized type");
        XSRETURN_UNDEF;
    }

    void * ptr = safemalloc(size);
    Affix_Pin * pin;
    Newxz(pin, 1, Affix_Pin);
    pin->size = size;
    pin->pointer = ptr;
    pin->managed = true;
    pin->type_arena = infix_arena_create(1024);

    // We unwrap the pointer type logic here similar to cast.
    // If the user passed "Int", we want the pin to be typed as "Int" (so $$pin reads an int).
    // _unwrap_pin_type handles the logic of "don't unwrap *void or *char".
    pin->type = _copy_type_graph_to_arena(pin->type_arena, _unwrap_pin_type(type));

    infix_arena_destroy(parse_arena);
    ST(0) = sv_2mortal(_new_pointer_obj(aTHX_ pin));
    XSRETURN(1);
}

XS_INTERNAL(Affix_calloc) {
    dXSARGS;
    dMY_CXT;
    if (items != 2)
        croak_xs_usage(cv, "count, type_signature");
    UV count = SvUV(ST(0));
    const char * signature = nullptr;
    SV * type_sv = ST(1);

lib/Affix.c  view on Meta::CPAN

        signature = SvPV_nolen(type_sv);

    bool live_hint = (signature[0] == '+');
    if (live_hint)
        signature++;

    infix_type * new_type = nullptr;
    infix_arena_t * parse_arena = nullptr;

    if (infix_type_from_signature(&new_type, &parse_arena, signature, MY_CXT.registry) != INFIX_SUCCESS) {
        SV * err_sv = _format_parse_error(aTHX_ "for cast", signature, infix_get_last_error());
        warn_sv(err_sv);
        if (parse_arena)
            infix_arena_destroy(parse_arena);
        XSRETURN_UNDEF;
    }

    /* Value (Copy) vs Pin (Reference) */
    bool return_as_value = false;
    bool is_string_type = false;

    if (new_type->category == INFIX_TYPE_PRIMITIVE || new_type->category == INFIX_TYPE_ENUM ||
        new_type->category == INFIX_TYPE_STRUCT || new_type->category == INFIX_TYPE_UNION) {
        return_as_value = true;
    }
    else if (new_type->category == INFIX_TYPE_POINTER) {
        const infix_type * pointee = new_type->meta.pointer_info.pointee_type;

        /* Check if casting to String (*char) or WString (*wchar_t) */
        if (pointee->category == INFIX_TYPE_PRIMITIVE) {
            if (pointee->meta.primitive_id == INFIX_PRIMITIVE_SINT8 ||
                pointee->meta.primitive_id == INFIX_PRIMITIVE_UINT8 ||
                /* Char check */
                infix_type_get_size(pointee) == 1) {
                return_as_value = true;
                is_string_type = true;
            }
#if defined(INFIX_OS_WINDOWS)
            else if (infix_type_get_size(pointee) == sizeof(wchar_t)) {
                return_as_value = true;
                is_string_type = true;
            }
#endif
        }
    }

    if (return_as_value) {
        /* Read memory -> Perl Scalar */
        SV * ret_val = sv_newmortal();

        const infix_type * resolved = _resolve_type(aTHX_ new_type);
        if (is_string_type) {
            /*
             * String pull handlers expect a pointer-to-pointer (char**).
             * 'ptr_val' IS the char*. So we pass '&ptr_val'.
             * The handler reads *(&ptr_val) -> ptr_val, then reads the string.
             */
            ptr2sv(aTHX_ nullptr, &ptr_val, ret_val, new_type);
        }
        else if (live_hint && (resolved->category == INFIX_TYPE_STRUCT || resolved->category == INFIX_TYPE_UNION)) {
            // Live struct return from cast: bypass ptr2sv and create blessed HV
            HV * hv = newHV();
            SV * rv = newRV_noinc(MUTABLE_SV(hv));
            sv_bless(rv, gv_stashpv("Affix::Live", GV_ADD));
            _populate_hv_from_c_struct(
                aTHX_ nullptr, hv, resolved, ptr_val, true, pin ? (pin->owner_sv ? pin->owner_sv : arg) : nullptr);
            ret_val = sv_2mortal(rv);
        }
        else if (resolved->category == INFIX_TYPE_UNION) {
            // Unions are always live!
            pull_union(aTHX_ nullptr, ret_val, resolved, ptr_val);
        }
        else {
            /*
             * Primitives expect a pointer to the value.
             * 'ptr_val' IS the address of the value. We pass 'ptr_val'.
             * The handler reads *(int*)ptr_val.
             */
            ptr2sv(aTHX_ nullptr, ptr_val, ret_val, new_type);
        }

        infix_arena_destroy(parse_arena);
        ST(0) = ret_val;
    }
    else {
        /* Return Alias Pin */
        Affix_Pin * new_pin;
        Newxz(new_pin, 1, Affix_Pin);
        new_pin->pointer = ptr_val;
        new_pin->managed = false;
        new_pin->type_arena = parse_arena;

        if (pin) {
            new_pin->owner_sv = pin->owner_sv ? pin->owner_sv : arg;
            SvREFCNT_inc(new_pin->owner_sv);
        }

        if (new_type->category == INFIX_TYPE_POINTER)
            new_pin->type = _unwrap_pin_type(new_type);
        else
            new_pin->type = new_type;

        // Create the object (SV wrapped in RV)
        SV * rv = _new_pointer_obj(aTHX_ new_pin);

        // Return the RV
        ST(0) = sv_2mortal(rv);
    }
    XSRETURN(1);
}

XS_INTERNAL(Affix_own) {
    dXSARGS;
    if (items < 1)
        croak_xs_usage(cv, "pin, [should_own]");

    Affix_Pin * pin = _get_pin_from_sv(aTHX_ ST(0));
    if (!pin) {
        warn("Argument is not a pinned pointer");
        XSRETURN_UNDEF;
    }

    if (items > 1) {
        // Don't dirty the memory if value hasn't changed

lib/Affix.c  view on Meta::CPAN

        warn("Failed to create char* type for strdup");
        XSRETURN_UNDEF;
    }

    ST(0) = sv_2mortal(_new_pointer_obj(aTHX_ pin));
    XSRETURN(1);
}

XS_INTERNAL(Affix_strnlen) {
    dXSARGS;
    if (items != 2)
        croak_xs_usage(cv, "ptr, maxlen");
    const char * ptr = (const char *)_resolve_readable_ptr(aTHX_ ST(0));
    size_t maxlen = (size_t)SvUV(ST(1));

    if (!ptr)
        XSRETURN_IV(0);

    // strnlen is not standard C89, so we implement it manually to be safe
    size_t len = 0;
    while (len < maxlen && ptr[len] != '\0')
        len++;
    ST(0) = sv_2mortal(newSVuv(len));
    XSRETURN(1);
}

XS_INTERNAL(Affix_is_null) {
    dXSARGS;
    if (items != 1)
        croak_xs_usage(cv, "ptr");
    const void * ptr = _resolve_readable_ptr(aTHX_ ST(0));
    ST(0) = ptr == nullptr ? &PL_sv_yes : &PL_sv_no;
    XSRETURN(1);
}

static const infix_type * _resolve_type(pTHX_ const infix_type * type) {
    if (type->category == INFIX_TYPE_NAMED_REFERENCE) {
        dMY_CXT;
        const infix_type * resolved = infix_registry_lookup_type(MY_CXT.registry, type->meta.named_reference.name);
        return resolved ? resolved : type;
    }
    return type;
}

void _populate_hv_from_c_struct(
    pTHX_ Affix * affix, HV * hv, const infix_type * type, void * p, bool live, SV * owner_sv) {
    hv_clear(hv);
    const infix_type * resolved_type = _resolve_type(aTHX_ type);
    for (size_t i = 0; i < resolved_type->meta.aggregate_info.num_members; ++i) {
        const infix_struct_member * member = &resolved_type->meta.aggregate_info.members[i];
        if (member->name) {
            void * member_ptr = (char *)p + member->offset;
            SV * member_sv = nullptr;

            if (live) {
                // Live mode: Create a Pin or Live view for this member
                const infix_type * resolved = _resolve_type(aTHX_ member->type);
                if (resolved->category == INFIX_TYPE_STRUCT || resolved->category == INFIX_TYPE_UNION) {
                    HV * sub_hv = newHV();
                    SV * sub_rv = newRV_noinc(MUTABLE_SV(sub_hv));
                    sv_bless(sub_rv, gv_stashpv("Affix::Live", GV_ADD));
                    _populate_hv_from_c_struct(aTHX_ affix, sub_hv, resolved, member_ptr, true, owner_sv);
                    member_sv = sub_rv;
                }
                else if (resolved->category == INFIX_TYPE_ARRAY) {
                    Affix_Pin * sub_pin;
                    Newxz(sub_pin, 1, Affix_Pin);
                    sub_pin->pointer = member_ptr;
                    sub_pin->managed = false;

                    if (owner_sv) {
                        sub_pin->owner_sv = owner_sv;
                        SvREFCNT_inc(sub_pin->owner_sv);
                    }

                    sub_pin->type_arena = infix_arena_create(256);
                    sub_pin->type = _copy_type_graph_to_arena(sub_pin->type_arena, member->type);
                    member_sv = _new_pointer_obj(aTHX_ sub_pin);
                }
                else {
                    member_sv = newSV(0);
                    _pin_sv(aTHX_ member_sv,
                            member->type,
                            member_ptr,
                            false,
                            owner_sv,
                            member->bit_offset,
                            member->bit_width);
                }
            }
            else if (member->is_bitfield) {
                // Bitfield pull: mask and shift
                member_sv = newSV(0);
                size_t sz = infix_type_get_size(member->type);
                uint64_t raw = 0;
                memcpy(&raw, member_ptr, sz);

                uint64_t val = (raw >> member->bit_offset) & (((uint64_t)1 << member->bit_width) - 1);
                sv_setuv(member_sv, val);
            }
            else {
                member_sv = newSV(0);
                ptr2sv(aTHX_ affix, member_ptr, member_sv, member->type);
            }

            if (member_sv)
                hv_store(hv, member->name, strlen(member->name), member_sv, 0);
        }
    }
}

// Cribbed from Perl::Destruct::Level so leak testing works without yet another prereq
XS_INTERNAL(Affix_set_destruct_level) {
    dVAR;
    dXSARGS;
    // TODO: report this with a warn(...)
    if (items != 1)
        croak_xs_usage(cv, "level");
    PL_perl_destruct_level = SvIV(ST(0));
    XSRETURN_EMPTY;
}

lib/Affix.c  view on Meta::CPAN

    Affix_Pin * pin = _get_pin_from_sv(aTHX_ ST(0));
    if (pin && pin->type) {
        if (pin->type->category == INFIX_TYPE_ARRAY) {
            ST(0) = sv_2mortal(newSVuv(pin->type->meta.array_info.num_elements));
            XSRETURN(1);
        }
        else if (pin->type->category == INFIX_TYPE_VOID ||
                 (pin->type->category == INFIX_TYPE_POINTER &&
                  pin->type->meta.pointer_info.pointee_type->category == INFIX_TYPE_VOID)) {
            // For void pointers, count is the byte size if known
            if (pin->size > 0) {
                ST(0) = sv_2mortal(newSVuv(pin->size));
                XSRETURN(1);
            }
        }
    }
    XSRETURN_UNDEF;
}

XS_INTERNAL(Affix_pin_size) {
    dXSARGS;
    if (items != 1)
        croak_xs_usage(cv, "pin");
    Affix_Pin * pin = _get_pin_from_sv(aTHX_ ST(0));
    if (pin) {
        ST(0) = sv_2mortal(newSVuv(pin->size));
        XSRETURN(1);
    }
    XSRETURN_UNDEF;
}

XS_INTERNAL(Affix_pin_get_at) {
    dXSARGS;
    if (items != 2)
        croak_xs_usage(cv, "pin, index");
    Affix_Pin * pin = _get_pin_from_sv(aTHX_ ST(0));
    IV index = SvIV(ST(1));
    if (!pin || !pin->type)
        croak("Not a valid pinned pointer");
    const infix_type * type = pin->type;
    const infix_type * elem_type = type;
    if (type->category == INFIX_TYPE_ARRAY)
        elem_type = type->meta.array_info.element_type;
    else if (type->category == INFIX_TYPE_POINTER && type->meta.pointer_info.pointee_type->category == INFIX_TYPE_VOID)
        elem_type = type->meta.pointer_info.pointee_type;
    else
        croak("Cannot index into non-aggregate type");

    size_t elem_size = infix_type_get_size(elem_type);
    if (elem_size == 0 && elem_type->category == INFIX_TYPE_VOID) {
        elem_size = 1;  // Byte-indexed for void*
        elem_type = infix_type_create_primitive(INFIX_PRIMITIVE_UINT8);
    }
    if (elem_size == 0)
        croak("Cannot index into zero-sized type");
    void * target = (char *)pin->pointer + (index * elem_size);

    if (elem_type->category == INFIX_TYPE_STRUCT || elem_type->category == INFIX_TYPE_UNION) {
        HV * hv = newHV();
        SV * rv = newRV_noinc(MUTABLE_SV(hv));
        sv_bless(rv, gv_stashpv("Affix::Live", GV_ADD));
        // We might need to pass the owner here too, but let's start with pins
        _populate_hv_from_c_struct(aTHX_ nullptr, hv, elem_type, target, true, pin->owner_sv ? pin->owner_sv : ST(0));
        ST(0) = sv_2mortal(rv);
    }
    else if (elem_type->category == INFIX_TYPE_ARRAY) {
        // Return a new Affix::Pointer for this sub-array (Live view)
        Affix_Pin * new_pin;
        Newxz(new_pin, 1, Affix_Pin);
        new_pin->pointer = target;
        new_pin->managed = false;

        new_pin->owner_sv = pin->owner_sv ? pin->owner_sv : ST(0);
        SvREFCNT_inc(new_pin->owner_sv);

        // We need to keep the type info alive. For now, copy it.
        new_pin->type_arena = infix_arena_create(256);
        new_pin->type = _copy_type_graph_to_arena(new_pin->type_arena, elem_type);

        ST(0) = sv_2mortal(_new_pointer_obj(aTHX_ new_pin));
    }
    else {
        SV * res = sv_newmortal();
        ptr2sv(aTHX_ nullptr, target, res, elem_type);
        ST(0) = res;
    }
    XSRETURN(1);
}

XS_INTERNAL(Affix_pin_set_at) {
    dXSARGS;
    if (items != 3)
        croak_xs_usage(cv, "pin, index, value");
    Affix_Pin * pin = _get_pin_from_sv(aTHX_ ST(0));
    IV index = SvIV(ST(1));
    SV * val_sv = ST(2);
    if (!pin || !pin->type)
        croak("Not a valid pinned pointer");
    const infix_type * type = pin->type;
    const infix_type * elem_type = type;
    if (type->category == INFIX_TYPE_ARRAY)
        elem_type = type->meta.array_info.element_type;
    else if (type->category == INFIX_TYPE_POINTER && type->meta.pointer_info.pointee_type->category == INFIX_TYPE_VOID)
        elem_type = type->meta.pointer_info.pointee_type;
    else
        croak("Cannot index into non-aggregate type");

    size_t elem_size = infix_type_get_size(elem_type);
    if (elem_size == 0 && elem_type->category == INFIX_TYPE_VOID) {
        elem_size = 1;
        elem_type = infix_type_create_primitive(INFIX_PRIMITIVE_UINT8);
    }
    if (elem_size == 0)
        croak("Cannot index into zero-sized type");
    void * target = (char *)pin->pointer + (index * elem_size);
    sv2ptr(aTHX_ nullptr, val_sv, target, elem_type);
    XSRETURN_EMPTY;
}

// Helper to register core internal types
static void _register_core_types(infix_registry_t * registry) {



( run in 0.888 second using v1.01-cache-2.11-cpan-39bf76dae61 )