Affix

 view release on metacpan or  search on metacpan

lib/Affix.c  view on Meta::CPAN


    return 1;
}

// Helper to rebuild Affix data in the new thread
static void rebuild_affix_data(pTHX_ Affix * affix) {
    //~ warn("rebuild_affix_data: %p", affix);
    dMY_CXT;
    infix_arena_t * parse_arena = nullptr;
    infix_type * ret_type = nullptr;
    infix_function_argument * args = nullptr;
    size_t num_args = 0, num_fixed = 0;

    // Re-parse signature using THIS thread's registry
    infix_status status =
        infix_signature_parse(affix->sig_str, &parse_arena, &ret_type, &args, &num_args, &num_fixed, MY_CXT.registry);

    if (status != INFIX_SUCCESS) {
        if (parse_arena)
            infix_arena_destroy(parse_arena);
        croak("Affix failed to rebuild in new thread: signature parse error");
    }

    // Prepare JIT types (handle array decay)
    infix_type ** jit_arg_types = nullptr;
    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) {
                infix_type * ptr_type = nullptr;
                status = infix_type_create_pointer_to(parse_arena, &ptr_type, t->meta.array_info.element_type);
                if (status != INFIX_SUCCESS) {
                    if (parse_arena)
                        infix_arena_destroy(parse_arena);
                    croak("Affix failed to rebuild in new thread: type clone error");
                }
                jit_arg_types[i] = ptr_type;
            }
            else
                jit_arg_types[i] = t;
        }
    }

    // Create trampoline
    status =
        infix_forward_create_manual(&affix->infix, ret_type, jit_arg_types, num_args, num_fixed, affix->target_addr);

    if (jit_arg_types)
        safefree(jit_arg_types);

    if (status != INFIX_SUCCESS) {
        infix_arena_destroy(parse_arena);
        croak("Affix failed to rebuild trampoline in new thread");
    }

    affix->cif = infix_forward_get_code(affix->infix);
    affix->ret_type = infix_forward_get_return_type(affix->infix);
    affix->unwrapped_ret_type = _unwrap_pin_type(affix->ret_type);
    affix->ret_pull_handler = get_pull_handler(aTHX_ affix->ret_type);
    // affix->ret_opcode is already set from parent, but safe to assume it matches

    // Allocate arenas & SV
    affix->args_arena = infix_arena_create(4096);
    affix->ret_arena = infix_arena_create(1024);
    affix->return_sv = newSV(0);
    if (affix->num_args > 0)
        Newx(affix->c_args, affix->num_args, void *);

    affix->variadic_cache = newHV();

    // 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(aTHX_ 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);
            if (!pointee_name && pointee->category == INFIX_TYPE_NAMED_REFERENCE)
                pointee_name = pointee->meta.named_reference.name;
            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) {

lib/Affix.c  view on Meta::CPAN


    // Other special types are opaque structs too. ...but they don't always mean anything in particular.
    if (infix_register_types(registry, "@StringList = **char;") != INFIX_SUCCESS)
        croak("Failed to register internal type alias '@StringList'");
    if (infix_register_types(registry, "@Buffer = *void;") != INFIX_SUCCESS)
        croak("Failed to register internal type alias '@Buffer'");
    if (infix_register_types(registry, "@SockAddr = *void;") != INFIX_SUCCESS)
        croak("Failed to register internal type alias '@SockAddr'");
}


static void _set_readonly_recursive(pTHX_ SV * sv, bool ro) {
    if (!sv || !SvOK(sv))
        return;
    SV * target = SvROK(sv) ? SvRV(sv) : sv;
    Affix_Pin_2_Point_Oh * pin = get_pin_v2(aTHX_ target);
    if (pin)
        pin->readonly = ro;

    if (SvTYPE(target) == SVt_PVHV) {
        HV * hv = (HV *)target;
        HE * entry;
        hv_iterinit(hv);
        while ((entry = hv_iternext(hv)))
            _set_readonly_recursive(aTHX_ hv_iterval(hv, entry), ro);
    }
    else if (SvTYPE(target) == SVt_PVAV) {
        AV * av = (AV *)target;
        SSize_t len = av_len(av);
        for (SSize_t i = 0; i <= len; i++) {
            SV ** val = av_fetch(av, i, 0);
            if (val && *val)
                _set_readonly_recursive(aTHX_ * val, ro);
        }
    }
}

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

    // Check for V2 Pin first
    if (is_pin_v2(aTHX_ ST(0))) {
        Affix_Pin_2_Point_Oh * pin_v2 = get_pin_v2(aTHX_ ST(0));
        if (items > 1) {
            bool ro = SvTRUE(ST(1));
            _set_readonly_recursive(aTHX_ ST(0), ro);
        }
        ST(0) = pin_v2->readonly ? &PL_sv_yes : &PL_sv_no;
        XSRETURN(1);
    }

    XSRETURN_UNDEF;
}

XS_INTERNAL(Affix_CLONE) {
    dXSARGS;
    PERL_UNUSED_VAR(items);

    // Initialize the new thread's context (copies bitwise from parent)
    MY_CXT_CLONE;

    // Capture the parent's registry pointer.
    // After MY_CXT_CLONE, MY_CXT refers to the new thread's context,
    // which has been initialized as a bitwise copy of the parent's context.
    infix_registry_t * parent_registry = MY_CXT.registry;

    // Overwrite shared pointers with fresh objects for the new thread
    MY_CXT.lib_registry = newHV();
    MY_CXT.callback_registry = newHV();
    MY_CXT.enum_registry = newHV();
    MY_CXT.coercion_cache = newHV();
    MY_CXT.stash_pointer = nullptr;

    // Deep copy the type registry.
    // This ensures typedefs and structs defined in the parent thread exist in the child thread,
    // but the child owns its own memory arena, making it thread-safe.
    if (parent_registry)
        MY_CXT.registry = infix_registry_clone(parent_registry);
    else
        MY_CXT.registry = infix_registry_create();

    if (!MY_CXT.registry)
        warn("Failed to initialize the global type registry in new thread");

    // Don't ccall _register_core_types here if we cloned, because the clone already contains @SV, @File, etc.
    if (!parent_registry)
        _register_core_types(MY_CXT.registry);

    XSRETURN_EMPTY;
}


#include "Affix/marshal.c"

// Runtime allocator callbacks that route infix's memory through Perl's
// allocator. They are installed once at load time via infix_set_allocator() in
// boot_Affix below; infix then dispatches every internal heap allocation
// through this table, so libinfix.a stays a plain standalone library with zero
// knowledge of Perl. The explicit (MEM_SIZE) casts keep this correct on any
// perl configuration, and Perl_safesys* derive the current interpreter from
// the calling thread's thread-local storage (dTHX under ALWAYS_NEED_THX
// builds), so the allocation is attributed to whichever interpreter the infix
// call happens on. Combined with Affix's per-thread ownership of infix objects
// (see Affix_CLONE and Affix_cv_dup), every infix allocation is both created
// and destroyed on the same interpreter, so Perl's pool validation
// (PERL_TRACK_MEMPOOL) never fires.
static void * affix_infix_malloc(size_t nbytes) { return Perl_safesysmalloc((MEM_SIZE)nbytes); }

static void * affix_infix_calloc(size_t nelem, size_t size) {
    return Perl_safesyscalloc((MEM_SIZE)nelem, (MEM_SIZE)size);
}

static void * affix_infix_realloc(void * ptr, size_t nbytes) { return Perl_safesysrealloc(ptr, (MEM_SIZE)nbytes); }

static void affix_infix_free(void * ptr) { Perl_safesysfree(ptr); }

void boot_Affix(pTHX_ CV * cv) {
    dVAR;
    dXSBOOTARGSXSAPIVERCHK;
    PERL_UNUSED_VAR(items);
#ifdef USE_ITHREADS
    my_perl = (PerlInterpreter *)PERL_GET_CONTEXT;
#endif
    MY_CXT_INIT;
    MY_CXT.lib_registry = newHV();
    MY_CXT.callback_registry = newHV();
    MY_CXT.enum_registry = newHV();
    MY_CXT.coercion_cache = newHV();
    MY_CXT.stash_pointer = nullptr;

    // Route all of infix's internal allocations through Perl's allocator before
    // any infix call below. infix_set_allocator() copies the table, and the
    // affix_infix_* callbacks above forward to Perl_safesys*, so Perl tracks
    // infix memory and pool validation (PERL_TRACK_MEMPOOL) never fires.
    // infix_allocator is process-global and single-threaded here (module load),
    // and cloned interpreters inherit the installed table.
    infix_set_allocator(&(infix_allocator_t){
        .malloc = affix_infix_malloc,
        .calloc = affix_infix_calloc,
        .realloc = affix_infix_realloc,
        .free = affix_infix_free,
    });

    MY_CXT.registry = infix_registry_create();
    if (!MY_CXT.registry)
        croak("Failed to initialize the global type registry");



( run in 1.147 second using v1.01-cache-2.11-cpan-364913b4093 )