File Coverage

Legba.xs
Criterion Covered Total %
statement 112 163 68.7
branch 65 130 50.0
condition n/a
subroutine n/a
pod n/a
total 177 293 60.4


line stmt bran cond sub pod time code
1             /*
2             * Legba.xs - Ultra-fast global slot storage for Perl
3             *
4             * Named after Papa Legba, the Vodou gatekeeper of crossroads.
5             *
6             * Uses custom ops for maximum speed:
7             * - No subroutine call overhead
8             * - Direct SV* pointer stored in op structure
9             * - Single pointer dereference for get/set
10             *
11             * Registry is a single SV whose PVX is a raw array of SV* pointers.
12             * Access is: ((SV**)SvPVX(registry))[idx] - pure pointer arithmetic.
13             */
14              
15             #define PERL_NO_GET_CONTEXT
16             #include "EXTERN.h"
17             #include "perl.h"
18             #include "XSUB.h"
19              
20             #include "ppport.h"
21              
22             /* Compatibility macros for op sibling handling (5.22+) */
23             #ifndef OpSIBLING
24             # define OpSIBLING(o) ((o)->op_sibling)
25             #endif
26              
27             #ifndef OpMORESIB_set
28             # define OpMORESIB_set(o, sib) ((o)->op_sibling = (sib))
29             #endif
30              
31             #ifndef OpLASTSIB_set
32             # define OpLASTSIB_set(o, parent) ((o)->op_sibling = NULL)
33             #endif
34              
35             /* cGVOPx_gv compatibility */
36             #ifndef cGVOPx_gv
37             # define cGVOPx_gv(o) ((GV*)cSVOPx(o)->op_sv)
38             #endif
39              
40             /* The global registry - single SV with PVX as SV*[] array */
41             static SV *registry = NULL;
42              
43             /* Name->index mapping (only used at compile time) */
44             static HV *slot_index = NULL;
45             static IV next_slot = 0;
46             static IV registry_size = 0;
47              
48             /* Macros for direct slot access - no function call overhead */
49             #define SLOT_ARRAY ((SV**)SvPVX(registry))
50             #define SLOT_AT(idx) (SLOT_ARRAY[idx])
51              
52             /* Custom op type for slot access - XOP API requires 5.14+ */
53             #if PERL_VERSION >= 14
54             static XOP slot_xop;
55             #endif
56             static Perl_ppaddr_t slot_op_ppaddr;
57              
58             /* Custom OP structure with slot SV* embedded */
59             typedef struct {
60             BASEOP
61             SV *slot; /* Direct pointer to slot SV */
62             } SLOTOP;
63              
64             /* pp function for slot getter - maximum speed, no EXTEND check */
65 0           PERL_STATIC_INLINE OP* pp_slot_get(pTHX) {
66 0           dSP;
67 0           SLOTOP *slotop = (SLOTOP*)PL_op;
68             /* Direct stack push - we know there's room for 1 value */
69 0 0         EXTEND(SP, 1);
70 0           PUSHs(slotop->slot);
71 0           PUTBACK;
72 0           return NORMAL;
73             }
74              
75             /* pp function for slot setter - executes as an op, no sub call */
76 0           PERL_STATIC_INLINE OP* pp_slot_set(pTHX) {
77 0           dSP;
78 0           SLOTOP *slotop = (SLOTOP*)PL_op;
79 0           SV *value = POPs;
80 0           sv_setsv(slotop->slot, value);
81 0           PUSHs(slotop->slot);
82 0           PUTBACK;
83 0           return NORMAL;
84             }
85              
86             /* Grow registry if needed */
87 222           static void grow_registry(pTHX_ IV needed) {
88 222 100         if (needed > registry_size) {
89 9           IV new_size = needed * 2;
90 9 50         if (new_size < 8) new_size = 8;
91 9 50         SvGROW(registry, new_size * sizeof(SV*));
    50          
92             /* Zero new slots */
93 9 50         Zero(SLOT_ARRAY + registry_size, new_size - registry_size, SV*);
94 9           registry_size = new_size;
95             }
96 222           }
97              
98             /* Get or create slot SV for a name (only called at import time) */
99 315           static SV* get_or_create_slot(pTHX_ const char *name, STRLEN len) {
100             SV **svp;
101             IV idx;
102            
103             /* Check if slot already exists */
104 315           svp = hv_fetch(slot_index, name, len, 0);
105 315 100         if (svp && SvIOK(*svp)) {
    50          
106 93           idx = SvIV(*svp);
107 93           return SLOT_AT(idx);
108             }
109            
110             /* Create new slot in registry */
111 222           idx = next_slot++;
112 222           hv_store(slot_index, name, len, newSViv(idx), 0);
113            
114 222           grow_registry(aTHX_ idx + 1);
115 222           SV *slot = newSV(0);
116 222           SLOT_AT(idx) = slot;
117 222           return slot;
118             }
119              
120             /* Create a custom slot op */
121 18           static OP* newSLOTOP(pTHX_ SV *slot, bool is_setter) {
122             SLOTOP *slotop;
123 18           NewOp(1101, slotop, 1, SLOTOP);
124 18           slotop->op_type = OP_CUSTOM;
125 18 100         slotop->op_ppaddr = is_setter ? pp_slot_set : pp_slot_get;
126 18           slotop->op_flags = OPf_WANT_SCALAR;
127 18           slotop->op_private = 0;
128 18           slotop->slot = slot;
129 18           return (OP*)slotop;
130             }
131              
132             /* Magic vtable to mark CVs as slot accessors */
133             static MGVTBL slot_accessor_vtbl = {0, 0, 0, 0, 0, 0, 0, 0};
134              
135             /* Check if a CV is a slot accessor */
136             #define CV_IS_SLOT_ACCESSOR(cv) (SvMAGICAL((SV*)cv) && mg_findext((SV*)cv, PERL_MAGIC_ext, &slot_accessor_vtbl))
137              
138             /* Get slot from CV magic */
139 0           static SV* cv_get_slot(pTHX_ CV *cv) {
140 0           MAGIC *mg = mg_findext((SV*)cv, PERL_MAGIC_ext, &slot_accessor_vtbl);
141 0 0         return mg ? (SV*)mg->mg_ptr : NULL;
142             }
143              
144             /* Old entersub checker */
145             static Perl_check_t old_entersub_checker;
146              
147             /* Our entersub checker - replaces slot accessor calls with custom ops */
148 9483           static OP* legba_ck_entersub(pTHX_ OP *entersubop) {
149             OP *aop, *cvop;
150             CV *cv;
151             GV *gv;
152             SV *slot;
153             SLOTOP *slotop;
154 9483           bool has_args = FALSE;
155            
156             /* Call original checker first */
157 9483           entersubop = old_entersub_checker(aTHX_ entersubop);
158            
159             /* Find the CV being called - it's the last kid */
160 9483           aop = cUNOPx(entersubop)->op_first;
161 9483 100         if (!aop) return entersubop;
162            
163             /* Skip pushmark and args to find cvop */
164 18279 50         for (cvop = aop; OpSIBLING(cvop); cvop = OpSIBLING(cvop)) {
    100          
    50          
165 8802 100         if (cvop != aop && cvop->op_type != OP_PUSHMARK && cvop->op_type != OP_NULL) {
    50          
    100          
166 5702           has_args = TRUE;
167             }
168             }
169            
170             /* Only optimize no-arg calls (getters) for now */
171             /* Setters fall back to XS accessor which is still fast */
172 9477 100         if (has_args) return entersubop;
173            
174             /* cvop should be rv2cv(gv) or similar */
175 6445 50         if (cvop->op_type != OP_RV2CV) return entersubop;
176 0 0         if (!cUNOPx(cvop)->op_first) return entersubop;
177 0 0         if (cUNOPx(cvop)->op_first->op_type != OP_GV) return entersubop;
178            
179 0           gv = cGVOPx_gv(cUNOPx(cvop)->op_first);
180 0 0         if (!gv || !GvCV(gv)) return entersubop;
    0          
181            
182 0           cv = GvCV(gv);
183 0           slot = cv_get_slot(aTHX_ cv);
184 0 0         if (!slot) return entersubop;
185            
186             /* This is a slot getter call - replace with custom op */
187 0           NewOp(1101, slotop, 1, SLOTOP);
188 0           slotop->op_type = OP_CUSTOM;
189 0           slotop->op_ppaddr = pp_slot_get;
190 0           slotop->op_flags = entersubop->op_flags & OPf_WANT;
191 0           slotop->op_private = 0;
192 0           slotop->op_next = entersubop->op_next;
193 0           slotop->slot = slot;
194            
195             /* Free old op tree */
196 0           op_free(entersubop);
197            
198 0           return (OP*)slotop;
199             }
200              
201             /* The slot accessor - fallback for when checker doesn't run */
202 22114           XS(slot_accessor)
203             {
204 22114           dXSARGS;
205             SV *slot;
206            
207 22114           slot = (SV*)CvXSUBANY(cv).any_ptr;
208            
209 22114 100         if (items == 0) {
210 10515           ST(0) = slot;
211 10515           XSRETURN(1);
212             } else {
213 11599           sv_setsv(slot, ST(0));
214 11599           ST(0) = slot;
215 11599           XSRETURN(1);
216             }
217             }
218              
219             MODULE = Legba PACKAGE = Legba
220              
221             PROTOTYPES: DISABLE
222              
223             BOOT:
224             {
225             /* Initialize the global registry SV with PVX as SV*[] array */
226 7           registry = newSV(0);
227 7 50         SvUPGRADE(registry, SVt_PV);
228 7 50         SvGROW(registry, 8 * sizeof(SV*));
    50          
229 7           SvPOK_on(registry);
230 7           Zero(SvPVX(registry), 8, SV*);
231 7           registry_size = 8;
232            
233             /* Initialize name->index mapping */
234 7           slot_index = newHV();
235 7           next_slot = 0;
236            
237             /* Install entersub checker to optimize slot accessor calls */
238 7           old_entersub_checker = PL_check[OP_ENTERSUB];
239 7           PL_check[OP_ENTERSUB] = legba_ck_entersub;
240            
241             /* Register custom op - XOP API requires 5.14+ */
242             #if PERL_VERSION >= 14
243 7           XopENTRY_set(&slot_xop, xop_name, "slot");
244 7           XopENTRY_set(&slot_xop, xop_desc, "slot access");
245 7           XopENTRY_set(&slot_xop, xop_class, OA_BASEOP);
246 7           Perl_custom_op_register(aTHX_ pp_slot_get, &slot_xop);
247             #endif
248             }
249              
250             # Create a getter op - returns OP* as UV
251             UV
252             _make_get_op(slot_name)
253             SV *slot_name
254             PREINIT:
255             const char *name;
256             STRLEN len;
257             SV *slot;
258             OP *op;
259             CODE:
260 15           name = SvPV(slot_name, len);
261 15           slot = get_or_create_slot(aTHX_ name, len);
262 15           op = newSLOTOP(aTHX_ slot, FALSE);
263 15 100         RETVAL = PTR2UV(op);
264             OUTPUT:
265             RETVAL
266              
267             # Create a setter op - returns OP* as UV
268             UV
269             _make_set_op(slot_name)
270             SV *slot_name
271             PREINIT:
272             const char *name;
273             STRLEN len;
274             SV *slot;
275             OP *op;
276             CODE:
277 3           name = SvPV(slot_name, len);
278 3           slot = get_or_create_slot(aTHX_ name, len);
279 3           op = newSLOTOP(aTHX_ slot, TRUE);
280 3 50         RETVAL = PTR2UV(op);
281             OUTPUT:
282             RETVAL
283              
284             # Internal: get slot value by name
285             SV*
286             _get(name)
287             SV *name
288             PREINIT:
289             const char *n;
290             STRLEN len;
291             SV **svp;
292             IV idx;
293             CODE:
294 75           n = SvPV(name, len);
295 75           svp = hv_fetch(slot_index, n, len, 0);
296 75 100         if (svp && SvIOK(*svp)) {
    50          
297 74           idx = SvIV(*svp);
298 74           RETVAL = SvREFCNT_inc(SLOT_AT(idx));
299             } else {
300 1           RETVAL = newSV(0);
301             }
302             OUTPUT:
303             RETVAL
304              
305             # Internal: set slot value by name
306             SV*
307             _set(name, value)
308             SV *name
309             SV *value
310             PREINIT:
311             const char *n;
312             STRLEN len;
313             SV *slot;
314             CODE:
315 82           n = SvPV(name, len);
316 82           slot = get_or_create_slot(aTHX_ n, len);
317 82           sv_setsv(slot, value);
318 82           RETVAL = SvREFCNT_inc(slot);
319             OUTPUT:
320             RETVAL
321              
322             # Internal: check if slot exists
323             int
324             _exists(name)
325             SV *name
326             PREINIT:
327             const char *n;
328             STRLEN len;
329             CODE:
330 8           n = SvPV(name, len);
331 8 50         RETVAL = hv_exists(slot_index, n, len);
332             OUTPUT:
333             RETVAL
334              
335             # Internal: delete a slot (sets to undef)
336             void
337             _delete(name)
338             SV *name
339             PREINIT:
340             const char *n;
341             STRLEN len;
342             SV **svp;
343             IV idx;
344             CODE:
345 3           n = SvPV(name, len);
346 3           svp = hv_fetch(slot_index, n, len, 0);
347 3 50         if (svp && SvIOK(*svp)) {
    50          
348 3           idx = SvIV(*svp);
349 3           sv_setsv(SLOT_AT(idx), &PL_sv_undef);
350             }
351              
352             # Internal: list all slot names
353             void
354             _keys()
355             PREINIT:
356             HE *entry;
357             I32 len;
358             PPCODE:
359 2           hv_iterinit(slot_index);
360 23 100         while ((entry = hv_iternext(slot_index))) {
361 21           char *key = hv_iterkey(entry, &len);
362 21 50         mXPUSHp(key, len);
363             }
364              
365             # Internal: clear all slots (reset to undef)
366             void
367             _clear()
368             PREINIT:
369             IV i;
370             CODE:
371 144 100         for (i = 0; i < next_slot; i++) {
372 139 50         if (SLOT_AT(i)) sv_setsv(SLOT_AT(i), &PL_sv_undef);
373             }
374              
375             # Install an accessor function into a package - stores SV* directly for speed
376             void
377             _install_accessor(pkg, slot_name)
378             SV *pkg
379             SV *slot_name
380             PREINIT:
381             const char *pkg_name;
382             const char *name;
383             STRLEN pkg_len, name_len;
384             char *full_name;
385             CV *cv;
386             CV *existing;
387             SV *slot;
388             HV *stash;
389             SV **svp;
390             CODE:
391 0           pkg_name = SvPV(pkg, pkg_len);
392 0           name = SvPV(slot_name, name_len);
393            
394             /* Check if accessor already exists in target package */
395 0           stash = gv_stashpvn(pkg_name, pkg_len, 0);
396 0 0         if (stash) {
397 0           svp = hv_fetch(stash, name, name_len, 0);
398 0 0         if (svp && isGV(*svp) && (existing = GvCV((GV*)*svp)) && CV_IS_SLOT_ACCESSOR(existing)) {
    0          
    0          
    0          
    0          
399             /* Already installed as slot accessor, skip */
400 0           return;
401             }
402             }
403            
404             /* Get or create slot SV */
405 0           slot = get_or_create_slot(aTHX_ name, name_len);
406            
407             /* Create full sub name: Package::slot_name */
408 0           Newx(full_name, pkg_len + name_len + 3, char);
409 0           sprintf(full_name, "%s::%s", pkg_name, name);
410            
411             /* Create the accessor CV - store SV* directly, no lookup needed */
412 0           cv = newXS(full_name, slot_accessor, __FILE__);
413 0           CvXSUBANY(cv).any_ptr = (void*)slot;
414            
415             /* Mark CV as slot accessor with magic - stores slot ptr for checker */
416 0           sv_magicext((SV*)cv, NULL, PERL_MAGIC_ext, &slot_accessor_vtbl, (char*)slot, 0);
417            
418 0           Safefree(full_name);
419              
420             # Get direct slot SV pointer as UV (for embedding in custom ops)
421             UV
422             _slot_ptr(slot_name)
423             SV *slot_name
424             PREINIT:
425             const char *name;
426             STRLEN len;
427             SV *slot;
428             CODE:
429 7           name = SvPV(slot_name, len);
430 7           slot = get_or_create_slot(aTHX_ name, len);
431 7 50         RETVAL = PTR2UV(slot);
432             OUTPUT:
433             RETVAL
434              
435             # Get the global registry SV (PVX is array of SV* pointers)
436             SV*
437             _registry()
438             CODE:
439 3           RETVAL = SvREFCNT_inc(registry);
440             OUTPUT:
441             RETVAL
442              
443             # import - called by 'use Legba qw/slot1 slot2/;'
444             void
445             import(...)
446             PREINIT:
447             const char *pkg_name;
448             STRLEN pkg_len;
449             I32 i;
450             const char *name;
451             STRLEN name_len;
452             SV *slot;
453             char *full_name;
454             CV *cv;
455             CV *existing;
456             HV *stash;
457             SV **svp;
458             GV *gv;
459             PPCODE:
460             /* Get caller package from COP stash */
461 100           stash = CopSTASH(PL_curcop);
462 100 50         if (stash) {
463 100 50         pkg_name = HvNAME(stash);
    50          
    50          
    0          
    50          
    50          
464 100 50         pkg_len = HvNAMELEN(stash);
    50          
    50          
    0          
    50          
    50          
465             } else {
466 0           stash = PL_defstash;
467 0           pkg_name = "main";
468 0           pkg_len = 4;
469             }
470            
471             /* Install accessor for each slot name */
472 309 100         for (i = 1; i < items; i++) {
473 209           name = SvPV(ST(i), name_len);
474            
475             /* Check if accessor already exists in caller's package */
476 209           svp = hv_fetch(stash, name, name_len, 0);
477 209 100         if (svp && isGV(*svp) && (existing = GvCV((GV*)*svp)) && CV_IS_SLOT_ACCESSOR(existing)) {
    50          
    50          
    50          
    50          
478             /* Already installed as slot accessor, skip */
479 1           continue;
480             }
481            
482             /* Get or create slot SV */
483 208           slot = get_or_create_slot(aTHX_ name, name_len);
484            
485             /* Create full sub name: Package::slot_name */
486 208           Newx(full_name, pkg_len + name_len + 3, char);
487 208           sprintf(full_name, "%s::%s", pkg_name, name);
488            
489             /* Create the accessor CV */
490 208           cv = newXS(full_name, slot_accessor, __FILE__);
491 208           CvXSUBANY(cv).any_ptr = (void*)slot;
492            
493             /* Mark CV as slot accessor with magic */
494 208           sv_magicext((SV*)cv, NULL, PERL_MAGIC_ext, &slot_accessor_vtbl, (char*)slot, 0);
495            
496 208           Safefree(full_name);
497             }
498 100           XSRETURN(0);