+ vmg_mg_magical(sv);
+
+ return 1;
+}
+
+/* ... OP info ............................................................. */
+
+#define VMG_OP_INFO_NAME 1
+#define VMG_OP_INFO_OBJECT 2
+
+#if XSH_THREADSAFE
+static perl_mutex vmg_op_name_init_mutex;
+#endif
+
+static U32 vmg_op_name_init = 0;
+static unsigned char vmg_op_name_len[MAXO] = { 0 };
+
+static void vmg_op_info_init(pTHX_ unsigned int opinfo) {
+#define vmg_op_info_init(W) vmg_op_info_init(aTHX_ (W))
+ switch (opinfo) {
+ case VMG_OP_INFO_NAME:
+ XSH_LOCK(&vmg_op_name_init_mutex);
+ if (!vmg_op_name_init) {
+ OPCODE t;
+ for (t = 0; t < OP_max; ++t)
+ vmg_op_name_len[t] = strlen(PL_op_name[t]);
+ vmg_op_name_init = 1;
+ }
+ XSH_UNLOCK(&vmg_op_name_init_mutex);
+ break;
+ case VMG_OP_INFO_OBJECT: {
+ dXSH_CXT;
+ if (!XSH_CXT.b__op_stashes[0]) {
+ int c;
+ require_pv("B.pm");
+ for (c = OPc_NULL; c < OPc_MAX; ++c)
+ XSH_CXT.b__op_stashes[c] = gv_stashpv(vmg_opclassnames[c], 1);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static SV *vmg_op_info(pTHX_ unsigned int opinfo) {
+#define vmg_op_info(W) vmg_op_info(aTHX_ (W))
+ if (!PL_op)
+ return &PL_sv_undef;
+
+ switch (opinfo) {
+ case VMG_OP_INFO_NAME: {
+ const char *name;
+ STRLEN name_len;
+ OPCODE t = PL_op->op_type;
+ name = OP_NAME(PL_op);
+ name_len = (t == OP_CUSTOM) ? strlen(name) : vmg_op_name_len[t];
+ return sv_2mortal(newSVpvn(name, name_len));
+ }
+ case VMG_OP_INFO_OBJECT: {
+ dXSH_CXT;
+ return sv_bless(sv_2mortal(newRV_noinc(newSViv(PTR2IV(PL_op)))),
+ XSH_CXT.b__op_stashes[vmg_opclass(PL_op)]);
+ }
+ default:
+ break;
+ }
+
+ return &PL_sv_undef;
+}
+
+/* --- svt callbacks ------------------------------------------------------- */
+
+#define VMG_CB_CALL_ARGS_MASK 15
+#define VMG_CB_CALL_ARGS_SHIFT 4
+#define VMG_CB_CALL_OPINFO (VMG_OP_INFO_NAME|VMG_OP_INFO_OBJECT) /* 1|2 */
+#define VMG_CB_CALL_GUARD 4
+
+static int vmg_dispell_guard_oncroak(pTHX_ void *ud) {
+ dXSH_CXT;
+
+ XSH_CXT.depth--;
+
+ /* If we're at the upmost magic call and we're about to die, we can just free
+ * the tokens right now, since we will jump past the problematic part of our
+ * caller. */
+ if (XSH_CXT.depth == 0 && XSH_CXT.freed_tokens) {
+ vmg_magic_chain_free(XSH_CXT.freed_tokens, NULL);
+ XSH_CXT.freed_tokens = NULL;
+ }
+
+ return 1;
+}
+
+static int vmg_dispell_guard_free(pTHX_ SV *sv, MAGIC *mg) {
+ vmg_magic_chain_free((MAGIC *) mg->mg_ptr, NULL);
+
+ return 0;
+}
+
+#if XSH_THREADSAFE
+
+static int vmg_dispell_guard_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *params) {
+ /* The freed magic tokens aren't cloned by perl because it cannot reach them
+ * (they have been detached from their parent SV when they were enqueued).
+ * Hence there's nothing to purge in the new thread. */
+ mg->mg_ptr = NULL;
+
+ return 0;
+}
+
+#endif /* XSH_THREADSAFE */
+
+static MGVTBL vmg_dispell_guard_vtbl = {
+ NULL, /* get */
+ NULL, /* set */
+ NULL, /* len */
+ NULL, /* clear */
+ vmg_dispell_guard_free, /* free */
+ NULL, /* copy */
+#if XSH_THREADSAFE
+ vmg_dispell_guard_dup, /* dup */
+#else
+ NULL, /* dup */
+#endif
+#if MGf_LOCAL
+ NULL, /* local */
+#endif /* MGf_LOCAL */
+};
+
+static SV *vmg_dispell_guard_new(pTHX_ MAGIC *root) {
+#define vmg_dispell_guard_new(R) vmg_dispell_guard_new(aTHX_ (R))
+ SV *guard;
+
+ guard = sv_newmortal();
+ vmg_sv_magicext(guard, NULL, &vmg_dispell_guard_vtbl, root, 0);
+
+ return guard;
+}
+
+static int vmg_cb_call(pTHX_ SV *cb, unsigned int flags, SV *sv, ...) {
+ va_list ap;
+ int ret = 0;
+ unsigned int i, args, opinfo;
+ MAGIC **chain = NULL;
+ SV *svr;
+
+ dSP;
+
+ args = flags & VMG_CB_CALL_ARGS_MASK;
+ flags >>= VMG_CB_CALL_ARGS_SHIFT;
+ opinfo = flags & VMG_CB_CALL_OPINFO;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHSTACKi(PERLSI_MAGIC);
+
+ PUSHMARK(SP);
+ EXTEND(SP, args + 1);
+ PUSHs(sv_2mortal(newRV_inc(sv)));
+ va_start(ap, sv);
+ for (i = 0; i < args; ++i) {
+ SV *sva = va_arg(ap, SV *);
+ PUSHs(sva ? sva : &PL_sv_undef);
+ }
+ va_end(ap);
+ if (opinfo)
+ XPUSHs(vmg_op_info(opinfo));
+ PUTBACK;
+
+ if (flags & VMG_CB_CALL_GUARD) {
+ dXSH_CXT;
+ XSH_CXT.depth++;
+ vmg_call_sv(cb, G_SCALAR, vmg_dispell_guard_oncroak, NULL);
+ XSH_CXT.depth--;
+ if (XSH_CXT.depth == 0 && XSH_CXT.freed_tokens)
+ chain = &XSH_CXT.freed_tokens;
+ } else {
+ vmg_call_sv(cb, G_SCALAR, 0, NULL);
+ }
+
+ SPAGAIN;
+ svr = POPs;
+ if (SvOK(svr))
+ ret = (int) SvIV(svr);
+ if (SvROK(svr))
+ SvREFCNT_inc(svr);
+ else
+ svr = NULL;
+ PUTBACK;
+
+ POPSTACK;
+
+ FREETMPS;
+ LEAVE;
+
+ if (svr && !SvTEMP(svr))
+ sv_2mortal(svr);
+
+ if (chain) {
+ vmg_dispell_guard_new(*chain);
+ *chain = NULL;
+ }
+
+ return ret;
+}
+
+#define VMG_CB_FLAGS(OI, A) \
+ ((((unsigned int) (OI)) << VMG_CB_CALL_ARGS_SHIFT) | (A))
+
+#define vmg_cb_call1(I, OI, S, A1) \
+ vmg_cb_call(aTHX_ (I), VMG_CB_FLAGS((OI), 1), (S), (A1))
+#define vmg_cb_call2(I, OI, S, A1, A2) \
+ vmg_cb_call(aTHX_ (I), VMG_CB_FLAGS((OI), 2), (S), (A1), (A2))
+#define vmg_cb_call3(I, OI, S, A1, A2, A3) \
+ vmg_cb_call(aTHX_ (I), VMG_CB_FLAGS((OI), 3), (S), (A1), (A2), (A3))
+
+/* ... Default no-op magic callback ........................................ */
+
+static int vmg_svt_default_noop(pTHX_ SV *sv, MAGIC *mg) {
+ return 0;
+}
+
+/* ... get magic ........................................................... */
+
+static int vmg_svt_get(pTHX_ SV *sv, MAGIC *mg) {
+ const vmg_wizard *w = vmg_wizard_from_mg_nocheck(mg);
+
+ return vmg_cb_call1(w->cb_get, w->opinfo, sv, mg->mg_obj);
+}
+
+#define vmg_svt_get_noop vmg_svt_default_noop
+
+/* ... set magic ........................................................... */
+
+static int vmg_svt_set(pTHX_ SV *sv, MAGIC *mg) {
+ const vmg_wizard *w = vmg_wizard_from_mg_nocheck(mg);
+
+ return vmg_cb_call1(w->cb_set, w->opinfo, sv, mg->mg_obj);