]> git.vpit.fr Git - perl/modules/indirect.git/blob - indirect.xs
Make the hint thread-safe and store a coderef called each time an indirect construct...
[perl/modules/indirect.git] / indirect.xs
1 /* This file is part of the indirect Perl module.
2  * See http://search.cpan.org/dist/indirect/ */
3
4 #define PERL_NO_GET_CONTEXT
5 #include "EXTERN.h"
6 #include "perl.h"
7 #include "XSUB.h"
8
9 #define __PACKAGE__     "indirect"
10 #define __PACKAGE_LEN__ (sizeof(__PACKAGE__)-1)
11
12 /* --- Compatibility wrappers ---------------------------------------------- */
13
14 #ifndef SvPV_const
15 # define SvPV_const SvPV
16 #endif
17
18 #ifndef SvPV_nolen_const
19 # define SvPV_nolen_const SvPV_nolen
20 #endif
21
22 #ifndef SvPVX_const
23 # define SvPVX_const SvPVX
24 #endif
25
26 #ifndef sv_catpvn_nomg
27 # define sv_catpvn_nomg sv_catpvn
28 #endif
29
30 #ifndef HvNAME_get
31 # define HvNAME_get(H) HvNAME(H)
32 #endif
33
34 #ifndef HvNAMELEN_get
35 # define HvNAMELEN_get(H) strlen(HvNAME_get(H))
36 #endif
37
38 #ifndef SvIS_FREED
39 # define SvIS_FREED(sv) ((sv)->sv_flags == SVTYPEMASK)
40 #endif
41
42 #define I_HAS_PERL(R, V, S) (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
43
44 #if I_HAS_PERL(5, 10, 0) || defined(PL_parser)
45 # ifndef PL_lex_inwhat
46 #  define PL_lex_inwhat PL_parser->lex_inwhat
47 # endif
48 # ifndef PL_linestr
49 #  define PL_linestr PL_parser->linestr
50 # endif
51 # ifndef PL_bufptr
52 #  define PL_bufptr PL_parser->bufptr
53 # endif
54 # ifndef PL_oldbufptr
55 #  define PL_oldbufptr PL_parser->oldbufptr
56 # endif
57 #else
58 # ifndef PL_lex_inwhat
59 #  define PL_lex_inwhat PL_Ilex_inwhat
60 # endif
61 # ifndef PL_linestr
62 #  define PL_linestr PL_Ilinestr
63 # endif
64 # ifndef PL_bufptr
65 #  define PL_bufptr PL_Ibufptr
66 # endif
67 # ifndef PL_oldbufptr
68 #  define PL_oldbufptr PL_Ioldbufptr
69 # endif
70 #endif
71
72 /* ... Thread safety and multiplicity ...................................... */
73
74 #ifndef I_MULTIPLICITY
75 # if defined(MULTIPLICITY) || defined(PERL_IMPLICIT_CONTEXT)
76 #  define I_MULTIPLICITY 1
77 # else
78 #  define I_MULTIPLICITY 0
79 # endif
80 #endif
81 #if I_MULTIPLICITY && !defined(tTHX)
82 # define tTHX PerlInterpreter*
83 #endif
84
85 #if I_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))
86 # define I_THREADSAFE 1
87 # ifndef MY_CXT_CLONE
88 #  define MY_CXT_CLONE \
89     dMY_CXT_SV;                                                      \
90     my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1)); \
91     Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t); \
92     sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
93 # endif
94 #else
95 # define I_THREADSAFE 0
96 #endif
97
98 /* --- Helpers ------------------------------------------------------------- */
99
100 /* ... Thread-safe hints ................................................... */
101
102 #if I_THREADSAFE
103
104 #define PTABLE_NAME        ptable_hints
105 #define PTABLE_VAL_FREE(V) if ((V) && !SvIS_FREED((SV *) (V))) SvREFCNT_dec(V)
106
107 #define pPTBL  pTHX
108 #define pPTBL_ pTHX_
109 #define aPTBL  aTHX
110 #define aPTBL_ aTHX_
111
112 #include "ptable.h"
113
114 #define ptable_hints_store(T, K, V) ptable_hints_store(aTHX_ (T), (K), (V))
115 #define ptable_hints_free(T)        ptable_hints_free(aTHX_ (T))
116
117 #define MY_CXT_KEY __PACKAGE__ "::_guts" XS_VERSION
118
119 typedef struct {
120  ptable *tbl;
121  tTHX    owner;
122 } my_cxt_t;
123
124 START_MY_CXT
125
126 STATIC void indirect_ptable_hints_clone(pTHX_ ptable_ent *ent, void *ud_) {
127  my_cxt_t *ud  = ud_;
128  SV       *val = ent->val;
129
130  if (ud->owner != aTHX) {
131   CLONE_PARAMS param;
132   AV *stashes = (SvTYPE(val) == SVt_PVHV && HvNAME_get(val)) ? newAV() : NULL;
133   param.stashes    = stashes;
134   param.flags      = 0;
135   param.proto_perl = ud->owner;
136   val = sv_dup(val, &param);
137   if (stashes) {
138    av_undef(stashes);
139    SvREFCNT_dec(stashes);
140   }
141  }
142
143  ptable_hints_store(ud->tbl, ent->key, val);
144  SvREFCNT_inc(val);
145 }
146
147 STATIC void indirect_thread_cleanup(pTHX_ void *);
148
149 STATIC void indirect_thread_cleanup(pTHX_ void *ud) {
150  int *level = ud;
151  SV  *id;
152
153  if (*level) {
154   *level = 0;
155   LEAVE;
156   SAVEDESTRUCTOR_X(indirect_thread_cleanup, level);
157   ENTER;
158  } else {
159   dMY_CXT;
160   PerlMemShared_free(level);
161   ptable_hints_free(MY_CXT.tbl);
162  }
163 }
164
165 STATIC SV *indirect_tag(pTHX_ SV *value) {
166 #define indirect_tag(V) indirect_tag(aTHX_ (V))
167  dMY_CXT;
168
169  value = SvOK(value) && SvROK(value) ? SvRV(value) : NULL;
170  /* We only need for the key to be an unique tag for looking up the value later.
171   * Allocated memory provides convenient unique identifiers, so that's why we
172   * use the value pointer as the key itself. */
173  ptable_hints_store(MY_CXT.tbl, value, value);
174  SvREFCNT_inc(value);
175
176  return newSVuv(PTR2UV(value));
177 }
178
179 STATIC SV *indirect_detag(pTHX_ const SV *hint) {
180 #define indirect_detag(H) indirect_detag(aTHX_ (H))
181  void *tag;
182  SV   *value;
183
184  if (!hint || !SvOK(hint) || !SvIOK(hint))
185   croak("Wrong hint");
186
187  tag = INT2PTR(void *, SvIVX(hint));
188  {
189   dMY_CXT;
190   value = ptable_fetch(MY_CXT.tbl, tag);
191  }
192
193  return value;
194 }
195
196 #else
197
198 STATIC SV *indirect_tag(pTHX_ SV *value) {
199 #define indirect_tag(V) indirect_tag(aTHX_ (V))
200  UV tag = 0;
201
202  if (SvOK(value) && SvROK(value)) {
203   value = SvRV(value);
204   SvREFCNT_inc(value);
205   tag = PTR2UV(value);
206  }
207
208  return newSVuv(tag);
209 }
210
211 #define indirect_detag(H) INT2PTR(SV *, SvUVX(H))
212
213 #endif /* I_THREADSAFE */
214
215 STATIC U32 indirect_hash = 0;
216
217 STATIC SV *indirect_hint(pTHX) {
218 #define indirect_hint() indirect_hint(aTHX)
219  SV *id;
220 #if I_HAS_PERL(5, 10, 0)
221  id = Perl_refcounted_he_fetch(aTHX_ PL_curcop->cop_hints_hash,
222                                      NULL,
223                                      __PACKAGE__, __PACKAGE_LEN__,
224                                      0,
225                                      indirect_hash);
226 #else
227  SV **val = hv_fetch(GvHV(PL_hintgv), __PACKAGE__, __PACKAGE_LEN__,
228                                                                  indirect_hash);
229  if (!val)
230   return 0;
231  id = *val;
232 #endif
233  return (id && SvOK(id)) ? id : NULL;
234 }
235
236 /* ... op -> source position ............................................... */
237
238 #define PTABLE_NAME        ptable_map
239 #define PTABLE_VAL_FREE(V) SvREFCNT_dec(V)
240
241 #define pPTBL  pTHX
242 #define pPTBL_ pTHX_
243 #define aPTBL  aTHX
244 #define aPTBL_ aTHX_
245
246 #include "ptable.h"
247
248 #define ptable_map_store(T, K, V) ptable_map_store(aTHX_ (T), (K), (V))
249 #define ptable_map_clear(T)       ptable_map_clear(aTHX_ (T))
250
251 STATIC ptable *indirect_map = NULL;
252
253 STATIC const char *indirect_linestr = NULL;
254
255 STATIC void indirect_map_store(pTHX_ const OP *o, const char *src, SV *sv) {
256 #define indirect_map_store(O, S, N) indirect_map_store(aTHX_ (O), (S), (N))
257  SV *val;
258
259  /* When lex_inwhat is set, we're in a quotelike environment (qq, qr, but not q)
260   * In this case the linestr has temporarly changed, but the old buffer should
261   * still be alive somewhere. */
262
263  if (!PL_lex_inwhat) {
264   const char *pl_linestr = SvPVX_const(PL_linestr);
265   if (indirect_linestr != pl_linestr) {
266    ptable_map_clear(indirect_map);
267    indirect_linestr = pl_linestr;
268   }
269  }
270
271  val = newSVsv(sv);
272  SvUPGRADE(val, SVt_PVIV);
273  SvUVX(val) = PTR2UV(src);
274  SvIOK_on(val);
275  SvIsUV_on(val);
276
277  ptable_map_store(indirect_map, o, val);
278 }
279
280 STATIC const char *indirect_map_fetch(pTHX_ const OP *o, SV ** const name) {
281 #define indirect_map_fetch(O, S) indirect_map_fetch(aTHX_ (O), (S))
282  SV *val;
283
284  if (indirect_linestr != SvPVX_const(PL_linestr))
285   return NULL;
286
287  val = ptable_fetch(indirect_map, o);
288  if (!val) {
289   *name = NULL;
290   return NULL;
291  }
292
293  *name = val;
294  return INT2PTR(const char *, SvUVX(val));
295 }
296
297 /* --- Check functions ----------------------------------------------------- */
298
299 STATIC const char *indirect_find(pTHX_ SV *sv, const char *s) {
300 #define indirect_find(N, S) indirect_find(aTHX_ (N), (S))
301  STRLEN len;
302  const char *p = NULL, *r = SvPV_const(sv, len);
303
304  if (len >= 1 && *r == '$') {
305   ++r;
306   --len;
307   s = strchr(s, '$');
308   if (!s)
309    return NULL;
310  }
311
312  p = strstr(s, r);
313  while (p) {
314   p += len;
315   if (!isALNUM(*p))
316    break;
317   p = strstr(p + 1, r);
318  }
319
320  return p;
321 }
322
323 /* ... ck_const ............................................................ */
324
325 STATIC OP *(*indirect_old_ck_const)(pTHX_ OP *) = 0;
326
327 STATIC OP *indirect_ck_const(pTHX_ OP *o) {
328  o = CALL_FPTR(indirect_old_ck_const)(aTHX_ o);
329
330  if (indirect_hint()) {
331   SV *sv = cSVOPo_sv;
332   if (SvPOK(sv) && (SvTYPE(sv) >= SVt_PV))
333    indirect_map_store(o, indirect_find(sv, PL_oldbufptr), sv);
334  }
335
336  return o;
337 }
338
339 /* ... ck_rv2sv ............................................................ */
340
341 STATIC OP *(*indirect_old_ck_rv2sv)(pTHX_ OP *) = 0;
342
343 STATIC OP *indirect_ck_rv2sv(pTHX_ OP *o) {
344  if (indirect_hint()) {
345   OP *op = cUNOPo->op_first;
346   SV *sv;
347   const char *name = NULL, *s;
348   STRLEN len;
349   OPCODE type = (OPCODE) op->op_type;
350
351   switch (type) {
352    case OP_GV:
353    case OP_GVSV: {
354     GV *gv = cGVOPx_gv(op);
355     name = GvNAME(gv);
356     len  = GvNAMELEN(gv);
357     break;
358    }
359    default:
360     if ((PL_opargs[type] & OA_CLASS_MASK) == OA_SVOP) {
361      SV *nsv = cSVOPx_sv(op);
362      if (SvPOK(nsv) && (SvTYPE(nsv) >= SVt_PV))
363       name = SvPV_const(nsv, len);
364     }
365   }
366   if (!name)
367    goto done;
368
369   sv = sv_2mortal(newSVpvn("$", 1));
370   sv_catpvn_nomg(sv, name, len);
371   s = indirect_find(sv, PL_oldbufptr);
372   if (!s) { /* If it failed, retry without the current stash */
373    const char *stash = HvNAME_get(PL_curstash);
374    STRLEN stashlen = HvNAMELEN_get(PL_curstash);
375
376    if ((len < stashlen + 2) || strnNE(name, stash, stashlen)
377        || name[stashlen] != ':' || name[stashlen+1] != ':') {
378     /* Failed again ? Try to remove main */
379     stash = "main";
380     stashlen = 4;
381     if ((len < stashlen + 2) || strnNE(name, stash, stashlen)
382         || name[stashlen] != ':' || name[stashlen+1] != ':')
383      goto done;
384    }
385
386    sv_setpvn(sv, "$", 1);
387    stashlen += 2;
388    sv_catpvn_nomg(sv, name + stashlen, len - stashlen);
389    s = indirect_find(sv, PL_oldbufptr);
390    if (!s)
391     goto done;
392   }
393
394   o = CALL_FPTR(indirect_old_ck_rv2sv)(aTHX_ o);
395   indirect_map_store(o, s, sv);
396   return o;
397  }
398
399 done:
400  return CALL_FPTR(indirect_old_ck_rv2sv)(aTHX_ o);
401 }
402
403 /* ... ck_padany ........................................................... */
404
405 STATIC OP *(*indirect_old_ck_padany)(pTHX_ OP *) = 0;
406
407 STATIC OP *indirect_ck_padany(pTHX_ OP *o) {
408  o = CALL_FPTR(indirect_old_ck_padany)(aTHX_ o);
409
410  if (indirect_hint()) {
411   SV *sv;
412   const char *s = PL_oldbufptr, *t = PL_bufptr - 1;
413
414   while (s < t && isSPACE(*s)) ++s;
415   if (*s == '$' && ++s <= t) {
416    while (s < t && isSPACE(*s)) ++s;
417    while (s < t && isSPACE(*t)) --t;
418    sv = sv_2mortal(newSVpvn("$", 1));
419    sv_catpvn_nomg(sv, s, t - s + 1);
420    indirect_map_store(o, s, sv);
421   }
422  }
423
424  return o;
425 }
426
427 /* ... ck_method ........................................................... */
428
429 STATIC OP *(*indirect_old_ck_method)(pTHX_ OP *) = 0;
430
431 STATIC OP *indirect_ck_method(pTHX_ OP *o) {
432  if (indirect_hint()) {
433   OP *op = cUNOPo->op_first;
434   SV *sv;
435   const char *s = indirect_map_fetch(op, &sv);
436   if (!s) {
437    sv = cSVOPx_sv(op);
438    if (!SvPOK(sv) || (SvTYPE(sv) < SVt_PV))
439     goto done;
440    sv = sv_mortalcopy(sv);
441    s  = indirect_find(sv, PL_oldbufptr);
442   }
443   o = CALL_FPTR(indirect_old_ck_method)(aTHX_ o);
444   /* o may now be a method_named */
445   indirect_map_store(o, s, sv);
446   return o;
447  }
448
449 done:
450  return CALL_FPTR(indirect_old_ck_method)(aTHX_ o);
451 }
452
453 /* ... ck_entersub ......................................................... */
454
455 STATIC OP *(*indirect_old_ck_entersub)(pTHX_ OP *) = 0;
456
457 STATIC OP *indirect_ck_entersub(pTHX_ OP *o) {
458  SV *hint = indirect_hint();
459
460  o = CALL_FPTR(indirect_old_ck_entersub)(aTHX_ o);
461
462  if (hint) {
463   const char *mpos, *opos;
464   SV *mnamesv, *onamesv;
465   OP *mop, *oop;
466   LISTOP *lop;
467
468   oop = o;
469   do {
470    lop = (LISTOP *) oop;
471    if (!(lop->op_flags & OPf_KIDS))
472     goto done;
473    oop = lop->op_first;
474   } while (oop->op_type != OP_PUSHMARK);
475   oop = oop->op_sibling;
476   mop = lop->op_last;
477
478   if (mop->op_type == OP_METHOD)
479    mop = cUNOPx(mop)->op_first;
480   else if (mop->op_type != OP_METHOD_NAMED)
481    goto done;
482
483   mpos = indirect_map_fetch(mop, &mnamesv);
484   if (!mpos)
485    goto done;
486
487   opos = indirect_map_fetch(oop, &onamesv);
488   if (!opos)
489    goto done;
490
491   if (mpos < opos) {
492    SV *code = indirect_detag(hint);
493
494    if (hint) {
495     dSP;
496
497     ENTER;
498     SAVETMPS;
499
500     PUSHMARK(SP);
501     EXTEND(SP, 2);
502     PUSHs(onamesv);
503     PUSHs(mnamesv);
504     PUTBACK;
505
506     call_sv(code, G_VOID);
507
508     PUTBACK;
509
510     FREETMPS;
511     LEAVE;
512    }
513   }
514  }
515
516 done:
517  return o;
518 }
519
520 STATIC U32 indirect_initialized = 0;
521
522 /* --- XS ------------------------------------------------------------------ */
523
524 MODULE = indirect      PACKAGE = indirect
525
526 PROTOTYPES: ENABLE
527
528 BOOT:
529 {
530  if (!indirect_initialized++) {
531 #if I_THREADSAFE
532   MY_CXT_INIT;
533   MY_CXT.tbl   = ptable_new();
534   MY_CXT.owner = aTHX;
535 #endif
536
537   indirect_map = ptable_new();
538
539   PERL_HASH(indirect_hash, __PACKAGE__, __PACKAGE_LEN__);
540
541   indirect_old_ck_const    = PL_check[OP_CONST];
542   PL_check[OP_CONST]       = MEMBER_TO_FPTR(indirect_ck_const);
543   indirect_old_ck_rv2sv    = PL_check[OP_RV2SV];
544   PL_check[OP_RV2SV]       = MEMBER_TO_FPTR(indirect_ck_rv2sv);
545   indirect_old_ck_padany   = PL_check[OP_PADANY];
546   PL_check[OP_PADANY]      = MEMBER_TO_FPTR(indirect_ck_padany);
547   indirect_old_ck_method   = PL_check[OP_METHOD];
548   PL_check[OP_METHOD]      = MEMBER_TO_FPTR(indirect_ck_method);
549   indirect_old_ck_entersub = PL_check[OP_ENTERSUB];
550   PL_check[OP_ENTERSUB]    = MEMBER_TO_FPTR(indirect_ck_entersub);
551  }
552 }
553
554 #if I_THREADSAFE
555
556 void
557 CLONE(...)
558 PROTOTYPE: DISABLE
559 PREINIT:
560  ptable *t;
561  int    *level;
562 CODE:
563  {
564   my_cxt_t ud;
565   dMY_CXT;
566   ud.tbl   = t = ptable_new();
567   ud.owner = MY_CXT.owner;
568   ptable_walk(MY_CXT.tbl, indirect_ptable_hints_clone, &ud);
569  }
570  {
571   MY_CXT_CLONE;
572   MY_CXT.tbl   = t;
573   MY_CXT.owner = aTHX;
574  }
575  {
576   level = PerlMemShared_malloc(sizeof *level);
577   *level = 1;
578   LEAVE;
579   SAVEDESTRUCTOR_X(indirect_thread_cleanup, level);
580   ENTER;
581  }
582
583 #endif
584
585 SV *
586 _tag(SV *value)
587 PROTOTYPE: $
588 CODE:
589  RETVAL = indirect_tag(value);
590 OUTPUT:
591  RETVAL