# define SU_HAS_NEW_CXT
#endif
-
#ifndef dVAR
# define dVAR dNOOP
#endif
# define NEGATIVE_INDICES_VAR "NEGATIVE_INDICES"
#endif
-/* CX_ARGARRAY(cx): the AV at pad[0] of the CV associated with CXt_SUB
- * context cx */
-
-#if XSH_HAS_PERL(5, 23, 8)
-# define CX_ARGARRAY(cx) \
- ((AV*)(AvARRAY(MUTABLE_AV( \
- PadlistARRAY(CvPADLIST(cx->blk_sub.cv))[ \
- CvDEPTH(cx->blk_sub.cv)]))[0]))
-/* XXX is the new def ok to use in lvalue cxt? Formerly it assigned to
- * blk_sub.argarray, now to pad[0]. Does this matter?
- */
-# define CX_ARGARRAY_set(cx,ary) \
- (AvARRAY(MUTABLE_AV( \
- PadlistARRAY(CvPADLIST(cx->blk_sub.cv))[ \
- CvDEPTH(cx->blk_sub.cv)]))[0] = (SV*)(ary))
-#else
-# define CX_ARGARRAY(cx) (cx->blk_sub.argarray)
-# define CX_ARGARRAY_set(cx,ary) (cx->blk_sub.argarray = (ary))
-#endif
-
-
/* --- Error messages ------------------------------------------------------ */
static const char su_stack_smash[] = "Cannot target a scope outside of the current stack";
I32 cxix;
- I32 target_depth;
- CV *target;
-
CV *callback;
CV *renamed;
+#ifdef SU_HAS_NEW_CXT
+ U8 *cxtypes; /* array of saved context types */
+ I32 gap; /* how many contexts have temporarily CXt_NULLed out*/
+ AV* argarray; /* the PL_curpad[0] of the uplevel sub */
+#else
+ I32 target_depth;
+ CV *target;
PERL_SI *si;
PERL_SI *old_curstackinfo;
AV *old_mainstack;
+ OP *old_op;
+ bool old_catch;
+ bool died;
+#endif
COP *old_curcop;
- OP *old_op;
#if SU_UPLEVEL_HIJACKS_RUNOPS
runops_proc_t old_runops;
#endif
- bool old_catch;
-
- bool died;
} su_uplevel_ud;
+#ifdef SU_HAS_NEW_CXT
+/* used to flag a context stack entry whose type has been temporarily
+ * set to CXt_NULL. It relies on perl not using this value for real
+ * CXt_NULL entries.
+ */
+# define CXp_SU_UPLEVEL_NULLED 0x20
+#endif
+
static su_uplevel_ud *su_uplevel_ud_new(pTHX) {
#define su_uplevel_ud_new() su_uplevel_ud_new(aTHX)
su_uplevel_ud *sud;
sud->tmp_uid_storage.used = 0;
sud->tmp_uid_storage.alloc = 0;
+#ifndef SU_HAS_NEW_CXT
Newx(si, 1, PERL_SI);
si->si_stack = newAV();
AvREAL_off(si->si_stack);
si->si_cxmax = -1;
sud->si = si;
+#endif
return sud;
}
static void su_uplevel_ud_delete(pTHX_ su_uplevel_ud *sud) {
#define su_uplevel_ud_delete(S) su_uplevel_ud_delete(aTHX_ (S))
+
+#ifndef SU_HAS_NEW_CXT
PERL_SI *si = sud->si;
Safefree(si->si_cxstack);
SvREFCNT_dec(si->si_stack);
Safefree(si);
+#endif
Safefree(sud->tmp_uid_storage.map);
new_cxt->uplevel_storage.top = NULL;
new_cxt->uplevel_storage.root = NULL;
new_cxt->uplevel_storage.count = 0;
-
new_cxt->uid_storage.map = NULL;
new_cxt->uid_storage.used = 0;
new_cxt->uid_storage.alloc = 0;
# define SU_SAVE_HELEM_OR_HDELETE_SIZE SU_SAVE_HELEM_SIZE
#endif
-
#ifndef SvCANEXISTDELETE
# define SvCANEXISTDELETE(sv) \
(!SvRMAGICAL(sv) \
/* --- Actions ------------------------------------------------------------- */
typedef struct {
- I32 orig_ix; /* original savestack_ix */
- I32 offset; /* how much we bumped this savestack index */
+ I32 orig_ix; /* original savestack_ix */
+ I32 offset; /* how much we bumped this savestack index */
} su_ud_origin_elem;
typedef struct {
- U8 type;
- U8 private;
+ U8 type;
+ U8 private;
/* spare */
- I32 depth;
+ I32 depth;
su_ud_origin_elem *origin;
} su_ud_common;
-
#define SU_UD_TYPE(U) (((su_ud_common *) (U))->type)
#define SU_UD_PRIVATE(U) (((su_ud_common *) (U))->private)
#define SU_UD_DEPTH(U) (((su_ud_common *) (U))->depth)
/* --- Pop a context back -------------------------------------------------- */
#ifdef DEBUGGING
-# define SU_CXNAME(C) PL_block_type[CxTYPE(C)]
+# define SU_CX_TYPENAME(T) PL_block_type[(T)]
#else
# if XSH_HAS_PERL(5, 23, 8)
static const char *su_block_type[] = {
"FORMAT"
};
# endif
-# define SU_CXNAME(C) su_block_type[CxTYPE(C)]
+# define SU_CX_TYPENAME(T) su_block_type[(T)]
#endif
+#define SU_CXNAME(C) SU_CX_TYPENAME(CxTYPE(C))
+
/* for debugging. These indicate how many ENTERs each context type
* does before the PUSHBLOCK */
# endif
};
-
-
/* push at least 'size' slots worth of padding onto the savestack */
static void su_ss_push_padding(pTHX_ void *ud, I32 size) {
save_alloc((size - SU_SAVE_ALLOC_SIZE)*sizeof(*PL_savestack), 0);
}
-
static void su_pop(pTHX_ void *ud);
-
-
/* push an su_pop destructor onto the savestack with suitable padding.
* first indicates that this is the first push of a destructor */
PL_savestack_ix <= origin[depth+1].orig_ix + origin[depth+1].offset);
}
-
/* this is called during each leave_scope() via SAVEDESTRUCTOR_X */
static void su_pop(pTHX_ void *ud) {
XSH_D(su_debug_log("%p: end pop: ss_ix=%d\n", ud, PL_savestack_ix));
}
-
/* --- Initialize the stack and the action userdata ------------------------ */
static void su_init(pTHX_ void *ud, I32 cxix, I32 size) {
*
* The passed cxix argument represents the scope we wish to inject into;
* we have to adjust all the savestack frame boundaries above (but not
- * including) that context.
+ * including) that context.
*/
Newx(origin, depth, su_ud_origin_elem);
su_ss_push_destructor(aTHX_ ud, depth-1, 1);
}
-
/* --- Unwind stack -------------------------------------------------------- */
static void su_unwind(pTHX_ void *ud_) {
dounwind(cxix);
mark = PL_markstack[cxstack[cxix].blk_oldmarksp];
- *PL_markstack_ptr = PL_stack_sp - PL_stack_base - items;
+ PUSHMARK(PL_stack_sp - items);
XSH_D({
I32 gimme = GIMME_V;
return 0;
}
-#if SU_UPLEVEL_HIJACKS_RUNOPS
+#if !defined(SU_HAS_NEW_CXT) && SU_UPLEVEL_HIJACKS_RUNOPS
static int su_uplevel_goto_runops(pTHX) {
#define su_uplevel_goto_runops() su_uplevel_goto_runops(aTHX)
switch (CxTYPE(cx)) {
case CXt_SUB:
if (CxHASARGS(cx)) {
- argarray = CX_ARGARRAY(cx);
+ argarray = cx->blk_sub.argarray;
goto done;
}
break;
#define su_at_underscore(C) PadARRAY(PadlistARRAY(CvPADLIST(C))[CvDEPTH(C)])[0]
-static void su_uplevel_restore(pTHX_ void *sus_) {
+#ifdef SU_HAS_NEW_CXT
+
+static void su_uplevel_restore_new(pTHX_ void *sus_) {
+ su_uplevel_ud *sud = sus_;
+ PERL_CONTEXT *cx;
+ I32 i;
+ U8 *saved_cxtypes = sud->cxtypes;
+
+ for (i = 0; i < sud->gap; i++) {
+ PERL_CONTEXT *cx = cxstack + sud->cxix + i;
+ XSH_D(su_debug_log("su_uplevel_restore: i=%d cxix=%d type %s => %s\n",
+ i, cx-cxstack, SU_CX_TYPENAME(CxTYPE(cx)),
+ SU_CX_TYPENAME(saved_cxtypes[i] & CXTYPEMASK)));
+ cx->cx_type = saved_cxtypes[i];
+ }
+ Safefree(saved_cxtypes);
+
+ /* renamed is a copy of callback, but they share the same CvPADLIST.
+ * At this point any calls to renamed should have exited so that its
+ * depth is back to that of of callback. At this point its safe to free
+ * renamed, then undo the extra ref count that was ensuring that callback
+ * remains alive
+ */
+ assert(sud->renamed);
+ assert(sud->callback);
+
+ CvDEPTH(sud->callback)--;
+ assert(CvDEPTH(sud->callback) == CvDEPTH(sud->renamed));
+ if (!CvISXSUB(sud->renamed)) {
+ CvDEPTH(sud->renamed) = 0;
+ CvPADLIST(sud->renamed) = NULL;
+ }
+ SvREFCNT_dec(sud->renamed);
+ SvREFCNT_dec(sud->callback);
+
+ SU_UPLEVEL_RESTORE(curcop);
+
+ su_uplevel_storage_delete(sud);
+
+ return;
+}
+
+#else
+
+/* 5.23.7 and earlier */
+
+static void su_uplevel_restore_old(pTHX_ void *sus_) {
su_uplevel_ud *sud = sus_;
PERL_SI *cur = sud->old_curstackinfo;
PERL_SI *si = sud->si;
* reached without a goto() happening, and the old argarray member is
* actually our fake argarray. Destroy it properly in that case. */
if (cx->blk_sub.cv == sud->renamed) {
- SvREFCNT_dec(CX_ARGARRAY(cx));
- CX_ARGARRAY_set(cx, argarray);
+ SvREFCNT_dec(cx->blk_sub.argarray);
+ cx->blk_sub.argarray = argarray;
}
CvDEPTH(sud->callback)--;
return;
}
+#endif
+
static CV *su_cv_clone(pTHX_ CV *proto, GV *gv) {
#define su_cv_clone(P, G) su_cv_clone(aTHX_ (P), (G))
dVAR;
return cv;
}
-static I32 su_uplevel(pTHX_ CV *callback, I32 cxix, I32 args) {
-#define su_uplevel(C, I, A) su_uplevel(aTHX_ (C), (I), (A))
+#ifdef SU_HAS_NEW_CXT
+
+/* this one-shot runops "loop" is designed to be called just before
+ * execution of the first op following an uplevel()'s entersub. It gets a
+ * chance to fix up the args as seen by caller(), before immediately
+ * falling through to the previous runops loop. Note that pp_entersub is
+ * called directly by call_sv() rather than being called from a runops
+ * loop.
+ */
+
+static int su_uplevel_runops_hook_entersub(pTHX) {
+ OP *op = PL_op;
+ dXSH_CXT;
+ su_uplevel_ud *sud = XSH_CXT.uplevel_storage.top;
+
+ /* Create a new array containing a copy of the original sub's call args,
+ * then stick it in PL_curpad[0] of the current running sub so that
+ * thay will be seen by caller().
+ */
+ assert(sud);
+ if (sud->argarray) {
+ AV *av = newAV();
+ AvREAL_off(av);
+ AvREIFY_on(av);
+ av_extend(av, AvMAX(sud->argarray));
+ AvFILLp(av) = AvFILLp(sud->argarray);
+ Copy(AvARRAY(sud->argarray), AvARRAY(av), AvFILLp(av) + 1, SV *);
+
+ /* should be referenced by PL_curpad[0] and *_ */
+ assert(SvREFCNT(PL_curpad[0]) > 1);
+ SvREFCNT_dec(PL_curpad[0]);
+
+ PL_curpad[0] = (SV*)av;
+ }
+
+ /* undo the temporary runops hook and fall through to a real runops loop. */
+ assert(sud->old_runops != su_uplevel_runops_hook_entersub);
+ PL_runops = sud->old_runops;
+ CALLRUNOPS(aTHX);
+ return 0;
+}
+
+static I32 su_uplevel_new(pTHX_ CV *callback, I32 cxix, I32 args) {
+ su_uplevel_ud *sud;
+ U8 *saved_cxtypes;
+ I32 i, ret;
+ I32 gimme;
+ CV *base_cv = cxstack[cxix].blk_sub.cv;
+ dSP;
+
+ assert(CxTYPE(&cxstack[cxix]) == CXt_SUB);
+
+ ENTER;
+
+ gimme = GIMME_V;
+
+ /* At this point SP points to the top arg.
+ * Shuffle the args down by one, eliminating the CV slot */
+ Move(SP - args + 1, SP - args, args, SV*);
+ SP--;
+ PUSHMARK(SP - args);
+ PUTBACK;
+
+ sud = su_uplevel_storage_new(cxix);
+
+ sud->cxix = cxix;
+ sud->callback = (CV*)SvREFCNT_inc_simple(callback);
+ sud->renamed = NULL;
+ sud->gap = cxstack_ix - cxix + 1;
+ sud->argarray = NULL;
+
+ Newx(saved_cxtypes, sud->gap, U8);
+ sud->cxtypes = saved_cxtypes;
+
+ SAVEDESTRUCTOR_X(su_uplevel_restore_new, sud);
+ SU_UPLEVEL_SAVE(curcop, cxstack[cxix].blk_oldcop);
+
+/* temporarily change the type of any contexts to NULL, so they're
+ * invisible to caller() etc. */
+ for (i = 0; i < sud->gap; i++) {
+ PERL_CONTEXT *cx = cxstack + cxix + i;
+ saved_cxtypes[i] = cx->cx_type; /* save type and flags */
+ XSH_D(su_debug_log("su_uplevel: i=%d cxix=%d type %-11s => %s\n",
+ i, cx-cxstack, SU_CX_TYPENAME(CxTYPE(cx)), SU_CX_TYPENAME(CXt_NULL)));
+ cx->cx_type = (CXt_NULL | CXp_SU_UPLEVEL_NULLED);
+ }
+
+ /* create a copy of the callback with a doctored name (as seen by
+ * caller). It shares the padlist with callback */
+ sud->renamed = su_cv_clone(callback, CvGV(base_cv));
+ sud->old_runops = PL_runops;
+
+ if (!CvISXSUB(sud->renamed) && CxHASARGS(&cxstack[cxix])) {
+ sud->argarray = (AV*)su_at_underscore(base_cv);
+ assert(PL_runops != su_uplevel_runops_hook_entersub);
+ /* set up a one-shot runops hook so that we can fake up the
+ * args as seen by caller() on return from pp_entersub */
+ PL_runops = su_uplevel_runops_hook_entersub;
+ }
+
+ CvDEPTH(callback)++; /* match what CvDEPTH(sud->renamed) is about to become */
+
+ ret = call_sv((SV*)sud->renamed, gimme);
+
+ LEAVE;
+
+ return ret;
+}
+
+#else
+
+static I32 su_uplevel_old(pTHX_ CV *callback, I32 cxix, I32 args) {
su_uplevel_ud *sud;
const PERL_CONTEXT *cx = cxstack + cxix;
PERL_SI *si;
sud->died = 1;
sud->callback = NULL;
sud->renamed = NULL;
- SAVEDESTRUCTOR_X(su_uplevel_restore, sud);
+ SAVEDESTRUCTOR_X(su_uplevel_restore_old, sud);
si = sud->si;
* target context, plus the forthcoming arguments. */
new_mark = cx->blk_oldsp;
av_extend(si->si_stack, new_mark + 1 + args + 1);
- Copy(PL_curstack, AvARRAY(si->si_stack), new_mark + 1, SV *);
+ Copy(AvARRAY(PL_curstack), AvARRAY(si->si_stack), new_mark + 1, SV *);
AvFILLp(si->si_stack) = new_mark;
SU_POISON(AvARRAY(si->si_stack) + new_mark + 1, args + 1, SV *);
if ((PL_op = PL_ppaddr[OP_ENTERSUB](aTHX))) {
PERL_CONTEXT *sub_cx = cxstack + cxstack_ix;
- AV *argarray = CX_ARGARRAY(cx);
+ AV *argarray = cx->blk_sub.argarray;
/* If pp_entersub() returns a non-null OP, it means that the callback is not
* an XSUB. */
av_extend(av, AvMAX(argarray));
AvFILLp(av) = AvFILLp(argarray);
Copy(AvARRAY(argarray), AvARRAY(av), AvFILLp(av) + 1, SV *);
- CX_ARGARRAY_set(sub_cx, av);
+ sub_cx->blk_sub.argarray = av;
} else {
- SvREFCNT_inc_simple_void(CX_ARGARRAY(sub_cx));
+ SvREFCNT_inc_simple_void(sub_cx->blk_sub.argarray);
}
if (su_uplevel_goto_static(CvROOT(renamed))) {
return ret;
}
+#endif
+
/* --- Unique context ID --------------------------------------------------- */
static su_uid *su_uid_storage_fetch(pTHX_ UV depth) {
return cxix;
}
+#ifdef SU_HAS_NEW_CXT
+
+/* convert a physical context stack index into the logical equivalent:
+ * one that ignores all the context frames hidden by uplevel().
+ * Perl-level functions use logical args (e.g. UP takes an optional logical
+ * value and returns a logical value), while we use and store *real*
+ * values internally.
+ */
+
+static I32 su_context_real2logical(pTHX_ I32 cxix) {
+# define su_context_real2logical(C) su_context_real2logical(aTHX_ (C))
+ PERL_CONTEXT *cx;
+ I32 i, gaps = 0;
+
+ for (i = 0; i <= cxix; i++) {
+ cx = cxstack + i;
+ if (cx->cx_type == (CXt_NULL | CXp_SU_UPLEVEL_NULLED))
+ gaps++;
+ }
+ XSH_D(su_debug_log("su_context_real2logical: %d => %d\n", cxix, cxix - gaps));
+ return cxix - gaps;
+}
+
+/* convert a logical context stack index (one that ignores all the context
+ * frames hidden by uplevel) into the physical equivalent
+ */
+
+static I32 su_context_logical2real(pTHX_ I32 cxix) {
+# define su_context_logical2real(C) su_context_logical2real(aTHX_ (C))
+ PERL_CONTEXT *cx;
+ I32 i, seen = -1;
+
+ for (i = 0; i <= cxstack_ix; i++) {
+ PERL_CONTEXT *cx = cxstack + i;
+ if (cx->cx_type != (CXt_NULL | CXp_SU_UPLEVEL_NULLED))
+ seen++;
+ if (seen >= cxix)
+ break;
+ }
+ XSH_D(su_debug_log("su_context_logical2real: %d => %d\n", cxix, i));
+ if (i > cxstack_ix)
+ i = cxstack_ix;
+ return i;
+}
+
+#else
+# define su_context_real2logical(C) (C)
+# define su_context_logical2real(C) (C)
+#endif
static I32 su_context_normalize_up(pTHX_ I32 cxix) {
#define su_context_normalize_up(C) su_context_normalize_up(aTHX_ (C))
/* --- XS ------------------------------------------------------------------ */
+/* D is real; B is logical. Returns real. */
+
#define SU_GET_CONTEXT(A, B, D) \
STMT_START { \
if (items > A) { \
cxix = 0; \
else if (cxix > cxstack_ix) \
goto default_cx; \
+ cxix = su_context_logical2real(cxix); \
} else { \
default_cx: \
cxix = (D); \
PREINIT:
I32 cxix;
PPCODE:
- cxix = su_context_here();
+ cxix = su_context_real2logical(su_context_here());
EXTEND(SP, 1);
mPUSHi(cxix);
XSRETURN(1);
--cxix;
cxix = su_context_skip_db(cxix);
cxix = su_context_normalize_up(cxix);
+ cxix = su_context_real2logical(cxix);
} else {
warn(su_stack_smash);
}
case CXt_SUB:
if (PL_DBsub && cx->blk_sub.cv == GvCV(PL_DBsub))
continue;
+ cxix = su_context_real2logical(cxix);
mPUSHi(cxix);
XSRETURN(1);
}
default:
continue;
case CXt_EVAL:
+ cxix = su_context_real2logical(cxix);
mPUSHi(cxix);
XSRETURN(1);
}
--cxix;
cxix = su_context_skip_db(cxix);
cxix = su_context_normalize_up(cxix);
+ cxix = su_context_real2logical(cxix);
}
EXTEND(SP, 1);
mPUSHi(cxix);
if (level >= 0)
warn(su_stack_smash);
EXTEND(SP, 1);
+ cxix = su_context_real2logical(cxix);
mPUSHi(cxix);
XSRETURN(1);
args = items - 2;
}
/* su_uplevel() takes care of extending the stack if needed. */
- ret = su_uplevel((CV *) code, cxix, args);
+#ifdef SU_HAS_NEW_CXT
+ ret = su_uplevel_new(aTHX_ (CV *) code, cxix, args);
+#else
+ ret = su_uplevel_old(aTHX_ (CV *) code, cxix, args);
+#endif
XSRETURN(ret);
default:
break;