File Coverage

Legba.xs
Criterion Covered Total %
statement 587 632 92.8
branch 268 438 61.1
condition n/a
subroutine n/a
pod n/a
total 855 1070 79.9


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             * Architecture ported from Ancient/slot (slot.c):
7             * - Plain SV** array with realloc; no PVX-buffer trick
8             * - Slot index stored in op_targ (standard OP field), not embedded as SV*
9             * in a custom struct — no dangling pointer risk on registry resize
10             * - Reactive watchers (optional, zero overhead without them)
11             *
12             * Legba extensions over Ancient/slot:
13             * - lock/freeze access control per slot
14             * - Dedicated SV* per slot (mutated via sv_setsv) so _slot_ptr remains
15             * stable across value changes
16             * - _slot_ptr / _make_get_op / _make_set_op for external op building
17             * - _registry introspection
18             */
19              
20             #define PERL_NO_GET_CONTEXT
21             #include "EXTERN.h"
22             #include "perl.h"
23             #include "XSUB.h"
24             #include "ppport.h"
25              
26             /* XS_INTERNAL - available since 5.16, fallback for older Perls */
27             #ifndef XS_INTERNAL
28             # define XS_INTERNAL(name) static XSPROTO(name)
29             #endif
30              
31             /* ============================================
32             Compatibility macros
33             ============================================ */
34              
35             #ifndef PERL_VERSION_GE
36             # define PERL_VERSION_GE(r,v,s) \
37             (PERL_REVISION > (r) || (PERL_REVISION == (r) && \
38             (PERL_VERSION > (v) || (PERL_VERSION == (v) && PERL_SUBVERSION >= (s)))))
39             #endif
40              
41             #ifndef OpHAS_SIBLING
42             # define OpHAS_SIBLING(o) ((o)->op_sibling != NULL)
43             #endif
44             #ifndef OpSIBLING
45             # define OpSIBLING(o) ((o)->op_sibling)
46             #endif
47             #ifndef OpMORESIB_set
48             # define OpMORESIB_set(o, sib) ((o)->op_sibling = (sib))
49             #endif
50             #ifndef OpLASTSIB_set
51             # define OpLASTSIB_set(o, parent) ((o)->op_sibling = NULL)
52             #endif
53             #ifndef SvREFCNT_inc_simple_NN
54             # define SvREFCNT_inc_simple_NN(sv) SvREFCNT_inc(sv)
55             #endif
56             #ifndef SvREFCNT_dec_NN
57             # define SvREFCNT_dec_NN(sv) SvREFCNT_dec(sv)
58             #endif
59              
60             /* PADNAMEf_CONST - compile-time constant flag (0x40 unused in standard perl) */
61             #ifndef PADNAMEf_CONST
62             # define PADNAMEf_CONST 0x40
63             #endif
64              
65             #if !PERL_VERSION_GE(5,18,0)
66             typedef SV PADNAME;
67             # define PadnamelistMAX(pn) (AvFILLp(pn))
68             # define PadnamelistARRAY(pn) ((PADNAME**)AvARRAY(pn))
69             # define PadnameFLAGS(pn) (SvFLAGS(pn))
70             # undef PADNAMEf_CONST
71             # define PADNAMEf_CONST 0
72             #elif !PERL_VERSION_GE(5,22,0)
73             # ifndef PadnameFLAGS
74             # define PadnameFLAGS(pn) (SvFLAGS((SV*)(pn)))
75             # endif
76             #endif
77              
78             /* cv_set_call_checker - 5.14+ only */
79             #if !PERL_VERSION_GE(5,14,0)
80             # define cv_set_call_checker(cv, checker, ckobj) /* no-op on pre-5.14 */
81             #endif
82              
83             /* XOP API - 5.14+ */
84             #if PERL_VERSION_GE(5,14,0)
85             # define LEGBA_HAS_XOP 1
86             #else
87             # define LEGBA_HAS_XOP 0
88             # ifndef XOP_DEFINED_BY_COMPAT
89             # define XOP_DEFINED_BY_COMPAT 1
90             typedef struct { const char *xop_name; const char *xop_desc; } XOP;
91             # endif
92             # ifndef XopENTRY_set
93             # define XopENTRY_set(xop, field, value) do { (xop)->field = (value); } while(0)
94             # endif
95             # ifdef PERL_IMPLICIT_CONTEXT
96             # define Perl_custom_op_register(ctx, ppfunc, xop) \
97             legba_compat_reg_xop((ctx), (Perl_ppaddr_t)(ppfunc), (xop)->xop_name, (xop)->xop_desc)
98             # else
99             # define Perl_custom_op_register(ppfunc, xop) \
100             legba_compat_reg_xop(aTHX_ (Perl_ppaddr_t)(ppfunc), (xop)->xop_name, (xop)->xop_desc)
101             # endif
102             static void legba_compat_reg_xop(pTHX_ Perl_ppaddr_t ppfunc, const char *name, const char *desc) {
103             if (!PL_custom_op_names) PL_custom_op_names = newHV();
104             if (!PL_custom_op_descs) PL_custom_op_descs = newHV();
105             hv_store(PL_custom_op_names, (char*)&ppfunc, sizeof(ppfunc), newSVpv(name, 0), 0);
106             hv_store(PL_custom_op_descs, (char*)&ppfunc, sizeof(ppfunc), newSVpv(desc, 0), 0);
107             }
108             #endif /* LEGBA_HAS_XOP */
109              
110             #ifndef dXSBOOTARGSXSAPIVERCHK
111             # define dXSBOOTARGSXSAPIVERCHK dXSARGS
112             #endif
113             #if !PERL_VERSION_GE(5,22,0)
114             # ifndef Perl_xs_boot_epilog
115             # ifdef PERL_IMPLICIT_CONTEXT
116             # define Perl_xs_boot_epilog(ctx, ax) XSRETURN_YES
117             # else
118             # define Perl_xs_boot_epilog(ax) XSRETURN_YES
119             # endif
120             # endif
121             #endif
122             #ifndef XS_EXTERNAL
123             # define XS_EXTERNAL(name) XS(name)
124             #endif
125              
126             /* ============================================
127             Globals
128             ============================================ */
129              
130             /*
131             * Each g_slots[i] is a dedicated SV* allocated once at slot creation and
132             * mutated via sv_setsv for value changes. The pointer itself never changes,
133             * so PTR2UV(g_slots[i]) is stable — required for _slot_ptr compatibility.
134             */
135             static SV **g_slots = NULL;
136             static IV g_slots_size = 0;
137             static IV g_slots_count= 0;
138             static char *g_has_watchers= NULL;
139              
140             /* Per-slot access-control flags (lock / freeze) */
141             static UV *slot_flags = NULL;
142             #define SLOT_FLAG_LOCKED 0x1
143             #define SLOT_FLAG_FROZEN 0x2
144              
145             /* Name <-> index mappings */
146             static HV *g_slot_index = NULL; /* slot name -> IV index */
147             static HV *g_slot_names = NULL; /* "N" (idx) -> slot name */
148             static HV *g_watchers = NULL; /* slot name -> AV of callbacks */
149              
150             /* Custom op type descriptors */
151             #if LEGBA_HAS_XOP
152             static XOP legba_get_xop;
153             static XOP legba_set_xop;
154             static XOP legba_watch_xop;
155             static XOP legba_unwatch_xop;
156             static XOP legba_unwatch_one_xop;
157             static XOP legba_clear_xop;
158             #endif
159              
160             /* Forward declaration */
161             static void fire_watchers(pTHX_ IV idx, SV *new_val);
162              
163             /* ============================================
164             Custom op pp functions
165             ============================================ */
166              
167 10403           static OP* pp_slot_get(pTHX) {
168 10403           dSP;
169 10403           IV idx = PL_op->op_targ;
170             #ifdef DEBUGGING
171             EXTEND(SP, 1);
172             #endif
173 10403           PUSHs(g_slots[idx]);
174 10403           PUTBACK;
175 10403           return NORMAL;
176             }
177              
178 11494           static OP* pp_slot_set(pTHX) {
179 11494           dSP;
180 11494           IV idx = PL_op->op_targ;
181 11494           SV *new_val = TOPs;
182 11494 100         if (slot_flags[idx] & (SLOT_FLAG_LOCKED | SLOT_FLAG_FROZEN)) {
183 6 100         croak("Attempt to set %s slot",
184             (slot_flags[idx] & SLOT_FLAG_FROZEN) ? "frozen" : "locked");
185             }
186 11488           sv_setsv(g_slots[idx], new_val);
187 11488 100         if (g_has_watchers[idx]) fire_watchers(aTHX_ idx, g_slots[idx]);
188 11488           SETs(g_slots[idx]);
189 11488           PUTBACK;
190 11488           return NORMAL;
191             }
192              
193 6           static OP* pp_slot_watch(pTHX) {
194 6           dSP;
195 6           IV idx = PL_op->op_targ;
196 6           SV *callback = POPs;
197             char key[32];
198 6           int klen = snprintf(key, sizeof(key), "%ld", (long)idx);
199 6           SV **name_svp = hv_fetch(g_slot_names, key, klen, 0);
200 6 50         if (name_svp) {
201             STRLEN name_len;
202 6           const char *name = SvPV(*name_svp, name_len);
203 6           SV **existing = hv_fetch(g_watchers, name, name_len, 0);
204             AV *callbacks;
205 6 100         if (existing && SvROK(*existing)) {
    50          
206 1           callbacks = (AV*)SvRV(*existing);
207             } else {
208 5           callbacks = newAV();
209 5           hv_store(g_watchers, name, name_len, newRV_noinc((SV*)callbacks), 0);
210             }
211 6           av_push(callbacks, SvREFCNT_inc(callback));
212 6           g_has_watchers[idx] = 1;
213             }
214 6           RETURN;
215             }
216              
217 3           static OP* pp_slot_unwatch(pTHX) {
218 3           IV idx = PL_op->op_targ;
219             char key[32];
220 3           int klen = snprintf(key, sizeof(key), "%ld", (long)idx);
221 3           SV **name_svp = hv_fetch(g_slot_names, key, klen, 0);
222 3 50         if (name_svp) {
223             STRLEN name_len;
224 3           const char *name = SvPV(*name_svp, name_len);
225 3           hv_delete(g_watchers, name, name_len, G_DISCARD);
226 3           g_has_watchers[idx] = 0;
227             }
228 3           return NORMAL;
229             }
230              
231 0           static OP* pp_slot_unwatch_one(pTHX) {
232 0           dSP;
233 0           IV idx = PL_op->op_targ;
234 0           SV *callback = POPs;
235             char key[32];
236 0           int klen = snprintf(key, sizeof(key), "%ld", (long)idx);
237 0           SV **name_svp = hv_fetch(g_slot_names, key, klen, 0);
238 0 0         if (name_svp) {
239             STRLEN name_len;
240 0           const char *name = SvPV(*name_svp, name_len);
241 0           SV **existing = hv_fetch(g_watchers, name, name_len, 0);
242 0 0         if (existing && SvROK(*existing)) {
    0          
243 0           AV *callbacks = (AV*)SvRV(*existing);
244 0           SSize_t i, len = av_len(callbacks);
245 0 0         for (i = len; i >= 0; i--) {
246 0           SV **cb = av_fetch(callbacks, i, 0);
247 0 0         if (cb && SvRV(*cb) == SvRV(callback))
    0          
248 0           av_delete(callbacks, i, G_DISCARD);
249             }
250 0 0         if (av_len(callbacks) < 0)
251 0           g_has_watchers[idx] = 0;
252             }
253             }
254 0           RETURN;
255             }
256              
257 3           static OP* pp_slot_clear(pTHX) {
258 3           IV idx = PL_op->op_targ;
259             char key[32];
260 3           int klen = snprintf(key, sizeof(key), "%ld", (long)idx);
261 3           SV **name_svp = hv_fetch(g_slot_names, key, klen, 0);
262 3           sv_setsv(g_slots[idx], &PL_sv_undef);
263 3 50         if (name_svp) {
264             STRLEN name_len;
265 3           const char *name = SvPV(*name_svp, name_len);
266 3           hv_delete(g_watchers, name, name_len, G_DISCARD);
267             }
268 3           g_has_watchers[idx] = 0;
269 3           return NORMAL;
270             }
271              
272             /* ============================================
273             Call checkers
274             ============================================ */
275              
276             /* Installed on each imported accessor CV.
277             * 0-arg call => getter custom op; 1-arg call => setter custom op. */
278 169           static OP* slot_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
279 169           CV *cv = (CV*)ckobj;
280 169           IV idx = CvXSUBANY(cv).any_iv;
281             OP *pushop, *cvop, *argop;
282             PERL_UNUSED_ARG(namegv);
283              
284 169           pushop = cUNOPx(entersubop)->op_first;
285 169 50         if (!OpHAS_SIBLING(pushop)) pushop = cUNOPx(pushop)->op_first;
286 169 50         argop = OpSIBLING(pushop);
287 169           cvop = argop;
288 250 50         while (OpHAS_SIBLING(cvop)) cvop = OpSIBLING(cvop);
    100          
289              
290 169 100         if (argop == cvop) {
291             /* Getter: no arguments */
292 88           OP *newop = newOP(OP_CUSTOM, 0);
293 88           newop->op_ppaddr = pp_slot_get;
294 88           newop->op_targ = idx;
295 88           op_free(entersubop);
296 88           return newop;
297 81 50         } else if (OpSIBLING(argop) == cvop) {
    50          
298             /* Setter: single argument */
299 81           OP *arg = argop;
300             OP *newop;
301 81           OpMORESIB_set(pushop, cvop);
302 81           OpLASTSIB_set(arg, NULL);
303 81           op_contextualize(arg, G_SCALAR);
304 81           newop = newUNOP(OP_NULL, 0, arg);
305 81           newop->op_type = OP_CUSTOM;
306 81           newop->op_ppaddr = pp_slot_set;
307 81           newop->op_targ = idx;
308 81           op_free(entersubop);
309 81           return newop;
310             }
311 0           return entersubop;
312             }
313              
314             /* Shared helper: extract constant string namesv from single-arg call */
315 32           static SV* get_const_namesv_1arg(pTHX_ OP *entersubop) {
316             OP *pushop, *cvop, *argop;
317 32           pushop = cUNOPx(entersubop)->op_first;
318 32 50         if (!OpHAS_SIBLING(pushop)) pushop = cUNOPx(pushop)->op_first;
319 32 50         argop = OpSIBLING(pushop);
320 32           cvop = argop;
321 64 50         while (OpHAS_SIBLING(cvop)) cvop = OpSIBLING(cvop);
    100          
322 32 50         if (argop == cvop || OpSIBLING(argop) != cvop) return NULL;
    50          
    50          
323 32 100         if (argop->op_type == OP_CONST)
324 29           return cSVOPx(argop)->op_sv;
325 3 50         if (argop->op_type == OP_PADSV) {
326 3           PADOFFSET po = argop->op_targ;
327 3 50         if (PL_comppad_name && po < (PADOFFSET)(PadnamelistMAX(PL_comppad_name) + 1)) {
    50          
328 3           PADNAME *pn = PadnamelistARRAY(PL_comppad_name)[po];
329 3 50         if (pn && (PadnameFLAGS(pn) & PADNAMEf_CONST) && PL_comppad) {
    50          
    0          
330 0           SV **svp = av_fetch(PL_comppad, po, 0);
331 0 0         if (svp && SvPOK(*svp)) return *svp;
    0          
332             }
333             }
334             }
335 3           return NULL;
336             }
337              
338             /* get('constant') / _get('constant') => getter custom op */
339 32           static OP* slot_get_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
340             SV *namesv;
341             PERL_UNUSED_ARG(namegv); PERL_UNUSED_ARG(ckobj);
342 32           namesv = get_const_namesv_1arg(aTHX_ entersubop);
343 32 100         if (namesv && SvPOK(namesv)) {
    50          
344             STRLEN name_len;
345 29           const char *name = SvPV(namesv, name_len);
346 29           SV **svp = hv_fetch(g_slot_index, name, name_len, 0);
347 29 100         if (svp) {
348 14           OP *newop = newOP(OP_CUSTOM, 0);
349 14           newop->op_ppaddr = pp_slot_get;
350 14           newop->op_targ = SvIV(*svp);
351 14           op_free(entersubop);
352 14           return newop;
353             }
354             }
355 18           return entersubop;
356             }
357              
358             /* set('constant', $val) / _set('constant', $val) => setter custom op */
359 44           static OP* slot_set_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
360             OP *pushop, *cvop, *argop, *valop;
361 44           SV *namesv = NULL;
362             PERL_UNUSED_ARG(namegv); PERL_UNUSED_ARG(ckobj);
363              
364 44           pushop = cUNOPx(entersubop)->op_first;
365 44 50         if (!OpHAS_SIBLING(pushop)) pushop = cUNOPx(pushop)->op_first;
366 44 50         argop = OpSIBLING(pushop);
367 44           cvop = argop;
368 132 50         while (OpHAS_SIBLING(cvop)) cvop = OpSIBLING(cvop);
    100          
369 44 50         valop = OpSIBLING(argop);
370 44 50         if (argop == cvop || !valop || valop == cvop || OpSIBLING(valop) != cvop)
    50          
    50          
    50          
    50          
371 0           return entersubop;
372              
373 44 100         if (argop->op_type == OP_CONST) {
374 42           namesv = cSVOPx(argop)->op_sv;
375 2 50         } else if (argop->op_type == OP_PADSV) {
376 2           PADOFFSET po = argop->op_targ;
377 2 50         if (PL_comppad_name && po < (PADOFFSET)(PadnamelistMAX(PL_comppad_name) + 1)) {
    50          
378 2           PADNAME *pn = PadnamelistARRAY(PL_comppad_name)[po];
379 2 50         if (pn && (PadnameFLAGS(pn) & PADNAMEf_CONST) && PL_comppad) {
    50          
    0          
380 0           SV **svp = av_fetch(PL_comppad, po, 0);
381 0 0         if (svp && SvPOK(*svp)) namesv = *svp;
    0          
382             }
383             }
384             }
385 44 100         if (namesv && SvPOK(namesv)) {
    50          
386             STRLEN name_len;
387 42           const char *name = SvPV(namesv, name_len);
388 42           SV **svp = hv_fetch(g_slot_index, name, name_len, 0);
389 42 100         if (svp) {
390             OP *newop;
391 15           OpMORESIB_set(pushop, cvop);
392 15           OpLASTSIB_set(valop, NULL);
393 15           op_contextualize(valop, G_SCALAR);
394 15           newop = newUNOP(OP_NULL, 0, valop);
395 15           newop->op_type = OP_CUSTOM;
396 15           newop->op_ppaddr = pp_slot_set;
397 15           newop->op_targ = SvIV(*svp);
398 15           op_free(entersubop);
399 15           return newop;
400             }
401             }
402 29           return entersubop;
403             }
404              
405             /* index('constant') => OP_CONST (fully constant-folded) */
406 14           static OP* slot_index_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
407             OP *pushop, *cvop, *argop;
408             PERL_UNUSED_ARG(namegv); PERL_UNUSED_ARG(ckobj);
409 14           pushop = cUNOPx(entersubop)->op_first;
410 14 50         if (!OpHAS_SIBLING(pushop)) pushop = cUNOPx(pushop)->op_first;
411 14 50         argop = OpSIBLING(pushop);
412 14           cvop = argop;
413 28 50         while (OpHAS_SIBLING(cvop)) cvop = OpSIBLING(cvop);
    100          
414 14 50         if (argop != cvop && OpSIBLING(argop) == cvop && argop->op_type == OP_CONST) {
    50          
    50          
    100          
415 13           SV *namesv = cSVOPx(argop)->op_sv;
416 13 50         if (SvPOK(namesv)) {
417             STRLEN name_len;
418 13           const char *name = SvPV(namesv, name_len);
419 13           SV **svp = hv_fetch(g_slot_index, name, name_len, 0);
420 13 100         if (svp) {
421 6           OP *newop = newSVOP(OP_CONST, 0, newSViv(SvIV(*svp)));
422 6           op_free(entersubop);
423 6           return newop;
424             }
425             }
426             }
427 8           return entersubop;
428             }
429              
430             /* watch('constant', $cb) => watch custom op */
431 15           static OP* slot_watch_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
432             OP *pushop, *cvop, *argop, *cbop;
433             PERL_UNUSED_ARG(namegv); PERL_UNUSED_ARG(ckobj);
434 15           pushop = cUNOPx(entersubop)->op_first;
435 15 50         if (!OpHAS_SIBLING(pushop)) pushop = cUNOPx(pushop)->op_first;
436 15 50         argop = OpSIBLING(pushop);
437 15           cvop = argop;
438 45 50         while (OpHAS_SIBLING(cvop)) cvop = OpSIBLING(cvop);
    100          
439 15 50         cbop = OpSIBLING(argop);
440 15 50         if (argop != cvop && cbop && cbop != cvop && OpSIBLING(cbop) == cvop
    50          
    50          
    50          
    50          
441 15 50         && argop->op_type == OP_CONST) {
442 15           SV *namesv = cSVOPx(argop)->op_sv;
443 15 50         if (SvPOK(namesv)) {
444             STRLEN name_len;
445 15           const char *name = SvPV(namesv, name_len);
446 15           SV **svp = hv_fetch(g_slot_index, name, name_len, 0);
447 15 100         if (svp) {
448             OP *newop;
449 6           OpMORESIB_set(pushop, cvop);
450 6           OpLASTSIB_set(cbop, NULL);
451 6           newop = newUNOP(OP_NULL, 0, cbop);
452 6           newop->op_type = OP_CUSTOM;
453 6           newop->op_ppaddr = pp_slot_watch;
454 6           newop->op_targ = SvIV(*svp);
455 6           op_free(entersubop);
456 6           return newop;
457             }
458             }
459             }
460 9           return entersubop;
461             }
462              
463             /* unwatch('constant') or unwatch('constant', $cb) */
464 5           static OP* slot_unwatch_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
465             OP *pushop, *cvop, *argop, *cbop;
466             PERL_UNUSED_ARG(namegv); PERL_UNUSED_ARG(ckobj);
467 5           pushop = cUNOPx(entersubop)->op_first;
468 5 50         if (!OpHAS_SIBLING(pushop)) pushop = cUNOPx(pushop)->op_first;
469 5 50         argop = OpSIBLING(pushop);
470 5           cvop = argop;
471 11 50         while (OpHAS_SIBLING(cvop)) cvop = OpSIBLING(cvop);
    100          
472 5 50         if (argop != cvop && argop->op_type == OP_CONST) {
    50          
473 5           SV *namesv = cSVOPx(argop)->op_sv;
474 5 50         if (SvPOK(namesv)) {
475             STRLEN name_len;
476 5           const char *name = SvPV(namesv, name_len);
477 5           SV **svp = hv_fetch(g_slot_index, name, name_len, 0);
478 5 100         if (svp) {
479 3           IV idx = SvIV(*svp);
480 3 50         cbop = OpSIBLING(argop);
481 3 50         if (cbop == cvop) {
482 3           OP *newop = newOP(OP_CUSTOM, 0);
483 3           newop->op_ppaddr = pp_slot_unwatch;
484 3           newop->op_targ = idx;
485 3           op_free(entersubop);
486 3           return newop;
487 0 0         } else if (OpSIBLING(cbop) == cvop) {
    0          
488             OP *newop;
489 0           OpMORESIB_set(pushop, cvop);
490 0           OpLASTSIB_set(cbop, NULL);
491 0           newop = newUNOP(OP_NULL, 0, cbop);
492 0           newop->op_type = OP_CUSTOM;
493 0           newop->op_ppaddr = pp_slot_unwatch_one;
494 0           newop->op_targ = idx;
495 0           op_free(entersubop);
496 0           return newop;
497             }
498             }
499             }
500             }
501 2           return entersubop;
502             }
503              
504             /* clear('constant') => clear custom op */
505 8           static OP* slot_clear_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
506             OP *pushop, *cvop, *argop;
507             PERL_UNUSED_ARG(namegv); PERL_UNUSED_ARG(ckobj);
508 8           pushop = cUNOPx(entersubop)->op_first;
509 8 50         if (!OpHAS_SIBLING(pushop)) pushop = cUNOPx(pushop)->op_first;
510 8 50         argop = OpSIBLING(pushop);
511 8           cvop = argop;
512 17 50         while (OpHAS_SIBLING(cvop)) cvop = OpSIBLING(cvop);
    100          
513 8 50         if (argop != cvop && OpSIBLING(argop) == cvop && argop->op_type == OP_CONST) {
    50          
    100          
    50          
514 7           SV *namesv = cSVOPx(argop)->op_sv;
515 7 50         if (SvPOK(namesv)) {
516             STRLEN name_len;
517 7           const char *name = SvPV(namesv, name_len);
518 7           SV **svp = hv_fetch(g_slot_index, name, name_len, 0);
519 7 100         if (svp) {
520 3           OP *newop = newOP(OP_CUSTOM, 0);
521 3           newop->op_ppaddr = pp_slot_clear;
522 3           newop->op_targ = SvIV(*svp);
523 3           op_free(entersubop);
524 3           return newop;
525             }
526             }
527             }
528 5           return entersubop;
529             }
530              
531             /* ============================================
532             XS slot accessor (fallback for unoptimized calls)
533             ============================================ */
534              
535 300           XS_INTERNAL(xs_slot_accessor) {
536 300           dXSARGS;
537 300           IV idx = CvXSUBANY(cv).any_iv;
538 300 100         if (items) {
539 150           UV flags = slot_flags[idx];
540 150 50         if (flags & (SLOT_FLAG_LOCKED | SLOT_FLAG_FROZEN)) {
541 0 0         croak("Attempt to set %s slot",
542             (flags & SLOT_FLAG_FROZEN) ? "frozen" : "locked");
543             }
544 150           sv_setsv(g_slots[idx], ST(0));
545 150 50         if (g_has_watchers[idx]) fire_watchers(aTHX_ idx, g_slots[idx]);
546 150           ST(0) = g_slots[idx];
547 150           XSRETURN(1);
548             }
549 150           ST(0) = g_slots[idx];
550 150           XSRETURN(1);
551             }
552              
553             /* ============================================
554             Watchers
555             ============================================ */
556              
557 1023           static void fire_watchers(pTHX_ IV idx, SV *new_val) {
558             char key[32];
559 1023           int klen = snprintf(key, sizeof(key), "%ld", (long)idx);
560 1023           SV **name_sv = hv_fetch(g_slot_names, key, klen, 0);
561 1023 50         if (!name_sv || !SvOK(*name_sv)) return;
    50          
562             {
563             STRLEN name_len;
564 1023           const char *name = SvPV(*name_sv, name_len);
565 1023           SV **svp = hv_fetch(g_watchers, name, name_len, 0);
566 1023 50         if (svp && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVAV) {
    50          
    50          
567 1023           AV *callbacks = (AV*)SvRV(*svp);
568 1023           SSize_t i, len = av_len(callbacks);
569 2053 100         for (i = 0; i <= len; i++) {
570 1031           SV **cb = av_fetch(callbacks, i, 0);
571 1031 100         if (cb && SvROK(*cb)) {
    50          
572 1029           dSP;
573 1029           ENTER; SAVETMPS;
574 1029 50         PUSHMARK(SP);
575 1029 50         mXPUSHs(newSVpvn(name, name_len));
576 1029 50         XPUSHs(new_val);
577 1029           PUTBACK;
578 1029           call_sv(*cb, G_DISCARD);
579 1028 50         FREETMPS; LEAVE;
580             }
581             }
582             }
583             }
584             }
585              
586             /* ============================================
587             Slot management helpers
588             ============================================ */
589              
590 492           static void ensure_slot_capacity(pTHX_ IV needed) {
591 492 100         if (needed >= g_slots_size) {
592 10 50         IV new_size = g_slots_size ? g_slots_size * 2 : 16;
593             IV i;
594 10 50         while (new_size <= needed) new_size *= 2;
595 10 50         Renew(g_slots, new_size, SV*);
596 10           Renew(g_has_watchers,new_size, char);
597 10 50         Renew(slot_flags, new_size, UV);
598 538 100         for (i = g_slots_size; i < new_size; i++) {
599 528           g_slots[i] = newSV(0); /* dedicated SV; pointer never replaced */
600 528           g_has_watchers[i] = 0;
601 528           slot_flags[i] = 0;
602             }
603 10           g_slots_size = new_size;
604             }
605 492           }
606              
607 492           static IV create_slot(pTHX_ const char *name, STRLEN name_len) {
608 492           IV idx = g_slots_count++;
609             char key[32];
610             int klen;
611 492           ensure_slot_capacity(aTHX_ idx);
612 492           hv_store(g_slot_index, name, name_len, newSViv(idx), 0);
613 492           klen = snprintf(key, sizeof(key), "%ld", (long)idx);
614 492           hv_store(g_slot_names, key, klen, newSVpvn(name, name_len), 0);
615 492           return idx;
616             }
617              
618 1160           static IV get_or_create_slot_idx(pTHX_ const char *name, STRLEN len) {
619 1160           SV **svp = hv_fetch(g_slot_index, name, len, 0);
620 1160 100         return svp ? SvIV(*svp) : create_slot(aTHX_ name, len);
621             }
622              
623 223           static void install_accessor(pTHX_ const char *pkg, const char *name, STRLEN name_len, IV idx) {
624             char full[512];
625             CV *cv;
626 223           snprintf(full, sizeof(full), "%s::%s", pkg, name);
627 223           cv = newXS(full, xs_slot_accessor, __FILE__);
628 223           CvXSUBANY(cv).any_iv = idx;
629 223           cv_set_call_checker(cv, slot_call_checker, (SV*)cv);
630 223           }
631              
632             /* ============================================
633             XS functions
634             ============================================ */
635              
636 113           XS_INTERNAL(xs_import) {
637 113           dXSARGS;
638 113 50         const char *pkg = HvNAME((HV*)CopSTASH(PL_curcop));
    50          
    50          
    0          
    50          
    50          
639             int i;
640 113 50         if (!pkg) pkg = "main";
641 338 100         for (i = 1; i < items; i++) {
642             STRLEN name_len;
643 225           const char *name = SvPV(ST(i), name_len);
644 225           SV **existing = hv_fetch(g_slot_index, name, name_len, 0);
645 225 100         IV idx = existing ? SvIV(*existing) : create_slot(aTHX_ name, name_len);
646             /* Skip if already installed as our accessor in this package */
647             {
648 225           HV *stash = gv_stashpv(pkg, GV_ADD);
649 225           SV **gvp = hv_fetch(stash, name, name_len, 0);
650 225 100         if (gvp && isGV(*gvp)) {
    50          
651 2           CV *ecv = GvCV((GV*)*gvp);
652 2 50         if (ecv && CvISXSUB(ecv) && CvXSUB(ecv) == xs_slot_accessor)
    50          
    50          
653 2           continue;
654             }
655             }
656 223           install_accessor(aTHX_ pkg, name, name_len, idx);
657             }
658 113           XSRETURN_EMPTY;
659             }
660              
661 46           XS_INTERNAL(xs_add) {
662 46           dXSARGS;
663             int i;
664 302 100         for (i = 0; i < items; i++) {
665             STRLEN name_len;
666 256           const char *name = SvPV(ST(i), name_len);
667 256 100         if (!hv_fetch(g_slot_index, name, name_len, 0))
668 255           create_slot(aTHX_ name, name_len);
669             }
670 46           XSRETURN_EMPTY;
671             }
672              
673 20           XS_INTERNAL(xs_watch) {
674 20           dXSARGS;
675             STRLEN name_len;
676             const char *name;
677             SV *callback;
678             SV **existing;
679             AV *callbacks;
680             SV **idx_sv;
681 20 50         if (items < 2) croak("Usage: Legba::watch($name, $callback)");
682 20           name = SvPV(ST(0), name_len);
683 20           callback = ST(1);
684 20           existing = hv_fetch(g_watchers, name, name_len, 0);
685 20 100         if (existing && SvROK(*existing)) {
    50          
686 5           callbacks = (AV*)SvRV(*existing);
687             } else {
688 15           callbacks = newAV();
689 15           hv_store(g_watchers, name, name_len, newRV_noinc((SV*)callbacks), 0);
690             }
691 20           av_push(callbacks, SvREFCNT_inc(callback));
692 20           idx_sv = hv_fetch(g_slot_index, name, name_len, 0);
693 20 50         if (idx_sv) g_has_watchers[SvIV(*idx_sv)] = 1;
694 20           XSRETURN_EMPTY;
695             }
696              
697 4           XS_INTERNAL(xs_unwatch) {
698 4           dXSARGS;
699             STRLEN name_len;
700             const char *name;
701             SV **idx_sv;
702 4           int clear_flag = 0;
703 4 50         if (items < 1) croak("Usage: Legba::unwatch($name [, $callback])");
704 4           name = SvPV(ST(0), name_len);
705 4 100         if (items == 1) {
706 2           hv_delete(g_watchers, name, name_len, G_DISCARD);
707 2           clear_flag = 1;
708             } else {
709 2           SV *callback = ST(1);
710 2           SV **existing = hv_fetch(g_watchers, name, name_len, 0);
711 2 50         if (existing && SvROK(*existing)) {
    50          
712 2           AV *callbacks = (AV*)SvRV(*existing);
713 2           SSize_t i, len = av_len(callbacks);
714 7 100         for (i = len; i >= 0; i--) {
715 5           SV **cb = av_fetch(callbacks, i, 0);
716 5 50         if (cb && SvRV(*cb) == SvRV(callback))
    100          
717 2           av_delete(callbacks, i, G_DISCARD);
718             }
719 2 50         if (av_len(callbacks) < 0) clear_flag = 1;
720             }
721             }
722 4 100         if (clear_flag) {
723 2           idx_sv = hv_fetch(g_slot_index, name, name_len, 0);
724 2 50         if (idx_sv) g_has_watchers[SvIV(*idx_sv)] = 0;
725             }
726 4           XSRETURN_EMPTY;
727             }
728              
729 18           XS_INTERNAL(xs_index) {
730 18           dXSARGS;
731             STRLEN name_len; const char *name; SV **svp;
732 18 50         if (items < 1) XSRETURN_UNDEF;
733 18           name = SvPV(ST(0), name_len);
734 18           svp = hv_fetch(g_slot_index, name, name_len, 0);
735 18 100         if (svp) { ST(0) = *svp; XSRETURN(1); }
736 1           XSRETURN_UNDEF;
737             }
738              
739 6           XS_INTERNAL(xs_get_by_idx) {
740 6           dXSARGS;
741             IV idx;
742 6 50         if (items < 1) XSRETURN_UNDEF;
743 6           idx = SvIV(ST(0));
744 6 50         if (idx >= 0 && idx < g_slots_count) { ST(0) = g_slots[idx]; XSRETURN(1); }
    100          
745 1           XSRETURN_UNDEF;
746             }
747              
748 10008           XS_INTERNAL(xs_set_by_idx) {
749 10008           dXSARGS;
750             IV idx;
751 10008 50         if (items < 2) XSRETURN_EMPTY;
752 10008           idx = SvIV(ST(0));
753 10008 50         if (idx >= 0 && idx < g_slots_count) {
    50          
754 10008           UV flags = slot_flags[idx];
755 10008 100         if (flags & (SLOT_FLAG_LOCKED | SLOT_FLAG_FROZEN))
756 2 100         croak("Attempt to set %s slot",
757             (flags & SLOT_FLAG_FROZEN) ? "frozen" : "locked");
758 10006           sv_setsv(g_slots[idx], ST(1));
759 10006 100         if (g_has_watchers[idx]) fire_watchers(aTHX_ idx, g_slots[idx]);
760 10006           ST(0) = g_slots[idx];
761 10006           XSRETURN(1);
762             }
763 0           XSRETURN_EMPTY;
764             }
765              
766 101           XS_INTERNAL(xs_get) {
767 101           dXSARGS;
768             STRLEN name_len; const char *name; SV **svp;
769 101 50         if (items < 1) XSRETURN_UNDEF;
770 101           name = SvPV(ST(0), name_len);
771 101           svp = hv_fetch(g_slot_index, name, name_len, 0);
772 101 100         if (svp) { ST(0) = g_slots[SvIV(*svp)]; XSRETURN(1); }
773 2           XSRETURN_UNDEF;
774             }
775              
776             /* _set / set: create slot if missing; respects lock/freeze */
777 1131           XS_INTERNAL(xs_set) {
778 1131           dXSARGS;
779             STRLEN name_len; const char *name;
780             IV idx;
781 1131 50         if (items < 2) XSRETURN_EMPTY;
782 1131           name = SvPV(ST(0), name_len);
783 1131           idx = get_or_create_slot_idx(aTHX_ name, name_len);
784 1131 100         if (slot_flags[idx] & (SLOT_FLAG_LOCKED | SLOT_FLAG_FROZEN))
785 2 100         croak("Attempt to set %s slot '%s'",
786             (slot_flags[idx] & SLOT_FLAG_FROZEN) ? "frozen" : "locked", name);
787 1129           sv_setsv(g_slots[idx], ST(1));
788 1129 100         if (g_has_watchers[idx]) fire_watchers(aTHX_ idx, g_slots[idx]);
789 1128           ST(0) = g_slots[idx];
790 1128           XSRETURN(1);
791             }
792              
793 2           XS_INTERNAL(xs_slots) {
794 2           dXSARGS;
795             HE *entry;
796             PERL_UNUSED_VAR(items);
797 2           SP -= items;
798 2           hv_iterinit(g_slot_index);
799 50 100         while ((entry = hv_iternext(g_slot_index)))
800 48 50         XPUSHs(hv_iterkeysv(entry));
801 2           PUTBACK;
802 2           return;
803             }
804              
805 26           XS_INTERNAL(xs_exists) {
806 26           dXSARGS;
807             STRLEN name_len; const char *name;
808 26 50         if (items != 1) croak("Usage: Legba::exists($name)");
809 26           name = SvPV(ST(0), name_len);
810 26 100         if (hv_exists(g_slot_index, name, name_len)) XSRETURN_YES;
811 7           XSRETURN_NO;
812             }
813              
814             /* clear($name, ...) - clear value + watchers, skips locked/frozen */
815 6           XS_INTERNAL(xs_clear_named) {
816 6           dXSARGS;
817             int i;
818 13 100         for (i = 0; i < items; i++) {
819             STRLEN name_len;
820 7           const char *name = SvPV(ST(i), name_len);
821 7           SV **svp = hv_fetch(g_slot_index, name, name_len, 0);
822 7 50         if (svp) {
823 7           IV idx = SvIV(*svp);
824 7 100         if (slot_flags[idx] & (SLOT_FLAG_LOCKED | SLOT_FLAG_FROZEN)) continue;
825 5           sv_setsv(g_slots[idx], &PL_sv_undef);
826 5           hv_delete(g_watchers, name, name_len, G_DISCARD);
827 5           g_has_watchers[idx] = 0;
828             }
829             }
830 6           XSRETURN_EMPTY;
831             }
832              
833 2           XS_INTERNAL(xs_clear_by_idx) {
834 2           dXSARGS;
835             int i;
836 5 100         for (i = 0; i < items; i++) {
837 3           IV idx = SvIV(ST(i));
838 3 50         if (idx >= 0 && idx < g_slots_count) {
    50          
839             char key[32]; int klen; SV **name_sv;
840 3           sv_setsv(g_slots[idx], &PL_sv_undef);
841 3           klen = snprintf(key, sizeof(key), "%ld", (long)idx);
842 3           name_sv = hv_fetch(g_slot_names, key, klen, 0);
843 3 50         if (name_sv && SvOK(*name_sv)) {
    50          
844             STRLEN name_len;
845 3           const char *name = SvPV(*name_sv, name_len);
846 3           hv_delete(g_watchers, name, name_len, G_DISCARD);
847             }
848 3           g_has_watchers[idx] = 0;
849             }
850             }
851 2           XSRETURN_EMPTY;
852             }
853              
854             /* _delete($name) - set to undef, slot still exists; respects lock/freeze */
855 5           XS_INTERNAL(xs_delete) {
856 5           dXSARGS;
857             STRLEN name_len; const char *name; SV **svp;
858 5 50         if (items < 1) XSRETURN_EMPTY;
859 5           name = SvPV(ST(0), name_len);
860 5           svp = hv_fetch(g_slot_index, name, name_len, 0);
861 5 50         if (svp) {
862 5           IV idx = SvIV(*svp);
863 5 100         if (slot_flags[idx] & (SLOT_FLAG_LOCKED | SLOT_FLAG_FROZEN))
864 2 100         croak("Attempt to delete %s slot '%s'",
865             (slot_flags[idx] & SLOT_FLAG_FROZEN) ? "frozen" : "locked", name);
866 3           sv_setsv(g_slots[idx], &PL_sv_undef);
867             }
868 3           XSRETURN_EMPTY;
869             }
870              
871             /* _keys() - list all slot names */
872 3           XS_INTERNAL(xs_keys) {
873 3           dXSARGS;
874             HE *entry;
875             PERL_UNUSED_VAR(items);
876 3           SP -= items;
877 3           hv_iterinit(g_slot_index);
878 48 100         while ((entry = hv_iternext(g_slot_index))) {
879 45           I32 klen; char *key = hv_iterkey(entry, &klen);
880 45 50         mXPUSHp(key, klen);
881             }
882 3           PUTBACK;
883 3           return;
884             }
885              
886             /* _clear() - clear all non-locked/frozen slot values (preserves slots/watchers) */
887 6           XS_INTERNAL(xs_clear_all) {
888 6           dXSARGS;
889             IV i;
890             PERL_UNUSED_VAR(items);
891 148 100         for (i = 0; i < g_slots_count; i++) {
892 142 100         if (!(slot_flags[i] & (SLOT_FLAG_LOCKED | SLOT_FLAG_FROZEN)))
893 141           sv_setsv(g_slots[i], &PL_sv_undef);
894             }
895 6           XSRETURN_EMPTY;
896             }
897              
898             /* _install_accessor($pkg, $slot_name) */
899 0           XS_INTERNAL(xs_install_accessor_fn) {
900 0           dXSARGS;
901             STRLEN pkg_len, name_len;
902             const char *pkg_name, *name;
903             IV idx;
904 0 0         if (items < 2) croak("Usage: Legba::_install_accessor($pkg, $slot_name)");
905 0           pkg_name = SvPV(ST(0), pkg_len);
906 0           name = SvPV(ST(1), name_len);
907 0           idx = get_or_create_slot_idx(aTHX_ name, name_len);
908 0           install_accessor(aTHX_ pkg_name, name, name_len, idx);
909 0           XSRETURN_EMPTY;
910             }
911              
912             /* _slot_ptr($name) - UV of the dedicated SV* (stable across value changes) */
913 11           XS_INTERNAL(xs_slot_ptr) {
914 11           dXSARGS;
915             STRLEN name_len; const char *name; IV idx;
916 11 50         if (items < 1) XSRETURN_UNDEF;
917 11           name = SvPV(ST(0), name_len);
918 11           idx = get_or_create_slot_idx(aTHX_ name, name_len);
919 11           ST(0) = sv_2mortal(newSVuv(PTR2UV(g_slots[idx])));
920 11           XSRETURN(1);
921             }
922              
923             /* _registry() - hashref of slot_name => index for introspection */
924 4           XS_INTERNAL(xs_registry) {
925 4           dXSARGS;
926             PERL_UNUSED_VAR(items);
927 4           ST(0) = sv_2mortal(newRV_inc((SV*)g_slot_index));
928 4           XSRETURN(1);
929             }
930              
931             /* _make_get_op($name) - allocate a getter OP, return address as UV */
932 15           XS_INTERNAL(xs_make_get_op) {
933 15           dXSARGS;
934             STRLEN name_len; const char *name; IV idx; OP *op;
935 15 50         if (items < 1) XSRETURN_UNDEF;
936 15           name = SvPV(ST(0), name_len);
937 15           idx = get_or_create_slot_idx(aTHX_ name, name_len);
938 15           op = newOP(OP_CUSTOM, 0);
939 15           op->op_ppaddr = pp_slot_get;
940 15           op->op_targ = idx;
941 15           ST(0) = sv_2mortal(newSVuv(PTR2UV(op)));
942 15           XSRETURN(1);
943             }
944              
945             /* _make_set_op($name) - allocate a setter OP, return address as UV */
946 3           XS_INTERNAL(xs_make_set_op) {
947 3           dXSARGS;
948             STRLEN name_len; const char *name; IV idx; OP *op;
949 3 50         if (items < 1) XSRETURN_UNDEF;
950 3           name = SvPV(ST(0), name_len);
951 3           idx = get_or_create_slot_idx(aTHX_ name, name_len);
952             /* Setter needs an operand child; use a null op as placeholder */
953 3           op = newUNOP(OP_NULL, 0, newOP(OP_NULL, 0));
954 3           op->op_type = OP_CUSTOM;
955 3           op->op_ppaddr = pp_slot_set;
956 3           op->op_targ = idx;
957 3           ST(0) = sv_2mortal(newSVuv(PTR2UV(op)));
958 3           XSRETURN(1);
959             }
960              
961             /* Lock / freeze */
962 7           XS_INTERNAL(xs_lock) {
963 7           dXSARGS;
964             STRLEN len; const char *n; SV **svp; IV idx;
965 7 50         if (items < 1) croak("Usage: Legba::_lock($name)");
966 7           n = SvPV(ST(0), len);
967 7           svp = hv_fetch(g_slot_index, n, len, 0);
968 7 100         if (!svp) croak("Cannot lock non-existent slot '%s'", n);
969 6           idx = SvIV(*svp);
970 6 100         if (slot_flags[idx] & SLOT_FLAG_FROZEN) croak("Cannot lock frozen slot '%s'", n);
971 5           slot_flags[idx] |= SLOT_FLAG_LOCKED;
972 5           XSRETURN_EMPTY;
973             }
974              
975 6           XS_INTERNAL(xs_unlock) {
976 6           dXSARGS;
977             STRLEN len; const char *n; SV **svp; IV idx;
978 6 50         if (items < 1) croak("Usage: Legba::_unlock($name)");
979 6           n = SvPV(ST(0), len);
980 6           svp = hv_fetch(g_slot_index, n, len, 0);
981 6 50         if (!svp) croak("Cannot unlock non-existent slot '%s'", n);
982 6           idx = SvIV(*svp);
983 6 100         if (slot_flags[idx] & SLOT_FLAG_FROZEN) croak("Cannot unlock frozen slot '%s'", n);
984 5           slot_flags[idx] &= ~SLOT_FLAG_LOCKED;
985 5           XSRETURN_EMPTY;
986             }
987              
988 6           XS_INTERNAL(xs_freeze) {
989 6           dXSARGS;
990             STRLEN len; const char *n; SV **svp; IV idx;
991 6 50         if (items < 1) croak("Usage: Legba::_freeze($name)");
992 6           n = SvPV(ST(0), len);
993 6           svp = hv_fetch(g_slot_index, n, len, 0);
994 6 100         if (!svp) croak("Cannot freeze non-existent slot '%s'", n);
995 5           idx = SvIV(*svp);
996 5           slot_flags[idx] |= SLOT_FLAG_FROZEN;
997 5           slot_flags[idx] &= ~SLOT_FLAG_LOCKED; /* frozen supersedes locked */
998 5           XSRETURN_EMPTY;
999             }
1000              
1001 3           XS_INTERNAL(xs_is_locked) {
1002 3           dXSARGS;
1003             STRLEN len; const char *n; SV **svp;
1004 3 50         if (items < 1) XSRETURN_NO;
1005 3           n = SvPV(ST(0), len);
1006 3           svp = hv_fetch(g_slot_index, n, len, 0);
1007 3 100         if (!svp) XSRETURN_NO;
1008 2 100         if (slot_flags[SvIV(*svp)] & SLOT_FLAG_LOCKED) XSRETURN_YES;
1009 1           XSRETURN_NO;
1010             }
1011              
1012 8           XS_INTERNAL(xs_is_frozen) {
1013 8           dXSARGS;
1014             STRLEN len; const char *n; SV **svp;
1015 8 50         if (items < 1) XSRETURN_NO;
1016 8           n = SvPV(ST(0), len);
1017 8           svp = hv_fetch(g_slot_index, n, len, 0);
1018 8 100         if (!svp) XSRETURN_NO;
1019 7 50         if (slot_flags[SvIV(*svp)] & SLOT_FLAG_FROZEN) XSRETURN_YES;
1020 0           XSRETURN_NO;
1021             }
1022              
1023             /* ============================================
1024             MODULE / BOOT
1025             ============================================ */
1026              
1027             MODULE = Legba PACKAGE = Legba
1028              
1029             PROTOTYPES: DISABLE
1030              
1031             BOOT:
1032             {
1033             #if LEGBA_HAS_XOP
1034 13           XopENTRY_set(&legba_get_xop, xop_name, "legba_get");
1035 13           XopENTRY_set(&legba_get_xop, xop_desc, "Legba slot getter");
1036 13           XopENTRY_set(&legba_get_xop, xop_class, OA_BASEOP);
1037 13           Perl_custom_op_register(aTHX_ pp_slot_get, &legba_get_xop);
1038              
1039 13           XopENTRY_set(&legba_set_xop, xop_name, "legba_set");
1040 13           XopENTRY_set(&legba_set_xop, xop_desc, "Legba slot setter");
1041 13           XopENTRY_set(&legba_set_xop, xop_class, OA_UNOP);
1042 13           Perl_custom_op_register(aTHX_ pp_slot_set, &legba_set_xop);
1043              
1044 13           XopENTRY_set(&legba_watch_xop, xop_name, "legba_watch");
1045 13           XopENTRY_set(&legba_watch_xop, xop_desc, "Legba slot watcher registration");
1046 13           XopENTRY_set(&legba_watch_xop, xop_class, OA_UNOP);
1047 13           Perl_custom_op_register(aTHX_ pp_slot_watch, &legba_watch_xop);
1048              
1049 13           XopENTRY_set(&legba_unwatch_xop, xop_name, "legba_unwatch");
1050 13           XopENTRY_set(&legba_unwatch_xop, xop_desc, "Legba slot unwatch all");
1051 13           XopENTRY_set(&legba_unwatch_xop, xop_class, OA_BASEOP);
1052 13           Perl_custom_op_register(aTHX_ pp_slot_unwatch, &legba_unwatch_xop);
1053              
1054 13           XopENTRY_set(&legba_unwatch_one_xop, xop_name, "legba_unwatch_one");
1055 13           XopENTRY_set(&legba_unwatch_one_xop, xop_desc, "Legba slot unwatch specific");
1056 13           XopENTRY_set(&legba_unwatch_one_xop, xop_class, OA_UNOP);
1057 13           Perl_custom_op_register(aTHX_ pp_slot_unwatch_one, &legba_unwatch_one_xop);
1058              
1059 13           XopENTRY_set(&legba_clear_xop, xop_name, "legba_clear");
1060 13           XopENTRY_set(&legba_clear_xop, xop_desc, "Legba slot clear");
1061 13           XopENTRY_set(&legba_clear_xop, xop_class, OA_BASEOP);
1062 13           Perl_custom_op_register(aTHX_ pp_slot_clear, &legba_clear_xop);
1063             #endif
1064              
1065             /* Initialise globals */
1066 13           g_slot_index = newHV();
1067 13           g_slot_names = newHV();
1068 13           g_watchers = newHV();
1069              
1070 13           g_slots_size = 16;
1071 13 50         Newx(g_slots, g_slots_size, SV*);
1072 13           Newxz(g_has_watchers,g_slots_size, char);
1073 13 50         Newxz(slot_flags, g_slots_size, UV);
1074             {
1075             IV i;
1076 221 100         for (i = 0; i < g_slots_size; i++)
1077 208           g_slots[i] = newSV(0); /* dedicated SV — pointer is stable */
1078             }
1079              
1080             /* import */
1081 13           newXS("Legba::import", xs_import, __FILE__);
1082              
1083             /* New API (from Ancient/slot) */
1084 13           newXS("Legba::add", xs_add, __FILE__);
1085 13           newXS("Legba::get_by_idx", xs_get_by_idx, __FILE__);
1086 13           newXS("Legba::set_by_idx", xs_set_by_idx, __FILE__);
1087 13           newXS("Legba::slots", xs_slots, __FILE__);
1088 13           newXS("Legba::exists", xs_exists, __FILE__);
1089 13           newXS("Legba::clear_by_idx", xs_clear_by_idx, __FILE__);
1090             {
1091 13           CV *cv = newXS("Legba::get", xs_get, __FILE__);
1092 13           cv_set_call_checker(cv, slot_get_call_checker, (SV*)cv);
1093             }
1094             {
1095 13           CV *cv = newXS("Legba::set", xs_set, __FILE__);
1096 13           cv_set_call_checker(cv, slot_set_call_checker, (SV*)cv);
1097             }
1098             {
1099 13           CV *cv = newXS("Legba::index", xs_index, __FILE__);
1100 13           cv_set_call_checker(cv, slot_index_call_checker, (SV*)cv);
1101             }
1102             {
1103 13           CV *cv = newXS("Legba::watch", xs_watch, __FILE__);
1104 13           cv_set_call_checker(cv, slot_watch_call_checker, (SV*)cv);
1105             }
1106             {
1107 13           CV *cv = newXS("Legba::unwatch", xs_unwatch, __FILE__);
1108 13           cv_set_call_checker(cv, slot_unwatch_call_checker, (SV*)cv);
1109             }
1110             {
1111 13           CV *cv = newXS("Legba::clear", xs_clear_named, __FILE__);
1112 13           cv_set_call_checker(cv, slot_clear_call_checker, (SV*)cv);
1113             }
1114              
1115             /* Backward-compatible API (_get, _set, _exists, _delete, _keys, _clear,
1116             _lock, _unlock, _freeze, _is_locked, _is_frozen, _install_accessor,
1117             _slot_ptr, _registry, _make_get_op, _make_set_op) */
1118             {
1119 13           CV *cv = newXS("Legba::_get", xs_get, __FILE__);
1120 13           cv_set_call_checker(cv, slot_get_call_checker, (SV*)cv);
1121             }
1122             {
1123 13           CV *cv = newXS("Legba::_set", xs_set, __FILE__);
1124 13           cv_set_call_checker(cv, slot_set_call_checker, (SV*)cv);
1125             }
1126 13           newXS("Legba::_exists", xs_exists, __FILE__);
1127 13           newXS("Legba::_delete", xs_delete, __FILE__);
1128 13           newXS("Legba::_keys", xs_keys, __FILE__);
1129 13           newXS("Legba::_clear", xs_clear_all, __FILE__);
1130 13           newXS("Legba::_lock", xs_lock, __FILE__);
1131 13           newXS("Legba::_unlock", xs_unlock, __FILE__);
1132 13           newXS("Legba::_freeze", xs_freeze, __FILE__);
1133 13           newXS("Legba::_is_locked", xs_is_locked, __FILE__);
1134 13           newXS("Legba::_is_frozen", xs_is_frozen, __FILE__);
1135 13           newXS("Legba::_install_accessor", xs_install_accessor_fn,__FILE__);
1136 13           newXS("Legba::_slot_ptr", xs_slot_ptr, __FILE__);
1137 13           newXS("Legba::_registry", xs_registry, __FILE__);
1138 13           newXS("Legba::_make_get_op", xs_make_get_op, __FILE__);
1139 13           newXS("Legba::_make_set_op", xs_make_set_op, __FILE__);
1140             }