]> git.vpit.fr Git - perl/modules/re-engine-Plugin.git/blob - Plugin.xs
Test thread safety
[perl/modules/re-engine-Plugin.git] / Plugin.xs
1 /* This file is part of the re::engine::Plugin Perl module.
2  * See http://search.cpan.org/dist/re-engine-Plugin/ */
3
4 #define PERL_NO_GET_CONTEXT
5 #include "EXTERN.h"
6 #include "perl.h"
7 #include "XSUB.h"
8
9 #include "Plugin.h"
10
11 #define __PACKAGE__     "re::engine::Plugin"
12 #define __PACKAGE_LEN__ (sizeof(__PACKAGE__)-1)
13
14 #define REP_HAS_PERL(R, V, S) (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
15
16 #ifndef REP_WORKAROUND_REQUIRE_PROPAGATION
17 # define REP_WORKAROUND_REQUIRE_PROPAGATION !REP_HAS_PERL(5, 10, 1)
18 #endif
19
20 /* ... Thread safety and multiplicity ...................................... */
21
22 /* Safe unless stated otherwise in Makefile.PL */
23 #ifndef REP_FORKSAFE
24 # define REP_FORKSAFE 1
25 #endif
26
27 #ifndef REP_MULTIPLICITY
28 # if defined(MULTIPLICITY) || defined(PERL_IMPLICIT_CONTEXT)
29 #  define REP_MULTIPLICITY 1
30 # else
31 #  define REP_MULTIPLICITY 0
32 # endif
33 #endif
34 #if REP_MULTIPLICITY && !defined(tTHX)
35 # define tTHX PerlInterpreter*
36 #endif
37
38 #if REP_MULTIPLICITY && defined(USE_ITHREADS) && defined(dMY_CXT) && defined(MY_CXT) && defined(START_MY_CXT) && defined(MY_CXT_INIT) && (defined(MY_CXT_CLONE) || defined(dMY_CXT_SV))
39 # define REP_THREADSAFE 1
40 # ifndef MY_CXT_CLONE
41 #  define MY_CXT_CLONE \
42     dMY_CXT_SV;                                                      \
43     my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1)); \
44     Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t); \
45     sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
46 # endif
47 #else
48 # define REP_THREADSAFE 0
49 # undef  dMY_CXT
50 # define dMY_CXT      dNOOP
51 # undef  MY_CXT
52 # define MY_CXT       rep_globaldata
53 # undef  START_MY_CXT
54 # define START_MY_CXT STATIC my_cxt_t MY_CXT;
55 # undef  MY_CXT_INIT
56 # define MY_CXT_INIT  NOOP
57 # undef  MY_CXT_CLONE
58 # define MY_CXT_CLONE NOOP
59 #endif
60
61 /* --- Helpers ------------------------------------------------------------- */
62
63 /* ... Thread-safe hints ................................................... */
64
65 typedef struct {
66  SV *comp;
67  SV *exec;
68 #if REP_WORKAROUND_REQUIRE_PROPAGATION
69  IV  require_tag;
70 #endif
71 } rep_hint_t;
72
73 #if REP_THREADSAFE
74
75 #define PTABLE_VAL_FREE(V) { \
76  rep_hint_t *h = (V);        \
77  SvREFCNT_dec(h->comp);      \
78  SvREFCNT_dec(h->exec);      \
79  PerlMemShared_free(h);      \
80 }
81
82 #define pPTBL  pTHX
83 #define pPTBL_ pTHX_
84 #define aPTBL  aTHX
85 #define aPTBL_ aTHX_
86
87 #include "ptable.h"
88
89 #define ptable_store(T, K, V) ptable_store(aTHX_ (T), (K), (V))
90 #define ptable_free(T)        ptable_free(aTHX_ (T))
91
92 #define MY_CXT_KEY __PACKAGE__ "::_guts" XS_VERSION
93
94 typedef struct {
95  ptable *tbl;
96  tTHX    owner;
97 } my_cxt_t;
98
99 START_MY_CXT
100
101 typedef struct {
102  ptable *tbl;
103 #if REP_HAS_PERL(5, 13, 2)
104  CLONE_PARAMS *params;
105 #else
106  CLONE_PARAMS params;
107 #endif
108 } rep_ptable_clone_ud;
109
110 #if REP_HAS_PERL(5, 13, 2)
111 # define rep_ptable_clone_ud_init(U, T, O) \
112    (U).tbl    = (T); \
113    (U).params = Perl_clone_params_new((O), aTHX)
114 # define rep_ptable_clone_ud_deinit(U) Perl_clone_params_del((U).params)
115 # define rep_dup_inc(S, U)             SvREFCNT_inc(sv_dup((S), (U)->params))
116 #else
117 # define rep_ptable_clone_ud_init(U, T, O) \
118    (U).tbl               = (T);     \
119    (U).params.stashes    = newAV(); \
120    (U).params.flags      = 0;       \
121    (U).params.proto_perl = (O)
122 # define rep_ptable_clone_ud_deinit(U) SvREFCNT_dec((U).params.stashes)
123 # define rep_dup_inc(S, U)             SvREFCNT_inc(sv_dup((S), &((U)->params)))
124 #endif
125
126 STATIC void rep_ptable_clone(pTHX_ ptable_ent *ent, void *ud_) {
127  rep_ptable_clone_ud *ud = ud_;
128  rep_hint_t *h1 = ent->val;
129  rep_hint_t *h2;
130
131  h2              = PerlMemShared_malloc(sizeof *h2);
132  h2->comp        = rep_dup_inc(h1->comp, ud);
133  h2->exec        = rep_dup_inc(h1->exec, ud);
134 #if REP_WORKAROUND_REQUIRE_PROPAGATION
135  h2->require_tag = PTR2IV(rep_dup_inc(INT2PTR(SV *, h1->require_tag), ud));
136 #endif
137
138  ptable_store(ud->tbl, ent->key, h2);
139 }
140
141 #include "reap.h"
142
143 STATIC void rep_thread_cleanup(pTHX_ void *ud) {
144  dMY_CXT;
145
146  ptable_free(MY_CXT.tbl);
147 }
148
149 #endif /* REP_THREADSAFE */
150
151 STATIC SV *rep_validate_callback(SV *code) {
152  if (!SvROK(code))
153   return NULL;
154
155  code = SvRV(code);
156  if (SvTYPE(code) < SVt_PVCV)
157   return NULL;
158
159  return SvREFCNT_inc_simple_NN(code);
160 }
161
162 #if REP_WORKAROUND_REQUIRE_PROPAGATION
163 STATIC IV rep_require_tag(pTHX) {
164 #define rep_require_tag() rep_require_tag(aTHX)
165  const CV *cv, *outside;
166
167  cv = PL_compcv;
168
169  if (!cv) {
170   /* If for some reason the pragma is operational at run-time, try to discover
171    * the current cv in use. */
172   const PERL_SI *si;
173
174   for (si = PL_curstackinfo; si; si = si->si_prev) {
175    I32 cxix;
176
177    for (cxix = si->si_cxix; cxix >= 0; --cxix) {
178     const PERL_CONTEXT *cx = si->si_cxstack + cxix;
179
180     switch (CxTYPE(cx)) {
181      case CXt_SUB:
182      case CXt_FORMAT:
183       /* The propagation workaround is only needed up to 5.10.0 and at that
184        * time format and sub contexts were still identical. And even later the
185        * cv members offsets should have been kept the same. */
186       cv = cx->blk_sub.cv;
187       goto get_enclosing_cv;
188      case CXt_EVAL:
189       cv = cx->blk_eval.cv;
190       goto get_enclosing_cv;
191      default:
192       break;
193     }
194    }
195   }
196
197   cv = PL_main_cv;
198  }
199
200 get_enclosing_cv:
201  for (outside = CvOUTSIDE(cv); outside; outside = CvOUTSIDE(cv))
202   cv = outside;
203
204  return PTR2IV(cv);
205 }
206 #endif /* REP_WORKAROUND_REQUIRE_PROPAGATION */
207
208 STATIC SV *rep_tag(pTHX_ SV *comp, SV *exec) {
209 #define rep_tag(C, E) rep_tag(aTHX_ (C), (E))
210  rep_hint_t *h;
211  dMY_CXT;
212
213  h              = PerlMemShared_malloc(sizeof *h);
214  h->comp        = rep_validate_callback(comp);
215  h->exec        = rep_validate_callback(exec);
216 #if REP_WORKAROUND_REQUIRE_PROPAGATION
217  h->require_tag = rep_require_tag();
218 #endif /* REP_WORKAROUND_REQUIRE_PROPAGATION */
219
220 #if REP_THREADSAFE
221  {
222   dMY_CXT;
223   /* We only need for the key to be an unique tag for looking up the value later
224    * Allocated memory provides convenient unique identifiers, so that's why we
225    * use the hint as the key itself. */
226   ptable_store(MY_CXT.tbl, h, h);
227  }
228 #endif /* REP_THREADSAFE */
229
230  return newSViv(PTR2IV(h));
231 }
232
233 STATIC const rep_hint_t *rep_detag(pTHX_ const SV *hint) {
234 #define rep_detag(H) rep_detag(aTHX_ (H))
235  rep_hint_t *h;
236
237  if (!(hint && SvIOK(hint)))
238   return NULL;
239
240  h = INT2PTR(rep_hint_t *, SvIVX(hint));
241 #if REP_THREADSAFE
242  {
243   dMY_CXT;
244   h = ptable_fetch(MY_CXT.tbl, h);
245  }
246 #endif /* REP_THREADSAFE */
247
248 #if REP_WORKAROUND_REQUIRE_PROPAGATION
249  if (rep_require_tag() != h->require_tag)
250   return NULL;
251 #endif /* REP_WORKAROUND_REQUIRE_PROPAGATION */
252
253  return h;
254 }
255
256 STATIC U32 rep_hash = 0;
257
258 STATIC const rep_hint_t *rep_hint(pTHX) {
259 #define rep_hint() rep_hint(aTHX)
260  SV *hint;
261
262 #ifdef cop_hints_fetch_pvn
263  hint = cop_hints_fetch_pvn(PL_curcop,
264                             __PACKAGE__, __PACKAGE_LEN__, rep_hash, 0);
265 #else
266  /* We already require 5.9.5 for the regexp engine API. */
267  hint = Perl_refcounted_he_fetch(aTHX_ PL_curcop->cop_hints_hash,
268                                        NULL,
269                                        __PACKAGE__, __PACKAGE_LEN__,
270                                        0,
271                                        rep_hash);
272 #endif
273
274  return rep_detag(hint);
275 }
276
277 REGEXP *
278 #if PERL_VERSION <= 10
279 Plugin_comp(pTHX_ const SV * const pattern, const U32 flags)
280 #else
281 Plugin_comp(pTHX_ SV * const pattern, U32 flags)
282 #endif
283 {
284     dSP;
285     struct regexp * rx;
286     REGEXP *RX;
287
288     re__engine__Plugin re;
289     const rep_hint_t *h;
290
291     STRLEN plen;
292     char *pbuf;
293
294     SV *obj;
295
296     h = rep_hint();
297     if (!h) /* This looks like a pragma leak. Apply the default behaviour */
298         return re_compile(pattern, flags);
299
300     /* exp/xend version of the pattern & length */
301     pbuf = SvPV((SV*)pattern, plen);
302
303     /* Our blessed object */
304     obj = newSV(0);
305     SvREFCNT_inc_simple_void_NN(obj);
306     Newxz(re, 1, struct replug);
307     sv_setref_pv(obj, "re::engine::Plugin", (void*)re);
308
309     newREGEXP(RX);
310     rx = rxREGEXP(RX);
311
312     re->rx = rx;                   /* Make the rx accessible from self->rx */
313     rx->intflags = flags;          /* Flags for internal use */
314     rx->extflags = flags;          /* Flags for perl to use */
315     rx->engine = RE_ENGINE_PLUGIN; /* Compile to use this engine */
316
317 #if PERL_VERSION <= 10
318     rx->refcnt = 1;                /* Refcount so we won't be destroyed */
319
320     /* Precompiled pattern for pp_regcomp to use */
321     rx->prelen = plen;
322     rx->precomp = savepvn(pbuf, rx->prelen);
323
324     /* Set up qr// stringification to be equivalent to the supplied
325      * pattern, this should be done via overload eventually.
326      */
327     rx->wraplen = rx->prelen;
328     Newx(rx->wrapped, rx->wraplen, char);
329     Copy(rx->precomp, rx->wrapped, rx->wraplen, char);
330 #endif
331
332     /* Store our private object */
333     rx->pprivate = obj;
334
335     /* Store the pattern for ->pattern */
336     re->pattern = (SV*)pattern;
337     SvREFCNT_inc_simple_void(re->pattern);
338
339     /* If there's an exec callback, store it into the private object so
340      * that it will be the one to be called, even if the engine changes
341      * in between */
342     if (h->exec) {
343         re->cb_exec = h->exec;
344         SvREFCNT_inc_simple_void_NN(h->exec);
345     }
346
347     re->cb_num_capture_buff_FETCH  = NULL;
348     re->cb_num_capture_buff_STORE  = NULL;
349     re->cb_num_capture_buff_LENGTH = NULL;
350
351     /* Call our callback function if one was defined, if not we've
352      * already set up all the stuff we're going to to need for
353      * subsequent exec and other calls */
354     if (h->comp) {
355         ENTER;    
356         SAVETMPS;
357    
358         PUSHMARK(SP);
359         XPUSHs(obj);
360         PUTBACK;
361
362         call_sv(h->comp, G_DISCARD);
363
364         FREETMPS;
365         LEAVE;
366     }
367
368     /* If any of the comp-time accessors were called we'll have to
369      * update the regexp struct with the new info.
370      */
371
372     Newxz(rx->offs, rx->nparens + 1, regexp_paren_pair);
373
374     return RX;
375 }
376
377 I32
378 Plugin_exec(pTHX_ REGEXP * const RX, char *stringarg, char *strend,
379             char *strbeg, I32 minend, SV *sv, void *data, U32 flags)
380 {
381     dSP;
382     I32 matched;
383     struct regexp *rx = rxREGEXP(RX);
384     GET_SELF_FROM_PPRIVATE(rx->pprivate);
385
386     if (self->cb_exec) {
387         SV *ret;
388
389         /* Store the current str for ->str */
390         SvREFCNT_dec(self->str);
391         self->str = sv;
392         SvREFCNT_inc_simple_void(self->str);
393
394         ENTER;
395         SAVETMPS;
396    
397         PUSHMARK(SP);
398         XPUSHs(rx->pprivate);
399         XPUSHs(sv);
400         PUTBACK;
401
402         call_sv(self->cb_exec, G_SCALAR);
403  
404         SPAGAIN;
405
406         ret = POPs;
407         if (SvTRUE(ret))
408             matched = 1;
409         else
410             matched = 0;
411
412         PUTBACK;
413         FREETMPS;
414         LEAVE;
415     } else {
416         matched = 0;
417     }
418
419     return matched;
420 }
421
422 char *
423 Plugin_intuit(pTHX_ REGEXP * const RX, SV *sv, char *strpos,
424                      char *strend, U32 flags, re_scream_pos_data *data)
425 {
426     PERL_UNUSED_ARG(RX);
427     PERL_UNUSED_ARG(sv);
428     PERL_UNUSED_ARG(strpos);
429     PERL_UNUSED_ARG(strend);
430     PERL_UNUSED_ARG(flags);
431     PERL_UNUSED_ARG(data);
432     return NULL;
433 }
434
435 SV *
436 Plugin_checkstr(pTHX_ REGEXP * const RX)
437 {
438     PERL_UNUSED_ARG(RX);
439     return NULL;
440 }
441
442 void
443 Plugin_free(pTHX_ REGEXP * const RX)
444 {
445     struct regexp *rx;
446     re__engine__Plugin self;
447
448     if (PL_dirty)
449         return;
450
451     rx = rxREGEXP(RX);
452     SELF_FROM_PPRIVATE(self, rx->pprivate);
453
454     SvREFCNT_dec(self->pattern);
455     SvREFCNT_dec(self->str);
456
457     SvREFCNT_dec(self->cb_exec);
458
459     SvREFCNT_dec(self->cb_num_capture_buff_FETCH);
460     SvREFCNT_dec(self->cb_num_capture_buff_STORE);
461     SvREFCNT_dec(self->cb_num_capture_buff_LENGTH);
462
463     self->rx = NULL;
464     Safefree(self);
465
466 /*
467     dSP;
468     SV * callback;
469
470     callback = self->cb_free;
471
472     if (callback) {
473         ENTER;
474         SAVETMPS;
475    
476         PUSHMARK(SP);
477         XPUSHs(rx->pprivate);
478         PUTBACK;
479
480         call_sv(callback, G_DISCARD);
481
482         PUTBACK;
483         FREETMPS;
484         LEAVE;
485     }
486     return;
487 */
488 }
489
490 void *
491 Plugin_dupe(pTHX_ REGEXP * const RX, CLONE_PARAMS *param)
492 {
493     struct regexp *rx = rxREGEXP(RX);
494     Perl_croak(aTHX_ "dupe not supported yet");
495     return rx->pprivate;
496 }
497
498
499 void
500 Plugin_numbered_buff_FETCH(pTHX_ REGEXP * const RX, const I32 paren,
501                            SV * const sv)
502 {
503     dSP;
504     I32 items;
505     SV * callback;
506     struct regexp *rx = rxREGEXP(RX);
507     GET_SELF_FROM_PPRIVATE(rx->pprivate);
508
509     callback = self->cb_num_capture_buff_FETCH;
510
511     if (callback) {
512         ENTER;
513         SAVETMPS;
514    
515         PUSHMARK(SP);
516         XPUSHs(rx->pprivate);
517         XPUSHs(sv_2mortal(newSViv(paren)));
518         PUTBACK;
519
520         items = call_sv(callback, G_SCALAR);
521         
522         if (items == 1) {
523             SV *ret;
524
525             SPAGAIN;
526             ret = POPs;
527             sv_setsv(sv, ret);
528         } else {
529             sv_setsv(sv, &PL_sv_undef);
530         }
531
532         PUTBACK;
533         FREETMPS;
534         LEAVE;
535     } else {
536         sv_setsv(sv, &PL_sv_undef);
537     }
538 }
539
540 void
541 Plugin_numbered_buff_STORE(pTHX_ REGEXP * const RX, const I32 paren,
542                            SV const * const value)
543 {
544     dSP;
545     SV * callback;
546     struct regexp *rx = rxREGEXP(RX);
547     GET_SELF_FROM_PPRIVATE(rx->pprivate);
548
549     callback = self->cb_num_capture_buff_STORE;
550
551     if (callback) {
552         ENTER;
553         SAVETMPS;
554    
555         PUSHMARK(SP);
556         XPUSHs(rx->pprivate);
557         XPUSHs(sv_2mortal(newSViv(paren)));
558         XPUSHs((SV *) value);
559         PUTBACK;
560
561         call_sv(callback, G_DISCARD);
562
563         PUTBACK;
564         FREETMPS;
565         LEAVE;
566     }
567 }
568
569 I32
570 Plugin_numbered_buff_LENGTH(pTHX_ REGEXP * const RX, const SV * const sv,
571                               const I32 paren)
572 {
573     dSP;
574     SV * callback;
575     struct regexp *rx = rxREGEXP(RX);
576     GET_SELF_FROM_PPRIVATE(rx->pprivate);
577
578     callback = self->cb_num_capture_buff_LENGTH;
579
580     if (callback) {
581         IV ret;
582
583         ENTER;
584         SAVETMPS;
585    
586         PUSHMARK(SP);
587         XPUSHs(rx->pprivate);
588         XPUSHs(sv_2mortal(newSViv(paren)));
589         PUTBACK;
590
591         call_sv(callback, G_SCALAR);
592
593         SPAGAIN;
594
595         ret = POPi;
596
597         PUTBACK;
598         FREETMPS;
599         LEAVE;
600
601         return (I32)ret;
602     } else {
603         /* TODO: call FETCH and get the length on that value */
604         return 0;
605     }
606 }
607
608
609 SV*
610 Plugin_named_buff (pTHX_ REGEXP * const RX, SV * const key, SV * const value,
611                    const U32 flags)
612 {
613     return NULL;
614 }
615
616 SV*
617 Plugin_named_buff_iter (pTHX_ REGEXP * const RX, const SV * const lastkey,
618                         const U32 flags)
619 {
620     return NULL;
621 }
622
623 SV*
624 Plugin_package(pTHX_ REGEXP * const RX)
625 {
626     PERL_UNUSED_ARG(RX);
627     return newSVpvs("re::engine::Plugin");
628 }
629
630 #if REP_THREADSAFE
631
632 STATIC U32 rep_initialized = 0;
633
634 STATIC void rep_teardown(pTHX_ void *root) {
635  if (!rep_initialized || aTHX != root)
636   return;
637
638  {
639   dMY_CXT;
640   ptable_free(MY_CXT.tbl);
641  }
642
643  rep_initialized = 0;
644 }
645
646 STATIC void rep_setup(pTHX) {
647 #define rep_setup() rep_setup(aTHX)
648  if (rep_initialized)
649   return;
650
651  {
652   MY_CXT_INIT;
653   MY_CXT.tbl   = ptable_new();
654   MY_CXT.owner = aTHX;
655  }
656
657  call_atexit(rep_teardown, aTHX);
658
659  rep_initialized = 1;
660 }
661
662 #else  /*  REP_THREADSAFE */
663
664 #define rep_setup()
665
666 #endif /* !REP_THREADSAFE */
667
668 STATIC U32 rep_booted = 0;
669
670 /* --- XS ------------------------------------------------------------------ */
671
672 MODULE = re::engine::Plugin       PACKAGE = re::engine::Plugin
673
674 PROTOTYPES: DISABLE
675
676 BOOT:
677 {
678     if (!rep_booted++) {
679         HV *stash;
680
681         PERL_HASH(rep_hash, __PACKAGE__, __PACKAGE_LEN__);
682
683         stash = gv_stashpvn(__PACKAGE__, __PACKAGE_LEN__, 1);
684         newCONSTSUB(stash, "REP_THREADSAFE",  newSVuv(REP_THREADSAFE));
685         newCONSTSUB(stash, "REP_FORKSAFE",    newSVuv(REP_FORKSAFE));
686     }
687
688     rep_setup();
689 }
690
691 #if REP_THREADSAFE
692
693 void
694 CLONE(...)
695 PREINIT:
696     ptable *t;
697 PPCODE:
698     {
699         rep_ptable_clone_ud ud;
700         dMY_CXT;
701
702         t = ptable_new();
703         rep_ptable_clone_ud_init(ud, t, MY_CXT.owner);
704         ptable_walk(MY_CXT.tbl, rep_ptable_clone, &ud);
705         rep_ptable_clone_ud_deinit(ud);
706     }
707     {
708         MY_CXT_CLONE;
709         MY_CXT.tbl   = t;
710         MY_CXT.owner = aTHX;
711     }
712     reap(3, rep_thread_cleanup, NULL);
713     XSRETURN(0);
714
715 #endif /* REP_THREADSAFE */
716
717 void
718 pattern(re::engine::Plugin self, ...)
719 PPCODE:
720     XPUSHs(self->pattern);
721
722 void
723 str(re::engine::Plugin self, ...)
724 PPCODE:
725     XPUSHs(self->str);
726
727 void
728 mod(re::engine::Plugin self)
729 PREINIT:
730     U32 flags;
731     char mods[5 + 1];
732     int n = 0, i;
733 PPCODE:
734     flags = self->rx->intflags;
735     if (flags & PMf_FOLD)         /* /i */
736         mods[n++] = 'i';
737     if (flags & PMf_MULTILINE)    /* /m */
738         mods[n++] = 'm';
739     if (flags & PMf_SINGLELINE)   /* /s */
740         mods[n++] = 's';
741     if (flags & PMf_EXTENDED)     /* /x */
742         mods[n++] = 'x';
743     if (flags & RXf_PMf_KEEPCOPY) /* /p */
744         mods[n++] = 'p';
745     mods[n] = '\0';
746     EXTEND(SP, 2 * n);
747     for (i = 0; i < n; ++i) {
748         mPUSHp(mods + i, 1);
749         PUSHs(&PL_sv_yes);
750     }
751     XSRETURN(2 * n);
752
753 void
754 stash(re::engine::Plugin self, ...)
755 PPCODE:
756     if (items > 1) {
757         SvREFCNT_dec(self->stash);
758         self->stash = ST(1);
759         SvREFCNT_inc_simple_void(self->stash);
760         XSRETURN_EMPTY;
761     } else {
762         XPUSHs(self->stash);
763     }
764
765 void
766 minlen(re::engine::Plugin self, ...)
767 PPCODE:
768     if (items > 1) {
769         self->rx->minlen = (I32)SvIV(ST(1));
770         XSRETURN_EMPTY;
771     } else {
772         if (self->rx->minlen) {
773             XPUSHs(sv_2mortal(newSViv(self->rx->minlen)));
774         } else {
775             XPUSHs(sv_2mortal(&PL_sv_undef));
776         }
777     }
778
779 void
780 gofs(re::engine::Plugin self, ...)
781 PPCODE:
782     if (items > 1) {
783         self->rx->gofs = (U32)SvIV(ST(1));
784         XSRETURN_EMPTY;
785     } else {
786         if (self->rx->gofs) {
787             XPUSHs(sv_2mortal(newSVuv(self->rx->gofs)));
788         } else {
789             XPUSHs(sv_2mortal(&PL_sv_undef));
790         }
791     }
792
793 void
794 nparens(re::engine::Plugin self, ...)
795 PPCODE:
796     if (items > 1) {
797         self->rx->nparens = (U32)SvIV(ST(1));
798         XSRETURN_EMPTY;
799     } else {
800         if (self->rx->nparens) {
801             XPUSHs(sv_2mortal(newSVuv(self->rx->nparens)));
802         } else {
803             XPUSHs(sv_2mortal(&PL_sv_undef));
804         }
805     }
806
807 void
808 _exec(re::engine::Plugin self, ...)
809 PPCODE:
810     if (items > 1) {
811         SvREFCNT_dec(self->cb_exec);
812         self->cb_exec = ST(1);
813         SvREFCNT_inc_simple_void(self->cb_exec);
814     }
815
816 void
817 _num_capture_buff_FETCH(re::engine::Plugin self, ...)
818 PPCODE:
819     if (items > 1) {
820         SvREFCNT_dec(self->cb_num_capture_buff_FETCH);
821         self->cb_num_capture_buff_FETCH = ST(1);
822         SvREFCNT_inc_simple_void(self->cb_num_capture_buff_FETCH);
823     }
824
825 void
826 _num_capture_buff_STORE(re::engine::Plugin self, ...)
827 PPCODE:
828     if (items > 1) {
829         SvREFCNT_dec(self->cb_num_capture_buff_STORE);
830         self->cb_num_capture_buff_STORE = ST(1);
831         SvREFCNT_inc_simple_void(self->cb_num_capture_buff_STORE);
832     }
833
834 void
835 _num_capture_buff_LENGTH(re::engine::Plugin self, ...)
836 PPCODE:
837     if (items > 1) {
838         SvREFCNT_dec(self->cb_num_capture_buff_LENGTH);
839         self->cb_num_capture_buff_LENGTH = ST(1);
840         SvREFCNT_inc_simple_void(self->cb_num_capture_buff_LENGTH);
841     }
842
843 SV *
844 _tag(SV *comp, SV *exec)
845 CODE:
846     RETVAL = rep_tag(comp, exec);
847 OUTPUT:
848     RETVAL
849
850 void
851 ENGINE()
852 PPCODE:
853     XPUSHs(sv_2mortal(newSViv(PTR2IV(&engine_plugin))));