+STATIC void vmg_mgwiz_free(pTHX_ MGWIZ *w) {
+#define vmg_mgwiz_free(W) vmg_mgwiz_free(aTHX_ (W))
+ if (!w)
+ return;
+
+ /* We reach this point in dirty state when ptable_free() is called from the
+ * atexit cleanup callback, and that the global table still holds a live
+ * wizard. This happens before all the SV bodies are freed, so all the wizard
+ * callbacks are still alive (as they are referenced by the undead wizard).
+ * Hence it is safe to decrement their refcount. Later on, the wizard free
+ * callback itself will trigger when the wizard body is reaped, but it will
+ * be skipped as it guards against dirty state - which is good since nothing
+ * has to be done anymore at that point. */
+
+ SvREFCNT_dec(w->cb_data);
+ SvREFCNT_dec(w->cb_get);
+ SvREFCNT_dec(w->cb_set);
+ SvREFCNT_dec(w->cb_len);
+ SvREFCNT_dec(w->cb_clear);
+ SvREFCNT_dec(w->cb_free);
+#if MGf_COPY
+ SvREFCNT_dec(w->cb_copy);
+#endif /* MGf_COPY */
+#if 0 /* MGf_DUP */
+ SvREFCNT_dec(w->cb_dup);
+#endif /* MGf_DUP */
+#if MGf_LOCAL
+ SvREFCNT_dec(w->cb_local);
+#endif /* MGf_LOCAL */
+#if VMG_UVAR
+ SvREFCNT_dec(w->cb_fetch);
+ SvREFCNT_dec(w->cb_store);
+ SvREFCNT_dec(w->cb_exists);
+ SvREFCNT_dec(w->cb_delete);
+#endif /* VMG_UVAR */
+
+ Safefree(w->vtbl);
+ Safefree(w);
+
+ return;
+}
+
+#if VMG_THREADSAFE
+
+#define VMG_CLONE_CB(N) \
+ z->cb_ ## N = (w->cb_ ## N) ? vmg_clone(w->cb_ ## N, w->owner) \
+ : NULL;
+
+STATIC MGWIZ *vmg_mgwiz_clone(pTHX_ const MGWIZ *w) {
+#define vmg_mgwiz_clone(W) vmg_mgwiz_clone(aTHX_ (W))
+ MGVTBL *t;
+ MGWIZ *z;
+
+ if (!w)
+ return NULL;
+
+ Newx(t, 1, MGVTBL);
+ Copy(w->vtbl, t, 1, MGVTBL);
+
+ Newx(z, 1, MGWIZ);
+
+ z->vtbl = t;
+ z->uvar = w->uvar;
+ z->opinfo = w->opinfo;
+
+ VMG_CLONE_CB(data);
+ VMG_CLONE_CB(get);
+ VMG_CLONE_CB(set);
+ VMG_CLONE_CB(len);
+ VMG_CLONE_CB(clear);
+ VMG_CLONE_CB(free);
+#if MGf_COPY
+ VMG_CLONE_CB(copy);
+#endif /* MGf_COPY */
+#if MGf_DUP
+ VMG_CLONE_CB(dup);
+#endif /* MGf_DUP */
+#if MGf_LOCAL
+ VMG_CLONE_CB(local);
+#endif /* MGf_LOCAL */
+#if VMG_UVAR
+ VMG_CLONE_CB(fetch);
+ VMG_CLONE_CB(store);
+ VMG_CLONE_CB(exists);
+ VMG_CLONE_CB(delete);
+#endif /* VMG_UVAR */
+
+ z->owner = aTHX;
+
+ return z;
+}
+
+#endif /* VMG_THREADSAFE */
+
+/* --- Context-safe global data -------------------------------------------- */
+
+#if VMG_THREADSAFE
+
+#define PTABLE_NAME ptable
+#define PTABLE_VAL_FREE(V) vmg_mgwiz_free(V)
+
+#define pPTBL pTHX
+#define pPTBL_ pTHX_
+#define aPTBL aTHX
+#define aPTBL_ aTHX_
+
+#include "ptable.h"
+
+#define ptable_store(T, K, V) ptable_store(aTHX_ (T), (K), (V))
+#define ptable_clear(T) ptable_clear(aTHX_ (T))
+#define ptable_free(T) ptable_free(aTHX_ (T))
+
+#endif /* VMG_THREADSAFE */
+
+#define MY_CXT_KEY __PACKAGE__ "::_guts" XS_VERSION
+
+typedef struct {
+#if VMG_THREADSAFE
+ ptable *wizards;
+ tTHX owner;
+#endif
+ HV *b__op_stashes[OPc_MAX];
+} my_cxt_t;
+
+START_MY_CXT
+
+#if VMG_THREADSAFE
+
+STATIC void vmg_ptable_clone(pTHX_ ptable_ent *ent, void *ud_) {
+ my_cxt_t *ud = ud_;
+ MGWIZ *w;
+
+ if (ud->owner == aTHX)
+ return;
+
+ w = vmg_mgwiz_clone(ent->val);
+ if (w)
+ ptable_store(ud->wizards, ent->key, w);
+}
+
+#endif /* VMG_THREADSAFE */
+
+/* --- Wizard objects ------------------------------------------------------ */
+
+STATIC int vmg_wizard_free(pTHX_ SV *sv, MAGIC *mg);
+
+STATIC MGVTBL vmg_wizard_vtbl = {
+ NULL, /* get */
+ NULL, /* set */
+ NULL, /* len */
+ NULL, /* clear */
+ vmg_wizard_free, /* free */
+#if MGf_COPY
+ NULL, /* copy */
+#endif /* MGf_COPY */
+#if MGf_DUP
+ NULL, /* dup */
+#endif /* MGf_DUP */
+#if MGf_LOCAL
+ NULL, /* local */
+#endif /* MGf_LOCAL */
+};
+
+/* ... Wizard constructor .................................................. */
+
+STATIC SV *vmg_wizard_new(pTHX_ const MGWIZ *w) {
+#define vmg_wizard_new(W) vmg_wizard_new(aTHX_ (W))
+ SV *wiz = newSVuv(PTR2IV(w));
+
+ if (w) {
+ MAGIC *mg = sv_magicext(wiz, NULL, PERL_MAGIC_ext, &vmg_wizard_vtbl, NULL, 0);
+ mg->mg_private = SIG_WZO;
+ }
+ SvREADONLY_on(wiz);
+
+ return wiz;
+}
+
+STATIC const SV *vmg_wizard_validate(pTHX_ const SV *wiz) {
+#define vmg_wizard_validate(W) vmg_wizard_validate(aTHX_ (W))
+ if (SvROK(wiz)) {
+ wiz = SvRV_const(wiz);
+ if (SvIOK(wiz))
+ return wiz;
+ }
+
+ croak(vmg_invalid_wiz);
+ /* Not reached */
+ return NULL;
+}
+
+#define vmg_wizard_id(W) SvIVX((const SV *) (W))
+#define vmg_wizard_main_mgwiz(W) INT2PTR(const MGWIZ *, vmg_wizard_id(W))
+
+/* ... Wizard destructor ................................................... */
+
+STATIC int vmg_wizard_free(pTHX_ SV *sv, MAGIC *mg) {
+ MGWIZ *w;
+
+ if (PL_dirty) /* During global destruction, the context is already freed */
+ return 0;
+
+ w = (MGWIZ *) vmg_wizard_main_mgwiz(sv);
+
+#if VMG_THREADSAFE
+ {
+ dMY_CXT;
+ ptable_store(MY_CXT.wizards, w, NULL);
+ }
+#else /* VMG_THREADSAFE */
+ vmg_mgwiz_free(w);
+#endif /* !VMG_THREADSAFE */
+
+ return 0;
+}
+
+#if VMG_THREADSAFE
+
+STATIC const MGWIZ *vmg_wizard_mgwiz(pTHX_ const SV *wiz) {
+#define vmg_wizard_mgwiz(W) vmg_wizard_mgwiz(aTHX_ ((const SV *) (W)))
+ const MGWIZ *w;
+
+ w = vmg_wizard_main_mgwiz(wiz);
+ if (w->owner == aTHX)
+ return w;
+
+ {
+ dMY_CXT;
+ return ptable_fetch(MY_CXT.wizards, w);
+ }
+}
+
+#else /* VMG_THREADSAFE */
+
+#define vmg_wizard_mgwiz(W) vmg_wizard_main_mgwiz(W)
+
+#endif /* !VMG_THREADSAFE */
+
+/* --- User-level functions implementation --------------------------------- */
+
+STATIC const MAGIC *vmg_find(const SV *sv, const SV *wiz) {
+ const MAGIC *mg, *moremagic;
+ IV wid;
+
+ if (SvTYPE(sv) < SVt_PVMG)
+ return NULL;
+
+ wid = vmg_wizard_id(wiz);
+ for (mg = SvMAGIC(sv); mg; mg = moremagic) {
+ moremagic = mg->mg_moremagic;
+ if (mg->mg_type == PERL_MAGIC_ext && mg->mg_private == SIG_WIZ) {
+ IV zid = vmg_wizard_id(mg->mg_ptr);
+ if (zid == wid)
+ return mg;
+ }
+ }
+
+ return NULL;
+}