File Coverage

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