]> git.vpit.fr Git - perl/modules/indirect.git/blobdiff - indirect.xs
Manually release MY_CXT.global_code at interpreter teardown time
[perl/modules/indirect.git] / indirect.xs
index f7b22d35696776b29abf9045c3b31153bf649430..ba0543dae7e778c864bac2a9543479f2a2317e53 100644 (file)
 # define SvPVX_const SvPVX
 #endif
 
-#ifndef SvREFCNT_inc_simple_NN
-# define SvREFCNT_inc_simple_NN SvREFCNT_inc
+#ifndef SvREFCNT_inc_simple_void_NN
+# ifdef SvREFCNT_inc_simple_NN
+#  define SvREFCNT_inc_simple_void_NN SvREFCNT_inc_simple_NN
+# else
+#  define SvREFCNT_inc_simple_void_NN SvREFCNT_inc
+# endif
 #endif
 
 #ifndef sv_catpvn_nomg
 # define HvNAMELEN_get(H) strlen(HvNAME_get(H))
 #endif
 
+#ifndef OpSIBLING
+# ifdef OP_SIBLING
+#  define OpSIBLING(O) OP_SIBLING(O)
+# else
+#  define OpSIBLING(O) ((O)->op_sibling)
+# endif
+#endif
+
 #define I_HAS_PERL(R, V, S) (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
 
 #if I_HAS_PERL(5, 10, 0) || defined(PL_parser)
@@ -71,6 +83,9 @@
 # ifndef PL_oldbufptr
 #  define PL_oldbufptr PL_parser->oldbufptr
 # endif
+# ifndef PL_lex_inwhat
+#  define PL_lex_inwhat PL_parser->lex_inwhat
+# endif
 #else
 # ifndef PL_linestr
 #  define PL_linestr PL_Ilinestr
 # ifndef PL_oldbufptr
 #  define PL_oldbufptr PL_Ioldbufptr
 # endif
+# ifndef PL_lex_inwhat
+#  define PL_lex_inwhat PL_Ilex_inwhat
+# endif
 #endif
 
 #ifndef I_WORKAROUND_REQUIRE_PROPAGATION
-# define I_WORKAROUND_REQUIRE_PROPAGATION !I_HAS_PERL(5, 12, 0)
+# define I_WORKAROUND_REQUIRE_PROPAGATION !I_HAS_PERL(5, 10, 1)
 #endif
 
 /* ... Thread safety and multiplicity ...................................... */
 #endif
 
 #ifndef I_MULTIPLICITY
-# if defined(MULTIPLICITY) || defined(PERL_IMPLICIT_CONTEXT)
+# if defined(MULTIPLICITY)
 #  define I_MULTIPLICITY 1
 # else
 #  define I_MULTIPLICITY 0
 # endif
 #endif
+#if I_MULTIPLICITY
+# ifndef PERL_IMPLICIT_CONTEXT
+#  error MULTIPLICITY builds must set PERL_IMPLICIT_CONTEXT
+# endif
+#endif
 #if I_MULTIPLICITY && !defined(tTHX)
 # define tTHX PerlInterpreter*
 #endif
 # undef  MY_CXT
 # define MY_CXT       indirect_globaldata
 # undef  START_MY_CXT
-# define START_MY_CXT STATIC my_cxt_t MY_CXT;
+# define START_MY_CXT static my_cxt_t MY_CXT;
 # undef  MY_CXT_INIT
 # define MY_CXT_INIT  NOOP
 # undef  MY_CXT_CLONE
 # define MY_CXT_CLONE NOOP
 #endif
 
+#if I_THREADSAFE
+/* We must use preexistent global mutexes or we will never be able to destroy
+ * them. */
+# if I_HAS_PERL(5, 9, 3)
+#  define I_LOADED_LOCK   MUTEX_LOCK(&PL_my_ctx_mutex)
+#  define I_LOADED_UNLOCK MUTEX_UNLOCK(&PL_my_ctx_mutex)
+# else
+#  define I_LOADED_LOCK   OP_REFCNT_LOCK
+#  define I_LOADED_UNLOCK OP_REFCNT_UNLOCK
+# endif
+#else
+# define I_LOADED_LOCK   NOOP
+# define I_LOADED_UNLOCK NOOP
+#endif
+
+#if defined(OP_CHECK_MUTEX_LOCK) && defined(OP_CHECK_MUTEX_UNLOCK)
+# define I_CHECK_LOCK   OP_CHECK_MUTEX_LOCK
+# define I_CHECK_UNLOCK OP_CHECK_MUTEX_UNLOCK
+#elif I_HAS_PERL(5, 9, 3)
+# define I_CHECK_LOCK   OP_REFCNT_LOCK
+# define I_CHECK_UNLOCK OP_REFCNT_UNLOCK
+#else
+/* Before perl 5.9.3, indirect_ck_*() calls are already protected by the
+ * I_LOADED mutex, which falls back to the OP_REFCNT mutex. Make sure we don't
+ * lock it twice. */
+# define I_CHECK_LOCK   NOOP
+# define I_CHECK_UNLOCK NOOP
+#endif
+
+typedef OP *(*indirect_ck_t)(pTHX_ OP *);
+
+#ifdef wrap_op_checker
+
+# define indirect_ck_replace(T, NC, OCP) wrap_op_checker((T), (NC), (OCP))
+
+#else
+
+static void indirect_ck_replace(pTHX_ OPCODE type, indirect_ck_t new_ck, indirect_ck_t *old_ck_p) {
+#define indirect_ck_replace(T, NC, OCP) indirect_ck_replace(aTHX_ (T), (NC), (OCP))
+ I_CHECK_LOCK;
+ if (!*old_ck_p) {
+  *old_ck_p      = PL_check[type];
+  PL_check[type] = new_ck;
+ }
+ I_CHECK_UNLOCK;
+}
+
+#endif
+
+static void indirect_ck_restore(pTHX_ OPCODE type, indirect_ck_t *old_ck_p) {
+#define indirect_ck_restore(T, OCP) indirect_ck_restore(aTHX_ (T), (OCP))
+ I_CHECK_LOCK;
+ if (*old_ck_p) {
+  PL_check[type] = *old_ck_p;
+  *old_ck_p      = 0;
+ }
+ I_CHECK_UNLOCK;
+}
+
 /* --- Helpers ------------------------------------------------------------- */
 
+/* ... Check if the module is loaded ....................................... */
+
+static I32 indirect_loaded = 0;
+
+#if I_THREADSAFE
+
+#define PTABLE_NAME        ptable_loaded
+#define PTABLE_VAL_FREE(V) NOOP
+
+#include "ptable.h"
+
+#define ptable_loaded_store(T, K, V) ptable_loaded_store(aPTBLMS_ (T), (K), (V))
+#define ptable_loaded_delete(T, K)   ptable_loaded_delete(aPTBLMS_ (T), (K))
+#define ptable_loaded_free(T)        ptable_loaded_free(aPTBLMS_ (T))
+
+static ptable *indirect_loaded_cxts = NULL;
+
+static int indirect_is_loaded(pTHX_ void *cxt) {
+#define indirect_is_loaded(C) indirect_is_loaded(aTHX_ (C))
+ int res = 0;
+
+ I_LOADED_LOCK;
+ if (indirect_loaded_cxts && ptable_fetch(indirect_loaded_cxts, cxt))
+  res = 1;
+ I_LOADED_UNLOCK;
+
+ return res;
+}
+
+static int indirect_set_loaded_locked(pTHX_ void *cxt) {
+#define indirect_set_loaded_locked(C) indirect_set_loaded_locked(aTHX_ (C))
+ int global_setup = 0;
+
+ if (indirect_loaded <= 0) {
+  assert(indirect_loaded == 0);
+  assert(!indirect_loaded_cxts);
+  indirect_loaded_cxts = ptable_new();
+  global_setup         = 1;
+ }
+ ++indirect_loaded;
+ assert(indirect_loaded_cxts);
+ ptable_loaded_store(indirect_loaded_cxts, cxt, cxt);
+
+ return global_setup;
+}
+
+static int indirect_clear_loaded_locked(pTHX_ void *cxt) {
+#define indirect_clear_loaded_locked(C) indirect_clear_loaded_locked(aTHX_ (C))
+ int global_teardown = 0;
+
+ if (indirect_loaded > 1) {
+  assert(indirect_loaded_cxts);
+  ptable_loaded_delete(indirect_loaded_cxts, cxt);
+  --indirect_loaded;
+ } else if (indirect_loaded_cxts) {
+  assert(indirect_loaded == 1);
+  ptable_loaded_free(indirect_loaded_cxts);
+  indirect_loaded_cxts = NULL;
+  indirect_loaded      = 0;
+  global_teardown      = 1;
+ }
+
+ return global_teardown;
+}
+
+#else
+
+#define indirect_is_loaded(C)           (indirect_loaded > 0)
+#define indirect_set_loaded_locked(C)   ((indirect_loaded++ <= 0) ? 1 : 0)
+#define indirect_clear_loaded_locked(C) ((--indirect_loaded <= 0) ? 1 : 0)
+
+#endif
+
 /* ... Thread-safe hints ................................................... */
 
 #if I_WORKAROUND_REQUIRE_PROPAGATION
@@ -182,10 +337,10 @@ typedef SV indirect_hint_t;
  * thread cleanup. */
 
 typedef struct {
+ char   *buf;
  STRLEN  pos;
  STRLEN  size;
  STRLEN  len;
- char   *buf;
  line_t  line;
 } indirect_op_info_t;
 
@@ -212,76 +367,104 @@ typedef struct {
  tTHX    owner;
 #endif
  ptable *map;
+ SV     *global_code;
 } my_cxt_t;
 
 START_MY_CXT
 
 #if I_THREADSAFE
 
-STATIC SV *indirect_clone(pTHX_ SV *sv, tTHX owner) {
-#define indirect_clone(S, O) indirect_clone(aTHX_ (S), (O))
- CLONE_PARAMS  param;
- AV           *stashes = NULL;
- SV           *dupsv;
-
- if (SvTYPE(sv) == SVt_PVHV && HvNAME_get(sv))
-  stashes = newAV();
-
- param.stashes    = stashes;
- param.flags      = 0;
- param.proto_perl = owner;
-
- dupsv = sv_dup(sv, &param);
-
- if (stashes) {
-  av_undef(stashes);
-  SvREFCNT_dec(stashes);
- }
-
- return SvREFCNT_inc(dupsv);
-}
-
-STATIC void indirect_ptable_clone(pTHX_ ptable_ent *ent, void *ud_) {
- my_cxt_t        *ud = ud_;
- indirect_hint_t *h1 = ent->val;
- indirect_hint_t *h2;
+typedef struct {
+ ptable *tbl;
+#if I_HAS_PERL(5, 13, 2)
+ CLONE_PARAMS *params;
+#else
+ CLONE_PARAMS params;
+#endif
+} indirect_ptable_clone_ud;
+
+#if I_HAS_PERL(5, 13, 2)
+# define indirect_ptable_clone_ud_init(U, T, O) \
+   (U).tbl    = (T); \
+   (U).params = Perl_clone_params_new((O), aTHX)
+# define indirect_ptable_clone_ud_deinit(U) Perl_clone_params_del((U).params)
+# define indirect_dup_inc(S, U)             SvREFCNT_inc(sv_dup((S), (U)->params))
+#else
+# define indirect_ptable_clone_ud_init(U, T, O) \
+   (U).tbl               = (T);     \
+   (U).params.stashes    = newAV(); \
+   (U).params.flags      = 0;       \
+   (U).params.proto_perl = (O)
+# define indirect_ptable_clone_ud_deinit(U) SvREFCNT_dec((U).params.stashes)
+# define indirect_dup_inc(S, U)             SvREFCNT_inc(sv_dup((S), &((U)->params)))
+#endif
 
- if (ud->owner == aTHX)
-  return;
+static void indirect_ptable_clone(pTHX_ ptable_ent *ent, void *ud_) {
+ indirect_ptable_clone_ud *ud = ud_;
+ indirect_hint_t          *h1 = ent->val;
+ indirect_hint_t          *h2;
 
 #if I_HINT_STRUCT
 
- h2       = PerlMemShared_malloc(sizeof *h2);
- h2->code = indirect_clone(h1->code, ud->owner);
- SvREFCNT_inc(h2->code);
+ h2              = PerlMemShared_malloc(sizeof *h2);
+ h2->code        = indirect_dup_inc(h1->code, ud);
 #if I_WORKAROUND_REQUIRE_PROPAGATION
- h2->require_tag = PTR2IV(indirect_clone(INT2PTR(SV *, h1->require_tag),
-                                         ud->owner));
+ h2->require_tag = PTR2IV(indirect_dup_inc(INT2PTR(SV *, h1->require_tag), ud));
 #endif
 
 #else  /*  I_HINT_STRUCT */
 
- h2 = indirect_clone(h1, ud->owner);
- SvREFCNT_inc(h2);
+ h2 = indirect_dup_inc(h1, ud);
 
 #endif /* !I_HINT_STRUCT */
 
  ptable_hints_store(ud->tbl, ent->key, h2);
 }
 
-#include "reap.h"
-
-STATIC void indirect_thread_cleanup(pTHX_ void *ud) {
+static void indirect_thread_cleanup(pTHX_ void *ud) {
+ int global_teardown;
  dMY_CXT;
 
+ global_teardown = indirect_clear_loaded_locked(&MY_CXT);
+ assert(!global_teardown);
+
+ SvREFCNT_dec(MY_CXT.global_code);
+ MY_CXT.global_code = NULL;
+
  ptable_free(MY_CXT.map);
+ MY_CXT.map = NULL;
+
  ptable_hints_free(MY_CXT.tbl);
+ MY_CXT.tbl = NULL;
+}
+
+static int indirect_endav_free(pTHX_ SV *sv, MAGIC *mg) {
+ SAVEDESTRUCTOR_X(indirect_thread_cleanup, NULL);
+
+ return 0;
 }
 
+static MGVTBL indirect_endav_vtbl = {
+ 0,
+ 0,
+ 0,
+ 0,
+ indirect_endav_free
+#if MGf_COPY
+ , 0
+#endif
+#if MGf_DUP
+ , 0
+#endif
+#if MGf_LOCAL
+ , 0
+#endif
+};
+
 #endif /* I_THREADSAFE */
 
 #if I_WORKAROUND_REQUIRE_PROPAGATION
-STATIC IV indirect_require_tag(pTHX) {
+static IV indirect_require_tag(pTHX) {
 #define indirect_require_tag() indirect_require_tag(aTHX)
  const CV *cv, *outside;
 
@@ -326,16 +509,22 @@ get_enclosing_cv:
 }
 #endif /* I_WORKAROUND_REQUIRE_PROPAGATION */
 
-STATIC SV *indirect_tag(pTHX_ SV *value) {
+static SV *indirect_tag(pTHX_ SV *value) {
 #define indirect_tag(V) indirect_tag(aTHX_ (V))
  indirect_hint_t *h;
- SV *code = NULL;
+ SV              *code = NULL;
+#if I_THREADSAFE
+ dMY_CXT;
+
+ if (!MY_CXT.tbl)
+  return newSViv(0);
+#endif /* I_THREADSAFE */
 
  if (SvROK(value)) {
   value = SvRV(value);
   if (SvTYPE(value) >= SVt_PVCV) {
    code = value;
-   SvREFCNT_inc_simple_NN(code);
+   SvREFCNT_inc_simple_void_NN(code);
   }
  }
 
@@ -350,66 +539,94 @@ STATIC SV *indirect_tag(pTHX_ SV *value) {
 #endif /* !I_HINT_STRUCT */
 
 #if I_THREADSAFE
- {
-  dMY_CXT;
-  /* We only need for the key to be an unique tag for looking up the value later
-   * Allocated memory provides convenient unique identifiers, so that's why we
-   * use the hint as the key itself. */
-  ptable_hints_store(MY_CXT.tbl, h, h);
- }
+ /* We only need for the key to be an unique tag for looking up the value later
+  * Allocated memory provides convenient unique identifiers, so that's why we
+  * use the hint as the key itself. */
+ ptable_hints_store(MY_CXT.tbl, h, h);
 #endif /* I_THREADSAFE */
 
  return newSViv(PTR2IV(h));
 }
 
-STATIC SV *indirect_detag(pTHX_ const SV *hint) {
+static SV *indirect_detag(pTHX_ const SV *hint) {
 #define indirect_detag(H) indirect_detag(aTHX_ (H))
  indirect_hint_t *h;
+#if I_THREADSAFE || I_WORKAROUND_REQUIRE_PROPAGATION
+ dMY_CXT;
+#endif
 
- if (!(hint && SvIOK(hint)))
+#if I_THREADSAFE
+ if (!MY_CXT.tbl)
   return NULL;
+#endif /* I_THREADSAFE */
 
  h = INT2PTR(indirect_hint_t *, SvIVX(hint));
 #if I_THREADSAFE
- {
-  dMY_CXT;
-  h = ptable_fetch(MY_CXT.tbl, h);
- }
+ h = ptable_fetch(MY_CXT.tbl, h);
 #endif /* I_THREADSAFE */
 
 #if I_WORKAROUND_REQUIRE_PROPAGATION
  if (indirect_require_tag() != h->require_tag)
-  return NULL;
+  return MY_CXT.global_code;
 #endif /* I_WORKAROUND_REQUIRE_PROPAGATION */
 
  return I_HINT_CODE(h);
 }
 
-STATIC U32 indirect_hash = 0;
+static VOL U32 indirect_hash = 0;
 
-STATIC SV *indirect_hint(pTHX) {
+static SV *indirect_hint(pTHX) {
 #define indirect_hint() indirect_hint(aTHX)
- SV **val;
+ SV *hint = NULL;
 
  if (IN_PERL_RUNTIME)
   return NULL;
 
- val = hv_fetch(GvHV(PL_hintgv), __PACKAGE__, __PACKAGE_LEN__, indirect_hash);
- if (!val)
+#if I_HAS_PERL(5, 10, 0) || defined(PL_parser)
+ if (!PL_parser)
   return NULL;
+#endif
 
- return indirect_detag(*val);
+#ifdef cop_hints_fetch_pvn
+ hint = cop_hints_fetch_pvn(PL_curcop, __PACKAGE__, __PACKAGE_LEN__,
+                                                              indirect_hash, 0);
+#elif I_HAS_PERL(5, 9, 5)
+ hint = Perl_refcounted_he_fetch(aTHX_ PL_curcop->cop_hints_hash,
+                                       NULL,
+                                       __PACKAGE__, __PACKAGE_LEN__,
+                                       0,
+                                       indirect_hash);
+#else
+ {
+  SV **val = hv_fetch(GvHV(PL_hintgv), __PACKAGE__, __PACKAGE_LEN__, 0);
+  if (val)
+   hint = *val;
+ }
+#endif
+
+ if (hint && SvIOK(hint)) {
+  return indirect_detag(hint);
+ } else {
+  dMY_CXT;
+  if (indirect_is_loaded(&MY_CXT))
+   return MY_CXT.global_code;
+  else
+   return NULL;
+ }
 }
 
 /* ... op -> source position ............................................... */
 
-STATIC void indirect_map_store(pTHX_ const OP *o, STRLEN pos, SV *sv, line_t line) {
+static void indirect_map_store(pTHX_ const OP *o, STRLEN pos, SV *sv, line_t line) {
 #define indirect_map_store(O, P, N, L) indirect_map_store(aTHX_ (O), (P), (N), (L))
  indirect_op_info_t *oi;
  const char *s;
  STRLEN len;
  dMY_CXT;
 
+ /* No need to check for MY_CXT.map != NULL because this code path is always
+  * guarded by indirect_hint(). */
+
  if (!(oi = ptable_fetch(MY_CXT.map, o))) {
   Newx(oi, 1, indirect_op_info_t);
   ptable_store(MY_CXT.map, o, oi);
@@ -436,58 +653,70 @@ STATIC void indirect_map_store(pTHX_ const OP *o, STRLEN pos, SV *sv, line_t lin
  oi->line = line;
 }
 
-STATIC const indirect_op_info_t *indirect_map_fetch(pTHX_ const OP *o) {
+static const indirect_op_info_t *indirect_map_fetch(pTHX_ const OP *o) {
 #define indirect_map_fetch(O) indirect_map_fetch(aTHX_ (O))
  dMY_CXT;
 
+ /* No need to check for MY_CXT.map != NULL because this code path is always
+  * guarded by indirect_hint(). */
+
  return ptable_fetch(MY_CXT.map, o);
 }
 
-STATIC void indirect_map_delete(pTHX_ const OP *o) {
+static void indirect_map_delete(pTHX_ const OP *o) {
 #define indirect_map_delete(O) indirect_map_delete(aTHX_ (O))
  dMY_CXT;
 
- ptable_delete(MY_CXT.map, o);
+ if (indirect_is_loaded(&MY_CXT) && MY_CXT.map)
+  ptable_delete(MY_CXT.map, o);
 }
 
 /* --- Check functions ----------------------------------------------------- */
 
-STATIC int indirect_find(pTHX_ SV *sv, const char *s, STRLEN *pos) {
-#define indirect_find(N, S, P) indirect_find(aTHX_ (N), (S), (P))
- STRLEN len;
- const char *p, *r = SvPV_const(sv, len);
-
- if (len >= 1 && *r == '$') {
-  ++r;
-  --len;
-  s = strchr(s, '$');
-  if (!s)
+static int indirect_find(pTHX_ SV *name_sv, const char *line_bufptr, STRLEN *name_pos) {
+#define indirect_find(NSV, LBP, NP) indirect_find(aTHX_ (NSV), (LBP), (NP))
+ STRLEN      name_len, line_len;
+ const char *name, *name_end;
+ const char *line, *line_end;
+ const char *p;
+
+ line     = SvPV_const(PL_linestr, line_len);
+ line_end = line + line_len;
+
+ name = SvPV_const(name_sv, name_len);
+ if (name_len >= 1 && *name == '$') {
+  ++name;
+  --name_len;
+  while (line_bufptr < line_end && *line_bufptr != '$')
+   ++line_bufptr;
+  if (line_bufptr >= line_end)
    return 0;
  }
+ name_end = name + name_len;
 
- p = s;
+ p = line_bufptr;
  while (1) {
-  p = strstr(p, r);
+  p = ninstr(p, line_end, name, name_end);
   if (!p)
    return 0;
-  if (!isALNUM(p[len]))
+  if (!isALNUM(p[name_len]))
    break;
-  /* p points to a word that has r as prefix, skip the rest of the word */
-  p += len + 1;
+  /* p points to a word that has name as prefix, skip the rest of the word */
+  p += name_len + 1;
   while (isALNUM(*p))
    ++p;
  }
 
- *pos = p - SvPVX_const(PL_linestr);
+ *name_pos = p - line;
 
  return 1;
 }
 
 /* ... ck_const ............................................................ */
 
-STATIC OP *(*indirect_old_ck_const)(pTHX_ OP *) = 0;
+static OP *(*indirect_old_ck_const)(pTHX_ OP *) = 0;
 
-STATIC OP *indirect_ck_const(pTHX_ OP *o) {
+static OP *indirect_ck_const(pTHX_ OP *o) {
  o = indirect_old_ck_const(aTHX_ o);
 
  if (indirect_hint()) {
@@ -497,6 +726,26 @@ STATIC OP *indirect_ck_const(pTHX_ OP *o) {
    STRLEN pos;
 
    if (indirect_find(sv, PL_oldbufptr, &pos)) {
+    STRLEN len;
+
+    /* If the constant is equal to the current package name, try to look for
+     * a "__PACKAGE__" coming before what we got. We only need to check this
+     * when we already had a match because __PACKAGE__ can only appear in
+     * direct method calls ("new __PACKAGE__" is a syntax error). */
+    len = SvCUR(sv);
+    if (PL_curstash
+        && len == (STRLEN) HvNAMELEN_get(PL_curstash)
+        && memcmp(SvPVX(sv), HvNAME_get(PL_curstash), len) == 0) {
+     STRLEN pos_pkg;
+     SV    *pkg = sv_newmortal();
+     sv_setpvn(pkg, "__PACKAGE__", sizeof("__PACKAGE__")-1);
+
+     if (indirect_find(pkg, PL_oldbufptr, &pos_pkg) && pos_pkg < pos) {
+      sv  = pkg;
+      pos = pos_pkg;
+     }
+    }
+
     indirect_map_store(o, pos, sv, CopLINE(&PL_compiling));
     return o;
    }
@@ -509,9 +758,9 @@ STATIC OP *indirect_ck_const(pTHX_ OP *o) {
 
 /* ... ck_rv2sv ............................................................ */
 
-STATIC OP *(*indirect_old_ck_rv2sv)(pTHX_ OP *) = 0;
+static OP *(*indirect_old_ck_rv2sv)(pTHX_ OP *) = 0;
 
-STATIC OP *indirect_ck_rv2sv(pTHX_ OP *o) {
+static OP *indirect_ck_rv2sv(pTHX_ OP *o) {
  if (indirect_hint()) {
   OP *op = cUNOPo->op_first;
   SV *sv;
@@ -576,9 +825,9 @@ done:
 
 /* ... ck_padany ........................................................... */
 
-STATIC OP *(*indirect_old_ck_padany)(pTHX_ OP *) = 0;
+static OP *(*indirect_old_ck_padany)(pTHX_ OP *) = 0;
 
-STATIC OP *indirect_ck_padany(pTHX_ OP *o) {
+static OP *indirect_ck_padany(pTHX_ OP *o) {
  o = indirect_old_ck_padany(aTHX_ o);
 
  if (indirect_hint()) {
@@ -603,10 +852,10 @@ STATIC OP *indirect_ck_padany(pTHX_ OP *o) {
 
 /* ... ck_scope ............................................................ */
 
-STATIC OP *(*indirect_old_ck_scope)  (pTHX_ OP *) = 0;
-STATIC OP *(*indirect_old_ck_lineseq)(pTHX_ OP *) = 0;
+static OP *(*indirect_old_ck_scope)  (pTHX_ OP *) = 0;
+static OP *(*indirect_old_ck_lineseq)(pTHX_ OP *) = 0;
 
-STATIC OP *indirect_ck_scope(pTHX_ OP *o) {
+static OP *indirect_ck_scope(pTHX_ OP *o) {
  OP *(*old_ck)(pTHX_ OP *) = 0;
 
  switch (o->op_type) {
@@ -630,9 +879,9 @@ STATIC OP *indirect_ck_scope(pTHX_ OP *o) {
 
 /* ... ck_method ........................................................... */
 
-STATIC OP *(*indirect_old_ck_method)(pTHX_ OP *) = 0;
+static OP *(*indirect_old_ck_method)(pTHX_ OP *) = 0;
 
-STATIC OP *indirect_ck_method(pTHX_ OP *o) {
+static OP *indirect_ck_method(pTHX_ OP *o) {
  if (indirect_hint()) {
   OP *op = cUNOPo->op_first;
 
@@ -673,9 +922,9 @@ done:
 /* "use foo/no foo" compiles its call to import/unimport directly to a
  * method_named op. */
 
-STATIC OP *(*indirect_old_ck_method_named)(pTHX_ OP *) = 0;
+static OP *(*indirect_old_ck_method_named)(pTHX_ OP *) = 0;
 
-STATIC OP *indirect_ck_method_named(pTHX_ OP *o) {
+static OP *indirect_ck_method_named(pTHX_ OP *o) {
  if (indirect_hint()) {
   STRLEN pos;
   line_t line;
@@ -705,9 +954,9 @@ done:
 
 /* ... ck_entersub ......................................................... */
 
-STATIC OP *(*indirect_old_ck_entersub)(pTHX_ OP *) = 0;
+static OP *(*indirect_old_ck_entersub)(pTHX_ OP *) = 0;
 
-STATIC OP *indirect_ck_entersub(pTHX_ OP *o) {
+static OP *indirect_ck_entersub(pTHX_ OP *o) {
  SV *code = indirect_hint();
 
  o = indirect_old_ck_entersub(aTHX_ o);
@@ -724,7 +973,7 @@ STATIC OP *indirect_ck_entersub(pTHX_ OP *o) {
     goto done;
    oop = lop->op_first;
   } while (oop->op_type != OP_PUSHMARK);
-  oop = oop->op_sibling;
+  oop = OpSIBLING(oop);
   mop = lop->op_last;
 
   if (!oop)
@@ -757,7 +1006,8 @@ STATIC OP *indirect_ck_entersub(pTHX_ OP *o) {
   /* When positions are identical, the method and the object must have the
    * same name. But it also means that it is an indirect call, as "foo->foo"
    * results in different positions. */
-  if (moi->pos <= ooi->pos) {
+  if (   moi->line < ooi->line
+      || (moi->line == ooi->line && moi->pos <= ooi->pos)) {
    SV *file;
    dSP;
 
@@ -791,77 +1041,86 @@ done:
  return o;
 }
 
-STATIC U32 indirect_initialized = 0;
+/* --- Module setup/teardown ----------------------------------------------- */
 
-STATIC void indirect_teardown(pTHX_ void *root) {
- if (!indirect_initialized)
-  return;
+static void indirect_teardown(pTHX_ void *interp) {
+ dMY_CXT;
 
 #if I_MULTIPLICITY
- if (aTHX != root)
+ if (aTHX != interp)
   return;
 #endif
 
- {
-  dMY_CXT;
-  ptable_free(MY_CXT.map);
+ I_LOADED_LOCK;
+
+ if (indirect_clear_loaded_locked(&MY_CXT)) {
+  indirect_ck_restore(OP_CONST,   &indirect_old_ck_const);
+  indirect_ck_restore(OP_RV2SV,   &indirect_old_ck_rv2sv);
+  indirect_ck_restore(OP_PADANY,  &indirect_old_ck_padany);
+  indirect_ck_restore(OP_SCOPE,   &indirect_old_ck_scope);
+  indirect_ck_restore(OP_LINESEQ, &indirect_old_ck_lineseq);
+
+  indirect_ck_restore(OP_METHOD,       &indirect_old_ck_method);
+  indirect_ck_restore(OP_METHOD_NAMED, &indirect_old_ck_method_named);
+  indirect_ck_restore(OP_ENTERSUB,     &indirect_old_ck_entersub);
+ }
+
+ I_LOADED_UNLOCK;
+
+ SvREFCNT_dec(MY_CXT.global_code);
+ MY_CXT.global_code = NULL;
+
+ ptable_free(MY_CXT.map);
+ MY_CXT.map = NULL;
+
 #if I_THREADSAFE
-  ptable_hints_free(MY_CXT.tbl);
+ ptable_hints_free(MY_CXT.tbl);
+ MY_CXT.tbl = NULL;
 #endif
- }
 
- PL_check[OP_CONST]           = MEMBER_TO_FPTR(indirect_old_ck_const);
- indirect_old_ck_const        = 0;
- PL_check[OP_RV2SV]           = MEMBER_TO_FPTR(indirect_old_ck_rv2sv);
- indirect_old_ck_rv2sv        = 0;
- PL_check[OP_PADANY]          = MEMBER_TO_FPTR(indirect_old_ck_padany);
- indirect_old_ck_padany       = 0;
- PL_check[OP_SCOPE]           = MEMBER_TO_FPTR(indirect_old_ck_scope);
- indirect_old_ck_scope        = 0;
- PL_check[OP_LINESEQ]         = MEMBER_TO_FPTR(indirect_old_ck_lineseq);
- indirect_old_ck_lineseq      = 0;
-
- PL_check[OP_METHOD]          = MEMBER_TO_FPTR(indirect_old_ck_method);
- indirect_old_ck_method       = 0;
- PL_check[OP_METHOD_NAMED]    = MEMBER_TO_FPTR(indirect_old_ck_method_named);
- indirect_old_ck_method_named = 0;
- PL_check[OP_ENTERSUB]        = MEMBER_TO_FPTR(indirect_old_ck_entersub);
- indirect_old_ck_entersub     = 0;
-
- indirect_initialized = 0;
+ return;
 }
 
-STATIC void indirect_setup(pTHX) {
+static void indirect_setup(pTHX) {
 #define indirect_setup() indirect_setup(aTHX)
- if (indirect_initialized)
-  return;
+ MY_CXT_INIT; /* Takes/release PL_my_ctx_mutex */
+
+ I_LOADED_LOCK;
+
+ if (indirect_set_loaded_locked(&MY_CXT)) {
+  PERL_HASH(indirect_hash, __PACKAGE__, __PACKAGE_LEN__);
+
+  indirect_ck_replace(OP_CONST,   indirect_ck_const,  &indirect_old_ck_const);
+  indirect_ck_replace(OP_RV2SV,   indirect_ck_rv2sv,  &indirect_old_ck_rv2sv);
+  indirect_ck_replace(OP_PADANY,  indirect_ck_padany, &indirect_old_ck_padany);
+  indirect_ck_replace(OP_SCOPE,   indirect_ck_scope,  &indirect_old_ck_scope);
+  indirect_ck_replace(OP_LINESEQ, indirect_ck_scope,  &indirect_old_ck_lineseq);
+
+  indirect_ck_replace(OP_METHOD,       indirect_ck_method,
+                                      &indirect_old_ck_method);
+  indirect_ck_replace(OP_METHOD_NAMED, indirect_ck_method_named,
+                                      &indirect_old_ck_method_named);
+  indirect_ck_replace(OP_ENTERSUB,     indirect_ck_entersub,
+                                      &indirect_old_ck_entersub);
+ }
+
+ I_LOADED_UNLOCK;
 
  {
-  MY_CXT_INIT;
+  HV *stash;
+
+  stash = gv_stashpvn(__PACKAGE__, __PACKAGE_LEN__, 1);
+  newCONSTSUB(stash, "I_THREADSAFE", newSVuv(I_THREADSAFE));
+  newCONSTSUB(stash, "I_FORKSAFE",   newSVuv(I_FORKSAFE));
+
 #if I_THREADSAFE
-  MY_CXT.tbl   = ptable_new();
-  MY_CXT.owner = aTHX;
+  MY_CXT.tbl         = ptable_new();
+  MY_CXT.owner       = aTHX;
 #endif
-  MY_CXT.map   = ptable_new();
- }
 
- indirect_old_ck_const        = PL_check[OP_CONST];
- PL_check[OP_CONST]           = MEMBER_TO_FPTR(indirect_ck_const);
- indirect_old_ck_rv2sv        = PL_check[OP_RV2SV];
- PL_check[OP_RV2SV]           = MEMBER_TO_FPTR(indirect_ck_rv2sv);
- indirect_old_ck_padany       = PL_check[OP_PADANY];
- PL_check[OP_PADANY]          = MEMBER_TO_FPTR(indirect_ck_padany);
- indirect_old_ck_scope        = PL_check[OP_SCOPE];
- PL_check[OP_SCOPE]           = MEMBER_TO_FPTR(indirect_ck_scope);
- indirect_old_ck_lineseq      = PL_check[OP_LINESEQ];
- PL_check[OP_LINESEQ]         = MEMBER_TO_FPTR(indirect_ck_scope);
-
- indirect_old_ck_method       = PL_check[OP_METHOD];
- PL_check[OP_METHOD]          = MEMBER_TO_FPTR(indirect_ck_method);
- indirect_old_ck_method_named = PL_check[OP_METHOD_NAMED];
- PL_check[OP_METHOD_NAMED]    = MEMBER_TO_FPTR(indirect_ck_method_named);
- indirect_old_ck_entersub     = PL_check[OP_ENTERSUB];
- PL_check[OP_ENTERSUB]        = MEMBER_TO_FPTR(indirect_ck_entersub);
+  MY_CXT.map         = ptable_new();
+  MY_CXT.global_code = NULL;
+ }
 
 #if I_MULTIPLICITY
  call_atexit(indirect_teardown, aTHX);
@@ -869,11 +1128,9 @@ STATIC void indirect_setup(pTHX) {
  call_atexit(indirect_teardown, NULL);
 #endif
 
indirect_initialized = 1;
return;
 }
 
-STATIC U32 indirect_booted = 0;
-
 /* --- XS ------------------------------------------------------------------ */
 
 MODULE = indirect      PACKAGE = indirect
@@ -882,16 +1139,6 @@ PROTOTYPES: ENABLE
 
 BOOT:
 {
- if (!indirect_booted++) {
-  HV *stash;
-
-  PERL_HASH(indirect_hash, __PACKAGE__, __PACKAGE_LEN__);
-
-  stash = gv_stashpvn(__PACKAGE__, __PACKAGE_LEN__, 1);
-  newCONSTSUB(stash, "I_THREADSAFE", newSVuv(I_THREADSAFE));
-  newCONSTSUB(stash, "I_FORKSAFE",   newSVuv(I_FORKSAFE));
- }
-
  indirect_setup();
 }
 
@@ -902,21 +1149,49 @@ CLONE(...)
 PROTOTYPE: DISABLE
 PREINIT:
  ptable *t;
+ SV     *global_code_dup;
+ GV     *gv;
 PPCODE:
  {
-  my_cxt_t ud;
+  indirect_ptable_clone_ud ud;
   dMY_CXT;
-  ud.tbl   = t = ptable_new();
-  ud.owner = MY_CXT.owner;
+  t = ptable_new();
+  indirect_ptable_clone_ud_init(ud, t, MY_CXT.owner);
   ptable_walk(MY_CXT.tbl, indirect_ptable_clone, &ud);
+  global_code_dup = indirect_dup_inc(MY_CXT.global_code, &ud);
+  indirect_ptable_clone_ud_deinit(ud);
  }
  {
   MY_CXT_CLONE;
-  MY_CXT.map   = ptable_new();
-  MY_CXT.tbl   = t;
-  MY_CXT.owner = aTHX;
+  MY_CXT.map         = ptable_new();
+  MY_CXT.tbl         = t;
+  MY_CXT.owner       = aTHX;
+  MY_CXT.global_code = global_code_dup;
+  {
+   int global_setup;
+   I_LOADED_LOCK;
+   global_setup = indirect_set_loaded_locked(&MY_CXT);
+   assert(!global_setup);
+   I_LOADED_UNLOCK;
+  }
+ }
+ gv = gv_fetchpv(__PACKAGE__ "::_THREAD_CLEANUP", 0, SVt_PVCV);
+ if (gv) {
+  CV *cv = GvCV(gv);
+  if (!PL_endav)
+   PL_endav = newAV();
+  SvREFCNT_inc(cv);
+  if (!av_store(PL_endav, av_len(PL_endav) + 1, (SV *) cv))
+   SvREFCNT_dec(cv);
+  sv_magicext((SV *) PL_endav, NULL, PERL_MAGIC_ext, &indirect_endav_vtbl, NULL, 0);
  }
- reap(3, indirect_thread_cleanup, NULL);
+ XSRETURN(0);
+
+void
+_THREAD_CLEANUP(...)
+PROTOTYPE: DISABLE
+PPCODE:
+ indirect_thread_cleanup(aTHX_ NULL);
  XSRETURN(0);
 
 #endif
@@ -928,3 +1203,18 @@ CODE:
  RETVAL = indirect_tag(value);
 OUTPUT:
  RETVAL
+
+void
+_global(SV *code)
+PROTOTYPE: $
+PPCODE:
+ if (!SvOK(code))
+  code = NULL;
+ else if (SvROK(code))
+  code = SvRV(code);
+ {
+  dMY_CXT;
+  SvREFCNT_dec(MY_CXT.global_code);
+  MY_CXT.global_code = SvREFCNT_inc(code);
+ }
+ XSRETURN(0);