Affix
view release on metacpan or search on metacpan
lib/Affix.c view on Meta::CPAN
croak("Affix failed to rebuild in new thread: signature parse error");
}
// Prepare JIT types (handle array decay)
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) {
infix_type * ptr_type = NULL;
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(original_type);
affix->plan[i].data.type = original_type;
affix->plan[i].data.index = i;
// Re-detect out params
if (original_type->category == INFIX_TYPE_POINTER) {
const infix_type * pointee = original_type->meta.pointer_info.pointee_type;
const char * pointee_name = infix_type_get_name(pointee);
bool is_sv_pointer = pointee_name && (strEQ(pointee_name, "SV") || strEQ(pointee_name, "@SV"));
if (!is_sv_pointer && pointee->category != INFIX_TYPE_REVERSE_TRAMPOLINE &&
pointee->category != INFIX_TYPE_VOID) {
temp_out_info[out_param_count].perl_stack_index = i;
temp_out_info[out_param_count].pointee_type = pointee;
temp_out_info[out_param_count].writer = get_out_param_writer(pointee);
out_param_count++;
}
}
else if (original_type->category == INFIX_TYPE_ARRAY) {
temp_out_info[out_param_count].perl_stack_index = i;
temp_out_info[out_param_count].pointee_type = original_type;
temp_out_info[out_param_count].writer = affix_array_writeback;
out_param_count++;
}
}
affix->plan[affix->num_args].opcode = OP_DONE;
// Setup OUT params
if (out_param_count > 0) {
affix->out_param_info = safemalloc(sizeof(OutParamInfo) * out_param_count);
memcpy(affix->out_param_info, temp_out_info, sizeof(OutParamInfo) * out_param_count);
}
safefree(temp_out_info);
// Done. parse_arena can go.
infix_arena_destroy(parse_arena);
}
static MGVTBL Affix_cv_vtbl = {0, 0, 0, 0, Affix_cv_free, 0, Affix_cv_dup, 0};
static MGVTBL Affix_coercion_vtbl = {0}; // Marker vtable for coerced values
// Helper to extract the signature string from a coerced SV
static const char * _get_coerced_sig(pTHX_ SV * sv) {
if (SvMAGICAL(sv)) {
MAGIC * mg = mg_findext(sv, PERL_MAGIC_ext, &Affix_coercion_vtbl);
if (mg && mg->mg_ptr)
return mg->mg_ptr;
}
return NULL;
}
void Affix_trigger_variadic(pTHX_ CV * cv) {
dSP;
dAXMARK;
dXSTARG;
dMY_CXT;
Affix * affix = (Affix *)CvXSUBANY(cv).any_ptr;
size_t items = SP - MARK;
if (items < affix->num_fixed_args)
croak(
"Not enough arguments for variadic function. Expected at least %zu, got %zu", affix->num_fixed_args, items);
// Construct the complete signature string dynamically
SV * sig_sv = sv_2mortal(newSVpv("", 0));
// Reconstruct fixed part from the cached sig_str (which ends in '; ...' or similar)
// We need to parse the original signature string to get the fixed part cleanly,
// OR we can reconstruct it from the plan.
// Simplest: The affix->sig_str contains the fixed part and the ';'.
// We assume affix->sig_str is like "(*char; ...)->int"
char * semi_ptr = strchr(affix->sig_str, ';');
if (!semi_ptr)
croak("Internal error: Variadic function missing semicolon in signature");
// Append fixed part up to and including ';'
sv_catpvn(sig_sv, affix->sig_str, (semi_ptr - affix->sig_str) + 1);
// Iterate varargs to infer types and append to signature
for (size_t i = affix->num_fixed_args; i < items; ++i) {
SV * arg = ST(i);
const char * coerced_sig = _get_coerced_sig(aTHX_ arg);
if (i > affix->num_fixed_args)
sv_catpvs(sig_sv, ",");
if (coerced_sig)
sv_catpv(sig_sv, coerced_sig);
else if (is_pin(aTHX_ arg))
// It's a pointer/struct pin. We treat it as a void pointer for the signature
// unless we can introspect the pin's type object deeply.
// For now, let's treat pins as '*void' (opaque pointer) in varargs unless coerced.
sv_catpvs(sig_sv, "*void");
else if (SvIOK(arg))
sv_catpvs(sig_sv, "sint64"); // Default integer promotion
else if (SvNOK(arg))
sv_catpvs(sig_sv, "double"); // Default float promotion
else if (SvPOK(arg))
sv_catpvs(sig_sv, "*char"); // Default string promotion
else // Fallback/Unknown
sv_catpvs(sig_sv, "sint64");
}
// Append return type part (find ')' in original sig)
char * close_paren = strrchr(affix->sig_str, ')');
if (close_paren)
sv_catpv(sig_sv, close_paren);
else
croak("Malformed signature string in affix");
const char * full_sig = SvPV_nolen(sig_sv);
// Check Cache
infix_forward_t * trampoline = NULL;
SV ** cache_entry = hv_fetch(affix->variadic_cache, full_sig, strlen(full_sig), 0);
if (cache_entry)
trampoline = INT2PTR(infix_forward_t *, SvIV(*cache_entry));
else {
// Cache Miss: Compile new trampoline
// We use the parsing logic to get types
infix_arena_t * temp_arena = NULL;
infix_type * ret_type = NULL;
infix_function_argument * args = NULL;
size_t num_args = 0, num_fixed = 0;
infix_status status =
infix_signature_parse(full_sig, &temp_arena, &ret_type, &args, &num_args, &num_fixed, MY_CXT.registry);
if (status != INFIX_SUCCESS) {
if (temp_arena)
infix_arena_destroy(temp_arena);
croak("Failed to compile variadic signature: %s", full_sig);
}
// Convert args to type array
infix_type ** arg_types = NULL;
if (num_args > 0) {
arg_types = safemalloc(sizeof(infix_type *) * num_args);
for (size_t i = 0; i < num_args; ++i)
arg_types[i] = args[i].type;
}
status = infix_forward_create_manual(&trampoline, ret_type, arg_types, num_args, num_fixed, affix->target_addr);
if (arg_types)
safefree(arg_types);
infix_arena_destroy(temp_arena);
if (status != INFIX_SUCCESS)
croak("Failed to create variadic trampoline");
// Store in cache
hv_store(affix->variadic_cache, full_sig, strlen(full_sig), newSViv(PTR2IV(trampoline)), 0);
}
// Execute
infix_cif_func cif = infix_forward_get_code(trampoline);
size_t num_args = infix_forward_get_num_args(trampoline);
const infix_type * ret_type = infix_forward_get_return_type(trampoline);
// Allocate args buffer (pointers)
void ** c_args = alloca(sizeof(void *) * num_args);
lib/Affix.c view on Meta::CPAN
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 *);
}
else {
alignment = infix_type_get_alignment(original_type);
size = infix_type_get_size(original_type);
}
if (alignment == 0)
alignment = 1;
current_offset = (current_offset + alignment - 1) & ~(alignment - 1);
affix->plan[i].data.c_arg_offset = current_offset;
current_offset += size;
affix->plan[i].executor = get_plan_step_executor(original_type);
affix->plan[i].opcode = get_opcode_for_type(original_type);
affix->plan[i].data.type = original_type; // Now points to persistent memory
affix->plan[i].data.index = i;
if (original_type->category == INFIX_TYPE_POINTER) {
const infix_type * pointee = original_type->meta.pointer_info.pointee_type;
const char * pointee_name = infix_type_get_name(pointee);
// Skip writeback for Pointer[@SV] to avoid corrupting Perl variables with void return values
// We assume SV* passed to C is owned by C for the duration and shouldn't be auto-updated
// (since the SV* itself is the value, not a pointer to a value we want copied back).
bool is_sv_pointer = pointee_name && (strEQ(pointee_name, "SV") || strEQ(pointee_name, "@SV"));
if (!is_sv_pointer && pointee->category != INFIX_TYPE_REVERSE_TRAMPOLINE &&
pointee->category != INFIX_TYPE_VOID) {
temp_out_info[out_param_count].perl_stack_index = i;
temp_out_info[out_param_count].pointee_type = pointee;
temp_out_info[out_param_count].writer = get_out_param_writer(pointee);
out_param_count++;
}
}
else if (original_type->category == INFIX_TYPE_ARRAY) {
// Register write-back handler for Arrays
temp_out_info[out_param_count].perl_stack_index = i;
temp_out_info[out_param_count].pointee_type = original_type;
temp_out_info[out_param_count].writer = affix_array_writeback;
out_param_count++;
}
}
affix->plan[affix->num_args].opcode = OP_DONE;
affix->total_args_size = current_offset;
affix->num_out_params = out_param_count;
if (out_param_count > 0) {
affix->out_param_info = safemalloc(sizeof(OutParamInfo) * out_param_count);
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));
( run in 0.643 second using v1.01-cache-2.11-cpan-5837b0d9d2c )