Affix
view release on metacpan or search on metacpan
lib/Affix.c view on Meta::CPAN
infix_arena_destroy(call_arena);
ST(0) = ret_sv;
XSRETURN(1);
}
XS_INTERNAL(Affix_coerce) {
dXSARGS;
if (items != 2)
croak_xs_usage(cv, "type, value_sv");
SV * type_sv = ST(0);
SV * target_sv = ST(1);
if (SvREADONLY(target_sv))
croak("Cannot coerce a read-only value");
const char * sig = _get_string_from_type_obj(aTHX_ type_sv);
if (!sig)
croak("Invalid type object passed to coerce");
// Attach magic to the SV containing the signature string
sv_magicext(target_sv, NULL, PERL_MAGIC_ext, &Affix_coercion_vtbl, sig, strlen(sig));
// Return the modified SV
ST(0) = target_sv;
XSRETURN(1);
}
XS_INTERNAL(Affix_affix) {
dXSARGS;
dXSI32;
dMY_CXT;
if (ix == 2 || ix == 4) {
if (items != 3)
croak_xs_usage(cv, "Affix::affix_bundle($target, $name, $signature)");
}
else {
// Allow 2 items for wrap($ptr, $sig)
if (items < 2 || items > 4)
croak_xs_usage(cv, "Affix::affix($target, ...)");
}
void * symbol = nullptr;
SV * target_sv = ST(0);
// Detect target type (library vs raw pointer)
bool is_raw_ptr_target = false;
if (_get_pin_from_sv(aTHX_ target_sv)) {
symbol = _get_pin_from_sv(aTHX_ target_sv)->pointer;
is_raw_ptr_target = true;
}
else if (SvIOK(target_sv) && !sv_isobject(target_sv)) {
symbol = INT2PTR(void *, SvUV(target_sv));
is_raw_ptr_target = true;
}
// Symbol lookup (unles it's a raw pointer)
const char * symbol_name_str = nullptr;
const char * rename_str = nullptr;
infix_library_t * lib_handle_for_symbol = nullptr;
bool created_implicit_handle = false;
// We only process names/libraries if we don't already have a raw pointer address
if (!is_raw_ptr_target) {
SV * name_sv = ST(1);
// Handle rename: affix($lib, ['real', 'alias'], ...)
if (SvROK(name_sv) && SvTYPE(SvRV(name_sv)) == SVt_PVAV) {
if (ix == 1 || ix == 3) // wrap/direct_wrap
croak("Cannot rename an anonymous Affix'd wrapper");
AV * name_av = (AV *)SvRV(name_sv);
if (av_count(name_av) != 2)
croak("Name spec arrayref must contain exactly two elements: [symbol_name, new_sub_name]");
SV ** sym_sv = av_fetch(name_av, 0, 0);
SV ** alias_sv = av_fetch(name_av, 1, 0);
if (!sym_sv || !alias_sv)
croak("Invalid name spec");
rename_str = SvPV_nolen(*alias_sv);
// Is the symbol inside the array a raw pointer?
// affix(undef, [$ptr, 'name'], ...)
if (_get_pin_from_sv(aTHX_ * sym_sv))
symbol = _get_pin_from_sv(aTHX_ * sym_sv)->pointer;
else if (SvIOK(*sym_sv))
symbol = INT2PTR(void *, SvUV(*sym_sv));
else
symbol_name_str = SvPV_nolen(*sym_sv);
}
else {
// Name a Scalar? (string or raw pointer)
// wrap(undef, $ptr, ...)
if (_get_pin_from_sv(aTHX_ name_sv))
symbol = _get_pin_from_sv(aTHX_ name_sv)->pointer;
else if (SvIOK(name_sv))
symbol = INT2PTR(void *, SvUV(name_sv));
else {
// It's a string name
symbol_name_str = SvPV_nolen(name_sv);
rename_str = symbol_name_str;
}
}
// Only load library if we don't have a direct symbol pointer yet
if (!symbol) {
if (sv_isobject(target_sv) && sv_derived_from(target_sv, "Affix::Lib")) {
IV tmp = SvIV((SV *)SvRV(target_sv));
lib_handle_for_symbol = INT2PTR(infix_library_t *, tmp);
_lib_registry_inc_ref(aTHX_ lib_handle_for_symbol);
created_implicit_handle = true;
}
else {
const char * path = SvOK(target_sv) ? SvPV_nolen(target_sv) : nullptr;
lib_handle_for_symbol = _get_lib_from_registry(aTHX_ path);
if (lib_handle_for_symbol)
created_implicit_handle = true;
}
if (lib_handle_for_symbol && symbol_name_str)
symbol = infix_library_get_symbol(lib_handle_for_symbol, symbol_name_str);
}
if (symbol == nullptr) {
if (created_implicit_handle) {
const char * lookup_path = SvOK(target_sv) ? SvPV_nolen(target_sv) : "";
SV ** entry_sv_ptr = hv_fetch(MY_CXT.lib_registry, lookup_path, strlen(lookup_path), 0);
if (entry_sv_ptr) {
LibRegistryEntry * entry = INT2PTR(LibRegistryEntry *, SvIV(*entry_sv_ptr));
entry->ref_count--;
if (entry->ref_count == 0) {
infix_library_close(entry->lib);
safefree(entry);
hv_delete_ent(MY_CXT.lib_registry, newSVpvn(lookup_path, strlen(lookup_path)), G_DISCARD, 0);
}
}
}
warn("Failed to locate symbol '%s'", symbol_name_str ? symbol_name_str : "(null)");
XSRETURN_UNDEF;
}
}
// Argument shifting (Determine where signature starts)
SV * args_sv = nullptr;
SV * ret_sv = nullptr;
SV * sig_sv = nullptr;
bool explicit_args = false; // true if using [Args] => Ret format
if (is_raw_ptr_target) {
// Mode A: wrap($ptr, [Args], Ret) -> items=3
// Mode B: wrap($ptr, "Signature") -> items=2
if (items == 3) {
args_sv = ST(1);
ret_sv = ST(2);
explicit_args = true;
}
else
sig_sv = ST(1);
}
else {
// Mode C: wrap($lib, $name, [Args], Ret) -> items=4
lib/Affix.c view on Meta::CPAN
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) {
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");
}
jit_arg_types[i] = ptr_type;
}
else
jit_arg_types[i] = t;
}
}
// Object init & trampoline generation
Affix * affix;
Newxz(affix, 1, Affix);
affix->return_sv = newSV(0);
affix->variadic_cache = newHV();
bool is_variadic = (strstr(signature, ";") != NULL);
affix->sig_str = savepv(signature);
if (rename_str)
affix->sym_name = savepv(rename_str);
affix->target_addr = symbol;
if (lib_handle_for_symbol)
affix->lib_handle = lib_handle_for_symbol;
// Create Trampoline using the JIT-optimized types
status = infix_forward_create_manual(&affix->infix, ret_type, jit_arg_types, num_args, num_fixed, symbol);
if (jit_arg_types)
safefree(jit_arg_types);
if (status != INFIX_SUCCESS) {
infix_error_details_t err = infix_get_last_error();
warn("Failed to create trampoline: %s", err.message);
_affix_destroy(aTHX_ affix);
infix_arena_destroy(parse_arena);
XSRETURN_UNDEF;
}
affix->cif = infix_forward_get_code(affix->infix);
affix->num_args = num_args;
affix->num_fixed_args = num_fixed;
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 = get_ret_opcode_for_type(affix->ret_type);
if (affix->ret_pull_handler == nullptr) {
_affix_destroy(aTHX_ affix);
warn("Unsupported return type");
infix_arena_destroy(parse_arena);
XSRETURN_UNDEF;
}
if (affix->num_args > 0)
Newx(affix->c_args, affix->num_args, void *);
else
affix->c_args = nullptr;
affix->args_arena = infix_arena_create(4096);
affix->ret_arena = infix_arena_create(1024);
// Build execution plan
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 *);
lib/Affix.c view on Meta::CPAN
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);
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) {
( run in 1.731 second using v1.01-cache-2.11-cpan-fe3c2283af0 )