This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
new perldelta
[perl5.git] / pp_hot.c
index 8a74f58..d87d325 100644 (file)
--- a/pp_hot.c
+++ b/pp_hot.c
 #include "EXTERN.h"
 #define PERL_IN_PP_HOT_C
 #include "perl.h"
+#include "regcomp.h"
 
 /* Hot code. */
 
+
+#ifdef PERL_RC_STACK
+
+/* common code for pp_wrap() and xs_wrap():
+ * free any original arguments, and bump and shift down any return
+ * args
+ */
+
+STATIC void
+S_pp_xs_wrap_return(pTHX_ I32 nargs, I32 old_sp)
+{
+    I32 nret = (I32)(PL_stack_sp - PL_stack_base) - old_sp;
+    assert(nret >= 0);
+
+    /* bump any returned values */
+    if (nret) {
+        SV **svp = PL_stack_sp - nret + 1;
+        while (svp <= PL_stack_sp) {
+            SvREFCNT_inc(*svp);
+            svp++;
+        }
+    }
+
+    PL_curstackinfo->si_stack_nonrc_base = 0;
+
+    /* free the original args and shift the returned valued down */
+    if (nargs) {
+        SV **svp = PL_stack_sp - nret;
+        I32 i = nargs;
+        while (i--) {
+            SvREFCNT_dec(*svp);
+            *svp = NULL;
+            svp--;
+        }
+
+        if (nret) {
+            Move(PL_stack_sp - nret + 1,
+                 PL_stack_sp - nret - nargs + 1,
+                 nret, SV*);
+        }
+        PL_stack_sp -= nargs;
+    }
+}
+
+/* pp_wrap():
+ * wrapper function for pp() functions to turn them into functions
+ * that can operate on a reference-counted stack, by taking a non-
+ * reference-counted copy of the current stack frame, calling the real
+ * pp() function, then incrementing the reference count of any returned
+ * args.
+ *
+ * nargs or nlists indicate the number of stack arguments or the
+ * number of stack lists (delimited by MARKs) which the function expects.
+ */
+OP*
+Perl_pp_wrap(pTHX_ Perl_ppaddr_t real_pp_fn, I32 nargs, int nlists)
+{
+    PERL_ARGS_ASSERT_PP_WRAP;
+
+    if (!rpp_stack_is_rc())
+        /* stack-already non-RC; nothing needing wrapping */
+        return real_pp_fn(aTHX);
+
+    OP *next_op;
+    I32 old_sp = (I32)(PL_stack_sp - PL_stack_base);
+
+    assert(nargs  >= 0);
+    assert(nlists >= 0);
+    assert(AvREAL(PL_curstack));
+
+    PL_curstackinfo->si_stack_nonrc_base = PL_stack_sp - PL_stack_base + 1;
+
+    if (nlists) {
+        assert(nargs == 0);
+        I32 mark  = PL_markstack_ptr[-nlists+1];
+        nargs = (PL_stack_sp - PL_stack_base) - mark;
+        assert(nlists <= 2); /* if ever more, make below a loop */
+        PL_markstack_ptr[0]  += nargs;
+        if (nlists == 2)
+            PL_markstack_ptr[-1] += nargs;
+    }
+
+    if (nargs) {
+        /* duplicate all the arg pointers further up the stack */
+        rpp_extend(nargs);
+        Copy(PL_stack_sp - nargs + 1, PL_stack_sp + 1, nargs, SV*);
+        PL_stack_sp += nargs;
+    }
+
+    next_op = real_pp_fn(aTHX);
+
+    /* we should still be a split stack */
+    assert(AvREAL(PL_curstack));
+    assert(PL_curstackinfo->si_stack_nonrc_base);
+
+    S_pp_xs_wrap_return(aTHX_ nargs, old_sp);
+
+    return next_op;
+}
+
+
+/* xs_wrap():
+ * similar in concept to pp_wrap: make a non-referenced-counted copy of
+ * a (not refcount aware) XS sub's args, call the XS subs, then bump any
+ * return values and free the original args */
+
+void
+Perl_xs_wrap(pTHX_ XSUBADDR_t xsub, CV *cv)
+{
+    PERL_ARGS_ASSERT_XS_WRAP;
+
+    I32 old_sp = (I32)(PL_stack_sp - PL_stack_base);
+    I32 mark  = PL_markstack_ptr[0];
+    I32 nargs = (PL_stack_sp - PL_stack_base) - mark;
+
+    /* we should be a fully refcounted stack */
+    assert(AvREAL(PL_curstack));
+    assert(!PL_curstackinfo->si_stack_nonrc_base);
+
+    PL_curstackinfo->si_stack_nonrc_base = PL_stack_sp - PL_stack_base + 1;
+
+
+    if (nargs) {
+        /* duplicate all the arg pointers further up the stack */
+        rpp_extend(nargs);
+        Copy(PL_stack_sp - nargs + 1, PL_stack_sp + 1, nargs, SV*);
+        PL_stack_sp += nargs;
+        PL_markstack_ptr[0]  += nargs;
+    }
+
+    xsub(aTHX_ cv);
+
+    S_pp_xs_wrap_return(aTHX_ nargs, old_sp);
+}
+
+#endif
+
+
+
+/* Private helper function for Perl_rpp_replace_2_1_COMMON()
+ * and rpp_popfree_2_NN().
+ * Free the two passed SVs, whose original ref counts are rc1 and rc2.
+ * Assumes the stack initially looked like
+ *    .... sv1 sv2
+ * and is now:
+ *    .... X
+ * but where sv2 is still on the slot above the current PL_stack_sp.
+ */
+
+void
+Perl_rpp_free_2_(pTHX_ SV *const sv1,  SV *const sv2,
+                       const U32 rc1,  const U32 rc2)
+{
+
+    PERL_ARGS_ASSERT_RPP_FREE_2_;
+
+#ifdef PERL_RC_STACK
+    if (rc1 > 1)
+        SvREFCNT(sv1) = rc1 - 1;
+    else {
+        /* temporarily reclaim sv2 on stack in case we die while freeing sv1 */
+        assert(PL_stack_sp[1] == sv2);
+        PL_stack_sp++;
+        Perl_sv_free2(aTHX_ sv1, rc1);
+        PL_stack_sp--;
+    }
+    if (rc2 > 1)
+        SvREFCNT(sv2) = rc2 - 1;
+    else
+        Perl_sv_free2(aTHX_ sv2, rc2);
+#else
+    PERL_UNUSED_VAR(sv1);
+    PERL_UNUSED_VAR(sv2);
+    PERL_UNUSED_VAR(rc1);
+    PERL_UNUSED_VAR(rc2);
+#endif
+}
+
+
+
+/* ----------------------------------------------------------- */
+
+
 PP(pp_const)
 {
-    dSP;
-    XPUSHs(cSVOP_sv);
-    RETURN;
+    rpp_xpush_1(cSVOP_sv);
+    return NORMAL;
 }
 
 PP(pp_nextstate)
 {
     PL_curcop = (COP*)PL_op;
     TAINT_NOT;         /* Each statement is presumed innocent */
-    PL_stack_sp = PL_stack_base + CX_CUR()->blk_oldsp;
+    rpp_popfree_to_NN(PL_stack_base + CX_CUR()->blk_oldsp);
     FREETMPS;
     PERL_ASYNC_CHECK();
     return NORMAL;
@@ -56,13 +239,12 @@ PP(pp_nextstate)
 
 PP(pp_gvsv)
 {
-    dSP;
-    EXTEND(SP,1);
-    if (UNLIKELY(PL_op->op_private & OPpLVAL_INTRO))
-       PUSHs(save_scalar(cGVOP_gv));
-    else
-       PUSHs(GvSVn(cGVOP_gv));
-    RETURN;
+    assert(SvTYPE(cGVOP_gv) == SVt_PVGV);
+    rpp_xpush_1(
+            UNLIKELY(PL_op->op_private & OPpLVAL_INTRO)
+                ? save_scalar(cGVOP_gv)
+                : GvSVn(cGVOP_gv));
+    return NORMAL;
 }
 
 
@@ -83,20 +265,20 @@ PP(pp_pushmark)
 
 PP(pp_stringify)
 {
-    dSP; dTARGET;
-    SV * const sv = TOPs;
-    SETs(TARG);
-    sv_copypv(TARG, sv);
+    dTARGET;
+    sv_copypv(TARG, *PL_stack_sp);
     SvSETMAGIC(TARG);
-    /* no PUTBACK, SETs doesn't inc/dec SP */
+    rpp_replace_1_1_NN(TARG);
     return NORMAL;
 }
 
 PP(pp_gv)
 {
-    dSP;
-    XPUSHs(MUTABLE_SV(cGVOP_gv));
-    RETURN;
+    /* cGVOP_gv might be a real GV or might be an RV to a CV */
+    assert(SvTYPE(cGVOP_gv) == SVt_PVGV ||
+           (SvTYPE(cGVOP_gv) <= SVt_PVMG && SvROK(cGVOP_gv) && SvTYPE(SvRV(cGVOP_gv)) == SVt_PVCV));
+    rpp_xpush_1(MUTABLE_SV(cGVOP_gv));
+    return NORMAL;
 }
 
 
@@ -106,136 +288,238 @@ PP(pp_and)
 {
     PERL_ASYNC_CHECK();
     {
-       /* SP is not used to remove a variable that is saved across the
-         sv_2bool_flags call in SvTRUE_NN, if a RISC/CISC or low/high machine
-         register or load/store vs direct mem ops macro is introduced, this
-         should be a define block between direct PL_stack_sp and dSP operations,
-         presently, using PL_stack_sp is bias towards CISC cpus */
-       SV * const sv = *PL_stack_sp;
-       if (!SvTRUE_NN(sv))
-           return NORMAL;
-       else {
-           if (PL_op->op_type == OP_AND)
-               --PL_stack_sp;
-           return cLOGOP->op_other;
-       }
+        SV * const sv = *PL_stack_sp;
+        if (!SvTRUE_NN(sv))
+            return NORMAL;
+        else {
+            if (PL_op->op_type == OP_AND)
+                rpp_popfree_1_NN();
+            return cLOGOP->op_other;
+        }
     }
 }
 
+/*
+ * Mashup of simple padsv + sassign OPs
+ * Doesn't support the following lengthy and unlikely sassign case:
+ *    (UNLIKELY(PL_op->op_private & OPpASSIGN_CV_TO_GV))
+ *  These cases have a separate optimization, so are not handled here:
+ *    (PL_op->op_private & OPpASSIGN_BACKWARDS) {or,and,dor}assign
+*/
+
+PP(pp_padsv_store)
+{
+    OP * const op = PL_op;
+    SV** const padentry = &PAD_SVl(op->op_targ);
+    SV* targ = *padentry; /* lvalue to assign into */
+    SV* const val = *PL_stack_sp; /* RHS value to assign */
+
+    /* !OPf_STACKED is not handled by this OP */
+    assert(op->op_flags & OPf_STACKED);
+
+    /* Inlined, simplified pp_padsv here */
+    if ((op->op_private & (OPpLVAL_INTRO|OPpPAD_STATE)) == OPpLVAL_INTRO) {
+        save_clearsv(padentry);
+    }
+
+    /* Inlined, simplified pp_sassign from here */
+    assert(TAINTING_get || !TAINT_get);
+    if (UNLIKELY(TAINT_get) && !SvTAINTED(val))
+        TAINT_NOT;
+
+    if (
+      UNLIKELY(SvTEMP(targ)) && !SvSMAGICAL(targ) && SvREFCNT(targ) == 1 &&
+      (!isGV_with_GP(targ) || SvFAKE(targ)) && ckWARN(WARN_MISC)
+    )
+        Perl_warner(aTHX_
+            packWARN(WARN_MISC), "Useless assignment to a temporary"
+        );
+    SvSetMagicSV(targ, val);
+
+    assert(GIMME_V == G_VOID);
+    rpp_popfree_1_NN();
+    return NORMAL;
+}
+
+
+/* A mashup of simplified AELEMFAST_LEX + SASSIGN OPs */
+
+PP(pp_aelemfastlex_store)
+{
+    OP * const op = PL_op;
+    SV* const val = *PL_stack_sp; /* RHS value to assign */
+    AV * const av = MUTABLE_AV(PAD_SV(op->op_targ));
+    const I8 key   = (I8)PL_op->op_private;
+    SV * targ = NULL;
+
+    /* !OPf_STACKED is not handled by this OP */
+    assert(op->op_flags & OPf_STACKED);
+
+    /* Inlined, simplified pp_aelemfast here */
+    assert(SvTYPE(av) == SVt_PVAV);
+
+    /* inlined av_fetch() for simple cases ... */
+    if (!SvRMAGICAL(av) && key >=0 && key <= AvFILLp(av)) {
+        targ = AvARRAY(av)[key];
+    }
+    /* ... else do it the hard way */
+    if (!targ) {
+        SV **svp = av_fetch(av, key, 1);
+
+        if (svp)
+            targ = *svp;
+        else
+            DIE(aTHX_ PL_no_aelem, (int)key);
+    }
+
+    /* Inlined, simplified pp_sassign from here */
+    assert(TAINTING_get || !TAINT_get);
+    if (UNLIKELY(TAINT_get) && !SvTAINTED(val))
+        TAINT_NOT;
+
+    /* This assertion is a deviation from pp_sassign, which uses an if()
+     * condition to check for "Useless assignment to a temporary" and
+     * warns if the condition is true. Here, the condition should NEVER
+     * be true when the LHS is the result of an array fetch. The
+     * assertion is here as a final check that this remains the case.
+     */
+    assert(!(SvTEMP(targ) && SvREFCNT(targ) == 1 && !SvSMAGICAL(targ)));
+
+    SvSetMagicSV(targ, val);
+
+    assert(GIMME_V == G_VOID);
+    rpp_popfree_1_NN();
+    return NORMAL;
+}
+
 PP(pp_sassign)
 {
-    dSP;
     /* sassign keeps its args in the optree traditionally backwards.
        So we pop them differently.
     */
-    SV *left = POPs; SV *right = TOPs;
+    SV *left  = PL_stack_sp[0];
+    SV *right = PL_stack_sp[-1];
 
     if (PL_op->op_private & OPpASSIGN_BACKWARDS) { /* {or,and,dor}assign */
-       SV * const temp = left;
-       left = right; right = temp;
+        SV * const temp = left;
+        left = right; right = temp;
+        PL_stack_sp[0]  = left;
+        PL_stack_sp[-1] = right;
     }
     assert(TAINTING_get || !TAINT_get);
     if (UNLIKELY(TAINT_get) && !SvTAINTED(right))
-       TAINT_NOT;
+        TAINT_NOT;
+
     if (UNLIKELY(PL_op->op_private & OPpASSIGN_CV_TO_GV)) {
         /* *foo =\&bar */
-       SV * const cv = SvRV(right);
-       const U32 cv_type = SvTYPE(cv);
-       const bool is_gv = isGV_with_GP(left);
-       const bool got_coderef = cv_type == SVt_PVCV || cv_type == SVt_PVFM;
-
-       if (!got_coderef) {
-           assert(SvROK(cv));
-       }
-
-       /* Can do the optimisation if left (LVALUE) is not a typeglob,
-          right (RVALUE) is a reference to something, and we're in void
-          context. */
-       if (!got_coderef && !is_gv && GIMME_V == G_VOID) {
-           /* Is the target symbol table currently empty?  */
-           GV * const gv = gv_fetchsv_nomg(left, GV_NOINIT, SVt_PVGV);
-           if (SvTYPE(gv) != SVt_PVGV && !SvOK(gv)) {
-               /* Good. Create a new proxy constant subroutine in the target.
-                  The gv becomes a(nother) reference to the constant.  */
-               SV *const value = SvRV(cv);
-
-               SvUPGRADE(MUTABLE_SV(gv), SVt_IV);
-               SvPCS_IMPORTED_on(gv);
-               SvRV_set(gv, value);
-               SvREFCNT_inc_simple_void(value);
-               SETs(left);
-               RETURN;
-           }
-       }
-
-       /* Need to fix things up.  */
-       if (!is_gv) {
-           /* Need to fix GV.  */
-           left = MUTABLE_SV(gv_fetchsv_nomg(left,GV_ADD, SVt_PVGV));
-       }
-
-       if (!got_coderef) {
-           /* We've been returned a constant rather than a full subroutine,
-              but they expect a subroutine reference to apply.  */
-           if (SvROK(cv)) {
-               ENTER_with_name("sassign_coderef");
-               SvREFCNT_inc_void(SvRV(cv));
-               /* newCONSTSUB takes a reference count on the passed in SV
-                  from us.  We set the name to NULL, otherwise we get into
-                  all sorts of fun as the reference to our new sub is
-                  donated to the GV that we're about to assign to.
-               */
-               SvRV_set(right, MUTABLE_SV(newCONSTSUB(GvSTASH(left), NULL,
-                                                     SvRV(cv))));
-               SvREFCNT_dec_NN(cv);
-               LEAVE_with_name("sassign_coderef");
-           } else {
-               /* What can happen for the corner case *{"BONK"} = \&{"BONK"};
-                  is that
-                  First:   ops for \&{"BONK"}; return us the constant in the
-                           symbol table
-                  Second:  ops for *{"BONK"} cause that symbol table entry
-                           (and our reference to it) to be upgraded from RV
-                           to typeblob)
-                  Thirdly: We get here. cv is actually PVGV now, and its
-                           GvCV() is actually the subroutine we're looking for
-
-                  So change the reference so that it points to the subroutine
-                  of that typeglob, as that's what they were after all along.
-               */
-               GV *const upgraded = MUTABLE_GV(cv);
-               CV *const source = GvCV(upgraded);
-
-               assert(source);
-               assert(CvFLAGS(source) & CVf_CONST);
-
-               SvREFCNT_inc_simple_void_NN(source);
-               SvREFCNT_dec_NN(upgraded);
-               SvRV_set(right, MUTABLE_SV(source));
-           }
-       }
+        SV * const cv = SvRV(right);
+        const U32 cv_type = SvTYPE(cv);
+        const bool is_gv = isGV_with_GP(left);
+        const bool got_coderef = cv_type == SVt_PVCV || cv_type == SVt_PVFM;
+
+        if (!got_coderef) {
+            assert(SvROK(cv));
+        }
+
+        /* Can do the optimisation if left (LVALUE) is not a typeglob,
+           right (RVALUE) is a reference to something, and we're in void
+           context. */
+        if (!got_coderef && !is_gv && GIMME_V == G_VOID) {
+            /* Is the target symbol table currently empty?  */
+            GV * const gv = gv_fetchsv_nomg(left, GV_NOINIT, SVt_PVGV);
+            if (SvTYPE(gv) != SVt_PVGV && !SvOK(gv)) {
+                /* Good. Create a new proxy constant subroutine in the target.
+                   The gv becomes a(nother) reference to the constant.  */
+                SV *const value = SvRV(cv);
+
+                SvUPGRADE(MUTABLE_SV(gv), SVt_IV);
+                SvPCS_IMPORTED_on(gv);
+                SvRV_set(gv, value);
+                SvREFCNT_inc_simple_void(value);
+                rpp_replace_2_1_NN(left);
+                return NORMAL;
+            }
+        }
+
+        /* Need to fix things up.  */
+        if (!is_gv) {
+            /* Need to fix GV.  */
+            SV *sv = MUTABLE_SV(gv_fetchsv_nomg(left,GV_ADD, SVt_PVGV));
+            rpp_replace_1_1_NN(sv);
+            left = sv;
+        }
+
+        if (!got_coderef) {
+            /* We've been returned a constant rather than a full subroutine,
+               but they expect a subroutine reference to apply.  */
+            if (SvROK(cv)) {
+                ENTER_with_name("sassign_coderef");
+                SvREFCNT_inc_void(SvRV(cv));
+                /* newCONSTSUB takes a reference count on the passed in SV
+                   from us.  We set the name to NULL, otherwise we get into
+                   all sorts of fun as the reference to our new sub is
+                   donated to the GV that we're about to assign to.
+                */
+                SvRV_set(right, MUTABLE_SV(newCONSTSUB(GvSTASH(left), NULL,
+                                                      SvRV(cv))));
+                SvREFCNT_dec_NN(cv);
+                LEAVE_with_name("sassign_coderef");
+            } else {
+                /* What can happen for the corner case *{"BONK"} = \&{"BONK"};
+                   is that
+                   First:   ops for \&{"BONK"}; return us the constant in the
+                            symbol table
+                   Second:  ops for *{"BONK"} cause that symbol table entry
+                            (and our reference to it) to be upgraded from RV
+                            to typeblob)
+                   Thirdly: We get here. cv is actually PVGV now, and its
+                            GvCV() is actually the subroutine we're looking for
+
+                   So change the reference so that it points to the subroutine
+                   of that typeglob, as that's what they were after all along.
+                */
+                GV *const upgraded = MUTABLE_GV(cv);
+                CV *const source = GvCV(upgraded);
+
+                assert(source);
+                assert(CvFLAGS(source) & CVf_CONST);
+
+                SvREFCNT_inc_simple_void_NN(source);
+                SvREFCNT_dec_NN(upgraded);
+                SvRV_set(right, MUTABLE_SV(source));
+            }
+        }
 
     }
     if (
-      UNLIKELY(SvTEMP(left)) && !SvSMAGICAL(left) && SvREFCNT(left) == 1 &&
+      rpp_is_lone(left) && !SvSMAGICAL(left) &&
       (!isGV_with_GP(left) || SvFAKE(left)) && ckWARN(WARN_MISC)
     )
-       Perl_warner(aTHX_
-           packWARN(WARN_MISC), "Useless assignment to a temporary"
-       );
+        Perl_warner(aTHX_
+            packWARN(WARN_MISC), "Useless assignment to a temporary"
+        );
     SvSetMagicSV(left, right);
-    SETs(left);
-    RETURN;
+    if (LIKELY(GIMME_V == G_VOID))
+        rpp_popfree_2_NN(); /* pop left and right */
+    else {
+        /* pop right, leave left on the stack */
+        assert(PL_stack_sp[-1] == right);
+        assert(PL_stack_sp[0]  == left);
+        *--PL_stack_sp = left;
+#ifdef PERL_RC_STACK
+        SvREFCNT_dec_NN(right);
+#endif
+    }
+
+    return NORMAL;
 }
 
 PP(pp_cond_expr)
 {
-    dSP;
-    SV *sv;
-
     PERL_ASYNC_CHECK();
-    sv = POPs;
-    RETURNOP(SvTRUE_NN(sv) ? cLOGOP->op_other : cLOGOP->op_next);
+    bool ok = SvTRUE_NN(*PL_stack_sp);
+    rpp_popfree_1_NN();
+    return (ok ? cLOGOP->op_other : cLOGOP->op_next);
 }
 
 PP(pp_unstack)
@@ -244,20 +528,26 @@ PP(pp_unstack)
     PERL_ASYNC_CHECK();
     TAINT_NOT;         /* Each statement is presumed innocent */
     cx  = CX_CUR();
-    PL_stack_sp = PL_stack_base + cx->blk_oldsp;
+    rpp_popfree_to_NN(PL_stack_base + CX_CUR()->blk_oldsp);
     FREETMPS;
     if (!(PL_op->op_flags & OPf_SPECIAL)) {
         assert(CxTYPE(cx) == CXt_BLOCK || CxTYPE_is_LOOP(cx));
-       CX_LEAVE_SCOPE(cx);
+        CX_LEAVE_SCOPE(cx);
     }
     return NORMAL;
 }
 
-PP(pp_concat)
+
+/* The main body of pp_concat, not including the magic/overload and
+ * stack handling.
+ * It does targ = left . right.
+ * Moved into a separate function so that pp_multiconcat() can use it
+ * too.
+ */
+
+PERL_STATIC_INLINE void
+S_do_concat(pTHX_ SV *left, SV *right, SV *targ, U8 targmy)
 {
-  dSP; dATARGET; tryAMAGICbin_MG(concat_amg, AMGf_assign);
-  {
-    dPOPTOPssrl;
     bool lbyte;
     STRLEN rlen;
     const char *rpv = NULL;
@@ -265,59 +555,73 @@ PP(pp_concat)
     bool rcopied = FALSE;
 
     if (TARG == right && right != left) { /* $r = $l.$r */
-       rpv = SvPV_nomg_const(right, rlen);
-       rbyte = !DO_UTF8(right);
-       right = newSVpvn_flags(rpv, rlen, SVs_TEMP);
-       rpv = SvPV_const(right, rlen);  /* no point setting UTF-8 here */
-       rcopied = TRUE;
+        rpv = SvPV_nomg_const(right, rlen);
+        rbyte = !DO_UTF8(right);
+        right = newSVpvn_flags(rpv, rlen, SVs_TEMP);
+        rpv = SvPV_const(right, rlen); /* no point setting UTF-8 here */
+        rcopied = TRUE;
     }
 
     if (TARG != left) { /* not $l .= $r */
         STRLEN llen;
         const char* const lpv = SvPV_nomg_const(left, llen);
-       lbyte = !DO_UTF8(left);
-       sv_setpvn(TARG, lpv, llen);
-       if (!lbyte)
-           SvUTF8_on(TARG);
-       else
-           SvUTF8_off(TARG);
+        lbyte = !DO_UTF8(left);
+        sv_setpvn(TARG, lpv, llen);
+        if (!lbyte)
+            SvUTF8_on(TARG);
+        else
+            SvUTF8_off(TARG);
     }
     else { /* $l .= $r   and   left == TARG */
-       if (!SvOK(left)) {
+        if (!SvOK(left)) {
             if ((left == right                          /* $l .= $l */
-                 || (PL_op->op_private & OPpTARGET_MY)) /* $l = $l . $r */
+                 || targmy)                             /* $l = $l . $r */
                 && ckWARN(WARN_UNINITIALIZED)
                 )
                 report_uninit(left);
             SvPVCLEAR(left);
-       }
+        }
         else {
             SvPV_force_nomg_nolen(left);
         }
-       lbyte = !DO_UTF8(left);
-       if (IN_BYTES)
-           SvUTF8_off(left);
+        lbyte = !DO_UTF8(left);
+        if (IN_BYTES)
+            SvUTF8_off(left);
     }
 
     if (!rcopied) {
-       rpv = SvPV_nomg_const(right, rlen);
-       rbyte = !DO_UTF8(right);
+        rpv = SvPV_nomg_const(right, rlen);
+        rbyte = !DO_UTF8(right);
     }
     if (lbyte != rbyte) {
-       if (lbyte)
-           sv_utf8_upgrade_nomg(TARG);
-       else {
-           if (!rcopied)
-               right = newSVpvn_flags(rpv, rlen, SVs_TEMP);
-           sv_utf8_upgrade_nomg(right);
-           rpv = SvPV_nomg_const(right, rlen);
-       }
+        if (lbyte)
+            sv_utf8_upgrade_nomg(TARG);
+        else {
+            if (!rcopied)
+                right = newSVpvn_flags(rpv, rlen, SVs_TEMP);
+            sv_utf8_upgrade_nomg(right);
+            rpv = SvPV_nomg_const(right, rlen);
+        }
     }
     sv_catpvn_nomg(TARG, rpv, rlen);
+    SvSETMAGIC(TARG);
+}
 
-    SETTARG;
-    RETURN;
-  }
+
+PP(pp_concat)
+{
+    SV *targ = (PL_op->op_flags & OPf_STACKED)
+                    ? PL_stack_sp[-1]
+                    : PAD_SV(PL_op->op_targ);
+
+    if (rpp_try_AMAGIC_2(concat_amg, AMGf_assign))
+       return NORMAL;
+
+    SV *right = PL_stack_sp[0];
+    SV *left  = PL_stack_sp[-1];
+    S_do_concat(aTHX_ left, right, targ, PL_op->op_private & OPpTARGET_MY);
+    rpp_replace_2_1_NN(targ);
+    return NORMAL;
 }
 
 
@@ -346,8 +650,8 @@ In addition:
                                sprintf "...%s...". Don't call '.'
                                overloading: only use '""' overloading.
 
-    OPpMULTICONCAT_STRINGIFY:  (for Deparse's benefit) the RHS was of the
-                               form "...$a...$b..." rather than
+    OPpMULTICONCAT_STRINGIFY:  the RHS was of the form
+                               "...$a...$b..." rather than
                                "..." . $a . "..." . $b . "..."
 
 An OP_MULTICONCAT is of type UNOP_AUX. The fixed slots of the aux array are
@@ -382,18 +686,16 @@ have differing overloading behaviour.
 
 PP(pp_multiconcat)
 {
-    dSP;
     SV *targ;                /* The SV to be assigned or appended to */
-    SV *dsv;                 /* the SV to concat args to (often == targ) */
-    char *dsv_pv;            /* where within SvPVX(dsv) we're writing to */
+    char *targ_pv;           /* where within SvPVX(targ) we're writing to */
     STRLEN targ_len;         /* SvCUR(targ) */
     SV **toparg;             /* the highest arg position on the stack */
     UNOP_AUX_item *aux;      /* PL_op->op_aux buffer */
     UNOP_AUX_item *const_lens; /* the segment length array part of aux */
     const char *const_pv;    /* the current segment of the const string buf */
     SSize_t nargs;           /* how many args were expected */
-    SSize_t stack_adj;       /* how much to adjust SP on return */
-    STRLEN grow;             /* final size of destination string (dsv) */
+    SSize_t stack_adj;       /* how much to adjust PL_stack_sp on return */
+    STRLEN grow;             /* final size of destination string (targ) */
     UV targ_count;           /* how many times targ has appeared on the RHS */
     bool is_append;          /* OPpMULTICONCAT_APPEND flag is set */
     bool slow_concat;        /* args too complex for quick concat */
@@ -402,7 +704,7 @@ PP(pp_multiconcat)
                                 for ease of testing and setting) */
     /* for each arg, holds the result of an SvPV() call */
     struct multiconcat_svpv {
-        char          *pv;
+        const char   *pv;
         SSize_t       len;
     }
         *targ_chain,         /* chain of slots where targ has appeared on RHS */
@@ -417,15 +719,18 @@ PP(pp_multiconcat)
 
     /* get targ from the stack or pad */
 
+    toparg = PL_stack_sp;
     if (PL_op->op_flags & OPf_STACKED) {
+        stack_adj++;
         if (is_append) {
             /* for 'expr .= ...', expr is the bottom item on the stack */
-            targ = SP[-nargs];
-            stack_adj++;
+            targ = PL_stack_sp[-nargs];
         }
-        else
+        else {
             /* for 'expr = ...', expr is the top item on the stack */
-            targ = POPs;
+            targ = *PL_stack_sp;
+            toparg--;
+        }
     }
     else {
         SV **svp = &(PAD_SVl(PL_op->op_targ));
@@ -436,15 +741,9 @@ PP(pp_multiconcat)
         }
         if (!nargs)
             /* $lex .= "const" doesn't cause anything to be pushed */
-            EXTEND(SP,1);
+            rpp_extend(1);
     }
 
-    toparg = SP;
-    SP -= (nargs - 1);
-    dsv           = targ; /* Set the destination for all concats. This is
-                             initially targ; later on, dsv may be switched
-                             to point to a TEMP SV if overloading is
-                             encountered.  */
     grow          = 1;    /* allow for '\0' at minimum */
     targ_count    = 0;
     targ_chain    = NULL;
@@ -465,175 +764,68 @@ PP(pp_multiconcat)
      * Where an arg is actually targ, the stringification is deferred:
      * the length is set to 0, and the slot is added to targ_chain.
      *
-     * If an overloaded arg is found, the loop is abandoned at that point,
-     * and dsv is set to an SvTEMP SV where the results-so-far will be
-     * accumulated.
+     * If a magic, overloaded, or otherwise weird arg is found, which
+     * might have side effects when stringified, the loop is abandoned and
+     * we goto a code block where a more basic 'emulate calling
+     * pp_cpncat() on each arg in turn' is done.
      */
 
-    for (; SP <= toparg; SP++, svpv_end++) {
-        bool simple_flags;
+    for (SV **svp = toparg - (nargs - 1); svp <= toparg; svp++, svpv_end++) {
         U32 utf8;
         STRLEN len;
         SV *sv;
 
         assert(svpv_end - svpv_buf < PERL_MULTICONCAT_MAXARG);
 
-        sv = *SP;
-        simple_flags = (SvFLAGS(sv) & (SVs_GMG|SVf_ROK|SVf_POK)) == SVf_POK;
+        sv = *svp;
 
         /* this if/else chain is arranged so that common/simple cases
          * take few conditionals */
 
-        if (LIKELY(simple_flags && (sv != targ))) {
-            /* common case: sv is a simple PV and not the targ */
-            svpv_end->pv  = SvPVX(sv);
+        if (LIKELY((SvFLAGS(sv) & (SVs_GMG|SVf_ROK|SVf_POK)) == SVf_POK)) {
+            /* common case: sv is a simple non-magical PV */
+            if (targ == sv) {
+                /* targ appears on RHS.
+                 * Delay storing PV pointer; instead, add slot to targ_chain
+                 * so it can be populated later, after targ has been grown and
+                 * we know its final SvPVX() address.
+                 */
+              targ_on_rhs:
+                svpv_end->len = 0; /* zerojng here means we can skip
+                                      updating later if targ_len == 0 */
+                svpv_end->pv  = (char*)targ_chain;
+                targ_chain    = svpv_end;
+                targ_count++;
+                continue;
+            }
+
             len           = SvCUR(sv);
+            svpv_end->pv  = SvPVX(sv);
         }
-        else if (simple_flags) {
-            /* sv is targ (but can't be magic or overloaded).
-             * Delay storing PV pointer; instead, add slot to targ_chain
-             * so it can be populated later, after targ has been grown and
-             * we know its final SvPVX() address.
+        else if (UNLIKELY(SvFLAGS(sv) & (SVs_GMG|SVf_ROK)))
+            /* may have side effects: tie, overload etc.
+             * Abandon 'stringify everything first' and handle
+             * args in strict order. Note that already-stringified args
+             * will be reprocessed, which is safe because the each first
+             * stringification would have been idempotent.
              */
-          targ_on_rhs:
-            svpv_end->len = 0; /* zerojng here means we can skip
-                                  updating later if targ_len == 0 */
-            svpv_end->pv  = (char*)targ_chain;
-            targ_chain    = svpv_end;
-            targ_count++;
-            continue;
-        }
-        else {
-            if (UNLIKELY(SvFLAGS(sv) & (SVs_GMG|SVf_ROK))) {
-                /* its got magic, is tied, and/or is overloaded */
-                SvGETMAGIC(sv);
-
-                if (UNLIKELY(SvAMAGIC(sv))
-                    && !(PL_op->op_private & OPpMULTICONCAT_FAKE))
-                {
-                    /* One of the RHS args is overloaded. Abandon stringifying
-                     * the args at this point, then in the concat loop later
-                     * on, concat the plain args stringified so far into a
-                     * TEMP SV. At the end of this function the remaining
-                     * args (including the current one) will be handled
-                     * specially, using overload calls.
-                     * FAKE implies an optimised sprintf which doesn't use
-                     * concat overloading, only "" overloading.
-                     */
-
-                    if (   svpv_end == svpv_buf + 1
-                           /* no const string segments */
-                        && aux[PERL_MULTICONCAT_IX_LENGTHS].ssize     == -1
-                        && aux[PERL_MULTICONCAT_IX_LENGTHS + 1].ssize == -1
-                    ) {
-                        /* special case: if the overloaded sv is the
-                         * second arg in the concat chain, stop at the
-                         * first arg rather than this, so that
-                         *
-                         *   $arg1 . $arg2
-                         *
-                         * invokes overloading as
-                         *
-                         *    concat($arg2, $arg1, 1)
-                         *
-                         * rather than
-                         *
-                         *    concat($arg2, "$arg1", 1)
-                         *
-                         * This means that if for example arg1 is a ref,
-                         * it gets passed as-is to the concat method
-                         * rather than a stringified copy. If it's not the
-                         * first arg, it doesn't matter, as in $arg0 .
-                         * $arg1 .  $arg2, where the result of ($arg0 .
-                         * $arg1) will already be a string.
-                         * THis isn't perfect: we'll have already
-                         * done SvPV($arg1) on the previous iteration;
-                         * and are now throwing away that result and
-                         * hoping arg1 hasn;t been affected.
-                         */
-                        svpv_end--;
-                        SP--;
-                    }
-
-                  setup_overload:
-                    dsv = newSVpvn_flags("", 0, SVs_TEMP);
-
-                    if (targ_chain) {
-                        /* Get the string value of targ and populate any
-                         * RHS slots which use it */
-                        char *pv = SvPV_nomg(targ, len);
-                        dst_utf8 |= (SvFLAGS(targ) & SVf_UTF8);
-                        grow += len * targ_count;
-                        do {
-                            struct multiconcat_svpv *p = targ_chain;
-                            targ_chain = (struct multiconcat_svpv *)(p->pv);
-                            p->pv  = pv;
-                            p->len = len;
-                        } while (targ_chain);
-                    }
-                    else if (is_append)
-                        SvGETMAGIC(targ);
-
-                    goto phase3;
-                }
-
-                if (SvFLAGS(sv) & SVs_RMG) {
-                    /* probably tied; copy it to guarantee separate values
-                     * each time it's used, e.g. "-$tied-$tied-$tied-",
-                     * since FETCH() isn't necessarily idempotent */
-                    SV *nsv = newSV(0);
-                    sv_setsv_flags(nsv, sv, SV_NOSTEAL);
-                    sv_2mortal(nsv);
-                    if (   sv == targ
-                        && is_append
-                        && nargs == 1
-                        /* no const string segments */
-                        && aux[PERL_MULTICONCAT_IX_LENGTHS].ssize   == -1
-                        && aux[PERL_MULTICONCAT_IX_LENGTHS+1].ssize == -1)
-                    {
-                        /* special-case $tied .= $tied.
-                         *
-                         * For something like
-                         *    sub FETCH { $i++ }
-                         * then
-                         *    $tied .= $tied . $tied . $tied;
-                         * will STORE "4123"
-                         * while
-                         *    $tied .= $tied
-                         * will STORE "12"
-                         *
-                         * i.e. for a single mutator concat, the LHS is
-                         * retrieved first; in all other cases it is
-                         * retrieved last. Whether this is sane behaviour
-                         * is open to debate; but for now, multiconcat (as
-                         * it is an optimisation) tries to reproduce
-                         * existing behaviour.
-                         */
-                        sv_catsv(nsv, sv);
-                        sv_setsv(sv,nsv);
-                        SP++;
-                        goto phase7; /* just return targ as-is */
-                    }
-
-                    sv = nsv;
-                }
-            }
-
-            if (sv == targ) {
-                /* must warn for each RH usage of targ, except that
-                 * we will later get one warning when doing
-                 * SvPV_force(targ), *except* on '.=' */
-                if (   !SvOK(sv)
-                    && (targ_chain || is_append)
-                    && ckWARN(WARN_UNINITIALIZED)
-                )
-                    report_uninit(sv);
-                goto targ_on_rhs;
-            }
-
-            /* stringify general SV */
+            goto do_magical;
+        else if (SvNIOK(sv)) {
+            if (targ == sv)
+              goto targ_on_rhs;
+            /* stringify general valid scalar */
             svpv_end->pv = sv_2pv_flags(sv, &len, 0);
         }
+        else if (!SvOK(sv)) {
+            if (ckWARN(WARN_UNINITIALIZED))
+                /* an undef value in the presence of warnings may trigger
+                 * side affects */
+                goto do_magical;
+            svpv_end->pv = "";
+            len = 0;
+        }
+        else
+            goto do_magical; /* something weird */
 
         utf8 = (SvFLAGS(sv) & SVf_UTF8);
         dst_utf8   |= utf8;
@@ -652,31 +844,9 @@ PP(pp_multiconcat)
      */
 
     if (is_append) {
-        if (UNLIKELY(SvFLAGS(targ) & (SVs_GMG|SVf_ROK))) {
-            SvGETMAGIC(targ); /* must do before SvAMAGIC() check */
-            if (UNLIKELY(SvAMAGIC(targ))) {
-                /* $overloaded .= ....;
-                 * accumulate RHS in a temp SV rather than targ,
-                 * then append tmp to targ at the end using overload
-                 */
-                assert(!targ_chain);
-                dsv = newSVpvn_flags("", 0, SVs_TEMP);
-
-                if (   svpv_end == svpv_buf + 1
-                       /* no const string segments */
-                    && aux[PERL_MULTICONCAT_IX_LENGTHS].ssize == -1
-                ) {
-                    /* special case $overloaded .= $arg1:
-                     * avoid stringifying $arg1.
-                     * Similar to the $arg1 . $arg2 case in phase1
-                     */
-                    svpv_end--;
-                    SP--;
-                }
-
-                goto phase3;
-            }
-        }
+        /* abandon quick route if using targ might have side effects */
+        if (UNLIKELY(SvFLAGS(targ) & (SVs_GMG|SVf_ROK)))
+            goto do_magical;
 
         if (SvOK(targ)) {
             U32 targ_utf8;
@@ -694,6 +864,10 @@ PP(pp_multiconcat)
             grow += targ_len * (targ_count + is_append);
             goto phase3;
         }
+        else if (ckWARN(WARN_UNINITIALIZED))
+            /* warning might have side effects */
+            goto do_magical;
+        /* the undef targ will be silently SvPVCLEAR()ed below */
     }
     else if (UNLIKELY(SvTYPE(targ) >= SVt_REGEXP)) {
         /* Assigning to some weird LHS type. Don't force the LHS to be an
@@ -705,28 +879,19 @@ PP(pp_multiconcat)
          * (which makes the 'F' typeglob an alias to the
          * '*main::F*main::F' typeglob).
          */
-        goto setup_overload;
+        goto do_magical;
     }
-    else if (targ_chain) {
+    else if (targ_chain)
         /* targ was found on RHS.
-         * We don't need the SvGETMAGIC() call and SvAMAGIC() test as
-         * both were already done earlier in the SvPV() loop; other
-         * than that we can share the same code with the append
-         * branch below.
-         * Note that this goto jumps directly into the SvOK() branch
-         * even if targ isn't SvOK(), to force an 'uninitialised'
-         * warning; e.g.
-         *   $undef .= ....           targ only on LHS: don't warn
-         *   $undef .= $undef ....    targ on RHS too:  warn
+         * Force stringify it, using the same code as the append branch
+         * above, except that we don't need the magic/overload/undef
+         * checks as these will already have been done in the phase 1
+         * loop.
          */
-        assert(!SvAMAGIC(targ));
         goto stringify_targ;
-    }
-
 
     /* unrolled SvPVCLEAR() - mostly: no need to grow or set SvCUR() to 0;
      * those will be done later. */
-    assert(targ == dsv);
     SV_CHECK_THINKFIRST_COW_DROP(targ);
     SvUPGRADE(targ, SVt_PV);
     SvFLAGS(targ) &= ~(SVf_OK|SVf_IVisUV|SVf_UTF8);
@@ -737,10 +902,10 @@ PP(pp_multiconcat)
     /* --------------------------------------------------------------
      * Phase 3:
      *
-     * UTF-8 tweaks and grow dsv:
+     * UTF-8 tweaks and grow targ:
      *
      * Now that we know the length and utf8-ness of both the targ and
-     * args, grow dsv to the size needed to accumulate all the args, based
+     * args, grow targ to the size needed to accumulate all the args, based
      * on whether targ appears on the RHS, whether we're appending, and
      * whether any non-utf8 args expand in size if converted to utf8.
      *
@@ -763,7 +928,7 @@ PP(pp_multiconcat)
      *   one set of segment lengths.
      *
      * * If the string has different plain and utf8 representations
-     *   (e.g. "\x80"), then then aux[PERL_MULTICONCAT_IX_PLAIN_PV/LEN]]
+     *   (e.g. "\x80"), then aux[PERL_MULTICONCAT_IX_PLAIN_PV/LEN]]
      *   holds the plain rep, while aux[PERL_MULTICONCAT_IX_UTF8_PV/LEN]
      *   holds the utf8 rep, and there are 2 sets of segment lengths,
      *   with the utf8 set following after the plain set.
@@ -779,7 +944,7 @@ PP(pp_multiconcat)
     /* turn off utf8 handling if 'use bytes' is in scope */
     if (UNLIKELY(dst_utf8 && IN_BYTES)) {
         dst_utf8 = 0;
-        SvUTF8_off(dsv);
+        SvUTF8_off(targ);
         /* undo all the negative lengths which flag utf8-ness */
         for (svpv_p = svpv_buf; svpv_p < svpv_end; svpv_p++) {
             SSize_t len = svpv_p->len;
@@ -810,7 +975,7 @@ PP(pp_multiconcat)
          * calculate how much extra growth is needed for all the chars
          * which will expand to two utf8 bytes.
          * Also, if the growth is non-zero, negate the length to indicate
-         * that this this is a variant string. Conversely, un-negate the
+         * that this is a variant string. Conversely, un-negate the
          * length on utf8 args (which was only needed to flag non-utf8
          * args in this loop */
         for (svpv_p = svpv_buf; svpv_p < svpv_end; svpv_p++) {
@@ -837,16 +1002,16 @@ PP(pp_multiconcat)
 
     /* unrolled SvGROW(), except don't check for SVf_IsCOW, which should
      * already have been dropped */
-    assert(!SvIsCOW(dsv));
-    dsv_pv = (SvLEN(dsv) < (grow) ? sv_grow(dsv,grow) : SvPVX(dsv));
+    assert(!SvIsCOW(targ));
+    targ_pv = (SvLEN(targ) < (grow) ? sv_grow(targ,grow) : SvPVX(targ));
 
 
     /* --------------------------------------------------------------
      * Phase 4:
      *
-     * Now that dsv (which is probably targ) has been grown, we know the
-     * final address of the targ PVX, if needed. Preserve / move targ
-     * contents if appending or if targ appears on RHS.
+     * Now that targ has been grown, we know the final address of the targ
+     * PVX, if needed. Preserve / move targ contents if appending or if
+     * targ appears on RHS.
      *
      * Also update svpv_buf slots in targ_chain.
      *
@@ -869,7 +1034,7 @@ PP(pp_multiconcat)
      * On exit, the targ contents will have been moved to the
      * earliest place they are needed (e.g. $x = "abc$x" will shift them
      * 3 bytes, while $x .= ... will leave them at the beginning);
-     * and dst_pv will point to the location within SvPVX(dsv) where the
+     * and dst_pv will point to the location within SvPVX(targ) where the
      * next arg should be copied.
      */
 
@@ -877,13 +1042,12 @@ PP(pp_multiconcat)
 
     if (targ_len) {
         struct multiconcat_svpv *tc_stop;
-        char *targ_pv = dsv_pv;
+        char *targ_buf = targ_pv; /* ptr to original targ string */
 
-        assert(targ == dsv);
         assert(is_append || targ_count);
 
         if (is_append) {
-            dsv_pv += targ_len;
+            targ_pv += targ_len;
             tc_stop = NULL;
         }
         else {
@@ -924,8 +1088,8 @@ PP(pp_multiconcat)
             }
 
             if (offset) {
-                targ_pv += offset;
-                Move(dsv_pv, targ_pv, targ_len, char);
+                targ_buf += offset;
+                Move(targ_pv, targ_buf, targ_len, char);
                 /* a negative length implies don't Copy(), but do increment */
                 svpv_p->len = -((SSize_t)targ_len);
                 slow_concat = TRUE;
@@ -934,7 +1098,7 @@ PP(pp_multiconcat)
                 /* skip the first targ copy */
                 svpv_base++;
                 const_lens++;
-                dsv_pv += targ_len;
+                targ_pv += targ_len;
             }
 
             /* Don't populate the first targ slot in the loop below; it's
@@ -948,7 +1112,7 @@ PP(pp_multiconcat)
         while (targ_chain != tc_stop) {
             struct multiconcat_svpv *p = targ_chain;
             targ_chain = (struct multiconcat_svpv *)(p->pv);
-            p->pv  = targ_pv;
+            p->pv  = targ_buf;
             p->len = (SSize_t)targ_len;
         }
     }
@@ -957,7 +1121,7 @@ PP(pp_multiconcat)
     /* --------------------------------------------------------------
      * Phase 5:
      *
-     * Append all the args in svpv_buf, plus the const strings, to dsv.
+     * Append all the args in svpv_buf, plus the const strings, to targ.
      *
      * On entry to this section the (pv,len) pairs in svpv_buf have the
      * following meanings:
@@ -965,7 +1129,7 @@ PP(pp_multiconcat)
      *    (pv, -(len+extra)) a plain string which will expand by 'extra'
      *                         bytes when converted to utf8
      *    (0,  -len)         left-most targ, whose content has already
-     *                         been copied. Just advance dsv_pv by len.
+     *                         been copied. Just advance targ_pv by len.
      */
 
     /* If there are no constant strings and no special case args
@@ -976,8 +1140,8 @@ PP(pp_multiconcat)
             SSize_t len = svpv_p->len;
             if (!len)
                 continue;
-            Copy(svpv_p->pv, dsv_pv, len, char);
-            dsv_pv += len;
+            Copy(svpv_p->pv, targ_pv, len, char);
+            targ_pv += len;
         }
         const_lens += (svpv_end - svpv_base + 1);
     }
@@ -985,27 +1149,27 @@ PP(pp_multiconcat)
         /* Note that we iterate the loop nargs+1 times: to append nargs
          * arguments and nargs+1 constant strings. For example, "-$a-$b-"
          */
-        svpv_p = svpv_base - 1;
+        svpv_p = svpv_base;
 
         for (;;) {
             SSize_t len = (const_lens++)->ssize;
 
             /* append next const string segment */
             if (len > 0) {
-                Copy(const_pv, dsv_pv, len, char);
-                dsv_pv   += len;
+                Copy(const_pv, targ_pv, len, char);
+                targ_pv   += len;
                 const_pv += len;
             }
 
-            if (++svpv_p == svpv_end)
+            if (svpv_p == svpv_end)
                 break;
 
             /* append next arg */
             len = svpv_p->len;
 
             if (LIKELY(len > 0)) {
-                Copy(svpv_p->pv, dsv_pv, len, char);
-                dsv_pv += len;
+                Copy(svpv_p->pv, targ_pv, len, char);
+                targ_pv += len;
             }
             else if (UNLIKELY(len < 0)) {
                 /* negative length indicates two special cases */
@@ -1013,141 +1177,265 @@ PP(pp_multiconcat)
                 len = -len;
                 if (UNLIKELY(p)) {
                     /* copy plain-but-variant pv to a utf8 targ */
-                    char * end_pv = dsv_pv + len;
+                    char * end_pv = targ_pv + len;
                     assert(dst_utf8);
-                    while (dsv_pv < end_pv) {
+                    while (targ_pv < end_pv) {
                         U8 c = (U8) *p++;
-                        append_utf8_from_native_byte(c, (U8**)&dsv_pv);
+                        append_utf8_from_native_byte(c, (U8**)&targ_pv);
                     }
                 }
                 else
                     /* arg is already-copied targ */
-                    dsv_pv += len;
+                    targ_pv += len;
             }
 
+            ++svpv_p;
         }
     }
 
-    *dsv_pv = '\0';
-    SvCUR_set(dsv, dsv_pv - SvPVX(dsv));
-    assert(grow >= SvCUR(dsv) + 1);
-    assert(SvLEN(dsv) >= SvCUR(dsv) + 1);
+    *targ_pv = '\0';
+    SvCUR_set(targ, targ_pv - SvPVX(targ));
+    assert(grow >= SvCUR(targ) + 1);
+    assert(SvLEN(targ) >= SvCUR(targ) + 1);
 
     /* --------------------------------------------------------------
      * Phase 6:
      *
-     * Handle overloading. If an overloaded arg or targ was detected
-     * earlier, dsv will have been set to a new mortal, and any args and
-     * consts to the left of the first overloaded arg will have been
-     * accumulated to it. This section completes any further concatenation
-     * steps with overloading handled.
+     * return result
      */
 
-    if (UNLIKELY(dsv != targ)) {
-        SV *res;
+    rpp_popfree_to_NN(PL_stack_sp - stack_adj);
+    SvTAINT(targ);
+    SvSETMAGIC(targ);
+    rpp_push_1(targ);
+    return NORMAL;
 
-        SvFLAGS(dsv) |= dst_utf8;
+    /* --------------------------------------------------------------
+     * Phase 7:
+     *
+     * We only get here if any of the args (or targ too in the case of
+     * append) have something which might cause side effects, such
+     * as magic, overload, or an undef value in the presence of warnings.
+     * In that case, any earlier attempt to stringify the args will have
+     * been abandoned, and we come here instead.
+     *
+     * Here, we concat each arg in turn the old-fashioned way: essentially
+     * emulating pp_concat() in a loop. This means that all the weird edge
+     * cases will be handled correctly, if not necessarily speedily.
+     *
+     * Note that some args may already have been stringified - those are
+     * processed again, which is safe, since only args without side-effects
+     * were stringified earlier.
+     */
 
-        if (SP <= toparg) {
-            /* Stringifying the RHS was abandoned because *SP
-             * is overloaded. dsv contains all the concatted strings
-             * before *SP. Apply the rest of the args using overloading.
+  do_magical:
+    {
+        SSize_t i, n;
+        SV *left = NULL;
+        SV *right;
+        SV* nexttarg;
+        bool nextappend;
+        U32 utf8 = 0;
+        SV **svp;
+        const char    *cpv  = aux[PERL_MULTICONCAT_IX_PLAIN_PV].pv;
+        SV            *csv  = NULL; /* SV which will hold cpv */
+        UNOP_AUX_item *lens = aux + PERL_MULTICONCAT_IX_LENGTHS;
+        Size_t arg_count = 0; /* how many args have been processed */
+
+        if (!cpv) {
+            cpv = aux[PERL_MULTICONCAT_IX_UTF8_PV].pv;
+            utf8 = SVf_UTF8;
+        }
+
+        svp = toparg - nargs + 1;
+
+        /* iterate for:
+         *   nargs arguments,
+         *   plus possible nargs+1 consts,
+         *   plus, if appending, a final targ in an extra last iteration
+         */
+
+        n = nargs *2 + 1;
+        for (i = 0; i <= n; i++) {
+            SSize_t len;
+
+            /* if necessary, stringify the final RHS result in
+             * something like $targ .= "$a$b$c" - simulating
+             * pp_stringify
              */
-            SV *left, *right, *res;
-            int i;
-            bool getmg = FALSE;
-                               /* number of args already concatted */
-            SSize_t n         = (nargs - 1) - (toparg - SP);
-                               /* current arg is either the first
-                                * or second value to be concatted
-                                * (including constant strings), so would
-                                * form part of the first concat */
-            bool first_concat = (    n == 0
-                                 || (n == 1 && const_lens[-2].ssize < 0
-                                            && const_lens[-1].ssize < 0));
-            int  f_assign     = first_concat ? 0 : AMGf_assign;
-
-            left = dsv;
-
-            for (; n < nargs; n++) {
-                /* loop twice, first applying the arg, then the const segment */
-                for (i = 0; i < 2; i++) {
-                    if (i) {
-                        /* append next const string segment */
-                        STRLEN len = (STRLEN)((const_lens++)->ssize);
-                        /* a length of -1 implies no constant string
-                         * rather than a zero-length one, e.g.
-                         * ($a . $b) versus ($a . "" . $b)
-                         */
-                        if ((SSize_t)len < 0)
-                            continue;
+            if (    i == n
+                && (PL_op->op_private &OPpMULTICONCAT_STRINGIFY)
+                && !(SvPOK(left))
+                /* extra conditions for backwards compatibility:
+                 * probably incorrect, but keep the existing behaviour
+                 * for now. The rules are:
+                 *     $x   = "$ov"     single arg: stringify;
+                 *     $x   = "$ov$y"   multiple args: don't stringify,
+                 *     $lex = "$ov$y$z" except TARGMY with at least 2 concats
+                 */
+                && (   arg_count == 1
+                    || (     arg_count >= 3
+                        && !is_append
+                        &&  (PL_op->op_private & OPpTARGET_MY)
+                        && !(PL_op->op_private & OPpLVAL_INTRO)
+                       )
+                   )
+            )
+            {
+                assert(aux[PERL_MULTICONCAT_IX_PADTMP2].pad_offset);
+                SV *tmp = PAD_SV(aux[PERL_MULTICONCAT_IX_PADTMP2].pad_offset);
+                sv_copypv(tmp, left);
+                SvSETMAGIC(tmp);
+                left = tmp;
+            }
 
-                        /* set right to the next constant string segment */
-                        right = newSVpvn_flags(const_pv, len,
-                                                    (dst_utf8 | SVs_TEMP));
-                        const_pv += len;
-                    }
+            /* do one extra iteration to handle $targ in $targ .= ... */
+            if (i == n && !is_append)
+                break;
+
+            /* get the next arg SV or regen the next const SV */
+            len = lens[i >> 1].ssize;
+            if (i == n) {
+                /* handle the final targ .= (....) */
+                right = left;
+                left = targ;
+            }
+            else if (i & 1)
+                right = svp[(i >> 1)];
+            else if (len < 0)
+                continue; /* no const in this position */
+            else {
+                /* Use one of our PADTMPs to fake up the SV which would
+                 * have been returned by an OP_CONST.  Try to reuse it if
+                 * possible. If the refcount has gone up, something like
+                 * overload code has taken a reference to it, so abandon
+                 * it */
+                if (!csv || SvREFCNT(csv) > 1 || SvLEN(csv) != 0) {
+                    if (csv)
+                        csv = newSV_type_mortal(SVt_PV);
                     else {
-                        /* append next arg */
-                        right = *SP++;
-                        if (getmg)
-                            SvGETMAGIC(right);
-                        else
-                            /* SvGETMAGIC already called on this SV just
-                             * before we broke from the loop earlier */
-                            getmg = TRUE;
-
-                        if (first_concat && n == 0 && const_lens[-1].ssize < 0) {
-                            /* nothing before the current arg; repeat the
-                             * loop to get a second arg */
-                            left = right;
-                            first_concat = FALSE;
-                            continue;
-                        }
+                        assert(aux[PERL_MULTICONCAT_IX_PADTMP1].pad_offset);
+                        csv = PAD_SV(
+                                aux[PERL_MULTICONCAT_IX_PADTMP1].pad_offset);
+                        SvUPGRADE(csv, SVt_PV);
                     }
 
-                    if ((SvAMAGIC(left) || SvAMAGIC(right))
-                        && (res = amagic_call(left, right, concat_amg, f_assign))
+                    if (utf8)
+                        SvUTF8_on(csv);
+                    SvREADONLY_on(csv);
+                    SvPOK_on(csv);
+                }
+                /* use the const string buffer directly with the
+                 * SvLEN==0 trick */
+
+                /* cast away constness because we think we know it's safe
+                 * (SvREADONLY) */
+                SvPV_set(csv, (char *)cpv);
+                SvLEN_set(csv, 0);
+                SvCUR_set(csv, len);
+
+                right = csv;
+                cpv += len;
+            }
+
+            arg_count++;
+
+            if (arg_count <= 1) {
+                left = right;
+                continue; /* need at least two SVs to concat together */
+            }
+
+            if (arg_count == 2 && i < n) {
+                /* for the first concat, use one of the PADTMPs to emulate
+                 * the PADTMP from OP_CONST. In later iterations this will
+                 * be appended to */
+                nexttarg = PAD_SV(aux[PERL_MULTICONCAT_IX_PADTMP0].pad_offset);
+                nextappend = FALSE;
+            }
+            else {
+                nexttarg = left;
+                nextappend = TRUE;
+            }
+
+            /* Handle possible overloading.
+             * This is basically an unrolled
+             *     tryAMAGICbin_MG(concat_amg, AMGf_assign);
+             * and
+             *     Perl_try_amagic_bin()
+             * call, but using left and right rather than
+             * PL_stack_sp[-1], PL_stack_sp[0],
+             * and not relying on OPf_STACKED implying .=
+             */
+
+            if ((SvFLAGS(left)|SvFLAGS(right)) & (SVf_ROK|SVs_GMG)) {
+                SvGETMAGIC(left);
+                if (left != right)
+                    SvGETMAGIC(right);
+
+                if ((SvAMAGIC(left) || SvAMAGIC(right))
+                    /* sprintf doesn't do concat overloading,
+                     * but allow for $x .= sprintf(...)
+                     */
+                    && (   !(PL_op->op_private & OPpMULTICONCAT_FAKE)
+                        || i == n)
                     )
-                        left = res;
-                    else {
-                        if (left != dsv) {
-                            sv_setsv(dsv, left);
-                            left = dsv;
+                {
+                    SV * const tmpsv = amagic_call(left, right, concat_amg,
+                                                (nextappend ? AMGf_assign: 0));
+                    if (tmpsv) {
+                        /* NB: tryAMAGICbin_MG() includes an OPpTARGET_MY test
+                         * here, which isn't needed as any implicit
+                         * assign done under OPpTARGET_MY is done after
+                         * this loop */
+                        if (nextappend) {
+                            sv_setsv(left, tmpsv);
+                            SvSETMAGIC(left);
                         }
-                        sv_catsv_nomg(left, right);
+                        else
+                            left = tmpsv;
+                        continue;
                     }
-                    f_assign = AMGf_assign;
+                }
+
+                /* if both args are the same magical value, make one a copy */
+                if (left == right && SvGMAGICAL(left)) {
+                    SV * targetsv = right;
+                    /* Print the uninitialized warning now, so it includes the
+                     * variable name. */
+                    if (!SvOK(right)) {
+                        if (ckWARN(WARN_UNINITIALIZED))
+                            report_uninit(right);
+                        targetsv = &PL_sv_no;
+                    }
+                    left = sv_mortalcopy_flags(targetsv, 0);
+                    SvGETMAGIC(right);
                 }
             }
-            dsv = left;
+
+            /* nexttarg = left . right */
+            S_do_concat(aTHX_ left, right, nexttarg, 0);
+            left = nexttarg;
         }
 
-        /* assign/append RHS (dsv) to LHS (targ) */
-        if (is_append)  {
-            if ((SvAMAGIC(targ) || SvAMAGIC(dsv))
-                && (res = amagic_call(targ, dsv, concat_amg, AMGf_assign))
-            )
-                sv_setsv(targ, res);
-            else
-                sv_catsv_nomg(targ, dsv);
+        /* Return the result of all RHS concats, unless this op includes
+         * an assign ($lex = x.y.z or expr = x.y.z), in which case copy
+         * to target (which will be $lex or expr).
+         * If we are appending, targ will already have been appended to in
+         * the loop */
+        if (  !is_append
+            && (   (PL_op->op_flags   & OPf_STACKED)
+                || (PL_op->op_private & OPpTARGET_MY))
+        ) {
+            sv_setsv(targ, left);
+            SvSETMAGIC(targ);
         }
         else
-            sv_setsv(targ, dsv);
-    }
-
-    /* --------------------------------------------------------------
-     * Phase 7:
-     *
-     * return result
-     */
+            targ = left;
 
-  phase7:
-
-    SP -= stack_adj;
-    SvTAINT(targ);
-    SETTARG;
-    RETURN;
+        rpp_popfree_to_NN(PL_stack_sp - stack_adj);
+        rpp_push_1(targ);
+        return NORMAL;
+    }
 }
 
 
@@ -1157,25 +1445,32 @@ PP(pp_multiconcat)
 STATIC OP*
 S_pushav(pTHX_ AV* const av)
 {
-    dSP;
     const SSize_t maxarg = AvFILL(av) + 1;
-    EXTEND(SP, maxarg);
+    rpp_extend(maxarg);
     if (UNLIKELY(SvRMAGICAL(av))) {
         PADOFFSET i;
         for (i=0; i < (PADOFFSET)maxarg; i++) {
             SV ** const svp = av_fetch(av, i, FALSE);
-            SP[i+1] = svp ? *svp : &PL_sv_undef;
+            rpp_push_1(LIKELY(svp)
+                       ? *svp
+                       : UNLIKELY(PL_op->op_flags & OPf_MOD)
+                          ? av_nonelem(av,i)
+                          : &PL_sv_undef
+            );
         }
     }
     else {
         PADOFFSET i;
         for (i=0; i < (PADOFFSET)maxarg; i++) {
-            SV * const sv = AvARRAY(av)[i];
-            SP[i+1] = LIKELY(sv) ? sv : &PL_sv_undef;
+            SV *sv = AvARRAY(av)[i];
+            rpp_push_1(LIKELY(sv)
+                       ? sv
+                       : UNLIKELY(PL_op->op_flags & OPf_MOD)
+                          ? av_nonelem(av,i)
+                          : &PL_sv_undef
+            );
         }
     }
-    SP += maxarg;
-    PUTBACK;
     return NORMAL;
 }
 
@@ -1184,25 +1479,24 @@ S_pushav(pTHX_ AV* const av)
 
 PP(pp_padrange)
 {
-    dSP;
     PADOFFSET base = PL_op->op_targ;
     int count = (int)(PL_op->op_private) & OPpPADRANGE_COUNTMASK;
     if (PL_op->op_flags & OPf_SPECIAL) {
         /* fake the RHS of my ($x,$y,..) = @_ */
-        PUSHMARK(SP);
+        PUSHMARK(PL_stack_sp);
         (void)S_pushav(aTHX_ GvAVn(PL_defgv));
-        SPAGAIN;
     }
 
     /* note, this is only skipped for compile-time-known void cxt */
     if ((PL_op->op_flags & OPf_WANT) != OPf_WANT_VOID) {
         int i;
 
-        EXTEND(SP, count);
-        PUSHMARK(SP);
+        rpp_extend(count);
+        PUSHMARK(PL_stack_sp);
         for (i = 0; i <count; i++)
-            *++SP = PAD_SV(base+i);
+            rpp_push_1(PAD_SV(base+i));
     }
+
     if (PL_op->op_private & OPpLVAL_INTRO) {
         SV **svp = &(PAD_SVl(base));
         const UV payload = (UV)(
@@ -1223,80 +1517,180 @@ PP(pp_padrange)
         for (i = 0; i <count; i++)
             SvPADSTALE_off(*svp++); /* mark lexical as active */
     }
-    RETURN;
+    return NORMAL;
 }
 
 
 PP(pp_padsv)
 {
-    dSP;
-    EXTEND(SP, 1);
     {
-       OP * const op = PL_op;
-       /* access PL_curpad once */
-       SV ** const padentry = &(PAD_SVl(op->op_targ));
-       {
-           dTARG;
-           TARG = *padentry;
-           PUSHs(TARG);
-           PUTBACK; /* no pop/push after this, TOPs ok */
-       }
-       if (op->op_flags & OPf_MOD) {
-           if (op->op_private & OPpLVAL_INTRO)
-               if (!(op->op_private & OPpPAD_STATE))
-                   save_clearsv(padentry);
-           if (op->op_private & OPpDEREF) {
-               /* TOPs is equivalent to TARG here.  Using TOPs (SP) rather
-                  than TARG reduces the scope of TARG, so it does not
-                  span the call to save_clearsv, resulting in smaller
-                  machine code. */
-               TOPs = vivify_ref(TOPs, op->op_private & OPpDEREF);
-           }
-       }
-       return op->op_next;
+        OP * const op = PL_op;
+        /* access PL_curpad once */
+        SV ** const padentry = &(PAD_SVl(op->op_targ));
+        {
+            dTARG;
+            TARG = *padentry;
+            rpp_xpush_1(TARG);
+        }
+        if (op->op_flags & OPf_MOD) {
+            if (op->op_private & OPpLVAL_INTRO)
+                if (!(op->op_private & OPpPAD_STATE))
+                    save_clearsv(padentry);
+            if (op->op_private & OPpDEREF) {
+                /* *sp is equivalent to TARG here.  Using *sp rather
+                   than TARG reduces the scope of TARG, so it does not
+                   span the call to save_clearsv, resulting in smaller
+                   machine code. */
+                rpp_replace_1_1_NN(
+                    vivify_ref(*PL_stack_sp, op->op_private & OPpDEREF));
+            }
+        }
+        return op->op_next;
     }
 }
 
+
+/* Implement readline(), and also <X> and <<X>> in the cases where X is
+ * seen by the parser as file-handle-ish rather than glob-ish.
+ *
+ * It expects at least one arg: the typeglob or scalar filehandle to read
+ * from. An empty <> isn't handled specially by this op; instead the parser
+ * will have planted a preceding gv(*ARGV) op.
+ *
+ * Scalar assignment is optimised away by making the assignment target be
+ * passed as a second argument, with OPf_STACKED set. For example,
+ *
+ *    $x[$i] = readline($fh);
+ *
+ * is implemented as if written as
+ *
+ *    readline($x[$i], $fh);
+ *
+ * (that is, if the perl-level readline function took two args, which it
+ * doesn't). The 'while (<>) {...}' construct is handled specially by the
+ * parser, but not specially by this op. The parser treats the condition
+ * as
+ *
+ *    defined($_ = <>)
+ *
+ * which is then optimised into the equivalent of
+ *
+ *   defined(readline($_, *ARGV))
+ *
+ * When called as a real function, e.g. (\&CORE::readline)->(*STDIN),
+ * pp_coreargs() will have pushed a NULL if no argument was supplied.
+ *
+ * The parser decides whether '<something>' in the perl src code causes an
+ * OP_GLOB or an OPREADLINE op to be planted.
+ */
+
 PP(pp_readline)
 {
-    dSP;
+    SV *arg = *PL_stack_sp;
+
     /* pp_coreargs pushes a NULL to indicate no args passed to
      * CORE::readline() */
-    if (TOPs) {
-       SvGETMAGIC(TOPs);
-       tryAMAGICunTARGETlist(iter_amg, 0);
-       PL_last_in_gv = MUTABLE_GV(*PL_stack_sp--);
+    if (arg) {
+        SvGETMAGIC(arg);
+
+        /* unrolled tryAMAGICunTARGETlist(iter_amg, 0) */
+        SV *tmpsv;
+        U8 gimme = GIMME_V;
+        if (UNLIKELY(SvAMAGIC(arg) &&
+            (tmpsv = amagic_call(arg, &PL_sv_undef, iter_amg,
+                                 AMGf_want_list | AMGf_noright
+                                |AMGf_unary))))
+        {
+            if (gimme == G_VOID) {
+                NOOP;
+            }
+            else if (gimme == G_LIST) {
+                SSize_t i;
+                SSize_t len;
+                assert(SvTYPE(tmpsv) == SVt_PVAV);
+                len = av_count((AV *)tmpsv);
+                assert(*PL_stack_sp == arg);
+                rpp_popfree_1_NN(); /* pop the original filehhandle arg */
+                /* no assignment target to pop */
+                assert(!(PL_op->op_flags & OPf_STACKED));
+                rpp_extend(len);
+                for (i = 0; i < len; ++i)
+                    /* amagic_call() naughtily doesn't increment the ref counts
+                     * of the items it pushes onto the temporary array. So we
+                     * don't need to decrement them when shifting off. */
+                    rpp_push_1(av_shift((AV *)tmpsv));
+            }
+            else { /* AMGf_want_scalar */
+                /* OPf_STACKED: assignment optimised away and target
+                 * on stack */
+                SV *targ = (PL_op->op_flags & OPf_STACKED)
+                                ? PL_stack_sp[-1]
+                                : PAD_SV(PL_op->op_targ);
+                sv_setsv(targ, tmpsv);
+                SvSETMAGIC(targ);
+                if (PL_op->op_flags & OPf_STACKED) {
+                    rpp_popfree_1_NN();
+                    assert(*PL_stack_sp == targ);
+                }
+                else
+                    rpp_replace_1_1_NN(targ);
+            }
+            return NORMAL;
+        }
+        /* end of unrolled tryAMAGICunTARGETlist */
+
+        PL_last_in_gv = MUTABLE_GV(*PL_stack_sp);
+#ifdef PERL_RC_STACK
+        /* PL_last_in_gv appears to be non-refcounted, so won't keep
+         * GV alive */
+        if (SvREFCNT(PL_last_in_gv) < 2)
+            sv_2mortal((SV*)PL_last_in_gv);
+#endif
+        rpp_popfree_1_NN();
     }
-    else PL_last_in_gv = PL_argvgv, PL_stack_sp--;
+    else {
+        PL_last_in_gv = PL_argvgv;
+        PL_stack_sp--;
+    }
+
+
+    /* is it *FOO, $fh, or 'FOO' ? */
     if (!isGV_with_GP(PL_last_in_gv)) {
-       if (SvROK(PL_last_in_gv) && isGV_with_GP(SvRV(PL_last_in_gv)))
-           PL_last_in_gv = MUTABLE_GV(SvRV(PL_last_in_gv));
-       else {
-           dSP;
-           XPUSHs(MUTABLE_SV(PL_last_in_gv));
-           PUTBACK;
-           Perl_pp_rv2gv(aTHX);
-           PL_last_in_gv = MUTABLE_GV(*PL_stack_sp--);
-            assert((SV*)PL_last_in_gv == &PL_sv_undef || isGV_with_GP(PL_last_in_gv));
-       }
+        if (SvROK(PL_last_in_gv) && isGV_with_GP(SvRV(PL_last_in_gv)))
+            PL_last_in_gv = MUTABLE_GV(SvRV(PL_last_in_gv));
+        else {
+            rpp_xpush_1(MUTABLE_SV(PL_last_in_gv));
+            Perl_pp_rv2gv(aTHX);
+            PL_last_in_gv = MUTABLE_GV(*PL_stack_sp);
+            rpp_popfree_1_NN();
+            assert(   (SV*)PL_last_in_gv == &PL_sv_undef
+                   || isGV_with_GP(PL_last_in_gv));
+        }
     }
+
     return do_readline();
 }
 
+
 PP(pp_eq)
 {
-    dSP;
-    SV *left, *right;
-
-    tryAMAGICbin_MG(eq_amg, AMGf_set|AMGf_numeric);
-    right = POPs;
-    left  = TOPs;
-    SETs(boolSV(
-       (SvIOK_notUV(left) && SvIOK_notUV(right))
-       ? (SvIVX(left) == SvIVX(right))
-       : ( do_ncmp(left, right) == 0)
+    if (rpp_try_AMAGIC_2(eq_amg, AMGf_numeric))
+        return NORMAL;
+
+    SV *right = PL_stack_sp[0];
+    SV *left  = PL_stack_sp[-1];
+
+    U32 flags_and = SvFLAGS(left) & SvFLAGS(right);
+    U32 flags_or  = SvFLAGS(left) | SvFLAGS(right);
+
+    rpp_replace_2_IMM_NN(boolSV(
+        ( (flags_and & SVf_IOK) && ((flags_or & SVf_IVisUV) ==0 ) )
+        ?    (SvIVX(left) == SvIVX(right))
+        : (flags_and & SVf_NOK)
+        ?    (SvNVX(left) == SvNVX(right))
+        : ( do_ncmp(left, right) == 0)
     ));
-    RETURN;
+    return NORMAL;
 }
 
 
@@ -1312,10 +1706,10 @@ PP(pp_preinc)
                 == SVf_IOK))
         && SvIVX(sv) != IV_MAX)
     {
-       SvIV_set(sv, SvIVX(sv) + 1);
+        SvIV_set(sv, SvIVX(sv) + 1);
     }
     else /* Do all the PERL_PRESERVE_IVUV and hard cases in sv_inc */
-       sv_inc(sv);
+        sv_inc(sv);
     SvSETMAGIC(sv);
     return NORMAL;
 }
@@ -1333,10 +1727,10 @@ PP(pp_predec)
                 == SVf_IOK))
         && SvIVX(sv) != IV_MIN)
     {
-       SvIV_set(sv, SvIVX(sv) - 1);
+        SvIV_set(sv, SvIVX(sv) - 1);
     }
     else /* Do all the PERL_PRESERVE_IVUV and hard cases  in sv_dec */
-       sv_dec(sv);
+        sv_dec(sv);
     SvSETMAGIC(sv);
     return NORMAL;
 }
@@ -1346,16 +1740,15 @@ PP(pp_predec)
 
 PP(pp_or)
 {
-    dSP;
     SV *sv;
     PERL_ASYNC_CHECK();
-    sv = TOPs;
+    sv = *PL_stack_sp;
     if (SvTRUE_NN(sv))
-       RETURN;
+        return NORMAL;
     else {
-       if (PL_op->op_type == OP_OR)
-            --SP;
-       RETURNOP(cLOGOP->op_other);
+        if (PL_op->op_type == OP_OR)
+            rpp_popfree_1_NN();
+        return cLOGOP->op_other;
     }
 }
 
@@ -1364,71 +1757,72 @@ PP(pp_or)
 
 PP(pp_defined)
 {
-    dSP;
-    SV* sv;
-    bool defined;
+    SV* sv = *PL_stack_sp;
+    bool defined = FALSE;
     const int op_type = PL_op->op_type;
     const bool is_dor = (op_type == OP_DOR || op_type == OP_DORASSIGN);
 
     if (is_dor) {
-       PERL_ASYNC_CHECK();
-        sv = TOPs;
+        PERL_ASYNC_CHECK();
+        if (UNLIKELY(!sv || !SvANY(sv))) {
+            if (op_type == OP_DOR)
+                rpp_popfree_1();
+            return cLOGOP->op_other;
+        }
+    }
+    else {
+        /* OP_DEFINED */
         if (UNLIKELY(!sv || !SvANY(sv))) {
-           if (op_type == OP_DOR)
-               --SP;
-            RETURNOP(cLOGOP->op_other);
+            rpp_replace_1_1(&PL_sv_no);
+            return NORMAL;
         }
     }
+
+    /* Historically what followed was a switch on SvTYPE(sv), handling SVt_PVAV,
+     * SVt_PVCV, SVt_PVHV and "default". `defined &sub` is still valid syntax,
+     * hence we still need the special case PVCV code. But AVs and HVs now
+     * should never arrive here... */
+#ifdef DEBUGGING
+    assert(SvTYPE(sv) != SVt_PVAV);
+    assert(SvTYPE(sv) != SVt_PVHV);
+#endif
+
+    if (UNLIKELY(SvTYPE(sv) == SVt_PVCV)) {
+        if (CvROOT(sv) || CvXSUB(sv))
+            defined = TRUE;
+    }
     else {
-       /* OP_DEFINED */
-        sv = POPs;
-        if (UNLIKELY(!sv || !SvANY(sv)))
-            RETPUSHNO;
-    }
-
-    defined = FALSE;
-    switch (SvTYPE(sv)) {
-    case SVt_PVAV:
-       if (AvMAX(sv) >= 0 || SvGMAGICAL(sv) || (SvRMAGICAL(sv) && mg_find(sv, PERL_MAGIC_tied)))
-           defined = TRUE;
-       break;
-    case SVt_PVHV:
-       if (HvARRAY(sv) || SvGMAGICAL(sv) || (SvRMAGICAL(sv) && mg_find(sv, PERL_MAGIC_tied)))
-           defined = TRUE;
-       break;
-    case SVt_PVCV:
-       if (CvROOT(sv) || CvXSUB(sv))
-           defined = TRUE;
-       break;
-    default:
-       SvGETMAGIC(sv);
-       if (SvOK(sv))
-           defined = TRUE;
-       break;
+        SvGETMAGIC(sv);
+        if (SvOK(sv))
+            defined = TRUE;
     }
 
     if (is_dor) {
         if(defined) 
-            RETURN; 
+            return NORMAL;
         if(op_type == OP_DOR)
-            --SP;
-        RETURNOP(cLOGOP->op_other);
+            rpp_popfree_1_NN();
+        return cLOGOP->op_other;
     }
     /* assuming OP_DEFINED */
-    if(defined) 
-        RETPUSHYES;
-    RETPUSHNO;
+    rpp_replace_1_IMM_NN(defined ? &PL_sv_yes : &PL_sv_no);
+    return NORMAL;
 }
 
 
 
 PP(pp_add)
 {
-    dSP; dATARGET; bool useleft; SV *svl, *svr;
+    bool useleft; SV *svl, *svr;
+    SV *targ = (PL_op->op_flags & OPf_STACKED)
+                    ? PL_stack_sp[-1]
+                    : PAD_SV(PL_op->op_targ);
+
+    if (rpp_try_AMAGIC_2(add_amg, AMGf_assign|AMGf_numeric))
+        return NORMAL;
 
-    tryAMAGICbin_MG(add_amg, AMGf_assign|AMGf_numeric);
-    svr = TOPs;
-    svl = TOPm1s;
+    svr = PL_stack_sp[0];
+    svl = PL_stack_sp[-1];
 
 #ifdef PERL_PRESERVE_IVUV
 
@@ -1449,10 +1843,8 @@ PP(pp_add)
              * simple integer add: if the top of both numbers
              * are 00  or 11, then it's safe */
             if (!( ((topl+1) | (topr+1)) & 2)) {
-                SP--;
                 TARGi(il + ir, 0); /* args not GMG, so can't be tainted */
-                SETs(TARG);
-                RETURN;
+                goto ret;
             }
             goto generic;
         }
@@ -1461,20 +1853,12 @@ PP(pp_add)
             NV nl = SvNVX(svl);
             NV nr = SvNVX(svr);
 
-            if (
-#if defined(NAN_COMPARE_BROKEN) && defined(Perl_isnan)
-                !Perl_isnan(nl) && nl == (NV)(il = (IV)nl)
-                && !Perl_isnan(nr) && nr == (NV)(ir = (IV)nr)
-#else
-                nl == (NV)(il = (IV)nl) && nr == (NV)(ir = (IV)nr)
-#endif
-                )
+            if (lossless_NV_to_IV(nl, &il) && lossless_NV_to_IV(nr, &ir)) {
                 /* nothing was lost by converting to IVs */
                 goto do_iv;
-            SP--;
+            }
             TARGn(nl + nr, 0); /* args not GMG, so can't be tainted */
-            SETs(TARG);
-            RETURN;
+            goto ret;
         }
     }
 
@@ -1528,101 +1912,103 @@ PP(pp_add)
     */
 
     if (SvIV_please_nomg(svr)) {
-       /* Unless the left argument is integer in range we are going to have to
-          use NV maths. Hence only attempt to coerce the right argument if
-          we know the left is integer.  */
-       UV auv = 0;
-       bool auvok = FALSE;
-       bool a_valid = 0;
-
-       if (!useleft) {
-           auv = 0;
-           a_valid = auvok = 1;
-           /* left operand is undef, treat as zero. + 0 is identity,
-              Could SETi or SETu right now, but space optimise by not adding
-              lots of code to speed up what is probably a rarish case.  */
-       } else {
-           /* Left operand is defined, so is it IV? */
-           if (SvIV_please_nomg(svl)) {
-               if ((auvok = SvUOK(svl)))
-                   auv = SvUVX(svl);
-               else {
-                   const IV aiv = SvIVX(svl);
-                   if (aiv >= 0) {
-                       auv = aiv;
-                       auvok = 1;      /* Now acting as a sign flag.  */
-                   } else {
-                       auv = (aiv == IV_MIN) ? (UV)aiv : (UV)(-aiv);
-                   }
-               }
-               a_valid = 1;
-           }
-       }
-       if (a_valid) {
-           bool result_good = 0;
-           UV result;
-           UV buv;
-           bool buvok = SvUOK(svr);
-       
-           if (buvok)
-               buv = SvUVX(svr);
-           else {
-               const IV biv = SvIVX(svr);
-               if (biv >= 0) {
-                   buv = biv;
-                   buvok = 1;
-               } else
-                    buv = (biv == IV_MIN) ? (UV)biv : (UV)(-biv);
-           }
-           /* ?uvok if value is >= 0. basically, flagged as UV if it's +ve,
-              else "IV" now, independent of how it came in.
-              if a, b represents positive, A, B negative, a maps to -A etc
-              a + b =>  (a + b)
-              A + b => -(a - b)
-              a + B =>  (a - b)
-              A + B => -(a + b)
-              all UV maths. negate result if A negative.
-              add if signs same, subtract if signs differ. */
-
-           if (auvok ^ buvok) {
-               /* Signs differ.  */
-               if (auv >= buv) {
-                   result = auv - buv;
-                   /* Must get smaller */
-                   if (result <= auv)
-                       result_good = 1;
-               } else {
-                   result = buv - auv;
-                   if (result <= buv) {
-                       /* result really should be -(auv-buv). as its negation
-                          of true value, need to swap our result flag  */
-                       auvok = !auvok;
-                       result_good = 1;
-                   }
-               }
-           } else {
-               /* Signs same */
-               result = auv + buv;
-               if (result >= auv)
-                   result_good = 1;
-           }
-           if (result_good) {
-               SP--;
-               if (auvok)
-                   SETu( result );
-               else {
-                   /* Negate result */
-                   if (result <= (UV)IV_MIN)
-                        SETi(result == (UV)IV_MIN
-                                ? IV_MIN : -(IV)result);
-                   else {
-                       /* result valid, but out of range for IV.  */
-                       SETn( -(NV)result );
-                   }
-               }
-               RETURN;
-           } /* Overflow, drop through to NVs.  */
-       }
+        /* Unless the left argument is integer in range we are going to have to
+           use NV maths. Hence only attempt to coerce the right argument if
+           we know the left is integer.  */
+        UV auv = 0;
+        bool auvok = FALSE;
+        bool a_valid = 0;
+
+        if (!useleft) {
+            auv = 0;
+            a_valid = auvok = 1;
+            /* left operand is undef, treat as zero. + 0 is identity,
+               Could TARGi or TARGu right now, but space optimise by not
+               adding lots of code to speed up what is probably a rare-ish
+               case. */
+        } else {
+            /* Left operand is defined, so is it IV? */
+            if (SvIV_please_nomg(svl)) {
+                if ((auvok = SvUOK(svl)))
+                    auv = SvUVX(svl);
+                else {
+                    const IV aiv = SvIVX(svl);
+                    if (aiv >= 0) {
+                        auv = aiv;
+                        auvok = 1;     /* Now acting as a sign flag.  */
+                    } else {
+                        /* Using 0- here and later to silence bogus warning
+                         * from MS VC */
+                        auv = (UV) (0 - (UV) aiv);
+                    }
+                }
+                a_valid = 1;
+            }
+        }
+        if (a_valid) {
+            bool result_good = 0;
+            UV result;
+            UV buv;
+            bool buvok = SvUOK(svr);
+        
+            if (buvok)
+                buv = SvUVX(svr);
+            else {
+                const IV biv = SvIVX(svr);
+                if (biv >= 0) {
+                    buv = biv;
+                    buvok = 1;
+                } else
+                    buv = (UV) (0 - (UV) biv);
+            }
+            /* ?uvok if value is >= 0. basically, flagged as UV if it's +ve,
+               else "IV" now, independent of how it came in.
+               if a, b represents positive, A, B negative, a maps to -A etc
+               a + b =>  (a + b)
+               A + b => -(a - b)
+               a + B =>  (a - b)
+               A + B => -(a + b)
+               all UV maths. negate result if A negative.
+               add if signs same, subtract if signs differ. */
+
+            if (auvok ^ buvok) {
+                /* Signs differ.  */
+                if (auv >= buv) {
+                    result = auv - buv;
+                    /* Must get smaller */
+                    if (result <= auv)
+                        result_good = 1;
+                } else {
+                    result = buv - auv;
+                    if (result <= buv) {
+                        /* result really should be -(auv-buv). as its negation
+                           of true value, need to swap our result flag  */
+                        auvok = !auvok;
+                        result_good = 1;
+                    }
+                }
+            } else {
+                /* Signs same */
+                result = auv + buv;
+                if (result >= auv)
+                    result_good = 1;
+            }
+            if (result_good) {
+                if (auvok)
+                    TARGu(result,1);
+                else {
+                    /* Negate result */
+                    if (result <= (UV)IV_MIN)
+                        TARGi(result == (UV)IV_MIN
+                                ? IV_MIN : -(IV)result, 1);
+                    else {
+                        /* result valid, but out of range for IV.  */
+                        TARGn(-(NV)result, 1);
+                    }
+                }
+                goto ret;
+            } /* Overflow, drop through to NVs.  */
+        }
     }
 
 #else
@@ -1630,16 +2016,19 @@ PP(pp_add)
 #endif
 
     {
-       NV value = SvNV_nomg(svr);
-       (void)POPs;
-       if (!useleft) {
-           /* left operand is undef, treat as zero. + 0.0 is identity. */
-           SETn(value);
-           RETURN;
-       }
-       SETn( value + SvNV_nomg(svl) );
-       RETURN;
+        NV value = SvNV_nomg(svr);
+        if (!useleft) {
+            /* left operand is undef, treat as zero. + 0.0 is identity. */
+            TARGn(value, 1);
+        }
+        else {
+            TARGn(value + SvNV_nomg(svl), 1);
+        }
     }
+
+  ret:
+    rpp_replace_2_1_NN(targ);
+    return NORMAL;
 }
 
 
@@ -1647,9 +2036,8 @@ PP(pp_add)
 
 PP(pp_aelemfast)
 {
-    dSP;
     AV * const av = PL_op->op_type == OP_AELEMFAST_LEX
-       ? MUTABLE_AV(PAD_SV(PL_op->op_targ)) : GvAVn(cGVOP_gv);
+        ? MUTABLE_AV(PAD_SV(PL_op->op_targ)) : GvAVn(cGVOP_gv);
     const U32 lval = PL_op->op_flags & OPf_MOD;
     const I8 key   = (I8)PL_op->op_private;
     SV** svp;
@@ -1657,14 +2045,14 @@ PP(pp_aelemfast)
 
     assert(SvTYPE(av) == SVt_PVAV);
 
-    EXTEND(SP, 1);
-
     /* inlined av_fetch() for simple cases ... */
     if (!SvRMAGICAL(av) && key >= 0 && key <= AvFILLp(av)) {
         sv = AvARRAY(av)[key];
-        if (sv) {
-            PUSHs(sv);
-            RETURN;
+        if (sv)
+            goto ret;
+        if (!lval) {
+            sv = &PL_sv_undef;
+            goto ret;
         }
     }
 
@@ -1676,142 +2064,163 @@ PP(pp_aelemfast)
         DIE(aTHX_ PL_no_aelem, (int)key);
 
     if (!lval && SvRMAGICAL(av) && SvGMAGICAL(sv)) /* see note in pp_helem() */
-       mg_get(sv);
-    PUSHs(sv);
-    RETURN;
+        mg_get(sv);
+
+  ret:
+    rpp_xpush_1(sv);
+    return NORMAL;
 }
 
 PP(pp_join)
 {
-    dSP; dMARK; dTARGET;
+    dMARK; dTARGET;
     MARK++;
-    do_join(TARG, *MARK, MARK, SP);
-    SP = MARK;
-    SETs(TARG);
-    RETURN;
+    do_join(TARG, *MARK, MARK, PL_stack_sp);
+    rpp_popfree_to_NN(MARK - 1);
+    rpp_push_1(TARG);
+    return NORMAL;
 }
 
+
 /* Oversized hot code. */
 
 /* also used for: pp_say() */
 
 PP(pp_print)
 {
-    dSP; dMARK; dORIGMARK;
+    dMARK; dORIGMARK;
     PerlIO *fp;
     MAGIC *mg;
     GV * const gv
-       = (PL_op->op_flags & OPf_STACKED) ? MUTABLE_GV(*++MARK) : PL_defoutgv;
+        = (PL_op->op_flags & OPf_STACKED) ? MUTABLE_GV(*++MARK) : PL_defoutgv;
     IO *io = GvIO(gv);
+    SV *retval = &PL_sv_undef;
 
     if (io
-       && (mg = SvTIED_mg((const SV *)io, PERL_MAGIC_tiedscalar)))
+        && (mg = SvTIED_mg((const SV *)io, PERL_MAGIC_tiedscalar)))
     {
       had_magic:
-       if (MARK == ORIGMARK) {
-           /* If using default handle then we need to make space to
-            * pass object as 1st arg, so move other args up ...
-            */
-           MEXTEND(SP, 1);
-           ++MARK;
-           Move(MARK, MARK + 1, (SP - MARK) + 1, SV*);
-           ++SP;
-       }
-       return Perl_tied_method(aTHX_ SV_CONST(PRINT), mark - 1, MUTABLE_SV(io),
-                               mg,
-                               (G_SCALAR | TIED_METHOD_ARGUMENTS_ON_STACK
-                                | (PL_op->op_type == OP_SAY
-                                   ? TIED_METHOD_SAY : 0)), sp - mark);
+        if (MARK == ORIGMARK) {
+            /* If using default handle then we need to make space to
+             * pass object as 1st arg, so move other args up ...
+             */
+            rpp_extend(1);
+            MARK = ORIGMARK; /* stack may have been realloced */
+            ++MARK;
+            Move(MARK, MARK + 1, (PL_stack_sp - MARK) + 1, SV*);
+            *MARK = NULL;
+            ++PL_stack_sp;
+        }
+        return Perl_tied_method(aTHX_ SV_CONST(PRINT), mark - 1, MUTABLE_SV(io),
+                                mg,
+                                (G_SCALAR | TIED_METHOD_ARGUMENTS_ON_STACK
+                                 | (PL_op->op_type == OP_SAY
+                                    ? TIED_METHOD_SAY : 0)),
+                                PL_stack_sp - mark);
     }
+
     if (!io) {
         if ( gv && GvEGVx(gv) && (io = GvIO(GvEGV(gv)))
-           && (mg = SvTIED_mg((const SV *)io, PERL_MAGIC_tiedscalar)))
+            && (mg = SvTIED_mg((const SV *)io, PERL_MAGIC_tiedscalar)))
             goto had_magic;
-       report_evil_fh(gv);
-       SETERRNO(EBADF,RMS_IFI);
-       goto just_say_no;
+        report_evil_fh(gv);
+        SETERRNO(EBADF,RMS_IFI);
+        goto just_say_no;
     }
     else if (!(fp = IoOFP(io))) {
-       if (IoIFP(io))
-           report_wrongway_fh(gv, '<');
-       else
-           report_evil_fh(gv);
-       SETERRNO(EBADF,IoIFP(io)?RMS_FAC:RMS_IFI);
-       goto just_say_no;
+        if (IoIFP(io))
+            report_wrongway_fh(gv, '<');
+        else
+            report_evil_fh(gv);
+        SETERRNO(EBADF,IoIFP(io)?RMS_FAC:RMS_IFI);
+        goto just_say_no;
     }
     else {
-       SV * const ofs = GvSV(PL_ofsgv); /* $, */
-       MARK++;
-       if (ofs && (SvGMAGICAL(ofs) || SvOK(ofs))) {
-           while (MARK <= SP) {
-               if (!do_print(*MARK, fp))
-                   break;
-               MARK++;
-               if (MARK <= SP) {
-                   /* don't use 'ofs' here - it may be invalidated by magic callbacks */
-                   if (!do_print(GvSV(PL_ofsgv), fp)) {
-                       MARK--;
-                       break;
-                   }
-               }
-           }
-       }
-       else {
-           while (MARK <= SP) {
-               if (!do_print(*MARK, fp))
-                   break;
-               MARK++;
-           }
-       }
-       if (MARK <= SP)
-           goto just_say_no;
-       else {
-           if (PL_op->op_type == OP_SAY) {
-               if (PerlIO_write(fp, "\n", 1) == 0 || PerlIO_error(fp))
-                   goto just_say_no;
-           }
+        SV * const ofs = GvSV(PL_ofsgv); /* $, */
+        MARK++;
+        if (ofs && (SvGMAGICAL(ofs) || SvOK(ofs))) {
+            while (MARK <= PL_stack_sp) {
+                if (!do_print(*MARK, fp))
+                    break;
+                MARK++;
+                if (MARK <= PL_stack_sp) {
+                    /* don't use 'ofs' here - it may be invalidated by magic callbacks */
+                    if (!do_print(GvSV(PL_ofsgv), fp)) {
+                        MARK--;
+                        break;
+                    }
+                }
+            }
+        }
+        else {
+            while (MARK <= PL_stack_sp) {
+                if (!do_print(*MARK, fp))
+                    break;
+                MARK++;
+            }
+        }
+        if (MARK <= PL_stack_sp)
+            goto just_say_no;
+        else {
+            if (PL_op->op_type == OP_SAY) {
+                if (PerlIO_write(fp, "\n", 1) == 0 || PerlIO_error(fp))
+                    goto just_say_no;
+            }
             else if (PL_ors_sv && SvOK(PL_ors_sv))
-               if (!do_print(PL_ors_sv, fp)) /* $\ */
-                   goto just_say_no;
+                if (!do_print(PL_ors_sv, fp)) /* $\ */
+                    goto just_say_no;
 
-           if (IoFLAGS(io) & IOf_FLUSH)
-               if (PerlIO_flush(fp) == EOF)
-                   goto just_say_no;
-       }
+            if (IoFLAGS(io) & IOf_FLUSH)
+                if (PerlIO_flush(fp) == EOF)
+                    goto just_say_no;
+        }
     }
-    SP = ORIGMARK;
-    XPUSHs(&PL_sv_yes);
-    RETURN;
+    retval = &PL_sv_yes;
 
   just_say_no:
-    SP = ORIGMARK;
-    XPUSHs(&PL_sv_undef);
-    RETURN;
+    rpp_popfree_to_NN(ORIGMARK);
+    if ((PL_op->op_flags & OPf_WANT) != OPf_WANT_VOID)
+        rpp_xpush_IMM(retval);
+    return NORMAL;
 }
 
 
 /* do the common parts of pp_padhv() and pp_rv2hv()
- * It assumes the caller has done EXTEND(SP, 1) or equivalent.
+ * It assumes the caller has done rpp_extend(1) or equivalent.
  * 'is_keys' indicates the OPpPADHV_ISKEYS/OPpRV2HV_ISKEYS flag is set.
  * 'has_targ' indicates that the op has a target - this should
  * be a compile-time constant so that the code can constant-folded as
- * appropriate
+ * appropriate. has_targ also implies that the caller has left an
+ * arg on the stack which needs freeing.
  * */
 
 PERL_STATIC_INLINE OP*
 S_padhv_rv2hv_common(pTHX_ HV *hv, U8 gimme, bool is_keys, bool has_targ)
 {
-    bool is_tied;
-    bool is_bool;
-    MAGIC *mg;
-    dSP;
-    IV  i;
-    SV *sv;
-
     assert(PL_op->op_type == OP_PADHV || PL_op->op_type == OP_RV2HV);
 
-    if (gimme == G_ARRAY) {
-        hv_pushkv(hv, 3);
+    if (gimme == G_LIST) {
+        /* push all (key,value) pairs onto stack */
+        if (has_targ) { /* i.e. if has arg still on stack */
+#ifdef PERL_RC_STACK
+            SSize_t sp_base = PL_stack_sp - PL_stack_base;
+            hv_pushkv(hv, 3);
+            /* Now safe to free the original arg on the stack and shuffle
+             * down one place anything pushed on top of it */
+            SSize_t nitems = PL_stack_sp - (PL_stack_base + sp_base);
+            SV *old_sv = PL_stack_sp[-nitems];
+            if (nitems)
+                Move(PL_stack_sp - nitems + 1,
+                     PL_stack_sp - nitems,    nitems, SV*);
+            PL_stack_sp--;
+            SvREFCNT_dec_NN(old_sv);
+#else
+            rpp_popfree_1_NN();
+            hv_pushkv(hv, 3);
+#endif
+        }
+        else
+            hv_pushkv(hv, 3);
         return NORMAL;
     }
 
@@ -1819,41 +2228,71 @@ S_padhv_rv2hv_common(pTHX_ HV *hv, U8 gimme, bool is_keys, bool has_targ)
         /* 'keys %h' masquerading as '%h': reset iterator */
         (void)hv_iterinit(hv);
 
-    if (gimme == G_VOID)
+    if (gimme == G_VOID) {
+        if (has_targ)
+            rpp_popfree_1_NN();
         return NORMAL;
+    }
 
-    is_bool = (     PL_op->op_private & OPpTRUEBOOL
-              || (  PL_op->op_private & OPpMAYBE_TRUEBOOL
-                  && block_gimme() == G_VOID));
-    is_tied = SvRMAGICAL(hv) && (mg = mg_find(MUTABLE_SV(hv), PERL_MAGIC_tied));
+    bool is_bool = (     PL_op->op_private & OPpTRUEBOOL
+                   || (  PL_op->op_private & OPpMAYBE_TRUEBOOL
+                      && block_gimme() == G_VOID));
 
-    if (UNLIKELY(is_tied)) {
+    MAGIC *is_tied_mg = SvRMAGICAL(hv)
+                        ? mg_find(MUTABLE_SV(hv), PERL_MAGIC_tied)
+                        : NULL;
+
+    IV  i = 0;
+    SV *sv = NULL;
+    if (UNLIKELY(is_tied_mg)) {
         if (is_keys && !is_bool) {
             i = 0;
             while (hv_iternext(hv))
                 i++;
+            /* hv finished with. Safe to free arg now */
+            if (has_targ)
+                rpp_popfree_1_NN();
             goto push_i;
         }
         else {
-            sv = magic_scalarpack(hv, mg);
-            goto push_sv;
+            sv = magic_scalarpack(hv, is_tied_mg);
+            /* hv finished with. Safe to free arg now */
+            if (has_targ)
+                rpp_popfree_1_NN();
+            rpp_push_1(sv);
         }
     }
     else {
+#if defined(DYNAMIC_ENV_FETCH) && defined(VMS)
+        /* maybe nothing set up %ENV for iteration yet...
+           do this always (not just if HvUSEDKEYS(hv) is currently 0) because
+           we ought to give a *consistent* answer to "how many keys?"
+           whether we ask this op in scalar context, or get the list of all
+           keys then check its length, and whether we do either with or without
+           an %ENV lookup first. prime_env_iter() returns quickly if nothing
+           needs doing. */
+        if (SvRMAGICAL((const SV *)hv)
+            && mg_find((const SV *)hv, PERL_MAGIC_env)) {
+            prime_env_iter();
+        }
+#endif
         i = HvUSEDKEYS(hv);
+
+        /* hv finished with. Safe to free arg now */
+        if (has_targ)
+            rpp_popfree_1_NN();
+
         if (is_bool) {
-            sv = i ? &PL_sv_yes : &PL_sv_zero;
-          push_sv:
-            PUSHs(sv);
+            rpp_push_IMM(i ? &PL_sv_yes : &PL_sv_zero);
         }
         else {
           push_i:
             if (has_targ) {
                 dTARGET;
-                PUSHi(i);
+                TARGi(i,1);
+                rpp_push_1(targ);
             }
             else
-#ifdef PERL_OP_PARENT
             if (is_keys) {
                 /* parent op should be an unused OP_KEYS whose targ we can
                  * use */
@@ -1864,15 +2303,14 @@ S_padhv_rv2hv_common(pTHX_ HV *hv, U8 gimme, bool is_keys, bool has_targ)
                 k = PL_op->op_sibparent;
                 assert(k->op_type == OP_KEYS);
                 TARG = PAD_SV(k->op_targ);
-                PUSHi(i);
+                TARGi(i,1);
+                rpp_push_1(targ);
             }
             else
-#endif
-                mPUSHi(i);
+                rpp_push_1_norc(newSViv(i));
         }
     }
 
-    PUTBACK;
     return NORMAL;
 }
 
@@ -1880,61 +2318,71 @@ S_padhv_rv2hv_common(pTHX_ HV *hv, U8 gimme, bool is_keys, bool has_targ)
 /* This is also called directly by pp_lvavref.  */
 PP(pp_padav)
 {
-    dSP; dTARGET;
+    dTARGET;
     U8 gimme;
+
     assert(SvTYPE(TARG) == SVt_PVAV);
     if (UNLIKELY( PL_op->op_private & OPpLVAL_INTRO ))
-       if (LIKELY( !(PL_op->op_private & OPpPAD_STATE) ))
-           SAVECLEARSV(PAD_SVl(PL_op->op_targ));
-    EXTEND(SP, 1);
+        if (LIKELY( !(PL_op->op_private & OPpPAD_STATE) ))
+            SAVECLEARSV(PAD_SVl(PL_op->op_targ));
 
-    if (PL_op->op_flags & OPf_REF) {
-       PUSHs(TARG);
-       RETURN;
-    }
-    else if (PL_op->op_private & OPpMAYBE_LVSUB) {
+
+    if (PL_op->op_flags & OPf_REF)
+        goto ret;
+
+    if (PL_op->op_private & OPpMAYBE_LVSUB) {
         const I32 flags = is_lvalue_sub();
         if (flags && !(flags & OPpENTERSUB_INARGS)) {
-           if (GIMME_V == G_SCALAR)
+            if (GIMME_V == G_SCALAR)
                 /* diag_listed_as: Can't return %s to lvalue scalar context */
                 Perl_croak(aTHX_ "Can't return array to lvalue scalar context");
-            PUSHs(TARG);
-            RETURN;
+            goto ret;
        }
     }
 
     gimme = GIMME_V;
-    if (gimme == G_ARRAY)
+    if (gimme == G_LIST)
         return S_pushav(aTHX_ (AV*)TARG);
 
-    if (gimme == G_SCALAR) {
-       const SSize_t maxarg = AvFILL(MUTABLE_AV(TARG)) + 1;
+    if (gimme == G_VOID)
+        return NORMAL;
+
+    {
+        const SSize_t maxarg = AvFILL(MUTABLE_AV(TARG)) + 1;
+        rpp_extend(1);
         if (!maxarg)
-            PUSHs(&PL_sv_zero);
+            targ = &PL_sv_zero;
         else if (PL_op->op_private & OPpTRUEBOOL)
-            PUSHs(&PL_sv_yes);
-        else
-            mPUSHi(maxarg);
+            targ = &PL_sv_yes;
+        else {
+            rpp_push_1_norc(newSViv(maxarg));
+            return NORMAL;
+        }
+        rpp_push_IMM(targ);
+        return NORMAL;
     }
-    RETURN;
+
+  ret:
+    rpp_xpush_1(targ);
+    return NORMAL;
 }
 
 
 PP(pp_padhv)
 {
-    dSP; dTARGET;
+    dTARGET;
     U8 gimme;
 
     assert(SvTYPE(TARG) == SVt_PVHV);
     if (UNLIKELY( PL_op->op_private & OPpLVAL_INTRO ))
-       if (LIKELY( !(PL_op->op_private & OPpPAD_STATE) ))
-           SAVECLEARSV(PAD_SVl(PL_op->op_targ));
+        if (LIKELY( !(PL_op->op_private & OPpPAD_STATE) ))
+            SAVECLEARSV(PAD_SVl(PL_op->op_targ));
 
-    EXTEND(SP, 1);
+    rpp_extend(1);
 
     if (PL_op->op_flags & OPf_REF) {
-        PUSHs(TARG);
-       RETURN;
+        rpp_push_1(TARG);
+        return NORMAL;
     }
     else if (PL_op->op_private & OPpMAYBE_LVSUB) {
         const I32 flags = is_lvalue_sub();
@@ -1942,8 +2390,8 @@ PP(pp_padhv)
             if (GIMME_V == G_SCALAR)
                 /* diag_listed_as: Can't return %s to lvalue scalar context */
                 Perl_croak(aTHX_ "Can't return hash to lvalue scalar context");
-            PUSHs(TARG);
-            RETURN;
+            rpp_push_1(TARG);
+            return NORMAL;
         }
     }
 
@@ -1960,90 +2408,105 @@ PP(pp_padhv)
 
 PP(pp_rv2av)
 {
-    dSP; dTOPss;
+    SV *sv = *PL_stack_sp;
     const U8 gimme = GIMME_V;
     static const char an_array[] = "an ARRAY";
     static const char a_hash[] = "a HASH";
     const bool is_pp_rv2av = PL_op->op_type == OP_RV2AV
-                         || PL_op->op_type == OP_LVAVREF;
+                          || PL_op->op_type == OP_LVAVREF;
     const svtype type = is_pp_rv2av ? SVt_PVAV : SVt_PVHV;
 
     SvGETMAGIC(sv);
     if (SvROK(sv)) {
-       if (UNLIKELY(SvAMAGIC(sv))) {
-           sv = amagic_deref_call(sv, is_pp_rv2av ? to_av_amg : to_hv_amg);
-       }
-       sv = SvRV(sv);
-       if (UNLIKELY(SvTYPE(sv) != type))
-           /* diag_listed_as: Not an ARRAY reference */
-           DIE(aTHX_ "Not %s reference", is_pp_rv2av ? an_array : a_hash);
-       else if (UNLIKELY(PL_op->op_flags & OPf_MOD
-               && PL_op->op_private & OPpLVAL_INTRO))
-           Perl_croak(aTHX_ "%s", PL_no_localize_ref);
+        if (UNLIKELY(SvAMAGIC(sv))) {
+            sv = amagic_deref_call(sv, is_pp_rv2av ? to_av_amg : to_hv_amg);
+        }
+        sv = SvRV(sv);
+        if (UNLIKELY(SvTYPE(sv) != type))
+            /* diag_listed_as: Not an ARRAY reference */
+            DIE(aTHX_ "Not %s reference", is_pp_rv2av ? an_array : a_hash);
+        else if (UNLIKELY(PL_op->op_flags & OPf_MOD
+                && PL_op->op_private & OPpLVAL_INTRO))
+            Perl_croak(aTHX_ "%s", PL_no_localize_ref);
     }
     else if (UNLIKELY(SvTYPE(sv) != type)) {
-           GV *gv;
-       
-           if (!isGV_with_GP(sv)) {
-               gv = Perl_softref2xv(aTHX_ sv, is_pp_rv2av ? an_array : a_hash,
-                                    type, &sp);
-               if (!gv)
-                   RETURN;
-           }
-           else {
-               gv = MUTABLE_GV(sv);
-           }
-           sv = is_pp_rv2av ? MUTABLE_SV(GvAVn(gv)) : MUTABLE_SV(GvHVn(gv));
-           if (PL_op->op_private & OPpLVAL_INTRO)
-               sv = is_pp_rv2av ? MUTABLE_SV(save_ary(gv)) : MUTABLE_SV(save_hash(gv));
+            GV *gv;
+        
+            if (!isGV_with_GP(sv)) {
+                gv = Perl_softref2xv(aTHX_ sv, is_pp_rv2av ? an_array : a_hash,
+                                     type);
+                if (!gv)
+                    return NORMAL;
+            }
+            else {
+                gv = MUTABLE_GV(sv);
+            }
+            sv = is_pp_rv2av ? MUTABLE_SV(GvAVn(gv)) : MUTABLE_SV(GvHVn(gv));
+            if (PL_op->op_private & OPpLVAL_INTRO)
+                sv = is_pp_rv2av ? MUTABLE_SV(save_ary(gv)) : MUTABLE_SV(save_hash(gv));
     }
     if (PL_op->op_flags & OPf_REF) {
-               SETs(sv);
-               RETURN;
+        rpp_replace_1_1_NN(sv);
+        return NORMAL;
     }
     else if (UNLIKELY(PL_op->op_private & OPpMAYBE_LVSUB)) {
-             const I32 flags = is_lvalue_sub();
-             if (flags && !(flags & OPpENTERSUB_INARGS)) {
-               if (gimme != G_ARRAY)
-                   goto croak_cant_return;
-               SETs(sv);
-               RETURN;
-             }
+              const I32 flags = is_lvalue_sub();
+              if (flags && !(flags & OPpENTERSUB_INARGS)) {
+                if (gimme != G_LIST)
+                    goto croak_cant_return;
+                rpp_replace_1_1_NN(sv);
+                return NORMAL;
+              }
     }
 
     if (is_pp_rv2av) {
-       AV *const av = MUTABLE_AV(sv);
-
-       if (gimme == G_ARRAY) {
-            SP--;
-            PUTBACK;
+        AV *const av = MUTABLE_AV(sv);
+
+        if (gimme == G_LIST) {
+#ifdef PERL_RC_STACK
+            SSize_t sp_base = PL_stack_sp - PL_stack_base;
+            (void)S_pushav(aTHX_ av);
+            /* Now safe to free the original arg on the stack and shuffle
+             * down one place anything pushed on top of it */
+            SSize_t nitems = PL_stack_sp - (PL_stack_base + sp_base);
+            SV *old_sv = PL_stack_sp[-nitems];
+            if (nitems)
+                Move(PL_stack_sp - nitems + 1,
+                     PL_stack_sp - nitems,    nitems, SV*);
+            PL_stack_sp--;
+            SvREFCNT_dec_NN(old_sv);
+            return NORMAL;
+#else
+            rpp_popfree_1_NN();
             return S_pushav(aTHX_ av);
-       }
+#endif
+        }
 
-       if (gimme == G_SCALAR) {
-           const SSize_t maxarg = AvFILL(av) + 1;
+        if (gimme == G_SCALAR) {
+            const SSize_t maxarg = AvFILL(av) + 1;
             if (PL_op->op_private & OPpTRUEBOOL)
-                SETs(maxarg ? &PL_sv_yes : &PL_sv_zero);
+                rpp_replace_1_IMM_NN(maxarg ? &PL_sv_yes : &PL_sv_zero);
             else {
                 dTARGET;
-                SETi(maxarg);
+                TARGi(maxarg, 1);
+                rpp_replace_1_1_NN(targ);
             }
-       }
+        }
     }
     else {
-        SP--; PUTBACK;
+        /* this static function is responsible for popping sv off stack */
         return S_padhv_rv2hv_common(aTHX_ (HV*)sv, gimme,
                         cBOOL(PL_op->op_private & OPpRV2HV_ISKEYS),
                         1 /* has_targ*/);
     }
-    RETURN;
+    return NORMAL;
 
  croak_cant_return:
     Perl_croak(aTHX_ "Can't return %s to lvalue scalar context",
-              is_pp_rv2av ? "array" : "hash");
-    RETURN;
+               is_pp_rv2av ? "array" : "hash");
 }
 
+
 STATIC void
 S_do_oddball(pTHX_ SV **oddkey, SV **firstkey)
 {
@@ -2051,18 +2514,18 @@ S_do_oddball(pTHX_ SV **oddkey, SV **firstkey)
 
     if (*oddkey) {
         if (ckWARN(WARN_MISC)) {
-           const char *err;
-           if (oddkey == firstkey &&
-               SvROK(*oddkey) &&
-               (SvTYPE(SvRV(*oddkey)) == SVt_PVAV ||
-                SvTYPE(SvRV(*oddkey)) == SVt_PVHV))
-           {
-               err = "Reference found where even-sized list expected";
-           }
-           else
-               err = "Odd number of elements in hash assignment";
-           Perl_warner(aTHX_ packWARN(WARN_MISC), "%s", err);
-       }
+            const char *err;
+            if (oddkey == firstkey &&
+                SvROK(*oddkey) &&
+                (SvTYPE(SvRV(*oddkey)) == SVt_PVAV ||
+                 SvTYPE(SvRV(*oddkey)) == SVt_PVHV))
+            {
+                err = "Reference found where even-sized list expected";
+            }
+            else
+                err = "Odd number of elements in hash assignment";
+            Perl_warner(aTHX_ packWARN(WARN_MISC), "%s", err);
+        }
 
     }
 }
@@ -2076,8 +2539,8 @@ S_do_oddball(pTHX_ SV **oddkey, SV **firstkey)
  * For example in ($a,$b) = ($b,$a), assigning the value of the first RHS
  * element ($b) to the first LH element ($a), modifies $a; when the
  * second assignment is done, the second RH element now has the wrong
- * value. So we initially replace the RHS with ($b, mortalcopy($a)).
- * Note that we don't need to make a mortal copy of $b.
+ * value. So we initially replace the RHS with ($b, copy($a)).
+ * Note that we don't need to make a copy of $b.
  *
  * The algorithm below works by, for every RHS element, mark the
  * corresponding LHS target element with SVf_BREAK. Then if the RHS
@@ -2103,7 +2566,6 @@ S_aassign_copy_common(pTHX_ SV **firstlelem, SV **lastlelem,
 #endif
 )
 {
-    dVAR;
     SV **relem;
     SV **lelem;
     SSize_t lcount = lastlelem - firstlelem + 1;
@@ -2153,7 +2615,14 @@ S_aassign_copy_common(pTHX_ SV **firstlelem, SV **lastlelem,
                 lcount = -1;
                 lelem--; /* no need to unmark this element */
             }
-            else if (!(do_rc1 && SvREFCNT(svl) == 1) && !SvIMMORTAL(svl)) {
+            else if (!(do_rc1 &&
+#ifdef PERL_RC_STACK
+                            SvREFCNT(svl) <= 2
+#else
+                            SvREFCNT(svl) == 1
+#endif
+                      ) && !SvIMMORTAL(svl))
+            {
                 SvFLAGS(svl) |= SVf_BREAK;
                 marked = TRUE;
             }
@@ -2185,6 +2654,11 @@ S_aassign_copy_common(pTHX_ SV **firstlelem, SV **lastlelem,
 
             TAINT_NOT; /* Each item is independent */
 
+#ifndef PERL_RC_STACK
+            /* The TODO test was eventually commented out. It's now been
+             * revived, but only on PERL_RC_STACK builds. Continue
+             * this hacky workaround otherwise - DAPM Sept 2023 */
+
             /* Dear TODO test in t/op/sort.t, I love you.
                (It's relying on a panic, not a "semi-panic" from newSVsv()
                and then an assertion failure below.)  */
@@ -2192,6 +2666,8 @@ S_aassign_copy_common(pTHX_ SV **firstlelem, SV **lastlelem,
                 Perl_croak(aTHX_ "panic: attempt to copy freed scalar %p",
                            (void*)svr);
             }
+#endif
+
             /* avoid break flag while copying; otherwise COW etc
              * disabled... */
             SvFLAGS(svr) &= ~SVf_BREAK;
@@ -2203,8 +2679,14 @@ S_aassign_copy_common(pTHX_ SV **firstlelem, SV **lastlelem,
                count bump.  (Although I suspect that the SV won't be
                stealable here anyway - DAPM).
                */
+#ifdef PERL_RC_STACK
+            *relem = newSVsv_flags(svr,
+                        SV_GMAGIC|SV_DO_COW_SVSETSV|SV_NOSTEAL);
+            SvREFCNT_dec_NN(svr);
+#else
             *relem = sv_mortalcopy_flags(svr,
                                 SV_GMAGIC|SV_DO_COW_SVSETSV|SV_NOSTEAL);
+#endif
             /* ... but restore afterwards in case it's needed again,
              * e.g. ($a,$b,$c) = (1,$a,$a)
              */
@@ -2228,10 +2710,104 @@ S_aassign_copy_common(pTHX_ SV **firstlelem, SV **lastlelem,
 }
 
 
+/* Helper function for pp_aassign(): after performing something like
+ *
+ *   ($<,$>) = ($>,$<);  # swap real and effective uids
+ *
+ * the assignment to the magic variables just sets various flags in
+ * PL_delaymagic; now we tell the OS to update the uids/gids atomically.
+ */
+
+STATIC void
+S_aassign_uid(pTHX)
+{
+    /* Will be used to set PL_tainting below */
+    Uid_t tmp_uid  = PerlProc_getuid();
+    Uid_t tmp_euid = PerlProc_geteuid();
+    Gid_t tmp_gid  = PerlProc_getgid();
+    Gid_t tmp_egid = PerlProc_getegid();
+
+    /* XXX $> et al currently silently ignore failures */
+    if (PL_delaymagic & DM_UID) {
+#ifdef HAS_SETRESUID
+        PERL_UNUSED_RESULT(
+           setresuid((PL_delaymagic & DM_RUID) ? PL_delaymagic_uid  : (Uid_t)-1,
+                     (PL_delaymagic & DM_EUID) ? PL_delaymagic_euid : (Uid_t)-1,
+                     (Uid_t)-1));
+#elif defined(HAS_SETREUID)
+        PERL_UNUSED_RESULT(
+            setreuid((PL_delaymagic & DM_RUID) ? PL_delaymagic_uid  : (Uid_t)-1,
+                     (PL_delaymagic & DM_EUID) ? PL_delaymagic_euid : (Uid_t)-1));
+#else
+#  ifdef HAS_SETRUID
+        if ((PL_delaymagic & DM_UID) == DM_RUID) {
+            PERL_UNUSED_RESULT(setruid(PL_delaymagic_uid));
+            PL_delaymagic &= ~DM_RUID;
+        }
+#  endif /* HAS_SETRUID */
+#  ifdef HAS_SETEUID
+        if ((PL_delaymagic & DM_UID) == DM_EUID) {
+            PERL_UNUSED_RESULT(seteuid(PL_delaymagic_euid));
+            PL_delaymagic &= ~DM_EUID;
+        }
+#  endif /* HAS_SETEUID */
+        if (PL_delaymagic & DM_UID) {
+            if (PL_delaymagic_uid != PL_delaymagic_euid)
+                Perl_die(aTHX_ "No setreuid available");
+            PERL_UNUSED_RESULT(PerlProc_setuid(PL_delaymagic_uid));
+        }
+#endif /* HAS_SETRESUID */
+
+        tmp_uid  = PerlProc_getuid();
+        tmp_euid = PerlProc_geteuid();
+    }
+
+    /* XXX $> et al currently silently ignore failures */
+    if (PL_delaymagic & DM_GID) {
+#ifdef HAS_SETRESGID
+        PERL_UNUSED_RESULT(
+            setresgid((PL_delaymagic & DM_RGID) ? PL_delaymagic_gid  : (Gid_t)-1,
+                      (PL_delaymagic & DM_EGID) ? PL_delaymagic_egid : (Gid_t)-1,
+                      (Gid_t)-1));
+#elif defined(HAS_SETREGID)
+        PERL_UNUSED_RESULT(
+            setregid((PL_delaymagic & DM_RGID) ? PL_delaymagic_gid  : (Gid_t)-1,
+                     (PL_delaymagic & DM_EGID) ? PL_delaymagic_egid : (Gid_t)-1));
+#else
+#  ifdef HAS_SETRGID
+        if ((PL_delaymagic & DM_GID) == DM_RGID) {
+            PERL_UNUSED_RESULT(setrgid(PL_delaymagic_gid));
+            PL_delaymagic &= ~DM_RGID;
+        }
+#  endif /* HAS_SETRGID */
+#  ifdef HAS_SETEGID
+        if ((PL_delaymagic & DM_GID) == DM_EGID) {
+            PERL_UNUSED_RESULT(setegid(PL_delaymagic_egid));
+            PL_delaymagic &= ~DM_EGID;
+        }
+#  endif /* HAS_SETEGID */
+        if (PL_delaymagic & DM_GID) {
+            if (PL_delaymagic_gid != PL_delaymagic_egid)
+                Perl_die(aTHX_ "No setregid available");
+            PERL_UNUSED_RESULT(PerlProc_setgid(PL_delaymagic_gid));
+        }
+#endif /* HAS_SETRESGID */
+
+        tmp_gid  = PerlProc_getgid();
+        tmp_egid = PerlProc_getegid();
+    }
+    TAINTING_set( TAINTING_get | (tmp_uid && (tmp_euid != tmp_uid || tmp_egid != tmp_gid)) );
+#ifdef NO_TAINT_SUPPORT
+    PERL_UNUSED_VAR(tmp_uid);
+    PERL_UNUSED_VAR(tmp_euid);
+    PERL_UNUSED_VAR(tmp_gid);
+    PERL_UNUSED_VAR(tmp_egid);
+#endif
+}
+
 
 PP(pp_aassign)
 {
-    dVAR; dSP;
     SV **lastlelem = PL_stack_sp;
     SV **lastrelem = PL_stack_base + POPMARK;
     SV **firstrelem = PL_stack_base + POPMARK + 1;
@@ -2240,7 +2816,7 @@ PP(pp_aassign)
     SV **relem;
     SV **lelem;
     U8 gimme;
-    /* PL_delaymagic is restored by JUMPENV_POP on dieing, so we
+    /* PL_delaymagic is restored by JMPENV_POP on dieing, so we
      * only need to save locally, not on the save stack */
     U16 old_delaymagic = PL_delaymagic;
 #ifdef DEBUGGING
@@ -2269,7 +2845,13 @@ PP(pp_aassign)
                 /* skip the scan if all scalars have a ref count of 1 */
                 for (lelem = firstlelem; lelem <= lastlelem; lelem++) {
                     SV *sv = *lelem;
-                    if (!sv || SvREFCNT(sv) == 1)
+                    if (!sv ||
+#ifdef PERL_RC_STACK
+                        SvREFCNT(sv) <= 2
+#else
+                        SvREFCNT(sv) == 1
+#endif
+                    )
                         continue;
                     if (SvTYPE(sv) != SVt_PVAV && SvTYPE(sv) != SVt_PVAV)
                         goto do_scan;
@@ -2300,31 +2882,39 @@ PP(pp_aassign)
 #endif
 
     gimme = GIMME_V;
+    bool is_list = (gimme == G_LIST);
     relem = firstrelem;
     lelem = firstlelem;
+#ifdef PERL_RC_STACK
+    /* Where we can reset stack to at the end, without needing to free
+     * each element. This is normally all the lelem's, but it can vary for
+     * things like odd number of hash elements, which pushes a
+     * &PL_sv_undef into the 'lvalue' part of the stack.
+     */
+    SV ** first_discard = firstlelem;
+#endif
 
     if (relem > lastrelem)
         goto no_relems;
 
     /* first lelem loop while there are still relems */
     while (LIKELY(lelem <= lastlelem)) {
-       bool alias = FALSE;
-       SV *lsv = *lelem++;
+        bool alias = FALSE;
+        SV *lsv = *lelem;
 
         TAINT_NOT; /* Each item stands on its own, taintwise. */
 
         assert(relem <= lastrelem);
-       if (UNLIKELY(!lsv)) {
-           alias = TRUE;
-           lsv = *lelem++;
-           ASSUME(SvTYPE(lsv) == SVt_PVAV);
-       }
-
-       switch (SvTYPE(lsv)) {
-       case SVt_PVAV: {
+        if (UNLIKELY(!lsv)) {
+            alias = TRUE;
+            lsv = *++lelem;
+            ASSUME(SvTYPE(lsv) == SVt_PVAV);
+        }
+
+        switch (SvTYPE(lsv)) {
+        case SVt_PVAV: {
             SV **svp;
             SSize_t i;
-            SSize_t tmps_base;
             SSize_t nelems = lastrelem - relem + 1;
             AV *ary = MUTABLE_AV(lsv);
 
@@ -2377,23 +2967,40 @@ PP(pp_aassign)
              * @a = ($a[0]) case, but the current implementation uses the
              * same algorithm regardless, so ignores that flag. (It *is*
              * used in the hash branch below, however).
-            */
+             *
+             *
+             * The net effect of this next block of code (apart from
+             * optimisations and aliasing) is to make a copy of each
+             * *relem and store the new SV both in the array and back on
+             * the *relem slot of the stack, overwriting the original.
+             * This new list of SVs will later be either returned
+             * (G_LIST), or popped.
+             *
+             * Note that under PERL_RC_STACK builds most of this
+             * complexity can be thrown away: things can be kept alive on
+             * the argument stack without involving the temps stack. In
+             * particular, the args are kept on the argument stack and
+             * processed from there, rather than their pointers being
+             * copied to the temps stack and then processed from there.
+             */
 
+#ifndef PERL_RC_STACK
             /* Reserve slots for ary, plus the elems we're about to copy,
              * then protect ary and temporarily void the remaining slots
              * with &PL_sv_undef */
             EXTEND_MORTAL(nelems + 1);
             PL_tmps_stack[++PL_tmps_ix] = SvREFCNT_inc_simple_NN(ary);
-            tmps_base = PL_tmps_ix + 1;
+            SSize_t tmps_base = PL_tmps_ix + 1;
             for (i = 0; i < nelems; i++)
                 PL_tmps_stack[tmps_base + i] = &PL_sv_undef;
             PL_tmps_ix += nelems;
+#endif
 
             /* Make a copy of each RHS elem and save on the tmps_stack
              * (or pass through where we can optimise away the copy) */
 
             if (UNLIKELY(alias)) {
-                U32 lval = (gimme == G_ARRAY)
+                U32 lval = (is_list)
                                 ? (PL_op->op_flags & OPf_MOD || LVRET) : 0;
                 for (svp = relem; svp <= lastrelem; svp++) {
                     SV *rsv = *svp;
@@ -2405,71 +3012,123 @@ PP(pp_aassign)
                    /* diag_listed_as: Assigned value is not %s reference */
                         DIE(aTHX_
                            "Assigned value is not a SCALAR reference");
-                    if (lval)
-                        *svp = rsv = sv_mortalcopy(rsv);
+                    if (lval) {
+                        /* XXX the 'mortal' part here is probably
+                         * unnecessary under PERL_RC_STACK.
+                         */
+                        rsv = sv_mortalcopy(rsv);
+                        rpp_replace_at_NN(svp, rsv);
+                    }
                     /* XXX else check for weak refs?  */
+#ifndef PERL_RC_STACK
                     rsv = SvREFCNT_inc_NN(SvRV(rsv));
                     assert(tmps_base <= PL_tmps_max);
                     PL_tmps_stack[tmps_base++] = rsv;
+#endif
                 }
             }
             else {
                 for (svp = relem; svp <= lastrelem; svp++) {
                     SV *rsv = *svp;
 
-                    if (SvTEMP(rsv) && !SvGMAGICAL(rsv) && SvREFCNT(rsv) == 1) {
+                    if (rpp_is_lone(rsv) && !SvGMAGICAL(rsv)) {
                         /* can skip the copy */
+#ifndef PERL_RC_STACK
                         SvREFCNT_inc_simple_void_NN(rsv);
+#endif
                         SvTEMP_off(rsv);
                     }
                     else {
                         SV *nsv;
-                        /* do get before newSV, in case it dies and leaks */
-                        SvGETMAGIC(rsv);
-                        nsv = newSV(0);
                         /* see comment in S_aassign_copy_common about
                          * SV_NOSTEAL */
-                        sv_setsv_flags(nsv, rsv,
-                                (SV_DO_COW_SVSETSV|SV_NOSTEAL));
-                        rsv = *svp = nsv;
+                        nsv = newSVsv_flags(rsv,
+                                (SV_DO_COW_SVSETSV|SV_NOSTEAL|SV_GMAGIC));
+#ifdef PERL_RC_STACK
+                        rpp_replace_at_norc_NN(svp, nsv);
+#else
+                        /* using rpp_replace_at_norc() would mortalise,
+                         * but we're manually adding nsv to the tmps stack
+                         * below already */
+                        rpp_replace_at_NN(svp, nsv);
+#endif
+
+                        rsv = nsv;
                     }
 
+#ifndef PERL_RC_STACK
                     assert(tmps_base <= PL_tmps_max);
                     PL_tmps_stack[tmps_base++] = rsv;
+#endif
                 }
             }
 
             if (SvRMAGICAL(ary) || AvFILLp(ary) >= 0) /* may be non-empty */
                 av_clear(ary);
 
-            /* store in the array, the SVs that are in the tmps stack */
+            /* Store in the array, the argument copies that are in the
+             * tmps stack (or for PERL_RC_STACK, on the args stack) */
 
+#ifndef PERL_RC_STACK
             tmps_base -= nelems;
-
-            if (SvMAGICAL(ary) || SvREADONLY(ary) || !AvREAL(ary)) {
+#endif
+            if (alias || SvMAGICAL(ary) || SvREADONLY(ary) || !AvREAL(ary)) {
                 /* for arrays we can't cheat with, use the official API */
                 av_extend(ary, nelems - 1);
                 for (i = 0; i < nelems; i++) {
-                    SV **svp = &(PL_tmps_stack[tmps_base + i]);
+                    SV **svp =
+#ifdef PERL_RC_STACK
+                        &relem[i];
+#else
+                        &(PL_tmps_stack[tmps_base + i]);
+#endif
+
                     SV *rsv = *svp;
+#ifdef PERL_RC_STACK
+                    if (alias) {
+                        assert(SvROK(rsv));
+                        rsv = SvRV(rsv);
+                    }
+#endif
+
                     /* A tied store won't take ownership of rsv, so keep
                      * the 1 refcnt on the tmps stack; otherwise disarm
                      * the tmps stack entry */
                     if (av_store(ary, i, rsv))
+#ifdef PERL_RC_STACK
+                        SvREFCNT_inc_simple_NN(rsv);
+#else
                         *svp = &PL_sv_undef;
+#endif
                     /* av_store() may have added set magic to rsv */;
                     SvSETMAGIC(rsv);
                 }
+#ifndef PERL_RC_STACK
                 /* disarm ary refcount: see comments below about leak */
                 PL_tmps_stack[tmps_base - 1] = &PL_sv_undef;
+#endif
             }
             else {
-                /* directly access/set the guts of the AV */
+                /* Simple array: directly access/set the guts of the AV */
                 SSize_t fill = nelems - 1;
                 if (fill > AvMAX(ary))
                     av_extend_guts(ary, fill, &AvMAX(ary), &AvALLOC(ary),
                                     &AvARRAY(ary));
                 AvFILLp(ary) = fill;
+#ifdef PERL_RC_STACK
+                Copy(relem, AvARRAY(ary), nelems, SV*);
+                /* ownership of one ref count of each elem passed to
+                 * array. Quietly remove old SVs from stack, or if need
+                 * to keep the list on the stack too, bump the count */
+                if (UNLIKELY(is_list))
+                    for (i = 0; i < nelems; i++)
+                        SvREFCNT_inc_void_NN(relem[i]);
+                else {
+                    assert(first_discard == relem + nelems);
+                    Zero(relem, nelems, SV*);
+                    first_discard = relem;
+                }
+#else
                 Copy(&(PL_tmps_stack[tmps_base]), AvARRAY(ary), nelems, SV*);
                 /* Quietly remove all the SVs from the tmps stack slots,
                  * since ary has now taken ownership of the refcnt.
@@ -2481,29 +3140,48 @@ PP(pp_aassign)
                          PL_tmps_ix - (tmps_base + nelems) + 1,
                          SV*);
                 PL_tmps_ix -= (nelems + 1);
+#endif
             }
 
-           if (UNLIKELY(PL_delaymagic & DM_ARRAY_ISA))
+            if (UNLIKELY(PL_delaymagic & DM_ARRAY_ISA))
                 /* its assumed @ISA set magic can't die and leak ary */
-               SvSETMAGIC(MUTABLE_SV(ary));
-            SvREFCNT_dec_NN(ary);
+                SvSETMAGIC(MUTABLE_SV(ary));
 
+#ifdef PERL_RC_STACK
+            assert(*lelem == (SV*)ary);
+            *lelem = NULL;
+#endif
+            lelem++;
+            SvREFCNT_dec_NN(ary);
             relem = lastrelem + 1;
-           goto no_relems;
+            goto no_relems;
         }
 
-       case SVt_PVHV: {                                /* normal hash */
+        case SVt_PVHV: {                               /* normal hash */
 
             SV **svp;
-            bool dirty_tmps;
             SSize_t i;
-            SSize_t tmps_base;
             SSize_t nelems = lastrelem - relem + 1;
             HV *hash = MUTABLE_HV(lsv);
 
             if (UNLIKELY(nelems & 1)) {
                 do_oddball(lastrelem, relem);
                 /* we have firstlelem to reuse, it's not needed any more */
+#ifdef PERL_RC_STACK
+                if (lelem == lastrelem + 1) {
+                    /* the lelem slot we want to use is the 
+                     * one keeping hash alive. Mortalise the hash
+                     * so it doesn't leak */
+                    assert(lastrelem[1] == (SV*)hash);
+                    sv_2mortal((SV*)hash);
+                }
+                else {
+                    /* safe to repurpose old lelem slot */
+                    assert(!lastrelem[1] || SvIMMORTAL(lastrelem[1]));
+                }
+                first_discard++;
+                assert(first_discard = lastrelem + 2);
+#endif
                 *++lastrelem = &PL_sv_undef;
                 nelems++;
             }
@@ -2515,7 +3193,8 @@ PP(pp_aassign)
              * copied (except for the SvTEMP optimisation), since they
              * need to be stored in the hash; while keys are only
              * processed where they might get prematurely freed or
-             * whatever. */
+             * whatever. The same comments about simplifying under
+             * PERL_RC_STACK apply here too */
 
             /* tmps stack slots:
              * * reserve a slot for the hash keepalive;
@@ -2524,16 +3203,19 @@ PP(pp_aassign)
              *   later;
              * then protect hash and temporarily void the remaining
              * value slots with &PL_sv_undef */
+#ifndef PERL_RC_STACK
             EXTEND_MORTAL(nelems + 1);
-
+#endif
              /* convert to number of key/value pairs */
              nelems >>= 1;
 
+#ifndef PERL_RC_STACK
             PL_tmps_stack[++PL_tmps_ix] = SvREFCNT_inc_simple_NN(hash);
-            tmps_base = PL_tmps_ix + 1;
+            SSize_t tmps_base = PL_tmps_ix + 1;
             for (i = 0; i < nelems; i++)
                 PL_tmps_stack[tmps_base + i] = &PL_sv_undef;
             PL_tmps_ix += nelems;
+#endif
 
             /* Make a copy of each RHS hash value and save on the tmps_stack
              * (or pass through where we can optimise away the copy) */
@@ -2541,40 +3223,56 @@ PP(pp_aassign)
             for (svp = relem + 1; svp <= lastrelem; svp += 2) {
                 SV *rsv = *svp;
 
-                if (SvTEMP(rsv) && !SvGMAGICAL(rsv) && SvREFCNT(rsv) == 1) {
+                if (rpp_is_lone(rsv) && !SvGMAGICAL(rsv)) {
                     /* can skip the copy */
+#ifndef PERL_RC_STACK
                     SvREFCNT_inc_simple_void_NN(rsv);
+#endif
                     SvTEMP_off(rsv);
                 }
                 else {
                     SV *nsv;
-                    /* do get before newSV, in case it dies and leaks */
-                    SvGETMAGIC(rsv);
-                    nsv = newSV(0);
                     /* see comment in S_aassign_copy_common about
                      * SV_NOSTEAL */
-                    sv_setsv_flags(nsv, rsv,
-                            (SV_DO_COW_SVSETSV|SV_NOSTEAL));
-                    rsv = *svp = nsv;
+                    nsv = newSVsv_flags(rsv,
+                            (SV_DO_COW_SVSETSV|SV_NOSTEAL|SV_GMAGIC));
+#ifdef PERL_RC_STACK
+                    rpp_replace_at_norc_NN(svp, nsv);
+#else
+                    /* using rpp_replace_at_norc() would mortalise,
+                     * but we're manually adding nsv to the tmps stack
+                     * below already */
+                    rpp_replace_at_NN(svp, nsv);
+#endif
+                    rsv = nsv;
                 }
 
+#ifndef PERL_RC_STACK
                 assert(tmps_base <= PL_tmps_max);
                 PL_tmps_stack[tmps_base++] = rsv;
+#endif
             }
+
+#ifndef PERL_RC_STACK
             tmps_base -= nelems;
+#endif
 
 
             /* possibly protect keys */
 
-            if (UNLIKELY(gimme == G_ARRAY)) {
+            if (UNLIKELY(is_list)) {
                 /* handle e.g.
                 *     @a = ((%h = ($$r, 1)), $r = "x");
                 *     $_++ for %h = (1,2,3,4);
                 */
+#ifndef PERL_RC_STACK
                 EXTEND_MORTAL(nelems);
-                for (svp = relem; svp <= lastrelem; svp += 2)
-                    *svp = sv_mortalcopy_flags(*svp,
-                                SV_GMAGIC|SV_DO_COW_SVSETSV|SV_NOSTEAL);
+#endif
+                for (svp = relem; svp <= lastrelem; svp += 2) {
+                    rpp_replace_at_norc_NN(svp,
+                        newSVsv_flags(*svp,
+                                SV_GMAGIC|SV_DO_COW_SVSETSV|SV_NOSTEAL));
+                }
             }
             else if (PL_op->op_private & OPpASSIGN_COMMON_AGG) {
                 /* for possible commonality, e.g.
@@ -2588,14 +3286,29 @@ PP(pp_aassign)
                  * cases, not just under OPpASSIGN_COMMON_AGG, but in
                  * practice, !OPpASSIGN_COMMON_AGG implies only
                  * constants or padtmps on the RHS.
+                 *
+                 * For PERL_RC_STACK, no danger of premature frees, so
+                 * just handle the magic.
                  */
+#ifdef PERL_RC_STACK
+                for (svp = relem; svp <= lastrelem; svp += 2) {
+                    SV *rsv = *svp;
+                    if (UNLIKELY(SvGMAGICAL(rsv)))
+                        /* XXX does this actually need to be copied, or
+                         * could we just call the get magic??? */
+                        rpp_replace_at_norc_NN(svp,
+                            newSVsv_flags(rsv,
+                                SV_GMAGIC|SV_DO_COW_SVSETSV|SV_NOSTEAL));
+                }
+#else
                 EXTEND_MORTAL(nelems);
                 for (svp = relem; svp <= lastrelem; svp += 2) {
                     SV *rsv = *svp;
                     if (UNLIKELY(SvGMAGICAL(rsv))) {
                         SSize_t n;
-                        *svp = sv_mortalcopy_flags(*svp,
-                                SV_GMAGIC|SV_DO_COW_SVSETSV|SV_NOSTEAL);
+                        rpp_replace_at_norc_NN(svp,
+                            newSVsv_flags(rsv,
+                                SV_GMAGIC|SV_DO_COW_SVSETSV|SV_NOSTEAL));
                         /* allow other branch to continue pushing
                          * onto tmps stack without checking each time */
                         n = (lastrelem - relem) >> 1;
@@ -2605,16 +3318,23 @@ PP(pp_aassign)
                         PL_tmps_stack[++PL_tmps_ix] =
                                     SvREFCNT_inc_simple_NN(rsv);
                 }
+#endif
             }
 
             if (SvRMAGICAL(hash) || HvUSEDKEYS(hash))
                 hv_clear(hash);
 
-            /* now assign the keys and values to the hash */
+            /* "nelems" was converted to the number of pairs earlier. */
+            if (nelems > PERL_HASH_DEFAULT_HvMAX) {
+                hv_ksplit(hash, nelems);
+            }
 
-            dirty_tmps = FALSE;
+            /* now assign the keys and values to the hash */
 
-            if (UNLIKELY(gimme == G_ARRAY)) {
+#ifndef PERL_RC_STACK
+            bool dirty_tmps = FALSE;
+#endif
+            if (UNLIKELY(is_list)) {
                 /* @a = (%h = (...)) etc */
                 SV **svp;
                 SV **topelem = relem;
@@ -2628,19 +3348,24 @@ PP(pp_aassign)
                          * stack location if we encountered dups earlier,
                          * The values will be updated later
                          */
-                        *topelem = key;
+                        rpp_replace_at_NN(topelem, key);
                         topelem += 2;
                     }
                     /* A tied store won't take ownership of val, so keep
                      * the 1 refcnt on the tmps stack; otherwise disarm
                      * the tmps stack entry */
                     if (hv_store_ent(hash, key, val, 0))
+#ifdef PERL_RC_STACK
+                        SvREFCNT_inc_simple_NN(val);
+#else
                         PL_tmps_stack[tmps_base + i] = &PL_sv_undef;
                     else
                         dirty_tmps = TRUE;
+#endif
                     /* hv_store_ent() may have added set magic to val */;
                     SvSETMAGIC(val);
                 }
+
                 if (topelem < svp) {
                     /* at this point we have removed the duplicate key/value
                      * pairs from the stack, but the remaining values may be
@@ -2652,7 +3377,8 @@ PP(pp_aassign)
                     while (relem < lastrelem) {
                         HE *he;
                         he = hv_fetch_ent(hash, *relem++, 0, 0);
-                        *relem++ = (he ? HeVAL(he) : &PL_sv_undef);
+                        rpp_replace_at_NN(relem++,
+                            (he ? HeVAL(he) : &PL_sv_undef));
                     }
                 }
             }
@@ -2661,15 +3387,46 @@ PP(pp_aassign)
                 for (i = 0, svp = relem; svp <= lastrelem; i++, svp++) {
                     SV *key = *svp++;
                     SV *val = *svp;
+#ifdef PERL_RC_STACK
+                    {
+                        HE *stored = hv_store_ent(hash, key, val, 0);
+                        /* hv_store_ent() may have added set magic to val */;
+                        SvSETMAGIC(val);
+                        /* remove key and val from stack */
+                        *svp = NULL;
+                        if (!stored)
+                            SvREFCNT_dec_NN(val);
+                        svp[-1] = NULL;
+                        SvREFCNT_dec_NN(key);
+                    }
+#else
                     if (hv_store_ent(hash, key, val, 0))
                         PL_tmps_stack[tmps_base + i] = &PL_sv_undef;
                     else
                         dirty_tmps = TRUE;
                     /* hv_store_ent() may have added set magic to val */;
                     SvSETMAGIC(val);
+#endif
                 }
+#ifdef PERL_RC_STACK
+                /* now that all the key and val slots on the stack have
+                 * been discarded, we can skip freeing them on return */
+                assert(first_discard == lastrelem + 1);
+                first_discard = relem;
+#endif
             }
 
+#ifdef PERL_RC_STACK
+            /* Disarm the ref-counted pointer on the stack. This will
+             * usually point to the hash, except for the case of an odd
+             * number of elems where the hash was mortalised and its slot
+             * on the stack was made part of the relems with the slot's
+             * value overwritten with &PL_sv_undef. */
+            if (*lelem == (SV*)hash) {
+                *lelem = NULL;
+                SvREFCNT_dec_NN(hash);
+            }
+#else
             if (dirty_tmps) {
                 /* there are still some 'live' recounts on the tmps stack
                  * - usually caused by storing into a tied hash. So let
@@ -2692,17 +3449,16 @@ PP(pp_aassign)
             }
 
             SvREFCNT_dec_NN(hash);
-
+#endif
+            lelem++;
             relem = lastrelem + 1;
-           goto no_relems;
-       }
-
-       default:
-           if (!SvIMMORTAL(lsv)) {
-                SV *ref;
+            goto no_relems;
+        }
 
+        default:
+            if (!SvIMMORTAL(lsv)) {
                 if (UNLIKELY(
-                  SvTEMP(lsv) && !SvSMAGICAL(lsv) && SvREFCNT(lsv) == 1 &&
+                    rpp_is_lone(lsv) && !SvSMAGICAL(lsv) &&
                   (!isGV_with_GP(lsv) || SvFAKE(lsv)) && ckWARN(WARN_MISC)
                 ))
                     Perl_warner(aTHX_
@@ -2710,11 +3466,13 @@ PP(pp_aassign)
                       "Useless assignment to a temporary"
                     );
 
+#ifndef PERL_RC_STACK
                 /* avoid freeing $$lsv if it might be needed for further
                  * elements, e.g. ($ref, $foo) = (1, $$ref) */
+                SV *ref;
                 if (   SvROK(lsv)
                     && ( ((ref = SvRV(lsv)), SvREFCNT(ref)) == 1)
-                    && lelem <= lastlelem
+                    && lelem < lastlelem
                 ) {
                     SSize_t ix;
                     SvREFCNT_inc_simple_void_NN(ref);
@@ -2723,17 +3481,24 @@ PP(pp_aassign)
                     if (UNLIKELY(ix >= PL_tmps_max))
                         /* speculatively grow enough to cover other
                          * possible refs */
-                         (void)tmps_grow_p(ix + (lastlelem - lelem));
+                         (void)tmps_grow_p(ix + (lastlelem - lelem + 1));
                     PL_tmps_stack[ix] = ref;
                 }
+#endif
 
                 sv_setsv(lsv, *relem);
-                *relem = lsv;
                 SvSETMAGIC(lsv);
+                if (UNLIKELY(is_list))
+                    rpp_replace_at_NN(relem, lsv);
+#ifdef PERL_RC_STACK
+                *lelem = NULL;
+                SvREFCNT_dec_NN(lsv);
+#endif
             }
+            lelem++;
             if (++relem > lastrelem)
                 goto no_relems;
-           break;
+            break;
         } /* switch */
     } /* while */
 
@@ -2742,17 +3507,17 @@ PP(pp_aassign)
 
     /* simplified lelem loop for when there are no relems left */
     while (LIKELY(lelem <= lastlelem)) {
-       SV *lsv = *lelem++;
+        SV *lsv = *lelem;
 
         TAINT_NOT; /* Each item stands on its own, taintwise. */
 
-       if (UNLIKELY(!lsv)) {
-           lsv = *lelem++;
-           ASSUME(SvTYPE(lsv) == SVt_PVAV);
-       }
+        if (UNLIKELY(!lsv)) {
+            lsv = *++lelem;
+            ASSUME(SvTYPE(lsv) == SVt_PVAV);
+        }
 
-       switch (SvTYPE(lsv)) {
-       case SVt_PVAV:
+        switch (SvTYPE(lsv)) {
+        case SVt_PVAV:
             if (SvRMAGICAL(lsv) || AvFILLp((SV*)lsv) >= 0) {
                 av_clear((AV*)lsv);
                 if (UNLIKELY(PL_delaymagic & DM_ARRAY_ISA))
@@ -2760,134 +3525,100 @@ PP(pp_aassign)
             }
             break;
 
-       case SVt_PVHV:
+        case SVt_PVHV:
             if (SvRMAGICAL(lsv) || HvUSEDKEYS((HV*)lsv))
                 hv_clear((HV*)lsv);
             break;
 
-       default:
-           if (!SvIMMORTAL(lsv)) {
+        default:
+            if (!SvIMMORTAL(lsv)) {
                 sv_set_undef(lsv);
                 SvSETMAGIC(lsv);
-                *relem++ = lsv;
             }
-           break;
+            if (UNLIKELY(is_list)) {
+                /* this usually grows the list of relems to be returned
+                 * into the stack space holding lelems (unless
+                 * there was previously a hash with dup elements) */
+#ifdef PERL_RC_STACK
+                assert(relem <= first_discard);
+                assert(relem <= lelem);
+                if (relem == first_discard)
+                    first_discard++;
+#endif
+                rpp_replace_at(relem++, lsv);
+#ifdef PERL_RC_STACK
+                if (relem == lelem + 1) {
+                    lelem++;
+                    /* skip the NULLing of the slot */
+                    continue;
+                }
+#endif
+            }
+            break;
         } /* switch */
+#ifdef PERL_RC_STACK
+        *lelem = NULL;
+        SvREFCNT_dec_NN(lsv);
+#endif
+        lelem++;
     } /* while */
 
     TAINT_NOT; /* result of list assign isn't tainted */
 
-    if (UNLIKELY(PL_delaymagic & ~DM_DELAY)) {
-       /* Will be used to set PL_tainting below */
-       Uid_t tmp_uid  = PerlProc_getuid();
-       Uid_t tmp_euid = PerlProc_geteuid();
-       Gid_t tmp_gid  = PerlProc_getgid();
-       Gid_t tmp_egid = PerlProc_getegid();
+    if (UNLIKELY(PL_delaymagic & ~DM_DELAY))
+        /* update system UIDs and/or GIDs */
+        S_aassign_uid(aTHX);
+    PL_delaymagic = old_delaymagic;
 
-        /* XXX $> et al currently silently ignore failures */
-       if (PL_delaymagic & DM_UID) {
-#ifdef HAS_SETRESUID
-           PERL_UNUSED_RESULT(
-               setresuid((PL_delaymagic & DM_RUID) ? PL_delaymagic_uid  : (Uid_t)-1,
-                         (PL_delaymagic & DM_EUID) ? PL_delaymagic_euid : (Uid_t)-1,
-                         (Uid_t)-1));
-#elif defined(HAS_SETREUID)
-            PERL_UNUSED_RESULT(
-                setreuid((PL_delaymagic & DM_RUID) ? PL_delaymagic_uid  : (Uid_t)-1,
-                         (PL_delaymagic & DM_EUID) ? PL_delaymagic_euid : (Uid_t)-1));
-#else
-#    ifdef HAS_SETRUID
-           if ((PL_delaymagic & DM_UID) == DM_RUID) {
-               PERL_UNUSED_RESULT(setruid(PL_delaymagic_uid));
-               PL_delaymagic &= ~DM_RUID;
-           }
-#    endif /* HAS_SETRUID */
-#    ifdef HAS_SETEUID
-           if ((PL_delaymagic & DM_UID) == DM_EUID) {
-               PERL_UNUSED_RESULT(seteuid(PL_delaymagic_euid));
-               PL_delaymagic &= ~DM_EUID;
-           }
-#    endif /* HAS_SETEUID */
-           if (PL_delaymagic & DM_UID) {
-               if (PL_delaymagic_uid != PL_delaymagic_euid)
-                   DIE(aTHX_ "No setreuid available");
-               PERL_UNUSED_RESULT(PerlProc_setuid(PL_delaymagic_uid));
-           }
-#endif /* HAS_SETRESUID */
+#ifdef PERL_RC_STACK
+    /* On ref-counted builds, the code above should have stored
+     * NULL in each lelem field and already freed each lelem. Thus
+     * the popfree_to() can start at a lower point.
+     * Under some circumstances, &PL_sv_undef might be stored rather than
+     * NULL, but this also doesn't need its refcount decrementing.
+     * Assert that this is true.
+     * Note that duplicate hash keys in list context can cause
+     * lastrelem and relem to be lower than at the start;
+     * while an odd number of hash elements can cause lastrelem to
+     * have a value one higher than at the start */
+#  ifdef DEBUGGING
+    for (SV **svp = first_discard; svp <= PL_stack_sp; svp++)
+        assert(!*svp || SvIMMORTAL(*svp));
+#  endif
+    PL_stack_sp = first_discard - 1;
+
+    /* now pop all the R elements too */
+    rpp_popfree_to_NN((is_list ? relem : firstrelem) - 1);
 
-           tmp_uid  = PerlProc_getuid();
-           tmp_euid = PerlProc_geteuid();
-       }
-        /* XXX $> et al currently silently ignore failures */
-       if (PL_delaymagic & DM_GID) {
-#ifdef HAS_SETRESGID
-           PERL_UNUSED_RESULT(
-                setresgid((PL_delaymagic & DM_RGID) ? PL_delaymagic_gid  : (Gid_t)-1,
-                          (PL_delaymagic & DM_EGID) ? PL_delaymagic_egid : (Gid_t)-1,
-                          (Gid_t)-1));
-#elif defined(HAS_SETREGID)
-           PERL_UNUSED_RESULT(
-                setregid((PL_delaymagic & DM_RGID) ? PL_delaymagic_gid  : (Gid_t)-1,
-                         (PL_delaymagic & DM_EGID) ? PL_delaymagic_egid : (Gid_t)-1));
 #else
-#    ifdef HAS_SETRGID
-           if ((PL_delaymagic & DM_GID) == DM_RGID) {
-               PERL_UNUSED_RESULT(setrgid(PL_delaymagic_gid));
-               PL_delaymagic &= ~DM_RGID;
-           }
-#    endif /* HAS_SETRGID */
-#    ifdef HAS_SETEGID
-           if ((PL_delaymagic & DM_GID) == DM_EGID) {
-               PERL_UNUSED_RESULT(setegid(PL_delaymagic_egid));
-               PL_delaymagic &= ~DM_EGID;
-           }
-#    endif /* HAS_SETEGID */
-           if (PL_delaymagic & DM_GID) {
-               if (PL_delaymagic_gid != PL_delaymagic_egid)
-                   DIE(aTHX_ "No setregid available");
-               PERL_UNUSED_RESULT(PerlProc_setgid(PL_delaymagic_gid));
-           }
-#endif /* HAS_SETRESGID */
-
-           tmp_gid  = PerlProc_getgid();
-           tmp_egid = PerlProc_getegid();
-       }
-       TAINTING_set( TAINTING_get | (tmp_uid && (tmp_euid != tmp_uid || tmp_egid != tmp_gid)) );
-#ifdef NO_TAINT_SUPPORT
-        PERL_UNUSED_VAR(tmp_uid);
-        PERL_UNUSED_VAR(tmp_euid);
-        PERL_UNUSED_VAR(tmp_gid);
-        PERL_UNUSED_VAR(tmp_egid);
+    /* pop all L and R elements apart from any being returned */
+    rpp_popfree_to_NN((is_list ? relem : firstrelem) - 1);
 #endif
-    }
-    PL_delaymagic = old_delaymagic;
 
-    if (gimme == G_VOID)
-       SP = firstrelem - 1;
-    else if (gimme == G_SCALAR) {
-       SP = firstrelem;
-        EXTEND(SP,1);
+    if (gimme == G_SCALAR) {
+        rpp_extend(1);
+        SV *sv;
         if (PL_op->op_private & OPpASSIGN_TRUEBOOL)
-            SETs((firstlelem - firstrelem) ? &PL_sv_yes : &PL_sv_zero);
+            rpp_push_IMM((firstlelem - firstrelem) ? &PL_sv_yes : &PL_sv_zero);
         else {
             dTARGET;
-            SETi(firstlelem - firstrelem);
+            TARGi(firstlelem - firstrelem, 1);
+            sv = targ;
+            rpp_push_1(sv);
         }
     }
-    else
-        SP = relem - 1;
 
-    RETURN;
+    return NORMAL;
 }
 
+
 PP(pp_qr)
 {
-    dSP;
     PMOP * const pm = cPMOP;
     REGEXP * rx = PM_GETRE(pm);
     regexp *prog = ReANY(rx);
     SV * const pkg = RXp_ENGINE(prog)->qr_package(aTHX_ (rx));
-    SV * const rv = sv_newmortal();
+    SV * const rv = newSV_type_mortal(SVt_IV);
     CV **cvp;
     CV *cv;
 
@@ -2903,27 +3634,69 @@ PP(pp_qr)
 
     cvp = &( ReANY((REGEXP *)SvRV(rv))->qr_anoncv);
     if (UNLIKELY((cv = *cvp) && CvCLONE(*cvp))) {
-       *cvp = cv_clone(cv);
-       SvREFCNT_dec_NN(cv);
+        *cvp = cv_clone(cv);
+        SvREFCNT_dec_NN(cv);
     }
 
     if (pkg) {
-       HV *const stash = gv_stashsv(pkg, GV_ADD);
-       SvREFCNT_dec_NN(pkg);
-       (void)sv_bless(rv, stash);
+        HV *const stash = gv_stashsv(pkg, GV_ADD);
+        SvREFCNT_dec_NN(pkg);
+        (void)sv_bless(rv, stash);
     }
 
     if (UNLIKELY(RXp_ISTAINTED(prog))) {
         SvTAINTED_on(rv);
         SvTAINTED_on(SvRV(rv));
     }
-    XPUSHs(rv);
-    RETURN;
+    rpp_xpush_1(rv);
+    return NORMAL;
+}
+
+STATIC bool
+S_are_we_in_Debug_EXECUTE_r(pTHX)
+{
+    /* Given a 'use re' is in effect, does it ask for outputting execution
+     * debug info?
+     *
+     * This is separated from the sole place it's called, an inline function,
+     * because it is the large-ish slow portion of the function */
+
+    DECLARE_AND_GET_RE_DEBUG_FLAGS_NON_REGEX;
+
+    return cBOOL(RE_DEBUG_FLAG(RE_DEBUG_EXECUTE_MASK));
+}
+
+PERL_STATIC_INLINE bool
+S_should_we_output_Debug_r(pTHX_ regexp *prog)
+{
+    PERL_ARGS_ASSERT_SHOULD_WE_OUTPUT_DEBUG_R;
+
+    /* pp_match can output regex debugging info.  This function returns a
+     * boolean as to whether or not it should.
+     *
+     * Under -Dr, it should.  Any reasonable compiler will optimize this bit of
+     * code away on non-debugging builds. */
+    if (UNLIKELY(DEBUG_r_TEST)) {
+        return TRUE;
+    }
+
+    /* If the regex engine is using the non-debugging execution routine, then
+     * no debugging should be output.  Same if the field is NULL that pluggable
+     * engines are not supposed to fill. */
+    if (     LIKELY(prog->engine->exec == &Perl_regexec_flags)
+        || UNLIKELY(prog->engine->op_comp == NULL))
+    {
+        return FALSE;
+    }
+
+    /* Otherwise have to check */
+    return S_are_we_in_Debug_EXECUTE_r(aTHX);
 }
 
+
 PP(pp_match)
 {
-    dSP; dTARG;
+    SV *targ;
     PMOP *pm = cPMOP;
     PMOP *dynpm = pm;
     const char *s;
@@ -2940,29 +3713,37 @@ PP(pp_match)
     const I32 oldsave = PL_savestack_ix;
     I32 had_zerolen = 0;
     MAGIC *mg = NULL;
+    SSize_t sp_base;
 
-    if (PL_op->op_flags & OPf_STACKED)
-       TARG = POPs;
+    if (PL_op->op_flags & OPf_STACKED) {
+        targ = PL_stack_sp[0];
+        /* We have to keep targ alive on the stack. At the end we have to
+         * free it and shuffle down all the return values by one.
+         * Remember the position.
+         */
+        sp_base = PL_stack_sp - PL_stack_base;
+        assert(sp_base > 0);
+    }
     else {
-        if (ARGTARG)
-            GETTARGET;
+        sp_base = 0;
+        if (PL_op->op_targ)
+            targ = PAD_SV(PL_op->op_targ);
         else {
-            TARG = DEFSV;
+            targ = DEFSV;
         }
-       EXTEND(SP,1);
+        rpp_extend(1);
     }
 
-    PUTBACK;                           /* EVAL blocks need stack_sp. */
     /* Skip get-magic if this is a qr// clone, because regcomp has
        already done it. */
     truebase = prog->mother_re
-        ? SvPV_nomg_const(TARG, len)
-        : SvPV_const(TARG, len);
+         ? SvPV_nomg_const(TARG, len)
+         : SvPV_const(TARG, len);
     if (!truebase)
-       DIE(aTHX_ "panic: pp_match");
+        DIE(aTHX_ "panic: pp_match");
     strend = truebase + len;
     rxtainted = (RXp_ISTAINTED(prog) ||
-                (TAINT_get && (pm->op_pmflags & PMf_RETAINT)));
+                 (TAINT_get && (pm->op_pmflags & PMf_RETAINT)));
     TAINT_NOT;
 
     /* We need to know this in case we fail out early - pos() must be reset */
@@ -2976,8 +3757,10 @@ PP(pp_match)
         pm->op_pmflags & PMf_USED
 #endif
     ) {
-        DEBUG_r(PerlIO_printf(Perl_debug_log, "?? already matched once"));
-       goto nope;
+        if (UNLIKELY(should_we_output_Debug_r(prog))) {
+            PerlIO_printf(Perl_debug_log, "?? already matched once");
+        }
+        goto nope;
     }
 
     /* handle the empty pattern */
@@ -2998,10 +3781,12 @@ PP(pp_match)
     }
 
     if (RXp_MINLEN(prog) >= 0 && (STRLEN)RXp_MINLEN(prog) > len) {
-        DEBUG_r(PerlIO_printf(Perl_debug_log, "String shorter than min possible regex match (%"
-                                              UVuf " < %" IVdf ")\n",
-                                              (UV)len, (IV)RXp_MINLEN(prog)));
-       goto nope;
+        if (UNLIKELY(should_we_output_Debug_r(prog))) {
+            PerlIO_printf(Perl_debug_log,
+                "String shorter than min possible regex match (%zd < %zd)\n",
+                                                        len, RXp_MINLEN(prog));
+        }
+        goto nope;
     }
 
     /* get pos() if //g */
@@ -3023,12 +3808,12 @@ PP(pp_match)
     )
 #endif
     {
-       r_flags |= (REXEC_COPY_STR|REXEC_COPY_SKIP_PRE);
-        /* in @a =~ /(.)/g, we iterate multiple times, but copy the buffer
+        r_flags |= (REXEC_COPY_STR|REXEC_COPY_SKIP_PRE);
+        /* In @a = /(.)/g, we iterate multiple times, but copy the buffer
          * only on the first iteration. Therefore we need to copy $' as well
          * as $&, to make the rest of the string available for captures in
          * subsequent iterations */
-        if (! (global && gimme == G_ARRAY))
+        if (! (global && gimme == G_LIST))
             r_flags |= REXEC_COPY_SKIP_POST;
     };
 #ifdef PERL_SAWAMPERSAND
@@ -3041,80 +3826,136 @@ PP(pp_match)
 
   play_it_again:
     if (global)
-       s = truebase + curpos;
+        s = truebase + curpos;
 
     if (!CALLREGEXEC(rx, (char*)s, (char *)strend, (char*)truebase,
-                    had_zerolen, TARG, NULL, r_flags))
-       goto nope;
+                     had_zerolen, TARG, NULL, r_flags))
+        goto nope;
 
     PL_curpm = pm;
     if (dynpm->op_pmflags & PMf_ONCE)
 #ifdef USE_ITHREADS
-       SvREADONLY_on(PL_regex_pad[dynpm->op_pmoffset]);
+        SvREADONLY_on(PL_regex_pad[dynpm->op_pmoffset]);
 #else
-       dynpm->op_pmflags |= PMf_USED;
+        dynpm->op_pmflags |= PMf_USED;
 #endif
 
     if (rxtainted)
-       RXp_MATCH_TAINTED_on(prog);
+        RXp_MATCH_TAINTED_on(prog);
     TAINT_IF(RXp_MATCH_TAINTED(prog));
 
     /* update pos */
 
-    if (global && (gimme != G_ARRAY || (dynpm->op_pmflags & PMf_CONTINUE))) {
+    if (global && (gimme != G_LIST || (dynpm->op_pmflags & PMf_CONTINUE))) {
         if (!mg)
             mg = sv_magicext_mglob(TARG);
-        MgBYTEPOS_set(mg, TARG, truebase, RXp_OFFS(prog)[0].end);
+        MgBYTEPOS_set(mg, TARG, truebase, RXp_OFFS_END(prog,0));
         if (RXp_ZERO_LEN(prog))
             mg->mg_flags |= MGf_MINMATCH;
         else
             mg->mg_flags &= ~MGf_MINMATCH;
     }
 
-    if ((!RXp_NPARENS(prog) && !global) || gimme != G_ARRAY) {
-       LEAVE_SCOPE(oldsave);
-       RETPUSHYES;
+    if ((!RXp_NPARENS(prog) && !global) || gimme != G_LIST) {
+        LEAVE_SCOPE(oldsave);
+        if (sp_base)
+            rpp_popfree_1(); /* free arg */
+        rpp_push_IMM(&PL_sv_yes);
+        return NORMAL;
     }
 
     /* push captures on stack */
 
     {
-       const I32 nparens = RXp_NPARENS(prog);
-       I32 i = (global && !nparens) ? 1 : 0;
-
-       SPAGAIN;                        /* EVAL blocks could move the stack. */
-       EXTEND(SP, nparens + i);
-       EXTEND_MORTAL(nparens + i);
-       for (i = !i; i <= nparens; i++) {
-           PUSHs(sv_newmortal());
-           if (LIKELY((RXp_OFFS(prog)[i].start != -1)
-                     && RXp_OFFS(prog)[i].end   != -1 ))
-            {
-               const I32 len = RXp_OFFS(prog)[i].end - RXp_OFFS(prog)[i].start;
-               const char * const s = RXp_OFFS(prog)[i].start + truebase;
-               if (UNLIKELY(  RXp_OFFS(prog)[i].end   < 0
-                            || RXp_OFFS(prog)[i].start < 0
-                            || len < 0
-                            || len > strend - s)
-                )
-                   DIE(aTHX_ "panic: pp_match start/end pointers, i=%ld, "
-                       "start=%ld, end=%ld, s=%p, strend=%p, len=%" UVuf,
-                       (long) i, (long) RXp_OFFS(prog)[i].start,
-                       (long)RXp_OFFS(prog)[i].end, s, strend, (UV) len);
-               sv_setpvn(*SP, s, len);
-               if (DO_UTF8(TARG) && is_utf8_string((U8*)s, len))
-                   SvUTF8_on(*SP);
-           }
-       }
-       if (global) {
-            curpos = (UV)RXp_OFFS(prog)[0].end;
-           had_zerolen = RXp_ZERO_LEN(prog);
-           PUTBACK;                    /* EVAL blocks may use stack */
-           r_flags |= REXEC_IGNOREPOS | REXEC_NOT_FIRST;
-           goto play_it_again;
-       }
-       LEAVE_SCOPE(oldsave);
-       RETURN;
+        const I32 logical_nparens = RXp_LOGICAL_NPARENS(prog);
+        /* This following statement is *devious* code. If we are in a global
+           match and the pattern has no parens in it, we should return $&
+           (offset pair 0). So we set logical_paren to 1 when we should return
+           $&, otherwise we set it to 0.
+
+           This allows us to simply add logical_nparens to logical_paren to
+           compute the number of elements we are going to return.
+
+           In the loop init we "not" it with: logical_paren = !logical_paren
+           which results in it being 0 inside the loop when we want to return
+           $&, and results in it being 1 when we want to return the parens.
+           Thus we either loop over 1..logical_nparens, or just over 0.
+
+           This is an elegant way to do this code-wise, but is super devious
+           and potentially confusing. When I first saw this logic I thought
+           "WTF?". But it makes sense after you poke it a while.
+
+           Frankly I probably would have done it differently, but it works so
+           I am leaving it. - Yves */
+        I32 logical_paren = (global && !logical_nparens) ? 1 : 0;
+        I32 *l2p = RXp_LOGICAL_TO_PARNO(prog);
+        /* This is used to step through the physical parens associated
+           with a given logical paren. */
+        I32 *p2l_next = RXp_PARNO_TO_LOGICAL_NEXT(prog);
+
+        rpp_extend(logical_nparens + logical_paren);    /* devious code ... */
+        EXTEND_MORTAL(logical_nparens + logical_paren); /* ... see above */
+
+        /* Loop over the logical parens in the pattern. This may not
+           correspond to the actual paren checked, as branch reset may
+           mean that there is more than one paren "behind" the logical
+           parens. Eg, in /(?|(a)|(b))/ there are two parens, but one
+           logical paren. */
+        for (logical_paren = !logical_paren;
+             logical_paren <= logical_nparens;
+             logical_paren++)
+        {
+            /* Now convert the logical_paren to the physical parens which
+               are "behind" it. If branch reset was not used, then
+               physical_paren and logical_paren are the same as each other
+               and we will only perform one iteration of the loop. */
+            I32 phys_paren = l2p ? l2p[logical_paren] : logical_paren;
+            SSize_t offs_start, offs_end;
+            /* We check the loop invariants below and break out of the loop
+               explicitly if our checks fail, so we use while (1) here to
+               avoid double testing a conditional. */
+            while (1) {
+                /* Check end offset first, as the start might be >=0 even
+                   though the end is -1, so testing the end first helps
+                   us avoid the start check.  Really we should be able to
+                   get away with ONLY testing the end, but testing both
+                   doesn't hurt much and preserves sanity. */
+                if (((offs_end   = RXp_OFFS_END(prog, phys_paren))   != -1) &&
+                    ((offs_start = RXp_OFFS_START(prog, phys_paren)) != -1))
+                {
+                    const SSize_t len = offs_end - offs_start;
+                    const char * const s = offs_start + truebase;
+                    if ( UNLIKELY( len < 0 || len > strend - s) ) {
+                        DIE(aTHX_ "panic: pp_match start/end pointers, paren=%" I32df ", "
+                            "start=%zd, end=%zd, s=%p, strend=%p, len=%zd",
+                            phys_paren, offs_start, offs_end, s, strend, len);
+                    }
+                    rpp_push_1(newSVpvn_flags(s, len,
+                        (DO_UTF8(TARG))
+                        ? SVf_UTF8|SVs_TEMP
+                        : SVs_TEMP)
+                    );
+                    break;
+                } else if (!p2l_next || !(phys_paren = p2l_next[phys_paren])) {
+                    /* Either logical_paren and phys_paren are the same and
+                       we won't have a p2l_next, or they aren't the same (and
+                       we do have a p2l_next) but we have exhausted the list
+                       of physical parens associated with this logical paren.
+                       Either way we are done, and we can push undef and break
+                       out of the loop. */
+                    rpp_push_1(sv_newmortal());
+                    break;
+                }
+            }
+        }
+        if (global) {
+            curpos = (UV)RXp_OFFS_END(prog,0);
+            had_zerolen = RXp_ZERO_LEN(prog);
+            r_flags |= REXEC_IGNOREPOS | REXEC_NOT_FIRST;
+            goto play_it_again;
+        }
+        LEAVE_SCOPE(oldsave);
+        goto ret_list;
     }
     NOT_REACHED; /* NOTREACHED */
 
@@ -3126,122 +3967,244 @@ PP(pp_match)
             mg->mg_len = -1;
     }
     LEAVE_SCOPE(oldsave);
-    if (gimme == G_ARRAY)
-       RETURN;
-    RETPUSHNO;
+    if (gimme != G_LIST) {
+        if (sp_base)
+            rpp_popfree_1(); /* free arg */
+        rpp_push_IMM(&PL_sv_no);
+        return NORMAL;
+    }
+
+  ret_list:
+    /* return when in list context (i.e. don't push YES/NO, but do return
+     * a (possibly empty) list of matches */
+    if (sp_base) {
+        /* need to free the original argument and shift any results down
+         * by one */
+        SSize_t nitems = PL_stack_sp - (PL_stack_base + sp_base);
+#ifdef PERL_RC_STACK
+        SV *old_sv = PL_stack_sp[-nitems];
+#endif
+        if (nitems)
+            Move(PL_stack_sp - nitems + 1,
+                 PL_stack_sp - nitems,    nitems, SV*);
+        PL_stack_sp--;
+#ifdef PERL_RC_STACK
+        SvREFCNT_dec_NN(old_sv);
+#endif
+    }
+
+    return NORMAL;
 }
 
+
+/* Perl_do_readline(): implement <$fh>, readline($fh) and glob('*.h')
+ *
+ * This function is tail-called by pp_readline(), pp_rcatline() and
+ * pp_glob(), and it may check PL_op's op_type and op_flags as
+ * appropriate.
+ *
+ * For file reading:
+ *    It reads the line(s) from PL_last_in_gv.
+ *    It returns a list of lines, or in scalar context, reads one line into
+ *       targ (or if OPf_STACKED, into the top SV on the stack), and
+ *       returns that. (If OP_RCATLINE, concats rather than sets).
+ *
+ *    So it normally expects zero args, or one arg when  the OPf_STACKED
+ *    optimisation is present.
+ *
+ * For file globbing:
+ *    Note that we don't normally reach here: we only get here if perl is
+ *    built with PERL_EXTERNAL_GLOB, which is normally only when
+ *    building miniperl.
+ *
+ *    Expects one arg, which is the pattern string (e.g. '*.h').
+ *    The caller sets PL_last_in_gv to a plain GV that just has a new
+ *    IO::File PVIO attached.
+ *
+ * Handles tied IO magic, but not overloading - that's the caller's
+ * responsibility.
+ *
+ * Handles the *ARGV filehandle specially, to do all the <> wizardry.
+ *
+ * In summary: on entry, the stack has zero or one items pushed, and
+ * looks like:
+ *
+ *  -       when OP_READLINE without OPf_STACKED
+ *  target  when OP_READLINE with    OPf_STACKED, or when OP_RCATLINE
+ *  '*.h'   when OP_GLOB
+ */
+
 OP *
 Perl_do_readline(pTHX)
 {
-    dSP; dTARGETSTACKED;
+
+    const I32 type = PL_op->op_type;
+
+    /* only readline/rcatline can have the STACKED optimisation,
+     * and rcatline *always* has it */
+    if (PL_op->op_flags & OPf_STACKED) {
+        assert(type != OP_GLOB);
+        assert(GIMME_V == G_SCALAR);
+    }
+    if (type == OP_RCATLINE)
+        assert(PL_op->op_flags & OPf_STACKED);
+
+    const U8 gimme = GIMME_V;
+    SV *targ  = (gimme == G_SCALAR)
+                    ? (PL_op->op_flags & OPf_STACKED)
+                        ? *PL_stack_sp
+                        : PAD_SV(PL_op->op_targ)
+                    : NULL;
     SV *sv;
     STRLEN tmplen = 0;
     STRLEN offset;
     PerlIO *fp;
     IO * const io = GvIO(PL_last_in_gv);
-    const I32 type = PL_op->op_type;
-    const U8 gimme = GIMME_V;
+
+    /* process tied file handle if present */
 
     if (io) {
-       const MAGIC *const mg = SvTIED_mg((const SV *)io, PERL_MAGIC_tiedscalar);
-       if (mg) {
-           Perl_tied_method(aTHX_ SV_CONST(READLINE), SP, MUTABLE_SV(io), mg, gimme, 0);
-           if (gimme == G_SCALAR) {
-               SPAGAIN;
-               SvSetSV_nosteal(TARG, TOPs);
-               SETTARG;
-           }
-           return NORMAL;
-       }
+        const MAGIC *const mg = SvTIED_mg((const SV *)io, PERL_MAGIC_tiedscalar);
+        if (mg) {
+            /* not possible for the faked-up IO passed by an OP_GLOB to be
+             * tied */
+            assert(type != OP_GLOB);
+            /* OPf_STACKED only applies when in scalar context */
+            assert(!(gimme != G_SCALAR && (PL_op->op_flags & OPf_STACKED)));
+
+            /* tied_method() frees everything currently above the passed
+             * mark, and returns any values at mark[1] onwards */
+            Perl_tied_method(aTHX_ SV_CONST(READLINE),
+                /* mark => */ PL_stack_sp,
+                              MUTABLE_SV(io), mg, gimme, 0);
+
+            if (gimme == G_SCALAR) {
+                SvSetSV_nosteal(targ, *PL_stack_sp);
+                SvSETMAGIC(targ);
+                if (PL_op->op_flags & OPf_STACKED) {
+                    /* free the tied method call's return value */
+                    rpp_popfree_1();
+                    assert(*PL_stack_sp == targ);
+                }
+                else
+                    rpp_replace_1_1(targ);
+            }
+            else
+                /* no targ to pop off the stack - any returned values
+                 * are in the right place in the stack */
+                assert(!(PL_op->op_flags & OPf_STACKED));
+
+            return NORMAL;
+        }
     }
+
     fp = NULL;
+
+    /* handle possible *ARGV, and check for read on write-only FH */
+
     if (io) {
-       fp = IoIFP(io);
-       if (!fp) {
-           if (IoFLAGS(io) & IOf_ARGV) {
-               if (IoFLAGS(io) & IOf_START) {
-                   IoLINES(io) = 0;
-                   if (av_tindex(GvAVn(PL_last_in_gv)) < 0) {
-                       IoFLAGS(io) &= ~IOf_START;
-                       do_open6(PL_last_in_gv, "-", 1, NULL, NULL, 0);
-                       SvTAINTED_off(GvSVn(PL_last_in_gv)); /* previous tainting irrelevant */
-                       sv_setpvs(GvSVn(PL_last_in_gv), "-");
-                       SvSETMAGIC(GvSV(PL_last_in_gv));
-                       fp = IoIFP(io);
-                       goto have_fp;
-                   }
-               }
-               fp = nextargv(PL_last_in_gv, PL_op->op_flags & OPf_SPECIAL);
-               if (!fp) { /* Note: fp != IoIFP(io) */
-                   (void)do_close(PL_last_in_gv, FALSE); /* now it does*/
-               }
-           }
-           else if (type == OP_GLOB)
-               fp = Perl_start_glob(aTHX_ POPs, io);
-       }
-       else if (type == OP_GLOB)
-           SP--;
-       else if (IoTYPE(io) == IoTYPE_WRONLY) {
-           report_wrongway_fh(PL_last_in_gv, '>');
-       }
+        fp = IoIFP(io);
+        if (fp) {
+            /* not possible for the faked-up IO passed by an OP_GLOB to
+             * have a file handle */
+            assert(type != OP_GLOB);
+
+            if (IoTYPE(io) == IoTYPE_WRONLY)
+                report_wrongway_fh(PL_last_in_gv, '>');
+        }
+        else {
+            if (IoFLAGS(io) & IOf_ARGV) {
+                if (IoFLAGS(io) & IOf_START) {
+                    IoLINES(io) = 0;
+                    if (av_count(GvAVn(PL_last_in_gv)) == 0) {
+                        IoFLAGS(io) &= ~IOf_START;
+                        do_open6(PL_last_in_gv, "-", 1, NULL, NULL, 0);
+                        SvTAINTED_off(GvSVn(PL_last_in_gv)); /* previous tainting irrelevant */
+                        sv_setpvs(GvSVn(PL_last_in_gv), "-");
+                        SvSETMAGIC(GvSV(PL_last_in_gv));
+                        fp = IoIFP(io);
+                        goto have_fp;
+                    }
+                }
+                fp = nextargv(PL_last_in_gv, PL_op->op_flags & OPf_SPECIAL);
+                if (!fp) { /* Note: fp != IoIFP(io) */
+                    (void)do_close(PL_last_in_gv, FALSE); /* now it does*/
+                }
+            }
+            else if (type == OP_GLOB) {
+                fp = Perl_start_glob(aTHX_ *PL_stack_sp, io);
+                rpp_popfree_1_NN();
+            }
+        }
     }
+
+    /* handle bad file handle */
+
     if (!fp) {
-       if ((!io || !(IoFLAGS(io) & IOf_START))
-           && ckWARN(WARN_CLOSED)
+        if ((!io || !(IoFLAGS(io) & IOf_START))
+            && ckWARN(WARN_CLOSED)
             && type != OP_GLOB)
-       {
-           report_evil_fh(PL_last_in_gv);
-       }
-       if (gimme == G_SCALAR) {
-           /* undef TARG, and push that undefined value */
-           if (type != OP_RCATLINE) {
-               sv_set_undef(TARG);
-           }
-           PUSHTARG;
-       }
-       RETURN;
+        {
+            report_evil_fh(PL_last_in_gv);
+        }
+
+        if (gimme == G_SCALAR) {
+            /* undef targ, and return that undefined value */
+            if (type != OP_RCATLINE)
+                sv_set_undef(targ);
+            if (!(PL_op->op_flags & OPf_STACKED))
+                rpp_push_1(targ);
+        }
+        return NORMAL;
     }
+
   have_fp:
+
+    /* prepare targ to have a string assigned to it */
+
     if (gimme == G_SCALAR) {
-       sv = TARG;
-       if (type == OP_RCATLINE && SvGMAGICAL(sv))
-           mg_get(sv);
-       if (SvROK(sv)) {
-           if (type == OP_RCATLINE)
-               SvPV_force_nomg_nolen(sv);
-           else
-               sv_unref(sv);
-       }
-       else if (isGV_with_GP(sv)) {
-           SvPV_force_nomg_nolen(sv);
-       }
-       SvUPGRADE(sv, SVt_PV);
-       tmplen = SvLEN(sv);     /* remember if already alloced */
-       if (!tmplen && !SvREADONLY(sv) && !SvIsCOW(sv)) {
+        sv = targ;
+        if (type == OP_RCATLINE && SvGMAGICAL(sv))
+            mg_get(sv);
+
+        if (SvROK(sv)) {
+            if (type == OP_RCATLINE)
+                SvPV_force_nomg_nolen(sv);
+            else
+                sv_unref(sv);
+        }
+        else if (isGV_with_GP(sv)) {
+            SvPV_force_nomg_nolen(sv);
+        }
+
+        SvUPGRADE(sv, SVt_PV);
+        tmplen = SvLEN(sv);    /* remember if already alloced */
+        if (!tmplen && !SvREADONLY(sv) && !SvIsCOW(sv)) {
             /* try short-buffering it. Please update t/op/readline.t
-            * if you change the growth length.
-            */
-           Sv_Grow(sv, 80);
-        }
-       offset = 0;
-       if (type == OP_RCATLINE && SvOK(sv)) {
-           if (!SvPOK(sv)) {
-               SvPV_force_nomg_nolen(sv);
-           }
-           offset = SvCUR(sv);
-       }
+             * if you change the growth length.
+             */
+            Sv_Grow(sv, 80);
+        }
+
+        offset = 0;
+        if (type == OP_RCATLINE && SvOK(sv)) {
+            if (!SvPOK(sv)) {
+                SvPV_force_nomg_nolen(sv);
+            }
+            offset = SvCUR(sv);
+        }
     }
     else {
-       sv = sv_2mortal(newSV(80));
-       offset = 0;
+        /* XXX on RC builds, push on stack rather than mortalize ? */
+        sv = sv_2mortal(newSV(80));
+        offset = 0;
     }
 
     /* This should not be marked tainted if the fp is marked clean */
 #define MAYBE_TAINT_LINE(io, sv) \
     if (!(IoFLAGS(io) & IOf_UNTAINT)) { \
-       TAINT;                          \
-       SvTAINTED_on(sv);               \
+        TAINT;                         \
+        SvTAINTED_on(sv);              \
     }
 
 /* delay EOF state for a snarfed empty file */
@@ -3249,157 +4212,198 @@ Perl_do_readline(pTHX)
     (gimme != G_SCALAR || SvCUR(sv)                                    \
      || (IoFLAGS(io) & IOf_NOLINE) || !RsSNARF(rs))
 
+    /* create one or more lines, or (if OP_GLOB), pathnames */
+
     for (;;) {
-       PUTBACK;
-       if (!sv_gets(sv, fp, offset)
-           && (type == OP_GLOB
-               || SNARF_EOF(gimme, PL_rs, io, sv)
-               || PerlIO_error(fp)))
-       {
-           PerlIO_clearerr(fp);
-           if (IoFLAGS(io) & IOf_ARGV) {
-               fp = nextargv(PL_last_in_gv, PL_op->op_flags & OPf_SPECIAL);
-               if (fp)
-                   continue;
-               (void)do_close(PL_last_in_gv, FALSE);
-           }
-           else if (type == OP_GLOB) {
-               if (!do_close(PL_last_in_gv, FALSE)) {
-                   Perl_ck_warner(aTHX_ packWARN(WARN_GLOB),
-                                  "glob failed (child exited with status %d%s)",
-                                  (int)(STATUS_CURRENT >> 8),
-                                  (STATUS_CURRENT & 0x80) ? ", core dumped" : "");
-               }
-           }
-           if (gimme == G_SCALAR) {
-               if (type != OP_RCATLINE) {
-                   SV_CHECK_THINKFIRST_COW_DROP(TARG);
-                   SvOK_off(TARG);
-               }
-               SPAGAIN;
-               PUSHTARG;
-           }
-           MAYBE_TAINT_LINE(io, sv);
-           RETURN;
-       }
-       MAYBE_TAINT_LINE(io, sv);
-       IoLINES(io)++;
-       IoFLAGS(io) |= IOf_NOLINE;
-       SvSETMAGIC(sv);
-       SPAGAIN;
-       XPUSHs(sv);
-       if (type == OP_GLOB) {
-           const char *t1;
-           Stat_t statbuf;
-
-           if (SvCUR(sv) > 0 && SvCUR(PL_rs) > 0) {
-               char * const tmps = SvEND(sv) - 1;
-               if (*tmps == *SvPVX_const(PL_rs)) {
-                   *tmps = '\0';
-                   SvCUR_set(sv, SvCUR(sv) - 1);
-               }
-           }
-           for (t1 = SvPVX_const(sv); *t1; t1++)
+        if (!sv_gets(sv, fp, offset)
+            && (type == OP_GLOB
+                || SNARF_EOF(gimme, PL_rs, io, sv)
+                || PerlIO_error(fp)))
+        {
+            if (IoFLAGS(io) & IOf_ARGV) {
+                fp = nextargv(PL_last_in_gv, PL_op->op_flags & OPf_SPECIAL);
+                if (fp) {
+                    continue;
+                }
+                (void)do_close(PL_last_in_gv, FALSE);
+            }
+            else if (type == OP_GLOB) {
+                /* clear any errors here so we only fail on the pclose()
+                   failing, which should only happen on the child
+                   failing
+                */
+                PerlIO_clearerr(fp);
+                if (!do_close(PL_last_in_gv, FALSE)) {
+                    Perl_ck_warner(aTHX_ packWARN(WARN_GLOB),
+                                   "glob failed (child exited with status %d%s)",
+                                   (int)(STATUS_CURRENT >> 8),
+                                   (STATUS_CURRENT & 0x80) ? ", core dumped" : "");
+                }
+            }
+
+            if (gimme == G_SCALAR) {
+                if (type != OP_RCATLINE) {
+                    SV_CHECK_THINKFIRST_COW_DROP(targ);
+                    SvOK_off(targ);
+                }
+                /* targ not already there? */
+                if (!(PL_op->op_flags & OPf_STACKED))
+                    rpp_push_1(targ);
+            }
+            else if (PL_op->op_flags & OPf_STACKED)
+                rpp_popfree_1_NN();
+
+            MAYBE_TAINT_LINE(io, sv);
+            return NORMAL;
+        }
+
+        MAYBE_TAINT_LINE(io, sv);
+        IoLINES(io)++;
+        IoFLAGS(io) |= IOf_NOLINE;
+        SvSETMAGIC(sv);
+        rpp_extend(1);
+        if (PL_op->op_flags & OPf_STACKED) {
+            /* push sv while keeping targ above it, so targ doesn't get
+             * freed */
+            assert(*PL_stack_sp == targ);
+            PL_stack_sp[1] = targ;
+            *PL_stack_sp++ = NULL;
+            rpp_replace_at(PL_stack_sp - 1, sv);
+        }
+        else 
+            rpp_push_1(sv);
+
+        if (type == OP_GLOB) {
+            const char *t1;
+            Stat_t statbuf;
+
+            /* chomp(sv) */
+            if (SvCUR(sv) > 0 && SvCUR(PL_rs) > 0) {
+                char * const tmps = SvEND(sv) - 1;
+                if (*tmps == *SvPVX_const(PL_rs)) {
+                    *tmps = '\0';
+                    SvCUR_set(sv, SvCUR(sv) - 1);
+                }
+            }
+
+            /* find longest substring of sv up to first metachar */
+            for (t1 = SvPVX_const(sv); *t1; t1++) {
 #ifdef __VMS
-               if (strchr("*%?", *t1))
+                if (memCHRs("*%?", *t1))
 #else
-               if (strchr("$&*(){}[]'\";\\|?<>~`", *t1))
+                if (memCHRs("$&*(){}[]'\";\\|?<>~`", *t1))
 #endif
-                       break;
-           if (*t1 && PerlLIO_lstat(SvPVX_const(sv), &statbuf) < 0) {
-               (void)POPs;             /* Unmatched wildcard?  Chuck it... */
-               continue;
-           }
-       } else if (SvUTF8(sv)) { /* OP_READLINE, OP_RCATLINE */
-            if (ckWARN(WARN_UTF8)) {
-               const U8 * const s = (const U8*)SvPVX_const(sv) + offset;
-               const STRLEN len = SvCUR(sv) - offset;
-               const U8 *f;
-
-               if (!is_utf8_string_loc(s, len, &f))
-                   /* Emulate :encoding(utf8) warning in the same case. */
-                   Perl_warner(aTHX_ packWARN(WARN_UTF8),
-                               "utf8 \"\\x%02X\" does not map to Unicode",
-                               f < (U8*)SvEND(sv) ? *f : 0);
-            }
-       }
-       if (gimme == G_ARRAY) {
-           if (SvLEN(sv) - SvCUR(sv) > 20) {
-               SvPV_shrink_to_cur(sv);
-           }
-           sv = sv_2mortal(newSV(80));
-           continue;
-       }
-       else if (gimme == G_SCALAR && !tmplen && SvLEN(sv) - SvCUR(sv) > 80) {
-           /* try to reclaim a bit of scalar space (only on 1st alloc) */
-           const STRLEN new_len
-               = SvCUR(sv) < 60 ? 80 : SvCUR(sv)+40; /* allow some slop */
-           SvPV_renew(sv, new_len);
-       }
-       RETURN;
-    }
+                        break;
+            }
+
+            if (*t1 && PerlLIO_lstat(SvPVX_const(sv), &statbuf) < 0) {
+                /* Unmatched wildcard?  Chuck it... */
+                /* no need to worry about targ still on top of stack */
+                assert(!(PL_op->op_flags & OPf_STACKED));
+                rpp_popfree_1();
+                continue;
+            }
+        } else if (SvUTF8(sv)) { /* OP_READLINE, OP_RCATLINE */
+             /* check line if valid Unicode */
+             if (ckWARN(WARN_UTF8)) {
+                const U8 * const s = (const U8*)SvPVX_const(sv) + offset;
+                const STRLEN len = SvCUR(sv) - offset;
+                const U8 *f;
+
+                if (!is_utf8_string_loc(s, len, &f))
+                    /* Emulate :encoding(utf8) warning in the same case. */
+                    Perl_warner(aTHX_ packWARN(WARN_UTF8),
+                                "utf8 \"\\x%02X\" does not map to Unicode",
+                                f < (U8*)SvEND(sv) ? *f : 0);
+             }
+        }
+
+        if (gimme == G_LIST) {
+            if (SvLEN(sv) - SvCUR(sv) > 20) {
+                SvPV_shrink_to_cur(sv);
+            }
+            /* XXX on RC builds, push on stack rather than mortalize ? */
+            sv = sv_2mortal(newSV(80));
+            continue;
+        }
+
+        if (gimme == G_SCALAR && !tmplen && SvLEN(sv) - SvCUR(sv) > 80) {
+            /* try to reclaim a bit of scalar space (only on 1st alloc) */
+            const STRLEN new_len
+                = SvCUR(sv) < 60 ? 80 : SvCUR(sv)+40; /* allow some slop */
+            SvPV_renew(sv, new_len);
+        }
+
+
+        if (PL_op->op_flags & OPf_STACKED)
+            rpp_popfree_1_NN(); /* finally remove targ */
+        /* return sv, which was recently pushed onto the stack */
+        return NORMAL;
+    } /* for (;;) */
 }
 
+
 PP(pp_helem)
 {
-    dSP;
     HE* he;
     SV **svp;
-    SV * const keysv = POPs;
-    HV * const hv = MUTABLE_HV(POPs);
+    SV * const keysv = PL_stack_sp[0];
+    HV * const hv = MUTABLE_HV(PL_stack_sp[-1]);
     const U32 lval = PL_op->op_flags & OPf_MOD || LVRET;
     const U32 defer = PL_op->op_private & OPpLVAL_DEFER;
     SV *sv;
     const bool localizing = PL_op->op_private & OPpLVAL_INTRO;
     bool preeminent = TRUE;
+    SV *retsv;
 
-    if (SvTYPE(hv) != SVt_PVHV)
-       RETPUSHUNDEF;
+    if (SvTYPE(hv) != SVt_PVHV) {
+        retsv = &PL_sv_undef;
+        goto ret;
+    }
 
     if (localizing) {
-       MAGIC *mg;
-       HV *stash;
+        MAGIC *mg;
+        HV *stash;
 
-       /* If we can determine whether the element exist,
-        * Try to preserve the existenceness of a tied hash
-        * element by using EXISTS and DELETE if possible.
-        * Fallback to FETCH and STORE otherwise. */
-       if (SvCANEXISTDELETE(hv))
-           preeminent = hv_exists_ent(hv, keysv, 0);
+        /* Try to preserve the existence of a tied hash
+         * element by using EXISTS and DELETE if possible.
+         * Fall back to FETCH and STORE otherwise. */
+        if (SvCANEXISTDELETE(hv))
+            preeminent = hv_exists_ent(hv, keysv, 0);
     }
 
     he = hv_fetch_ent(hv, keysv, lval && !defer, 0);
     svp = he ? &HeVAL(he) : NULL;
     if (lval) {
-       if (!svp || !*svp || *svp == &PL_sv_undef) {
-           SV* lv;
-           SV* key2;
-           if (!defer) {
-               DIE(aTHX_ PL_no_helem_sv, SVfARG(keysv));
-           }
-           lv = sv_newmortal();
-           sv_upgrade(lv, SVt_PVLV);
-           LvTYPE(lv) = 'y';
-           sv_magic(lv, key2 = newSVsv(keysv), PERL_MAGIC_defelem, NULL, 0);
-           SvREFCNT_dec_NN(key2);      /* sv_magic() increments refcount */
-           LvTARG(lv) = SvREFCNT_inc_simple_NN(hv);
-           LvTARGLEN(lv) = 1;
-           PUSHs(lv);
-           RETURN;
-       }
-       if (localizing) {
-           if (HvNAME_get(hv) && isGV_or_RVCV(*svp))
-               save_gp(MUTABLE_GV(*svp), !(PL_op->op_flags & OPf_SPECIAL));
-           else if (preeminent)
-               save_helem_flags(hv, keysv, svp,
-                    (PL_op->op_flags & OPf_SPECIAL) ? 0 : SAVEf_SETMAGIC);
-           else
-               SAVEHDELETE(hv, keysv);
-       }
-       else if (PL_op->op_private & OPpDEREF) {
-           PUSHs(vivify_ref(*svp, PL_op->op_private & OPpDEREF));
-           RETURN;
-       }
+        if (!svp || !*svp || *svp == &PL_sv_undef) {
+            SV* lv;
+            SV* key2;
+            if (!defer) {
+                DIE(aTHX_ PL_no_helem_sv, SVfARG(keysv));
+            }
+            lv = newSV_type_mortal(SVt_PVLV);
+            LvTYPE(lv) = 'y';
+            sv_magic(lv, key2 = newSVsv(keysv), PERL_MAGIC_defelem, NULL, 0);
+            SvREFCNT_dec_NN(key2);     /* sv_magic() increments refcount */
+            LvTARG(lv) = SvREFCNT_inc_simple_NN(hv);
+            LvTARGLEN(lv) = 1;
+            retsv = lv;
+            goto ret;
+        }
+
+        if (localizing) {
+            if (HvNAME_get(hv) && isGV_or_RVCV(*svp))
+                save_gp(MUTABLE_GV(*svp), !(PL_op->op_flags & OPf_SPECIAL));
+            else if (preeminent)
+                save_helem_flags(hv, keysv, svp,
+                     (PL_op->op_flags & OPf_SPECIAL) ? 0 : SAVEf_SETMAGIC);
+            else
+                SAVEHDELETE(hv, keysv);
+        }
+        else if (PL_op->op_private & OPpDEREF) {
+            retsv = vivify_ref(*svp, PL_op->op_private & OPpDEREF);
+            goto ret;;
+        }
     }
     sv = (svp && *svp ? *svp : &PL_sv_undef);
     /* Originally this did a conditional C<sv = sv_mortalcopy(sv)>; this
@@ -3415,9 +4419,12 @@ PP(pp_helem)
      * compromise, do the get magic here. (The MGf_GSKIP flag will stop it
      * being called too many times). */
     if (!lval && SvRMAGICAL(hv) && SvGMAGICAL(sv))
-       mg_get(sv);
-    PUSHs(sv);
-    RETURN;
+        mg_get(sv);
+    retsv = sv;
+
+  ret:
+    rpp_replace_2_1_NN(retsv);
+    return NORMAL;
 }
 
 
@@ -3426,14 +4433,14 @@ PP(pp_helem)
 
 STATIC GV *
 S_softref2xv_lite(pTHX_ SV *const sv, const char *const what,
-               const svtype type)
+                const svtype type)
 {
     if (PL_op->op_private & HINT_STRICT_REFS) {
-       if (SvOK(sv))
-           Perl_die(aTHX_ PL_no_symref_sv, sv,
-                    (SvPOKp(sv) && SvCUR(sv)>32 ? "..." : ""), what);
-       else
-           Perl_die(aTHX_ PL_no_usym, what);
+        if (SvOK(sv))
+            Perl_die(aTHX_ PL_no_symref_sv, sv,
+                     (SvPOKp(sv) && SvCUR(sv)>32 ? "..." : ""), what);
+        else
+            Perl_die(aTHX_ PL_no_usym, what);
     }
     if (!SvOK(sv))
         Perl_die(aTHX_ PL_no_usym, what);
@@ -3447,7 +4454,7 @@ S_softref2xv_lite(pTHX_ SV *const sv, const char *const what,
  * op_aux points to an array of unions of UV / IV / SV* / PADOFFSET.
  * Each of these either contains a set of actions, or an argument, such as
  * an IV to use as an array index, or a lexical var to retrieve.
- * Several actions re stored per UV; we keep shifting new actions off the
+ * Several actions are stored per UV; we keep shifting new actions off the
  * one UV, and only reload when it becomes zero.
  */
 
@@ -3460,9 +4467,10 @@ PP(pp_multideref)
     assert(actions);
     /* this tells find_uninit_var() where we're up to */
     PL_multideref_pc = items;
+    bool replace = FALSE;
 
     while (1) {
-        /* there are three main classes of action; the first retrieve
+        /* there are three main classes of action; the first retrieves
          * the initial AV or HV from a variable or the stack; the second
          * does the equivalent of an unrolled (/DREFAV, rv2av, aelem),
          * the third an unrolled (/DREFHV, rv2hv, helem).
@@ -3485,9 +4493,8 @@ PP(pp_multideref)
 
         case MDEREF_AV_pop_rv2av_aelem:             /* expr->[...] */
             {
-                dSP;
-                sv = POPs;
-                PUTBACK;
+                sv = *PL_stack_sp;
+                replace = TRUE;
                 goto do_AV_rv2av_aelem;
             }
 
@@ -3606,10 +4613,9 @@ PP(pp_multideref)
                         MAGIC *mg;
                         HV *stash;
 
-                        /* If we can determine whether the element exist,
-                         * Try to preserve the existenceness of a tied array
+                        /* Try to preserve the existence of a tied array
                          * element by using EXISTS and DELETE if possible.
-                         * Fallback to FETCH and STORE otherwise. */
+                         * Fall back to FETCH and STORE otherwise. */
                         if (SvCANEXISTDELETE(av))
                             preeminent = av_exists(av, elem);
                     }
@@ -3621,14 +4627,20 @@ PP(pp_multideref)
                             IV len;
                             if (!defer)
                                 DIE(aTHX_ PL_no_aelem, elem);
-                            len = av_tindex(av);
-                            sv = sv_2mortal(newSVavdefelem(av,
-                            /* Resolve a negative index now, unless it points
-                             * before the beginning of the array, in which
-                             * case record it for error reporting in
-                             * magic_setdefelem. */
-                                elem < 0 && len + elem >= 0
-                                    ? len + elem : elem, 1));
+                            len = av_top_index(av);
+                            /* Resolve a negative index that falls within
+                             * the array.  Leave it negative it if falls
+                             * outside the array.  */
+                             if (elem < 0 && len + elem >= 0)
+                                 elem = len + elem;
+                             if (elem >= 0 && elem <= len)
+                                 /* Falls within the array.  */
+                                 sv = av_nonelem(av,elem);
+                             else
+                                 /* Falls outside the array.  If it is neg-
+                                    ative, magic_setdefelem will use the
+                                    index for error reporting.  */
+                                sv = sv_2mortal(newSVavdefelem(av,elem,1));
                         }
                         else {
                             if (UNLIKELY(localizing)) {
@@ -3652,9 +4664,11 @@ PP(pp_multideref)
             }
           finish:
             {
-                dSP;
-                XPUSHs(sv);
-                RETURN;
+                if (replace)
+                    rpp_replace_1_1_NN(sv);
+                else
+                    rpp_xpush_1(sv);
+                return NORMAL;
             }
             /* NOTREACHED */
 
@@ -3673,9 +4687,8 @@ PP(pp_multideref)
 
         case MDEREF_HV_pop_rv2hv_helem:             /* expr->{...} */
             {
-                dSP;
-                sv = POPs;
-                PUTBACK;
+                sv = *PL_stack_sp;
+                replace = TRUE;
                 goto do_HV_rv2hv_helem;
             }
 
@@ -3793,10 +4806,9 @@ PP(pp_multideref)
                         MAGIC *mg;
                         HV *stash;
 
-                        /* If we can determine whether the element exist,
-                         * Try to preserve the existenceness of a tied hash
+                        /* Try to preserve the existence of a tied hash
                          * element by using EXISTS and DELETE if possible.
-                         * Fallback to FETCH and STORE otherwise. */
+                         * Fall back to FETCH and STORE otherwise. */
                         if (SvCANEXISTDELETE(hv))
                             preeminent = hv_exists_ent(hv, keysv, 0);
                     }
@@ -3811,8 +4823,7 @@ PP(pp_multideref)
                             SV* key2;
                             if (!defer)
                                 DIE(aTHX_ PL_no_helem_sv, SVfARG(keysv));
-                            lv = sv_newmortal();
-                            sv_upgrade(lv, SVt_PVLV);
+                            lv = newSV_type_mortal(SVt_PVLV);
                             LvTYPE(lv) = 'y';
                             sv_magic(lv, key2 = newSVsv(keysv),
                                                 PERL_MAGIC_defelem, NULL, 0);
@@ -3858,198 +4869,278 @@ PP(pp_multideref)
 
 PP(pp_iter)
 {
-    PERL_CONTEXT *cx;
-    SV *oldsv;
-    SV **itersvp;
+    PERL_CONTEXT *cx = CX_CUR();
+    SV **itersvp = CxITERVAR(cx);
+    const U8 type = CxTYPE(cx);
+
+    /* Classic "for" syntax iterates one-at-a-time.
+       Many-at-a-time for loops are only for lexicals declared as part of the
+       for loop, and rely on all the lexicals being in adjacent pad slots.
+
+       Curiously, even if the iterator variable is a lexical, the pad offset is
+       stored in the targ slot of the ENTERITER op, meaning that targ of this OP
+       has always been zero. Hence we can use this op's targ to hold "how many"
+       for many-at-a-time. We actually store C<how_many - 1>, so that for the
+       case of one-at-a-time we have zero (as before), as this makes all the
+       logic of the for loop below much simpler, with all the other
+       one-at-a-time cases just falling out of this "naturally". */
+    PADOFFSET how_many = PL_op->op_targ;
+    PADOFFSET i = 0;
 
-    SV *sv;
-    AV *av;
-    IV ix;
-    IV inc;
-
-    cx = CX_CUR();
-    itersvp = CxITERVAR(cx);
     assert(itersvp);
 
-    switch (CxTYPE(cx)) {
+    for (; i <= how_many; ++i ) {
+        SV *oldsv;
+        SV *sv;
+        AV *av;
+        IV ix;
+        IV inc;
+
+        switch (type) {
 
-    case CXt_LOOP_LAZYSV: /* string increment */
-    {
-        SV* cur = cx->blk_loop.state_u.lazysv.cur;
-        SV *end = cx->blk_loop.state_u.lazysv.end;
-        /* If the maximum is !SvOK(), pp_enteriter substitutes PL_sv_no.
-           It has SvPVX of "" and SvCUR of 0, which is what we want.  */
-        STRLEN maxlen = 0;
-        const char *max = SvPV_const(end, maxlen);
-        if (DO_UTF8(end) && IN_UNI_8_BIT)
-            maxlen = sv_len_utf8_nomg(end);
-        if (UNLIKELY(SvNIOK(cur) || SvCUR(cur) > maxlen))
-            goto retno;
-
-        oldsv = *itersvp;
-        /* NB: on the first iteration, oldsv will have a ref count of at
-         * least 2 (one extra from blk_loop.itersave), so the GV or pad
-         * slot will get localised; on subsequent iterations the RC==1
-         * optimisation may kick in and the SV will be reused. */
-         if (oldsv && LIKELY(SvREFCNT(oldsv) == 1 && !SvMAGICAL(oldsv))) {
-            /* safe to reuse old SV */
-            sv_setsv(oldsv, cur);
-        }
-        else
-        {
-            /* we need a fresh SV every time so that loop body sees a
-             * completely new SV for closures/references to work as
-             * they used to */
-            *itersvp = newSVsv(cur);
-            SvREFCNT_dec(oldsv);
-        }
-        if (strEQ(SvPVX_const(cur), max))
-            sv_setiv(cur, 0); /* terminate next time */
-        else
-            sv_inc(cur);
-        break;
-    }
+        case CXt_LOOP_LAZYSV: /* string increment */
+            {
+                SV* cur = cx->blk_loop.state_u.lazysv.cur;
+                SV *end = cx->blk_loop.state_u.lazysv.end;
+                /* If the maximum is !SvOK(), pp_enteriter substitutes PL_sv_no.
+                   It has SvPVX of "" and SvCUR of 0, which is what we want.  */
+                STRLEN maxlen = 0;
+                const char *max = SvPV_const(end, maxlen);
+                bool pad_it = FALSE;
+                if (DO_UTF8(end) && IN_UNI_8_BIT)
+                    maxlen = sv_len_utf8_nomg(end);
+                if (UNLIKELY(SvNIOK(cur) || SvCUR(cur) > maxlen)) {
+                    if (LIKELY(!i)) {
+                        goto retno;
+                    }
+                    /* We are looping n-at-a-time and the range isn't a multiple
+                       of n, so we fill the rest of the lexicals with undef.
+                       This only happens on the last iteration of the loop, and
+                       we will have already set up the "terminate next time"
+                       condition earlier in this for loop for this call of the
+                       ITER op when we set up the lexical corresponding to the
+                       last value in the range. Hence we don't goto retno (yet),
+                       and just below we don't repeat the setup for "terminate
+                       next time". */
+                    pad_it = TRUE;
+                }
 
-    case CXt_LOOP_LAZYIV: /* integer increment */
-    {
-        IV cur = cx->blk_loop.state_u.lazyiv.cur;
-       if (UNLIKELY(cur > cx->blk_loop.state_u.lazyiv.end))
-           goto retno;
+                oldsv = *itersvp;
+                /* NB: on the first iteration, oldsv will have a ref count of at
+                 * least 2 (one extra from blk_loop.itersave), so the GV or pad
+                 * slot will get localised; on subsequent iterations the RC==1
+                 * optimisation may kick in and the SV will be reused. */
+                if (UNLIKELY(pad_it)) {
+                    *itersvp = &PL_sv_undef;
+                    SvREFCNT_dec(oldsv);
+                }
+                else if (oldsv && LIKELY(SvREFCNT(oldsv) == 1 && !SvMAGICAL(oldsv))) {
+                    /* safe to reuse old SV */
+                    sv_setsv(oldsv, cur);
+                }
+                else {
+                    /* we need a fresh SV every time so that loop body sees a
+                     * completely new SV for closures/references to work as
+                     * they used to */
+                    *itersvp = newSVsv(cur);
+                    SvREFCNT_dec(oldsv);
+                }
 
-        oldsv = *itersvp;
-       /* see NB comment above */
-       if (oldsv && LIKELY(SvREFCNT(oldsv) == 1 && !SvMAGICAL(oldsv))) {
-           /* safe to reuse old SV */
+                if (UNLIKELY(pad_it)) {
+                    /* We're "beyond the end" of the iterator here, filling the
+                       extra lexicals with undef, so we mustn't do anything
+                       (further) to the iterator itself at this point.
+                       (Observe how the other two blocks modify the iterator's
+                       value) */
+                }
+                else if (strEQ(SvPVX_const(cur), max))
+                    sv_setiv(cur, 0); /* terminate next time */
+                else
+                    sv_inc(cur);
+                break;
+            }
 
-            if (    (SvFLAGS(oldsv) & (SVTYPEMASK|SVf_THINKFIRST|SVf_IVisUV))
-                 == SVt_IV)
+        case CXt_LOOP_LAZYIV: /* integer increment */
             {
-                /* Cheap SvIOK_only().
-                 * Assert that flags which SvIOK_only() would test or
-                 * clear can't be set, because we're SVt_IV */
-                assert(!(SvFLAGS(oldsv) &
-                    (SVf_OOK|SVf_UTF8|(SVf_OK & ~(SVf_IOK|SVp_IOK)))));
-                SvFLAGS(oldsv) |= (SVf_IOK|SVp_IOK);
-                /* SvIV_set() where sv_any points to head */
-                oldsv->sv_u.svu_iv = cur;
+                IV cur = cx->blk_loop.state_u.lazyiv.cur;
+                bool pad_it = FALSE;
+                if (UNLIKELY(cur > cx->blk_loop.state_u.lazyiv.end)) {
+                    if (LIKELY(!i)) {
+                        goto retno;
+                    }
+                    pad_it = TRUE;
+                }
+
+                oldsv = *itersvp;
+                /* see NB comment above */
+                if (UNLIKELY(pad_it)) {
+                    *itersvp = &PL_sv_undef;
+                    SvREFCNT_dec(oldsv);
+                }
+                else if (oldsv && LIKELY(SvREFCNT(oldsv) == 1 && !SvMAGICAL(oldsv))) {
+                    /* safe to reuse old SV */
+
+                    if (    (SvFLAGS(oldsv) & (SVTYPEMASK|SVf_THINKFIRST|SVf_IVisUV))
+                         == SVt_IV) {
+                        /* Cheap SvIOK_only().
+                         * Assert that flags which SvIOK_only() would test or
+                         * clear can't be set, because we're SVt_IV */
+                        assert(!(SvFLAGS(oldsv) &
+                                 (SVf_OOK|SVf_UTF8|(SVf_OK & ~(SVf_IOK|SVp_IOK)))));
+                        SvFLAGS(oldsv) |= (SVf_IOK|SVp_IOK);
+                        /* SvIV_set() where sv_any points to head */
+                        oldsv->sv_u.svu_iv = cur;
+
+                    }
+                    else
+                        sv_setiv(oldsv, cur);
+                }
+                else {
+                    /* we need a fresh SV every time so that loop body sees a
+                     * completely new SV for closures/references to work as they
+                     * used to */
+                    *itersvp = newSViv(cur);
+                    SvREFCNT_dec(oldsv);
+                }
 
+                if (UNLIKELY(pad_it)) {
+                    /* We're good (see "We are looping n-at-a-time" comment
+                       above). */
+                }
+                else if (UNLIKELY(cur == IV_MAX)) {
+                    /* Handle end of range at IV_MAX */
+                    cx->blk_loop.state_u.lazyiv.end = IV_MIN;
+                } else
+                    ++cx->blk_loop.state_u.lazyiv.cur;
+                break;
             }
-            else
-                sv_setiv(oldsv, cur);
-       }
-       else
-       {
-           /* we need a fresh SV every time so that loop body sees a
-            * completely new SV for closures/references to work as they
-            * used to */
-           *itersvp = newSViv(cur);
-           SvREFCNT_dec(oldsv);
-       }
-
-       if (UNLIKELY(cur == IV_MAX)) {
-           /* Handle end of range at IV_MAX */
-           cx->blk_loop.state_u.lazyiv.end = IV_MIN;
-       } else
-           ++cx->blk_loop.state_u.lazyiv.cur;
-        break;
-    }
-
-    case CXt_LOOP_LIST: /* for (1,2,3) */
-
-        assert(OPpITER_REVERSED == 2); /* so inc becomes -1 or 1 */
-        inc = 1 - (PL_op->op_private & OPpITER_REVERSED);
-        ix = (cx->blk_loop.state_u.stack.ix += inc);
-        if (UNLIKELY(inc > 0
-                        ? ix > cx->blk_oldsp
-                        : ix <= cx->blk_loop.state_u.stack.basesp)
-        )
-            goto retno;
 
-        sv = PL_stack_base[ix];
-        av = NULL;
-        goto loop_ary_common;
+        case CXt_LOOP_LIST: /* for (1,2,3) */
 
-    case CXt_LOOP_ARY: /* for (@ary) */
+            assert(OPpITER_REVERSED == 2); /* so inc becomes -1 or 1 */
+            inc = (IV)1 - (IV)(PL_op->op_private & OPpITER_REVERSED);
+            ix = (cx->blk_loop.state_u.stack.ix += inc);
+            if (UNLIKELY(inc > 0
+                         ? ix > cx->blk_oldsp
+                         : ix <= cx->blk_loop.state_u.stack.basesp)
+                ) {
+                if (LIKELY(!i)) {
+                    goto retno;
+                }
 
-        av = cx->blk_loop.state_u.ary.ary;
-        inc = 1 - (PL_op->op_private & OPpITER_REVERSED);
-        ix = (cx->blk_loop.state_u.ary.ix += inc);
-        if (UNLIKELY(inc > 0
-                        ? ix > AvFILL(av)
-                        : ix < 0)
-        )
-            goto retno;
+                sv = &PL_sv_undef;
+            }
+            else {
+                sv = PL_stack_base[ix];
+            }
 
-        if (UNLIKELY(SvRMAGICAL(av))) {
-            SV * const * const svp = av_fetch(av, ix, FALSE);
-            sv = svp ? *svp : NULL;
-        }
-        else {
-            sv = AvARRAY(av)[ix];
-        }
+            av = NULL;
+            goto loop_ary_common;
 
-      loop_ary_common:
+        case CXt_LOOP_ARY: /* for (@ary) */
 
-        if (UNLIKELY(cx->cx_type & CXp_FOR_LVREF)) {
-            SvSetMagicSV(*itersvp, sv);
-            break;
-        }
+            av = cx->blk_loop.state_u.ary.ary;
+            inc = (IV)1 - (IV)(PL_op->op_private & OPpITER_REVERSED);
+            ix = (cx->blk_loop.state_u.ary.ix += inc);
+            if (UNLIKELY(inc > 0
+                         ? ix > AvFILL(av)
+                         : ix < 0)
+                ) {
+                if (LIKELY(!i)) {
+                    goto retno;
+                }
 
-        if (LIKELY(sv)) {
-            if (UNLIKELY(SvIS_FREED(sv))) {
-                *itersvp = NULL;
-                Perl_croak(aTHX_ "Use of freed value in iteration");
-            }
-            if (SvPADTMP(sv)) {
-                sv = newSVsv(sv);
+                sv = &PL_sv_undef;
+            } else if (UNLIKELY(SvRMAGICAL(av))) {
+                SV * const * const svp = av_fetch(av, ix, FALSE);
+                sv = svp ? *svp : NULL;
             }
             else {
-                SvTEMP_off(sv);
-                SvREFCNT_inc_simple_void_NN(sv);
+                sv = AvARRAY(av)[ix];
             }
-        }
-        else if (av) {
-            sv = newSVavdefelem(av, ix, 0);
-        }
-        else
-            sv = &PL_sv_undef;
 
-        oldsv = *itersvp;
-        *itersvp = sv;
-        SvREFCNT_dec(oldsv);
-        break;
+        loop_ary_common:
+
+            if (UNLIKELY(cx->cx_type & CXp_FOR_LVREF)) {
+                SvSetMagicSV(*itersvp, sv);
+                break;
+            }
+
+            if (LIKELY(sv)) {
+                if (UNLIKELY(SvIS_FREED(sv))) {
+                    *itersvp = NULL;
+                    Perl_croak(aTHX_ "Use of freed value in iteration");
+                }
+                if (SvPADTMP(sv)) {
+                    sv = newSVsv(sv);
+                }
+                else {
+                    SvTEMP_off(sv);
+                    SvREFCNT_inc_simple_void_NN(sv);
+                }
+            }
+            else if (av) {
+                sv = newSVavdefelem(av, ix, 0);
+            }
+            else
+                sv = &PL_sv_undef;
+
+            oldsv = *itersvp;
+            *itersvp = sv;
+            SvREFCNT_dec(oldsv);
+            break;
+
+        default:
+            DIE(aTHX_ "panic: pp_iter, type=%u", CxTYPE(cx));
+        }
 
-    default:
-       DIE(aTHX_ "panic: pp_iter, type=%u", CxTYPE(cx));
+        /* Only relevant for a many-at-a-time loop: */
+        ++itersvp;
     }
 
-    /* Bypass pushing &PL_sv_yes and calling pp_and(); instead
+    /* Try to bypass pushing &PL_sv_yes and calling pp_and(); instead
      * jump straight to the AND op's op_other */
     assert(PL_op->op_next->op_type == OP_AND);
-    assert(PL_op->op_next->op_ppaddr == Perl_pp_and);
-    return cLOGOPx(PL_op->op_next)->op_other;
+    if (PL_op->op_next->op_ppaddr == Perl_pp_and) {
+        return cLOGOPx(PL_op->op_next)->op_other;
+    }
+    else {
+        /* An XS module has replaced the op_ppaddr, so fall back to the slow,
+         * obvious way. */
+        /* pp_enteriter should have pre-extended the stack */
+        EXTEND_SKIP(PL_stack_sp, 1);
+        rpp_push_IMM(&PL_sv_yes);
+        return PL_op->op_next;
+    }
 
   retno:
-    /* Bypass pushing &PL_sv_no and calling pp_and(); instead
+    /* Try to bypass pushing &PL_sv_no and calling pp_and(); instead
      * jump straight to the AND op's op_next */
     assert(PL_op->op_next->op_type == OP_AND);
-    assert(PL_op->op_next->op_ppaddr == Perl_pp_and);
     /* pp_enteriter should have pre-extended the stack */
     EXTEND_SKIP(PL_stack_sp, 1);
     /* we only need this for the rare case where the OP_AND isn't
      * in void context, e.g. $x = do { for (..) {...} };
-     * but its cheaper to just push it rather than testing first
+     * (or for when an XS module has replaced the op_ppaddr)
+     * but it's cheaper to just push it rather than testing first
      */
-    *++PL_stack_sp = &PL_sv_no;
-    return PL_op->op_next->op_next;
+    rpp_push_IMM(&PL_sv_no);
+    if (PL_op->op_next->op_ppaddr == Perl_pp_and) {
+        return PL_op->op_next->op_next;
+    }
+    else {
+        /* An XS module has replaced the op_ppaddr, so fall back to the slow,
+         * obvious way. */
+        return PL_op->op_next;
+    }
 }
 
 
 /*
 A description of how taint works in pattern matching and substitution.
 
-This is all conditional on NO_TAINT_SUPPORT not being defined. Under
-NO_TAINT_SUPPORT, taint-related operations should become no-ops.
+This is all conditional on NO_TAINT_SUPPORT remaining undefined (the default).
+Under NO_TAINT_SUPPORT, taint-related operations should become no-ops.
 
 While the pattern is being assembled/concatenated and then compiled,
 PL_tainted will get set (via TAINT_set) if any component of the pattern
@@ -4080,34 +5171,34 @@ There are four destinations of taint and they are affected by the sources
 according to the rules below:
 
     * the return value (not including /r):
-       tainted by the source string and pattern, but only for the
-       number-of-iterations case; boolean returns aren't tainted;
+        tainted by the source string and pattern, but only for the
+        number-of-iterations case; boolean returns aren't tainted;
     * the modified string (or modified copy under /r):
-       tainted by the source string, pattern, and replacement strings;
+        tainted by the source string, pattern, and replacement strings;
     * $1 et al:
-       tainted by the pattern, and under 'use re "taint"', by the source
-       string too;
+        tainted by the pattern, and under 'use re "taint"', by the source
+        string too;
     * PL_taint - i.e. whether subsequent code (e.g. in a /e block) is tainted:
-       should always be unset before executing subsequent code.
+        should always be unset before executing subsequent code.
 
 The overall action of pp_subst is:
 
     * at the start, set bits in rxtainted indicating the taint status of
-       the various sources.
+        the various sources.
 
     * After each pattern execution, update the SUBST_TAINT_PAT bit in
-       rxtainted if RXf_TAINTED_SEEN has been set, to indicate that the
-       pattern has subsequently become tainted via locale ops.
+        rxtainted if RXf_TAINTED_SEEN has been set, to indicate that the
+        pattern has subsequently become tainted via locale ops.
 
     * If control is being passed to pp_substcont to execute a /e block,
-       save rxtainted in the CXt_SUBST block, for future use by
-       pp_substcont.
+        save rxtainted in the CXt_SUBST block, for future use by
+        pp_substcont.
 
     * Whenever control is being returned to perl code (either by falling
-       off the "end" of pp_subst/pp_substcont, or by entering a /e block),
-       use the flag bits in rxtainted to make all the appropriate types of
-       destination taint visible; e.g. set RXf_TAINTED_SEEN so that $1
-       et al will appear tainted.
+        off the "end" of pp_subst/pp_substcont, or by entering a /e block),
+        use the flag bits in rxtainted to make all the appropriate types of
+        destination taint visible; e.g. set RXf_TAINTED_SEEN so that $1
+        et al will appear tainted.
 
 pp_match is just a simpler version of the above.
 
@@ -4115,7 +5206,7 @@ pp_match is just a simpler version of the above.
 
 PP(pp_subst)
 {
-    dSP; dTARG;
+    dTARG;
     PMOP *pm = cPMOP;
     PMOP *rpm = pm;
     char *s;
@@ -4126,7 +5217,7 @@ PP(pp_subst)
     SSize_t maxiters;
     bool once;
     U8 rxtainted = 0; /* holds various SUBST_TAINT_* flag bits.
-                       See "how taint works" above */
+                        See "how taint works" above */
     char *orig;
     U8 r_flags;
     REGEXP *rx = PM_GETRE(pm);
@@ -4134,26 +5225,40 @@ PP(pp_subst)
     STRLEN len;
     int force_on_match = 0;
     const I32 oldsave = PL_savestack_ix;
-    STRLEN slen;
     bool doutf8 = FALSE; /* whether replacement is in utf8 */
 #ifdef PERL_ANY_COW
     bool was_cow;
 #endif
     SV *nsv = NULL;
-    /* known replacement string? */
-    SV *dstr = (pm->op_pmflags & PMf_CONST) ? POPs : NULL;
+    SSize_t sp_offset = 0; /* number of items left on stack */
+    SV *dstr;
+    SV *retval;
 
     PERL_ASYNC_CHECK();
 
-    if (PL_op->op_flags & OPf_STACKED)
-       TARG = POPs;
+    if (pm->op_pmflags & PMf_CONST) {
+        /* known replacement string */
+        dstr = *PL_stack_sp;
+        sp_offset++;
+    }
+    else
+        dstr = NULL;
+
+    if (PL_op->op_flags & OPf_STACKED) {
+        /*  expr =~ s///;  */
+        TARG = PL_stack_sp[-sp_offset];
+        sp_offset++;
+    }
     else {
         if (ARGTARG)
+            /*  $lex =~ s///;  */
             GETTARGET;
         else {
+            /* s///;  */
             TARG = DEFSV;
         }
-       EXTEND(SP,1);
+        if (!sp_offset)
+            rpp_extend(1);
     }
 
     SvGETMAGIC(TARG); /* must come before cow check */
@@ -4163,47 +5268,48 @@ PP(pp_subst)
 #endif
     if (!(rpm->op_pmflags & PMf_NONDESTRUCT)) {
 #ifndef PERL_ANY_COW
-       if (SvIsCOW(TARG))
-           sv_force_normal_flags(TARG,0);
+        if (SvIsCOW(TARG))
+            sv_force_normal_flags(TARG,0);
 #endif
-       if ((SvREADONLY(TARG)
-               || ( ((SvTYPE(TARG) == SVt_PVGV && isGV_with_GP(TARG))
-                     || SvTYPE(TARG) > SVt_PVLV)
-                    && !(SvTYPE(TARG) == SVt_PVGV && SvFAKE(TARG)))))
-           Perl_croak_no_modify();
+        if ((SvREADONLY(TARG)
+                || ( ((SvTYPE(TARG) == SVt_PVGV && isGV_with_GP(TARG))
+                      || SvTYPE(TARG) > SVt_PVLV)
+                     && !(SvTYPE(TARG) == SVt_PVGV && SvFAKE(TARG)))))
+            Perl_croak_no_modify();
     }
-    PUTBACK;
 
     orig = SvPV_nomg(TARG, len);
     /* note we don't (yet) force the var into being a string; if we fail
      * to match, we leave as-is; on successful match however, we *will*
      * coerce into a string, then repeat the match */
     if (!SvPOKp(TARG) || SvTYPE(TARG) == SVt_PVGV || SvVOK(TARG))
-       force_on_match = 1;
+        force_on_match = 1;
 
     /* only replace once? */
     once = !(rpm->op_pmflags & PMf_GLOBAL);
 
     /* See "how taint works" above */
     if (TAINTING_get) {
-       rxtainted  = (
-           (SvTAINTED(TARG) ? SUBST_TAINT_STR : 0)
-         | (RXp_ISTAINTED(prog) ? SUBST_TAINT_PAT : 0)
-         | ((pm->op_pmflags & PMf_RETAINT) ? SUBST_TAINT_RETAINT : 0)
-         | ((  (once && !(rpm->op_pmflags & PMf_NONDESTRUCT))
+        rxtainted  = (
+            (SvTAINTED(TARG) ? SUBST_TAINT_STR : 0)
+          | (RXp_ISTAINTED(prog) ? SUBST_TAINT_PAT : 0)
+          | ((pm->op_pmflags & PMf_RETAINT) ? SUBST_TAINT_RETAINT : 0)
+          | ((  (once && !(rpm->op_pmflags & PMf_NONDESTRUCT))
              || (PL_op->op_private & OPpTRUEBOOL)) ? SUBST_TAINT_BOOLRET : 0));
-       TAINT_NOT;
+        TAINT_NOT;
     }
 
   force_it:
     if (!pm || !orig)
-       DIE(aTHX_ "panic: pp_subst, pm=%p, orig=%p", pm, orig);
+        DIE(aTHX_ "panic: pp_subst, pm=%p, orig=%p", pm, orig);
 
     strend = orig + len;
-    slen = DO_UTF8(TARG) ? utf8_length((U8*)orig, (U8*)strend) : len;
-    maxiters = 2 * slen + 10;  /* We can match twice at each
-                                  position, once with zero-length,
-                                  second time with non-zero. */
+    /* We can match twice at each position, once with zero-length,
+     * second time with non-zero.
+     * Don't handle utf8 specially; we can use length-in-bytes as an
+     * upper bound on length-in-characters, and avoid the cpu-cost of
+     * computing a tighter bound. */
+    maxiters = 2 * len + 10;
 
     /* handle the empty pattern */
     if (!RX_PRELEN(rx) && PL_curpm && !prog->mother_re) {
@@ -4236,330 +5342,425 @@ PP(pp_subst)
 
     if (!CALLREGEXEC(rx, orig, strend, orig, 0, TARG, NULL, r_flags))
     {
-       SPAGAIN;
-       PUSHs(rpm->op_pmflags & PMf_NONDESTRUCT ? TARG : &PL_sv_no);
-       LEAVE_SCOPE(oldsave);
-       RETURN;
+        SV *ret = rpm->op_pmflags & PMf_NONDESTRUCT ? TARG : &PL_sv_no;
+        if (dstr)
+            rpp_popfree_1_NN(); /* pop replacement string */
+        if (PL_op->op_flags & OPf_STACKED)
+            rpp_replace_1_1_NN(ret); /* pop LHS of =~ */
+        else
+            rpp_push_1(ret);
+        LEAVE_SCOPE(oldsave);
+        return NORMAL;
     }
     PL_curpm = pm;
 
     /* known replacement string? */
     if (dstr) {
-       /* replacement needing upgrading? */
-       if (DO_UTF8(TARG) && !doutf8) {
-            nsv = sv_newmortal();
-            SvSetSV(nsv, dstr);
-            sv_utf8_upgrade(nsv);
-            c = SvPV_const(nsv, clen);
-            doutf8 = TRUE;
-       }
-       else {
-           c = SvPV_const(dstr, clen);
-           doutf8 = DO_UTF8(dstr);
-       }
-
-       if (UNLIKELY(TAINT_get))
-           rxtainted |= SUBST_TAINT_REPL;
+        /* replacement needing upgrading? */
+        if (DO_UTF8(TARG) && !doutf8) {
+             nsv = sv_newmortal();
+             SvSetSV(nsv, dstr);
+             sv_utf8_upgrade(nsv);
+             c = SvPV_const(nsv, clen);
+             doutf8 = TRUE;
+        }
+        else {
+            c = SvPV_const(dstr, clen);
+            doutf8 = DO_UTF8(dstr);
+        }
+
+        if (UNLIKELY(TAINT_get))
+            rxtainted |= SUBST_TAINT_REPL;
     }
     else {
-       c = NULL;
-       doutf8 = FALSE;
+        c = NULL;
+        doutf8 = FALSE;
     }
     
-    /* can do inplace substitution? */
     if (c
 #ifdef PERL_ANY_COW
-       && !was_cow
+        && !was_cow
 #endif
-        && (I32)clen <= RXp_MINLENRET(prog)
+        && (SSize_t)clen <= RXp_MINLENRET(prog)
         && (  once
            || !(r_flags & REXEC_COPY_STR)
            || (!SvGMAGICAL(dstr) && !(RXp_EXTFLAGS(prog) & RXf_EVAL_SEEN))
            )
         && !(RXp_EXTFLAGS(prog) & RXf_NO_INPLACE_SUBST)
-       && (!doutf8 || SvUTF8(TARG))
-       && !(rpm->op_pmflags & PMf_NONDESTRUCT))
+        && (!doutf8 || SvUTF8(TARG))
+        && !(rpm->op_pmflags & PMf_NONDESTRUCT))
     {
+        /* known replacement string and can do in-place substitution */
 
 #ifdef PERL_ANY_COW
         /* string might have got converted to COW since we set was_cow */
-       if (SvIsCOW(TARG)) {
-         if (!force_on_match)
-           goto have_a_cow;
-         assert(SvVOK(TARG));
-       }
+        if (SvIsCOW(TARG)) {
+          if (!force_on_match)
+            goto have_a_cow;
+          assert(SvVOK(TARG));
+        }
 #endif
-       if (force_on_match) {
+        if (force_on_match) {
             /* redo the first match, this time with the orig var
              * forced into being a string */
-           force_on_match = 0;
-           orig = SvPV_force_nomg(TARG, len);
-           goto force_it;
-       }
+            force_on_match = 0;
+            orig = SvPV_force_nomg(TARG, len);
+            goto force_it;
+        }
 
-       if (once) {
+        if (once) {
             char *d, *m;
-           if (RXp_MATCH_TAINTED(prog)) /* run time pattern taint, eg locale */
-               rxtainted |= SUBST_TAINT_PAT;
-           m = orig + RXp_OFFS(prog)[0].start;
-           d = orig + RXp_OFFS(prog)[0].end;
-           s = orig;
-           if (m - s > strend - d) {  /* faster to shorten from end */
-                I32 i;
-               if (clen) {
-                   Copy(c, m, clen, char);
-                   m += clen;
-               }
-               i = strend - d;
-               if (i > 0) {
-                   Move(d, m, i, char);
-                   m += i;
-               }
-               *m = '\0';
-               SvCUR_set(TARG, m - s);
-           }
-           else {      /* faster from front */
-                I32 i = m - s;
-               d -= clen;
+            if (RXp_MATCH_TAINTED(prog)) /* run time pattern taint, eg locale */
+                rxtainted |= SUBST_TAINT_PAT;
+            m = orig + RXp_OFFS_START(prog,0);
+            d = orig + RXp_OFFS_END(prog,0);
+            s = orig;
+            if (m - s > strend - d) {  /* faster to shorten from end */
+                SSize_t i;
+                if (clen) {
+                    Copy(c, m, clen, char);
+                    m += clen;
+                }
+                i = strend - d;
+                if (i > 0) {
+                    Move(d, m, i, char);
+                    m += i;
+                }
+                *m = '\0';
+                SvCUR_set(TARG, m - s);
+            }
+            else {     /* faster from front */
+                SSize_t i = m - s;
+                d -= clen;
                 if (i > 0)
                     Move(s, d - i, i, char);
-               sv_chop(TARG, d-i);
-               if (clen)
-                   Copy(c, d, clen, char);
-           }
-           SPAGAIN;
-           PUSHs(&PL_sv_yes);
-       }
-       else {
+                sv_chop(TARG, d-i);
+                if (clen)
+                    Copy(c, d, clen, char);
+            }
+            retval = &PL_sv_yes;
+            goto ret;
+        }
+        else {
             char *d, *m;
-            d = s = RXp_OFFS(prog)[0].start + orig;
-           do {
-                I32 i;
-               if (UNLIKELY(iters++ > maxiters))
-                   DIE(aTHX_ "Substitution loop");
+            d = s = RXp_OFFS_START(prog,0) + orig;
+            do {
+                SSize_t i;
+                if (UNLIKELY(iters++ > maxiters))
+                    DIE(aTHX_ "Substitution loop");
                 /* run time pattern taint, eg locale */
-               if (UNLIKELY(RXp_MATCH_TAINTED(prog)))
-                   rxtainted |= SUBST_TAINT_PAT;
-               m = RXp_OFFS(prog)[0].start + orig;
-               if ((i = m - s)) {
-                   if (s != d)
-                       Move(s, d, i, char);
-                   d += i;
-               }
-               if (clen) {
-                   Copy(c, d, clen, char);
-                   d += clen;
-               }
-               s = RXp_OFFS(prog)[0].end + orig;
-           } while (CALLREGEXEC(rx, s, strend, orig,
-                                s == m, /* don't match same null twice */
-                                TARG, NULL,
+                if (UNLIKELY(RXp_MATCH_TAINTED(prog)))
+                    rxtainted |= SUBST_TAINT_PAT;
+                m = RXp_OFFS_START(prog,0) + orig;
+                if ((i = m - s)) {
+                    if (s != d)
+                        Move(s, d, i, char);
+                    d += i;
+                }
+                if (clen) {
+                    Copy(c, d, clen, char);
+                    d += clen;
+                }
+                s = RXp_OFFS_END(prog,0) + orig;
+            } while (CALLREGEXEC(rx, s, strend, orig,
+                                 s == m, /* don't match same null twice */
+                                 TARG, NULL,
                      REXEC_NOT_FIRST|REXEC_IGNOREPOS|REXEC_FAIL_ON_UNDERFLOW));
-           if (s != d) {
-                I32 i = strend - s;
-               SvCUR_set(TARG, d - SvPVX_const(TARG) + i);
-               Move(s, d, i+1, char);          /* include the NUL */
-           }
-           SPAGAIN;
+            if (s != d) {
+                SSize_t i = strend - s;
+                SvCUR_set(TARG, d - SvPVX_const(TARG) + i);
+                Move(s, d, i+1, char);         /* include the NUL */
+            }
             assert(iters);
-            if (PL_op->op_private & OPpTRUEBOOL)
-                PUSHs(&PL_sv_yes);
-            else
-                mPUSHi(iters);
-       }
+            goto ret_iters;
+        }
     }
     else {
-       bool first;
+        /* not known replacement string or can't do in-place substitution) */
+        bool first;
         char *m;
-       SV *repl;
-       if (force_on_match) {
+        SV *repl;
+        if (force_on_match) {
             /* redo the first match, this time with the orig var
              * forced into being a string */
-           force_on_match = 0;
-           if (rpm->op_pmflags & PMf_NONDESTRUCT) {
-               /* I feel that it should be possible to avoid this mortal copy
-                  given that the code below copies into a new destination.
-                  However, I suspect it isn't worth the complexity of
-                  unravelling the C<goto force_it> for the small number of
-                  cases where it would be viable to drop into the copy code. */
-               TARG = sv_2mortal(newSVsv(TARG));
-           }
-           orig = SvPV_force_nomg(TARG, len);
-           goto force_it;
-       }
+            force_on_match = 0;
+            if (rpm->op_pmflags & PMf_NONDESTRUCT) {
+                /* I feel that it should be possible to avoid this mortal copy
+                   given that the code below copies into a new destination.
+                   However, I suspect it isn't worth the complexity of
+                   unravelling the C<goto force_it> for the small number of
+                   cases where it would be viable to drop into the copy code. */
+                TARG = sv_2mortal(newSVsv(TARG));
+            }
+            orig = SvPV_force_nomg(TARG, len);
+            goto force_it;
+        }
 #ifdef PERL_ANY_COW
       have_a_cow:
 #endif
-       if (RXp_MATCH_TAINTED(prog)) /* run time pattern taint, eg locale */
-           rxtainted |= SUBST_TAINT_PAT;
-       repl = dstr;
-        s = RXp_OFFS(prog)[0].start + orig;
-       dstr = newSVpvn_flags(orig, s-orig,
+        if (RXp_MATCH_TAINTED(prog)) /* run time pattern taint, eg locale */
+            rxtainted |= SUBST_TAINT_PAT;
+        repl = dstr;
+        s = RXp_OFFS_START(prog,0) + orig;
+        dstr = newSVpvn_flags(orig, s-orig,
                     SVs_TEMP | (DO_UTF8(TARG) ? SVf_UTF8 : 0));
-       if (!c) {
-           PERL_CONTEXT *cx;
-           SPAGAIN;
+        if (!c) {
+        /* not known replacement string - call out to ops and OP_SUBSTCONT */
+            PERL_CONTEXT *cx;
             m = orig;
-           /* note that a whole bunch of local vars are saved here for
-            * use by pp_substcont: here's a list of them in case you're
-            * searching for places in this sub that uses a particular var:
-            * iters maxiters r_flags oldsave rxtainted orig dstr targ
-            * s m strend rx once */
-           CX_PUSHSUBST(cx);
-           RETURNOP(cPMOP->op_pmreplrootu.op_pmreplroot);
-       }
-       first = TRUE;
-       do {
-           if (UNLIKELY(iters++ > maxiters))
-               DIE(aTHX_ "Substitution loop");
-           if (UNLIKELY(RXp_MATCH_TAINTED(prog)))
-               rxtainted |= SUBST_TAINT_PAT;
-           if (RXp_MATCH_COPIED(prog) && RXp_SUBBEG(prog) != orig) {
-               char *old_s    = s;
-               char *old_orig = orig;
+            /* note that a whole bunch of local vars are saved here for
+             * use by pp_substcont: here's a list of them in case you're
+             * searching for places in this sub that uses a particular var:
+             * iters maxiters r_flags oldsave rxtainted orig dstr targ
+             * s m strend rx once */
+            CX_PUSHSUBST(cx);
+            return cPMOP->op_pmreplrootu.op_pmreplroot;
+        }
+
+        /* We get here if it's a known replacement string, but can't
+         * substitute in-place */
+
+        first = TRUE;
+        do {
+            if (UNLIKELY(iters++ > maxiters))
+                DIE(aTHX_ "Substitution loop");
+            if (UNLIKELY(RXp_MATCH_TAINTED(prog)))
+                rxtainted |= SUBST_TAINT_PAT;
+            if (RXp_MATCH_COPIED(prog) && RXp_SUBBEG(prog) != orig) {
+                char *old_s    = s;
+                char *old_orig = orig;
                 assert(RXp_SUBOFFSET(prog) == 0);
 
-               orig = RXp_SUBBEG(prog);
-               s = orig + (old_s - old_orig);
-               strend = s + (strend - old_s);
-           }
-           m = RXp_OFFS(prog)[0].start + orig;
-           sv_catpvn_nomg_maybeutf8(dstr, s, m - s, DO_UTF8(TARG));
-           s = RXp_OFFS(prog)[0].end + orig;
-           if (first) {
-               /* replacement already stringified */
-             if (clen)
-               sv_catpvn_nomg_maybeutf8(dstr, c, clen, doutf8);
-             first = FALSE;
-           }
-           else {
-               sv_catsv(dstr, repl);
-           }
-           if (once)
-               break;
-       } while (CALLREGEXEC(rx, s, strend, orig,
+                orig = RXp_SUBBEG(prog);
+                s = orig + (old_s - old_orig);
+                strend = s + (strend - old_s);
+            }
+            m = RXp_OFFS_START(prog,0) + orig;
+            sv_catpvn_nomg_maybeutf8(dstr, s, m - s, DO_UTF8(TARG));
+            s = RXp_OFFS_END(prog,0) + orig;
+            if (first) {
+                /* replacement already stringified */
+              if (clen)
+                sv_catpvn_nomg_maybeutf8(dstr, c, clen, doutf8);
+              first = FALSE;
+            }
+            else {
+                sv_catsv(dstr, repl);
+            }
+            if (once)
+                break;
+        } while (CALLREGEXEC(rx, s, strend, orig,
                              s == m,    /* Yields minend of 0 or 1 */
-                            TARG, NULL,
+                             TARG, NULL,
                     REXEC_NOT_FIRST|REXEC_IGNOREPOS|REXEC_FAIL_ON_UNDERFLOW));
         assert(strend >= s);
-       sv_catpvn_nomg_maybeutf8(dstr, s, strend - s, DO_UTF8(TARG));
-
-       if (rpm->op_pmflags & PMf_NONDESTRUCT) {
-           /* From here on down we're using the copy, and leaving the original
-              untouched.  */
-           TARG = dstr;
-           SPAGAIN;
-           PUSHs(dstr);
-       } else {
+        sv_catpvn_nomg_maybeutf8(dstr, s, strend - s, DO_UTF8(TARG));
+
+        if (rpm->op_pmflags & PMf_NONDESTRUCT) {
+            /* From here on down we're using the copy, and leaving the original
+               untouched.  */
+            TARG = dstr;
+            retval = dstr;
+            goto ret;
+        } else {
 #ifdef PERL_ANY_COW
-           /* The match may make the string COW. If so, brilliant, because
-              that's just saved us one malloc, copy and free - the regexp has
-              donated the old buffer, and we malloc an entirely new one, rather
-              than the regexp malloc()ing a buffer and copying our original,
-              only for us to throw it away here during the substitution.  */
-           if (SvIsCOW(TARG)) {
-               sv_force_normal_flags(TARG, SV_COW_DROP_PV);
-           } else
+            /* The match may make the string COW. If so, brilliant, because
+               that's just saved us one malloc, copy and free - the regexp has
+               donated the old buffer, and we malloc an entirely new one, rather
+               than the regexp malloc()ing a buffer and copying our original,
+               only for us to throw it away here during the substitution.  */
+            if (SvIsCOW(TARG)) {
+                sv_force_normal_flags(TARG, SV_COW_DROP_PV);
+            } else
 #endif
-           {
-               SvPV_free(TARG);
-           }
-           SvPV_set(TARG, SvPVX(dstr));
-           SvCUR_set(TARG, SvCUR(dstr));
-           SvLEN_set(TARG, SvLEN(dstr));
-           SvFLAGS(TARG) |= SvUTF8(dstr);
-           SvPV_set(dstr, NULL);
-
-           SPAGAIN;
-            if (PL_op->op_private & OPpTRUEBOOL)
-                PUSHs(&PL_sv_yes);
-            else
-                mPUSHi(iters);
-       }
+            {
+                SvPV_free(TARG);
+            }
+            SvPV_set(TARG, SvPVX(dstr));
+            SvCUR_set(TARG, SvCUR(dstr));
+            SvLEN_set(TARG, SvLEN(dstr));
+            SvFLAGS(TARG) |= SvUTF8(dstr);
+            SvPV_set(dstr, NULL);
+            goto ret_iters;
+        }
+    }
+
+  ret_iters:
+    if (PL_op->op_private & OPpTRUEBOOL)
+        retval = &PL_sv_yes;
+    else {
+        retval = sv_newmortal();
+        sv_setiv(retval, iters);
     }
 
+  ret:
+    if (dstr)
+        rpp_popfree_1_NN(); /* pop replacement string */
+    if (PL_op->op_flags & OPf_STACKED)
+        rpp_replace_1_1_NN(retval); /* pop LHS of =~ */
+    else
+        rpp_push_1(retval);
+
     if (!(rpm->op_pmflags & PMf_NONDESTRUCT)) {
-       (void)SvPOK_only_UTF8(TARG);
+        (void)SvPOK_only_UTF8(TARG);
     }
 
     /* See "how taint works" above */
     if (TAINTING_get) {
-       if ((rxtainted & SUBST_TAINT_PAT) ||
-           ((rxtainted & (SUBST_TAINT_STR|SUBST_TAINT_RETAINT)) ==
-                               (SUBST_TAINT_STR|SUBST_TAINT_RETAINT))
-       )
-           (RXp_MATCH_TAINTED_on(prog)); /* taint $1 et al */
-
-       if (!(rxtainted & SUBST_TAINT_BOOLRET)
-           && (rxtainted & (SUBST_TAINT_STR|SUBST_TAINT_PAT))
-       )
-           SvTAINTED_on(TOPs);  /* taint return value */
-       else
-           SvTAINTED_off(TOPs);  /* may have got tainted earlier */
-
-       /* needed for mg_set below */
-       TAINT_set(
-         cBOOL(rxtainted & (SUBST_TAINT_STR|SUBST_TAINT_PAT|SUBST_TAINT_REPL))
+        if ((rxtainted & SUBST_TAINT_PAT) ||
+            ((rxtainted & (SUBST_TAINT_STR|SUBST_TAINT_RETAINT)) ==
+                                (SUBST_TAINT_STR|SUBST_TAINT_RETAINT))
+        )
+            (RXp_MATCH_TAINTED_on(prog)); /* taint $1 et al */
+
+        if (!(rxtainted & SUBST_TAINT_BOOLRET)
+            && (rxtainted & (SUBST_TAINT_STR|SUBST_TAINT_PAT))
+        )
+            SvTAINTED_on(retval);  /* taint return value */
+        else
+            SvTAINTED_off(retval);  /* may have got tainted earlier */
+
+        /* needed for mg_set below */
+        TAINT_set(
+          cBOOL(rxtainted & (SUBST_TAINT_STR|SUBST_TAINT_PAT|SUBST_TAINT_REPL))
         );
-       SvTAINT(TARG);
+        SvTAINT(TARG);
     }
     SvSETMAGIC(TARG); /* PL_tainted must be correctly set for this mg_set */
     TAINT_NOT;
     LEAVE_SCOPE(oldsave);
-    RETURN;
+    return NORMAL;
 }
 
+
 PP(pp_grepwhile)
 {
-    dSP;
-    dPOPss;
+    /* Understanding the stack during a grep.
+     *
+     * 'grep expr, args' is implemented in the form of
+     *     grepstart;
+     *     do {
+     *          expr;
+     *          grepwhile;
+     *     } while (args);
+     *
+     * The stack examples below are in the form of 'perl -Ds' output,
+     * where any stack element indexed by PL_markstack_ptr[i] has a star
+     * just to the right of it.  In addition, the corresponding i value
+     * is displayed under the indexed stack element.
+     *
+     * On entry to grepwhile, the stack looks like this:
+     *
+     *      =>   *  M1..Mn  X1  *  X2..Xn  C  *  R1..Rn  BOOL
+     *       [-2]          [-1]           [0]
+     *
+     * where:
+     *   M1..Mn   Accumulated args which have been matched so far.
+     *   X1..Xn   Random discardable elements from previous iterations.
+     *   C        The current (just processed) arg, still aliased to $_.
+     *   R1..Rn   The args remaining to be processed.
+     *   BOOL     the result of the just-executed grep expression.
+     *
+     * Note that it is easiest to think of the top two stack marks as both
+     * being one too high, and so it would make more sense to have had the
+     * marks like this:
+     *
+     *      =>   *  M1..Mn  *  X1..Xn  *  C  R1..Rn  BOOL
+     *      [-2]       [-1]        [0]
+     *
+     * where the stack is divided neatly into 3 groups:
+     *   - matched,
+     *   - discarded,
+     *   - being, or yet to be, processed.
+     * But off-by-one is the way it is currently, and it works as long as
+     * we keep it consistent and bear it in mind.
+     *
+     * pp_grepwhile() does the following:
+     *
+     * - for a match, replace the X1 pointer with a pointer to C and bump
+     *     PL_markstack_ptr[-1]
+     * - if more args to process, bump PL_markstack_ptr[0] and update the
+     *     $_ alias, else
+     * - remove top 3 MARKs and return M1..Mn, or a scalar,
+     *     or void as appropriate.
+     *
+     */
+
+    bool match = SvTRUE_NN(*PL_stack_sp);
+    rpp_popfree_1_NN();
+
+    if (match) {
+        SV **from_p = PL_stack_base + PL_markstack_ptr[0];
+        SV **to_p   = PL_stack_base + PL_markstack_ptr[-1]++;
+        SV *from    = *from_p;
+        SV *to      = *to_p;
+
+        if (from != to) {
+            *to_p = from;
+#ifdef PERL_RC_STACK
+            SvREFCNT_inc_simple_void_NN(from);
+            SvREFCNT_dec(to);
+#endif
+        }
+    }
 
-    if (SvTRUE_NN(sv))
-       PL_stack_base[PL_markstack_ptr[-1]++] = PL_stack_base[*PL_markstack_ptr];
     ++*PL_markstack_ptr;
     FREETMPS;
     LEAVE_with_name("grep_item");                                      /* exit inner scope */
 
     /* All done yet? */
-    if (UNLIKELY(PL_stack_base + *PL_markstack_ptr > SP)) {
-       I32 items;
-       const U8 gimme = GIMME_V;
-
-       LEAVE_with_name("grep");                                        /* exit outer scope */
-       (void)POPMARK;                          /* pop src */
-       items = --*PL_markstack_ptr - PL_markstack_ptr[-1];
-       (void)POPMARK;                          /* pop dst */
-       SP = PL_stack_base + POPMARK;           /* pop original mark */
-       if (gimme == G_SCALAR) {
-            if (PL_op->op_private & OPpTRUEBOOL)
-                PUSHs(items ? &PL_sv_yes : &PL_sv_zero);
-            else {
-               dTARGET;
-               PUSHi(items);
+    if (UNLIKELY(PL_stack_base + *PL_markstack_ptr > PL_stack_sp)) {
+        SSize_t items;
+        const U8 gimme = GIMME_V;
+
+        LEAVE_with_name("grep");                                       /* exit outer scope */
+        (void)POPMARK;                         /* pop src */
+        items = --*PL_markstack_ptr - PL_markstack_ptr[-1];
+        (void)POPMARK;                         /* pop dst */
+        SV **base = PL_stack_base + POPMARK;   /* pop original mark */
+
+        if (gimme == G_LIST)
+            rpp_popfree_to_NN(base + items);
+        else {
+            rpp_popfree_to_NN(base);
+            if (gimme == G_SCALAR) {
+                if (PL_op->op_private & OPpTRUEBOOL)
+                    rpp_push_IMM(items ? &PL_sv_yes : &PL_sv_zero);
+                else {
+                    dTARGET;
+                    TARGi(items,1);
+                    rpp_push_1(TARG);
+                }
             }
-       }
-       else if (gimme == G_ARRAY)
-           SP += items;
-       RETURN;
+        }
+
+        return NORMAL;
     }
     else {
-       SV *src;
-
-       ENTER_with_name("grep_item");                                   /* enter inner scope */
-       SAVEVPTR(PL_curpm);
-
-       src = PL_stack_base[TOPMARK];
-       if (SvPADTMP(src)) {
-           src = PL_stack_base[TOPMARK] = sv_mortalcopy(src);
-           PL_tmps_floor++;
-       }
-       SvTEMP_off(src);
-       DEFSV_set(src);
+        SV *src;
+
+        ENTER_with_name("grep_item");                                  /* enter inner scope */
+        SAVEVPTR(PL_curpm);
+
+        src = PL_stack_base[TOPMARK];
+        if (SvPADTMP(src)) {
+            SV *newsrc = sv_mortalcopy(src);
+             PL_stack_base[TOPMARK] = newsrc;
+#ifdef PERL_RC_STACK
+            SvREFCNT_inc_simple_void_NN(newsrc);
+            SvREFCNT_dec(src);
+#endif
+            src = newsrc;
+            PL_tmps_floor++;
+        }
+        SvTEMP_off(src);
+        DEFSV_set(src);
 
-       RETURNOP(cLOGOP->op_other);
+        return cLOGOP->op_other;
     }
 }
 
+
 /* leave_adjust_stacks():
  *
  * Process a scope's return args (in the range from_sp+1 .. PL_stack_sp),
@@ -4623,8 +5824,6 @@ PP(pp_grepwhile)
 void
 Perl_leave_adjust_stacks(pTHX_ SV **from_sp, SV **to_sp, U8 gimme, int pass)
 {
-    dVAR;
-    dSP;
     SSize_t tmps_base; /* lowest index into tmps stack that needs freeing now */
     SSize_t nargs;
 
@@ -4632,27 +5831,37 @@ Perl_leave_adjust_stacks(pTHX_ SV **from_sp, SV **to_sp, U8 gimme, int pass)
 
     TAINT_NOT;
 
-    if (gimme == G_ARRAY) {
-        nargs = SP - from_sp;
+    if (gimme == G_LIST) {
+        nargs = PL_stack_sp - from_sp;
         from_sp++;
     }
     else {
         assert(gimme == G_SCALAR);
-        if (UNLIKELY(from_sp >= SP)) {
+        if (UNLIKELY(from_sp >= PL_stack_sp)) {
             /* no return args */
-            assert(from_sp == SP);
-            EXTEND(SP, 1);
-            *++SP = &PL_sv_undef;
-            to_sp = SP;
-            nargs   = 0;
+            assert(from_sp == PL_stack_sp);
+            rpp_xpush_IMM(&PL_sv_undef);
         }
-        else {
-            from_sp = SP;
-            nargs   = 1;
+        from_sp = PL_stack_sp;
+        nargs   = 1;
+    }
+
+    /* common code for G_SCALAR and G_LIST */
+
+#ifdef PERL_RC_STACK
+    {
+        /* free any items from the stack which are about to get
+         * over-written */
+        SV **p = from_sp - 1;
+        assert(p >= to_sp);
+        while (p > to_sp) {
+            SV *sv = *p;
+            *p-- = NULL;
+            SvREFCNT_dec(sv);
         }
     }
+#endif
 
-    /* common code for G_SCALAR and G_ARRAY */
 
     tmps_base = PL_tmps_floor + 1;
 
@@ -4702,13 +5911,17 @@ Perl_leave_adjust_stacks(pTHX_ SV **from_sp, SV **to_sp, U8 gimme, int pass)
 #endif
 
             if (
-               pass == 0 ? (SvTEMP(sv) && !SvMAGICAL(sv) && SvREFCNT(sv) == 1)
+               pass == 0 ? (rpp_is_lone(sv) && !SvMAGICAL(sv))
              : pass == 1 ? ((SvTEMP(sv) || SvPADTMP(sv)) && !SvMAGICAL(sv) && SvREFCNT(sv) == 1)
              : pass == 2 ? (!SvPADTMP(sv))
              : 1)
             {
                 /* pass through: skip copy for logic or optimisation
                  * reasons; instead mortalise it, except that ... */
+
+#ifdef PERL_RC_STACK
+                from_sp[-1] = NULL;
+#endif
                 *++to_sp = sv;
 
                 if (SvTEMP(sv)) {
@@ -4781,12 +5994,11 @@ Perl_leave_adjust_stacks(pTHX_ SV **from_sp, SV **to_sp, U8 gimme, int pass)
                  *    ++PL_tmps_ix, moving the previous occupant there
                  *    instead.
                  */
-                SV *newsv = newSV(0);
+                SV *newsv = newSV_type(SVt_NULL);
 
                 PL_tmps_stack[++PL_tmps_ix] = *tmps_basep;
                 /* put it on the tmps stack early so it gets freed if we die */
                 *tmps_basep++ = newsv;
-                *++to_sp = newsv;
 
                 if (SvTYPE(sv) <= SVt_IV) {
                     /* arg must be one of undef, IV/UV, or RV: skip
@@ -4840,6 +6052,17 @@ Perl_leave_adjust_stacks(pTHX_ SV **from_sp, SV **to_sp, U8 gimme, int pass)
                     TAINT_NOT; /* Each item is independent */
                 }
 
+
+#ifdef PERL_RC_STACK
+                from_sp[-1] = NULL;
+                SvREFCNT_dec_NN(sv);
+                assert(!to_sp[1]);
+                *++to_sp = newsv;
+                SvREFCNT_inc_simple_void_NN(newsv);
+#else
+                *++to_sp = newsv;
+#endif
+
             }
         } while (--nargs);
 
@@ -4899,14 +6122,14 @@ PP(pp_leavesub)
         /* entry zero of a stack is always PL_sv_undef, which
          * simplifies converting a '()' return into undef in scalar context */
         assert(PL_stack_sp > PL_stack_base || *PL_stack_base == &PL_sv_undef);
-       return 0;
+        return 0;
     }
 
     gimme = cx->blk_gimme;
     oldsp = PL_stack_base + cx->blk_oldsp; /* last arg of previous frame */
 
     if (gimme == G_VOID)
-        PL_stack_sp = oldsp;
+        rpp_popfree_to_NN(oldsp);
     else
         leave_adjust_stacks(oldsp, oldsp, gimme, 0);
 
@@ -4926,18 +6149,27 @@ PP(pp_leavesub)
 void
 Perl_clear_defarray(pTHX_ AV* av, bool abandon)
 {
-    const SSize_t fill = AvFILLp(av);
-
     PERL_ARGS_ASSERT_CLEAR_DEFARRAY;
 
-    if (LIKELY(!abandon && SvREFCNT(av) == 1 && !SvMAGICAL(av))) {
-        av_clear(av);
+    if (LIKELY(!abandon && SvREFCNT(av) == 1 && !SvMAGICAL(av))
+#ifndef PERL_RC_STACK
+        && !AvREAL(av)
+#endif
+    ) {
+        clear_defarray_simple(av);
+#ifndef PERL_RC_STACK
         AvREIFY_only(av);
+#endif
     }
     else {
-        AV *newav = newAV();
-        av_extend(newav, fill);
+        /* abandon */
+        const SSize_t size = AvFILLp(av) + 1;
+        /* The ternary gives consistency with av_extend() */
+        AV *newav = newAV_alloc_xz(size < PERL_ARRAY_NEW_MIN_KEY ?
+                                         PERL_ARRAY_NEW_MIN_KEY : size);
+#ifndef PERL_RC_STACK
         AvREIFY_only(newav);
+#endif
         PAD_SVl(0) = MUTABLE_SV(newav);
         SvREFCNT_dec_NN(av);
     }
@@ -4946,14 +6178,14 @@ Perl_clear_defarray(pTHX_ AV* av, bool abandon)
 
 PP(pp_entersub)
 {
-    dSP; dPOPss;
     GV *gv;
     CV *cv;
     PERL_CONTEXT *cx;
     I32 old_savestack_ix;
+    SV *sv = *PL_stack_sp;
 
     if (UNLIKELY(!sv))
-       goto do_die;
+        goto do_die;
 
     /* Locate the CV to call:
      * - most common case: RV->CV: f(), $ref->():
@@ -4998,7 +6230,6 @@ PP(pp_entersub)
               do_ref:
                 if (UNLIKELY(SvAMAGIC(sv))) {
                     sv = amagic_deref_call(sv, to_cv_amg);
-                    /* Don't SPAGAIN here.  */
                 }
             }
             else {
@@ -5037,32 +6268,32 @@ PP(pp_entersub)
     assert(cv);
     assert((void*)&CvROOT(cv) == (void*)&CvXSUB(cv));
     while (UNLIKELY(!CvROOT(cv))) {
-       GV* autogv;
-       SV* sub_name;
-
-       /* anonymous or undef'd function leaves us no recourse */
-       if (CvLEXICAL(cv) && CvHASGV(cv))
-           DIE(aTHX_ "Undefined subroutine &%" SVf " called",
-                      SVfARG(cv_name(cv, NULL, 0)));
-       if (CvANON(cv) || !CvHASGV(cv)) {
-           DIE(aTHX_ "Undefined subroutine called");
-       }
-
-       /* autoloaded stub? */
-       if (cv != GvCV(gv = CvGV(cv))) {
-           cv = GvCV(gv);
-       }
-       /* should call AUTOLOAD now? */
-       else {
+        GV* autogv;
+        SV* sub_name;
+
+        /* anonymous or undef'd function leaves us no recourse */
+        if (CvLEXICAL(cv) && CvHASGV(cv))
+            DIE(aTHX_ "Undefined subroutine &%" SVf " called",
+                       SVfARG(cv_name(cv, NULL, 0)));
+        if (CvANON(cv) || !CvHASGV(cv)) {
+            DIE(aTHX_ "Undefined subroutine called");
+        }
+
+        /* autoloaded stub? */
+        if (cv != GvCV(gv = CvGV(cv))) {
+            cv = GvCV(gv);
+        }
+        /* should call AUTOLOAD now? */
+        else {
           try_autoload:
-           autogv = gv_autoload_pvn(GvSTASH(gv), GvNAME(gv), GvNAMELEN(gv),
+            autogv = gv_autoload_pvn(GvSTASH(gv), GvNAME(gv), GvNAMELEN(gv),
                                      (GvNAMEUTF8(gv) ? SVf_UTF8 : 0)
                                     |(PL_op->op_flags & OPf_REF
                                        ? GV_AUTOLOAD_ISMETHOD
                                        : 0));
             cv = autogv ? GvCV(autogv) : NULL;
-       }
-       if (!cv) {
+        }
+        if (!cv) {
             sub_name = sv_newmortal();
             gv_efullname3(sub_name, gv, NULL);
             DIE(aTHX_ "Undefined subroutine &%" SVf " called", SVfARG(sub_name));
@@ -5071,31 +6302,33 @@ PP(pp_entersub)
 
     /* unrolled "CvCLONE(cv) && ! CvCLONED(cv)" */
     if (UNLIKELY((CvFLAGS(cv) & (CVf_CLONE|CVf_CLONED)) == CVf_CLONE))
-       DIE(aTHX_ "Closure prototype called");
+        DIE(aTHX_ "Closure prototype called");
 
     if (UNLIKELY((PL_op->op_private & OPpENTERSUB_DB) && GvCV(PL_DBsub)
             && !CvNODEBUG(cv)))
     {
-        Perl_get_db_sub(aTHX_ &sv, cv);
-        if (CvISXSUB(cv))
-            PL_curcopdb = PL_curcop;
+         Perl_get_db_sub(aTHX_ &sv, cv);
+         if (CvISXSUB(cv))
+             PL_curcopdb = PL_curcop;
          if (CvLVALUE(cv)) {
              /* check for lsub that handles lvalue subroutines */
-            cv = GvCV(gv_fetchpvs("DB::lsub", GV_ADDMULTI, SVt_PVCV));
+             cv = GvCV(gv_fetchpvs("DB::lsub", GV_ADDMULTI, SVt_PVCV));
              /* if lsub not found then fall back to DB::sub */
-            if (!cv) cv = GvCV(PL_DBsub);
+             if (!cv) cv = GvCV(PL_DBsub);
          } else {
              cv = GvCV(PL_DBsub);
          }
 
-       if (!cv || (!CvXSUB(cv) && !CvSTART(cv)))
-           DIE(aTHX_ "No DB::sub routine defined");
+        if (!cv || (!CvXSUB(cv) && !CvSTART(cv)))
+            DIE(aTHX_ "No DB::sub routine defined");
     }
 
+    rpp_popfree_1_NN(); /* finished with sv now */
+
     if (!(CvISXSUB(cv))) {
-       /* This path taken at least 75% of the time   */
-       dMARK;
-       PADLIST *padlist;
+        /* This path taken at least 75% of the time   */
+        dMARK;
+        PADLIST *padlist;
         I32 depth;
         bool hasargs;
         U8 gimme;
@@ -5105,43 +6338,57 @@ PP(pp_entersub)
          * in the caller's tmps frame, so they won't be freed until after
          * we return from the sub.
          */
-       {
+        {
             SV **svp = MARK;
-            while (svp < SP) {
+            while (svp < PL_stack_sp) {
                 SV *sv = *++svp;
                 if (!sv)
                     continue;
-                if (SvPADTMP(sv))
-                    *svp = sv = sv_mortalcopy(sv);
+                if (SvPADTMP(sv)) {
+                    SV *newsv = sv_mortalcopy(sv);
+                    *svp = newsv;
+#ifdef PERL_RC_STACK
+                    /* should just skip the mortalisation instead */
+                    SvREFCNT_inc_simple_void_NN(newsv);
+                    SvREFCNT_dec_NN(sv);
+#endif
+                    sv = newsv;
+                }
                 SvTEMP_off(sv);
-           }
+            }
         }
 
         gimme = GIMME_V;
-       cx = cx_pushblock(CXt_SUB, gimme, MARK, old_savestack_ix);
+        cx = cx_pushblock(CXt_SUB, gimme, MARK, old_savestack_ix);
         hasargs = cBOOL(PL_op->op_flags & OPf_STACKED);
-       cx_pushsub(cx, cv, PL_op->op_next, hasargs);
-
-       padlist = CvPADLIST(cv);
-       if (UNLIKELY((depth = ++CvDEPTH(cv)) >= 2))
-           pad_push(padlist, depth);
-       PAD_SET_CUR_NOSAVE(padlist, depth);
-       if (LIKELY(hasargs)) {
-           AV *const av = MUTABLE_AV(PAD_SVl(0));
+        cx_pushsub(cx, cv, PL_op->op_next, hasargs);
+
+        padlist = CvPADLIST(cv);
+        if (UNLIKELY((depth = ++CvDEPTH(cv)) >= 2))
+            pad_push(padlist, depth);
+        PAD_SET_CUR_NOSAVE(padlist, depth);
+        if (LIKELY(hasargs)) {
+            AV *const av = MUTABLE_AV(PAD_SVl(0));
             SSize_t items;
             AV **defavp;
 
-           defavp = &GvAV(PL_defgv);
-           cx->blk_sub.savearray = *defavp;
-           *defavp = MUTABLE_AV(SvREFCNT_inc_simple_NN(av));
+            defavp = &GvAV(PL_defgv);
+            cx->blk_sub.savearray = *defavp;
+            *defavp = MUTABLE_AV(SvREFCNT_inc_simple_NN(av));
 
             /* it's the responsibility of whoever leaves a sub to ensure
              * that a clean, empty AV is left in pad[0]. This is normally
              * done by cx_popsub() */
-            assert(!AvREAL(av) && AvFILLp(av) == -1);
 
-            items = SP - MARK;
-           if (UNLIKELY(items - 1 > AvMAX(av))) {
+#ifdef PERL_RC_STACK
+            assert(AvREAL(av));
+#else
+            assert(!AvREAL(av));
+#endif
+            assert(AvFILLp(av) == -1);
+
+            items = PL_stack_sp - MARK;
+            if (UNLIKELY(items - 1 > AvMAX(av))) {
                 SV **ary = AvALLOC(av);
                 Renew(ary, items, SV*);
                 AvMAX(av) = items - 1;
@@ -5151,94 +6398,103 @@ PP(pp_entersub)
 
             if (items)
                 Copy(MARK+1,AvARRAY(av),items,SV*);
-           AvFILLp(av) = items - 1;
-       }
-       if (UNLIKELY((cx->blk_u16 & OPpENTERSUB_LVAL_MASK) == OPpLVAL_INTRO &&
-           !CvLVALUE(cv)))
+            AvFILLp(av) = items - 1;
+#ifdef PERL_RC_STACK
+            /* transfer ownership of the arguments' refcounts to av */
+            PL_stack_sp = MARK;
+#endif
+        }
+        if (UNLIKELY((cx->blk_u16 & OPpENTERSUB_LVAL_MASK) == OPpLVAL_INTRO &&
+            !CvLVALUE(cv)))
             DIE(aTHX_ "Can't modify non-lvalue subroutine call of &%" SVf,
                 SVfARG(cv_name(cv, NULL, 0)));
-       /* warning must come *after* we fully set up the context
-        * stuff so that __WARN__ handlers can safely dounwind()
-        * if they want to
-        */
-       if (UNLIKELY(depth == PERL_SUB_DEPTH_WARN
+        /* warning must come *after* we fully set up the context
+         * stuff so that __WARN__ handlers can safely dounwind()
+         * if they want to
+         */
+        if (UNLIKELY(depth == PERL_SUB_DEPTH_WARN
                 && ckWARN(WARN_RECURSION)
                 && !(PERLDB_SUB && cv == GvCV(PL_DBsub))))
-           sub_crush_depth(cv);
-       RETURNOP(CvSTART(cv));
+            sub_crush_depth(cv);
+        return CvSTART(cv);
     }
     else {
-       SSize_t markix = TOPMARK;
+        SSize_t markix = TOPMARK;
         bool is_scalar;
 
         ENTER;
         /* pretend we did the ENTER earlier */
-       PL_scopestack[PL_scopestack_ix - 1] = old_savestack_ix;
+        PL_scopestack[PL_scopestack_ix - 1] = old_savestack_ix;
 
-       SAVETMPS;
-       PUTBACK;
+        SAVETMPS;
 
-       if (UNLIKELY(((PL_op->op_private
-              & CX_PUSHSUB_GET_LVALUE_MASK(Perl_is_lvalue_sub)
+        if (UNLIKELY(((PL_op->op_private
+               & CX_PUSHSUB_GET_LVALUE_MASK(Perl_is_lvalue_sub)
              ) & OPpENTERSUB_LVAL_MASK) == OPpLVAL_INTRO &&
-           !CvLVALUE(cv)))
+            !CvLVALUE(cv)))
             DIE(aTHX_ "Can't modify non-lvalue subroutine call of &%" SVf,
                 SVfARG(cv_name(cv, NULL, 0)));
 
-       if (UNLIKELY(!(PL_op->op_flags & OPf_STACKED) && GvAV(PL_defgv))) {
-           /* Need to copy @_ to stack. Alternative may be to
-            * switch stack to @_, and copy return values
-            * back. This would allow popping @_ in XSUB, e.g.. XXXX */
-           AV * const av = GvAV(PL_defgv);
-           const SSize_t items = AvFILL(av) + 1;
-
-           if (items) {
-               SSize_t i = 0;
-               const bool m = cBOOL(SvRMAGICAL(av));
-               /* Mark is at the end of the stack. */
-               EXTEND(SP, items);
-               for (; i < items; ++i)
-               {
-                   SV *sv;
-                   if (m) {
-                       SV ** const svp = av_fetch(av, i, 0);
-                       sv = svp ? *svp : NULL;
-                   }
-                   else sv = AvARRAY(av)[i];
-                   if (sv) SP[i+1] = sv;
-                   else {
-                       SP[i+1] = newSVavdefelem(av, i, 1);
-                   }
-               }
-               SP += items;
-               PUTBACK ;               
-           }
-       }
-       else {
-           SV **mark = PL_stack_base + markix;
-           SSize_t items = SP - mark;
-           while (items--) {
-               mark++;
-               if (*mark && SvPADTMP(*mark)) {
-                   *mark = sv_mortalcopy(*mark);
+        if (UNLIKELY(!(PL_op->op_flags & OPf_STACKED) && GvAV(PL_defgv))) {
+            /* Need to copy @_ to stack. Alternative may be to
+             * switch stack to @_, and copy return values
+             * back. This would allow popping @_ in XSUB, e.g.. XXXX */
+            AV * const av = GvAV(PL_defgv);
+            const SSize_t items = AvFILL(av) + 1;
+
+            if (items) {
+                SSize_t i = 0;
+                const bool m = cBOOL(SvRMAGICAL(av));
+                /* Mark is at the end of the stack. */
+                rpp_extend(items);
+                for (; i < items; ++i)
+                {
+                    SV *sv;
+                    if (m) {
+                        SV ** const svp = av_fetch(av, i, 0);
+                        sv = svp ? *svp : NULL;
+                    }
+                    else
+                        sv = AvARRAY(av)[i];
+
+                    rpp_push_1(sv ? sv : av_nonelem(av, i));
                 }
-           }
-       }
-       /* We assume first XSUB in &DB::sub is the called one. */
-       if (UNLIKELY(PL_curcopdb)) {
-           SAVEVPTR(PL_curcop);
-           PL_curcop = PL_curcopdb;
-           PL_curcopdb = NULL;
-       }
-       /* Do we need to open block here? XXXX */
+            }
+        }
+        else {
+            SV **mark = PL_stack_base + markix;
+            SSize_t items = PL_stack_sp - mark;
+            while (items--) {
+                mark++;
+                if (*mark && SvPADTMP(*mark)) {
+                    SV *oldsv = *mark;
+                    SV *newsv = sv_mortalcopy(oldsv);
+                    *mark = newsv;
+#ifdef PERL_RC_STACK
+                    /* should just skip the mortalisation instead */
+                    SvREFCNT_inc_simple_void_NN(newsv);
+                    SvREFCNT_dec_NN(oldsv);
+#endif
+                }
+            }
+        }
+
+        /* We assume first XSUB in &DB::sub is the called one. */
+        if (UNLIKELY(PL_curcopdb)) {
+            SAVEVPTR(PL_curcop);
+            PL_curcop = PL_curcopdb;
+            PL_curcopdb = NULL;
+        }
+        /* Do we need to open block here? XXXX */
 
         /* calculate gimme here as PL_op might get changed and then not
          * restored until the LEAVE further down */
         is_scalar = (GIMME_V == G_SCALAR);
 
-       /* CvXSUB(cv) must not be NULL because newXS() refuses NULL xsub address */
-       assert(CvXSUB(cv));
-       CvXSUB(cv)(aTHX_ cv);
+        /* CvXSUB(cv) must not be NULL because newXS() refuses NULL xsub address */
+        assert(CvXSUB(cv));
+
+        rpp_invoke_xs(cv);
 
 #if defined DEBUGGING && !defined DEBUGGING_RE_ONLY
         /* This duplicates the check done in runops_debug(), but provides more
@@ -5255,16 +6511,29 @@ PP(pp_entersub)
                     PL_stack_base, PL_stack_sp,
                     PL_stack_base + PL_curstackinfo->si_stack_hwm);
 #endif
-       /* Enforce some sanity in scalar context. */
-       if (is_scalar) {
+        /* Enforce some sanity in scalar context. */
+        if (is_scalar) {
             SV **svp = PL_stack_base + markix + 1;
             if (svp != PL_stack_sp) {
+#ifdef PERL_RC_STACK
+                if (svp < PL_stack_sp) {
+                    /* move return value to bottom of stack frame
+                     * and free everything else */
+                    SV* retsv = *PL_stack_sp;
+                    *PL_stack_sp = *svp;
+                    *svp = retsv;
+                    rpp_popfree_to_NN(svp);
+                }
+                else
+                    rpp_push_IMM(&PL_sv_undef);
+#else
                 *svp = svp > PL_stack_sp ? &PL_sv_undef : *PL_stack_sp;
                 PL_stack_sp = svp;
+#endif
             }
-       }
-       LEAVE;
-       return NORMAL;
+        }
+        LEAVE;
+        return NORMAL;
     }
 }
 
@@ -5274,10 +6543,10 @@ Perl_sub_crush_depth(pTHX_ CV *cv)
     PERL_ARGS_ASSERT_SUB_CRUSH_DEPTH;
 
     if (CvANON(cv))
-       Perl_warner(aTHX_ packWARN(WARN_RECURSION), "Deep recursion on anonymous subroutine");
+        Perl_warner(aTHX_ packWARN(WARN_RECURSION), "Deep recursion on anonymous subroutine");
     else {
-       Perl_warner(aTHX_ packWARN(WARN_RECURSION), "Deep recursion on subroutine \"%" SVf "\"",
-                   SVfARG(cv_name(cv,NULL,0)));
+        Perl_warner(aTHX_ packWARN(WARN_RECURSION), "Deep recursion on subroutine \"%" SVf "\"",
+                    SVfARG(cv_name(cv,NULL,0)));
     }
 }
 
@@ -5305,80 +6574,88 @@ Perl_croak_caller(const char *pat, ...)
 
 PP(pp_aelem)
 {
-    dSP;
     SV** svp;
-    SV* const elemsv = POPs;
+    SV* const elemsv =  PL_stack_sp[0];
     IV elem = SvIV(elemsv);
-    AV *const av = MUTABLE_AV(POPs);
+    AV *const av = MUTABLE_AV(PL_stack_sp[-1]);
     const U32 lval = PL_op->op_flags & OPf_MOD || LVRET;
     const U32 defer = PL_op->op_private & OPpLVAL_DEFER;
     const bool localizing = PL_op->op_private & OPpLVAL_INTRO;
     bool preeminent = TRUE;
     SV *sv;
+    SV *retsv;
 
     if (UNLIKELY(SvROK(elemsv) && !SvGAMAGIC(elemsv) && ckWARN(WARN_MISC)))
-       Perl_warner(aTHX_ packWARN(WARN_MISC),
-                   "Use of reference \"%" SVf "\" as array index",
-                   SVfARG(elemsv));
-    if (UNLIKELY(SvTYPE(av) != SVt_PVAV))
-       RETPUSHUNDEF;
+        Perl_warner(aTHX_ packWARN(WARN_MISC),
+                    "Use of reference \"%" SVf "\" as array index",
+                    SVfARG(elemsv));
+    if (UNLIKELY(SvTYPE(av) != SVt_PVAV)) {
+        retsv = &PL_sv_undef;
+        goto ret;
+    }
 
     if (UNLIKELY(localizing)) {
-       MAGIC *mg;
-       HV *stash;
+        MAGIC *mg;
+        HV *stash;
 
-       /* If we can determine whether the element exist,
-        * Try to preserve the existenceness of a tied array
-        * element by using EXISTS and DELETE if possible.
-        * Fallback to FETCH and STORE otherwise. */
-       if (SvCANEXISTDELETE(av))
-           preeminent = av_exists(av, elem);
+        /* Try to preserve the existence of a tied array
+         * element by using EXISTS and DELETE if possible.
+         * Fall back to FETCH and STORE otherwise. */
+        if (SvCANEXISTDELETE(av))
+            preeminent = av_exists(av, elem);
     }
 
     svp = av_fetch(av, elem, lval && !defer);
     if (lval) {
 #ifdef PERL_MALLOC_WRAP
-        if (SvUOK(elemsv)) {
-             const UV uv = SvUV(elemsv);
-             elem = uv > IV_MAX ? IV_MAX : uv;
-        }
-        else if (SvNOK(elemsv))
-             elem = (IV)SvNV(elemsv);
-        if (elem > 0) {
-             static const char oom_array_extend[] =
-               "Out of memory during array extend"; /* Duplicated in av.c */
-             MEM_WRAP_CHECK_1(elem,SV*,oom_array_extend);
-        }
+         if (SvUOK(elemsv)) {
+              const UV uv = SvUV(elemsv);
+              elem = uv > IV_MAX ? IV_MAX : uv;
+         }
+         else if (SvNOK(elemsv))
+              elem = (IV)SvNV(elemsv);
+         if (elem > 0) {
+              MEM_WRAP_CHECK_s(elem,SV*,"Out of memory during array extend");
+         }
 #endif
-       if (!svp || !*svp) {
-           IV len;
-           if (!defer)
-               DIE(aTHX_ PL_no_aelem, elem);
-           len = av_tindex(av);
-           mPUSHs(newSVavdefelem(av,
-           /* Resolve a negative index now, unless it points before the
-              beginning of the array, in which case record it for error
-              reporting in magic_setdefelem. */
-               elem < 0 && len + elem >= 0 ? len + elem : elem,
-               1));
-           RETURN;
-       }
-       if (UNLIKELY(localizing)) {
-           if (preeminent)
-               save_aelem(av, elem, svp);
-           else
-               SAVEADELETE(av, elem);
-       }
-       else if (PL_op->op_private & OPpDEREF) {
-           PUSHs(vivify_ref(*svp, PL_op->op_private & OPpDEREF));
-           RETURN;
-       }
+        if (!svp || !*svp) {
+            IV len;
+            if (!defer)
+                DIE(aTHX_ PL_no_aelem, elem);
+            len = av_top_index(av);
+            /* Resolve a negative index that falls within the array.  Leave
+               it negative it if falls outside the array.  */
+            if (elem < 0 && len + elem >= 0)
+                elem = len + elem;
+            if (elem >= 0 && elem <= len)
+                /* Falls within the array.  */
+                retsv = av_nonelem(av, elem);
+            else
+                /* Falls outside the array.  If it is negative,
+                   magic_setdefelem will use the index for error reporting.
+                 */
+                retsv = sv_2mortal(newSVavdefelem(av, elem, 1));
+            goto ret;
+        }
+        if (UNLIKELY(localizing)) {
+            if (preeminent)
+                save_aelem(av, elem, svp);
+            else
+                SAVEADELETE(av, elem);
+        }
+        else if (PL_op->op_private & OPpDEREF) {
+            retsv = vivify_ref(*svp, PL_op->op_private & OPpDEREF);
+            goto ret;
+        }
     }
     sv = (svp ? *svp : &PL_sv_undef);
     if (!lval && SvRMAGICAL(av) && SvGMAGICAL(sv)) /* see note in pp_helem() */
-       mg_get(sv);
-    PUSHs(sv);
-    RETURN;
+        mg_get(sv);
+    retsv = sv;
+
+  ret:
+    rpp_replace_2_1_NN(retsv);
+    return NORMAL;
 }
 
 SV*
@@ -5388,30 +6665,30 @@ Perl_vivify_ref(pTHX_ SV *sv, U32 to_what)
 
     SvGETMAGIC(sv);
     if (!SvOK(sv)) {
-       if (SvREADONLY(sv))
-           Perl_croak_no_modify();
-       prepare_SV_for_RV(sv);
-       switch (to_what) {
-       case OPpDEREF_SV:
-           SvRV_set(sv, newSV(0));
-           break;
-       case OPpDEREF_AV:
-           SvRV_set(sv, MUTABLE_SV(newAV()));
-           break;
-       case OPpDEREF_HV:
-           SvRV_set(sv, MUTABLE_SV(newHV()));
-           break;
-       }
-       SvROK_on(sv);
-       SvSETMAGIC(sv);
-       SvGETMAGIC(sv);
+        if (SvREADONLY(sv))
+            Perl_croak_no_modify();
+        prepare_SV_for_RV(sv);
+        switch (to_what) {
+        case OPpDEREF_SV:
+            SvRV_set(sv, newSV_type(SVt_NULL));
+            break;
+        case OPpDEREF_AV:
+            SvRV_set(sv, MUTABLE_SV(newAV()));
+            break;
+        case OPpDEREF_HV:
+            SvRV_set(sv, MUTABLE_SV(newHV()));
+            break;
+        }
+        SvROK_on(sv);
+        SvSETMAGIC(sv);
+        SvGETMAGIC(sv);
     }
     if (SvGMAGICAL(sv)) {
-       /* copy the sv without magic to prevent magic from being
-          executed twice */
-       SV* msv = sv_newmortal();
-       sv_setsv_nomg(msv, sv);
-       return msv;
+        /* copy the sv without magic to prevent magic from being
+           executed twice */
+        SV* msv = sv_newmortal();
+        sv_setsv_nomg(msv, sv);
+        return msv;
     }
     return sv;
 }
@@ -5423,78 +6700,102 @@ S_opmethod_stash(pTHX_ SV* meth)
     HV* stash;
 
     SV* const sv = PL_stack_base + TOPMARK == PL_stack_sp
-       ? (Perl_croak(aTHX_ "Can't call method \"%" SVf "\" without a "
-                           "package or object reference", SVfARG(meth)),
-          (SV *)NULL)
-       : *(PL_stack_base + TOPMARK + 1);
+        ? (Perl_croak(aTHX_ "Can't call method \"%" SVf "\" without a "
+                            "package or object reference", SVfARG(meth)),
+           (SV *)NULL)
+        : *(PL_stack_base + TOPMARK + 1);
 
     PERL_ARGS_ASSERT_OPMETHOD_STASH;
 
     if (UNLIKELY(!sv))
        undefined:
-       Perl_croak(aTHX_ "Can't call method \"%" SVf "\" on an undefined value",
-                  SVfARG(meth));
+        Perl_croak(aTHX_ "Can't call method \"%" SVf "\" on an undefined value",
+                   SVfARG(meth));
 
     if (UNLIKELY(SvGMAGICAL(sv))) mg_get(sv);
     else if (SvIsCOW_shared_hash(sv)) { /* MyClass->meth() */
-       stash = gv_stashsv(sv, GV_CACHE_ONLY);
-       if (stash) return stash;
+        stash = gv_stashsv(sv, GV_CACHE_ONLY);
+        if (stash) return stash;
     }
 
     if (SvROK(sv))
-       ob = MUTABLE_SV(SvRV(sv));
+        ob = MUTABLE_SV(SvRV(sv));
     else if (!SvOK(sv)) goto undefined;
     else if (isGV_with_GP(sv)) {
-       if (!GvIO(sv))
-           Perl_croak(aTHX_ "Can't call method \"%" SVf "\" "
-                            "without a package or object reference",
-                             SVfARG(meth));
-       ob = sv;
-       if (SvTYPE(ob) == SVt_PVLV && LvTYPE(ob) == 'y') {
-           assert(!LvTARGLEN(ob));
-           ob = LvTARG(ob);
-           assert(ob);
-       }
-       *(PL_stack_base + TOPMARK + 1) = sv_2mortal(newRV(ob));
+        if (!GvIO(sv))
+            Perl_croak(aTHX_ "Can't call method \"%" SVf "\" "
+                             "without a package or object reference",
+                              SVfARG(meth));
+        ob = sv;
+        if (SvTYPE(ob) == SVt_PVLV && LvTYPE(ob) == 'y') {
+            assert(!LvTARGLEN(ob));
+            ob = LvTARG(ob);
+            assert(ob);
+        }
+        /* Replace the object at the base of the stack frame.
+         * This is "below" whatever pp_wrap has wrapped, so needs freeing.
+         */
+        SV *newsv = sv_2mortal(newRV(ob));
+        SV **svp = (PL_stack_base + TOPMARK + 1);
+#ifdef PERL_RC_STACK
+        SV *oldsv = *svp;
+#endif
+        *svp = newsv;
+#ifdef PERL_RC_STACK
+        SvREFCNT_inc_simple_void_NN(newsv);
+        SvREFCNT_dec_NN(oldsv);
+#endif
     }
     else {
-       /* this isn't a reference */
-       GV* iogv;
+        /* this isn't a reference */
+        GV* iogv;
         STRLEN packlen;
         const char * const packname = SvPV_nomg_const(sv, packlen);
         const U32 packname_utf8 = SvUTF8(sv);
         stash = gv_stashpvn(packname, packlen, packname_utf8 | GV_CACHE_ONLY);
         if (stash) return stash;
 
-       if (!(iogv = gv_fetchpvn_flags(
-               packname, packlen, packname_utf8, SVt_PVIO
-            )) ||
-           !(ob=MUTABLE_SV(GvIO(iogv))))
-       {
-           /* this isn't the name of a filehandle either */
-           if (!packlen)
-           {
-               Perl_croak(aTHX_ "Can't call method \"%" SVf "\" "
-                                "without a package or object reference",
-                                 SVfARG(meth));
-           }
-           /* assume it's a package name */
-           stash = gv_stashpvn(packname, packlen, packname_utf8);
-           if (stash) return stash;
-           else return MUTABLE_HV(sv);
-       }
-       /* it _is_ a filehandle name -- replace with a reference */
-       *(PL_stack_base + TOPMARK + 1) = sv_2mortal(newRV(MUTABLE_SV(iogv)));
+        if (!(iogv = gv_fetchpvn_flags(
+                packname, packlen, packname_utf8, SVt_PVIO
+             )) ||
+            !(ob=MUTABLE_SV(GvIO(iogv))))
+        {
+            /* this isn't the name of a filehandle either */
+            if (!packlen)
+            {
+                Perl_croak(aTHX_ "Can't call method \"%" SVf "\" "
+                                 "without a package or object reference",
+                                  SVfARG(meth));
+            }
+            /* assume it's a package name */
+            stash = gv_stashpvn(packname, packlen, packname_utf8);
+            if (stash) return stash;
+            else return MUTABLE_HV(sv);
+        }
+        /* it _is_ a filehandle name -- replace with a reference.
+         * Replace the object at the base of the stack frame.
+         * This is "below" whatever pp_wrap has wrapped, so needs freeing.
+         */
+        SV *newsv = sv_2mortal(newRV(MUTABLE_SV(iogv)));
+        SV **svp = (PL_stack_base + TOPMARK + 1);
+#ifdef PERL_RC_STACK
+        SV *oldsv = *svp;
+#endif
+        *svp = newsv;
+#ifdef PERL_RC_STACK
+        SvREFCNT_inc_simple_void_NN(newsv);
+        SvREFCNT_dec_NN(oldsv);
+#endif
     }
 
     /* if we got here, ob should be an object or a glob */
     if (!ob || !(SvOBJECT(ob)
-                || (isGV_with_GP(ob)
-                    && (ob = MUTABLE_SV(GvIO((const GV *)ob)))
-                    && SvOBJECT(ob))))
+                 || (isGV_with_GP(ob)
+                     && (ob = MUTABLE_SV(GvIO((const GV *)ob)))
+                     && SvOBJECT(ob))))
     {
-       Perl_croak(aTHX_ "Can't call method \"%" SVf "\" on unblessed reference",
-                  SVfARG((SvPOK(meth) && SvPVX(meth) == PL_isa_DOES)
+        Perl_croak(aTHX_ "Can't call method \"%" SVf "\" on unblessed reference",
+                   SVfARG((SvPOK(meth) && SvPVX(meth) == PL_isa_DOES)
                                         ? newSVpvs_flags("DOES", SVs_TEMP)
                                         : meth));
     }
@@ -5504,16 +6805,15 @@ S_opmethod_stash(pTHX_ SV* meth)
 
 PP(pp_method)
 {
-    dSP;
     GV* gv;
     HV* stash;
-    SV* const meth = TOPs;
+    SV* const meth = *PL_stack_sp;
 
     if (SvROK(meth)) {
         SV* const rmeth = SvRV(meth);
         if (SvTYPE(rmeth) == SVt_PVCV) {
-            SETs(rmeth);
-            RETURN;
+            rpp_replace_1_1_NN(rmeth);
+            return NORMAL;
         }
     }
 
@@ -5522,8 +6822,8 @@ PP(pp_method)
     gv = gv_fetchmethod_sv_flags(stash, meth, GV_AUTOLOAD|GV_CROAK);
     assert(gv);
 
-    SETs(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
-    RETURN;
+    rpp_replace_1_1_NN(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
+    return NORMAL;
 }
 
 #define METHOD_CHECK_CACHE(stash,cache,meth)                           \
@@ -5533,16 +6833,15 @@ PP(pp_method)
         if (isGV(gv) && GvCV(gv) && (!GvCVGEN(gv) || GvCVGEN(gv)       \
              == (PL_sub_generation + HvMROMETA(stash)->cache_gen)))    \
         {                                                              \
-            XPUSHs(MUTABLE_SV(GvCV(gv)));                              \
-            RETURN;                                                    \
+            rpp_xpush_1(MUTABLE_SV(GvCV(gv)));                         \
+            return NORMAL;                                             \
         }                                                              \
     }                                                                  \
 
 PP(pp_method_named)
 {
-    dSP;
     GV* gv;
-    SV* const meth = cMETHOPx_meth(PL_op);
+    SV* const meth = cMETHOP_meth;
     HV* const stash = opmethod_stash(meth);
 
     if (LIKELY(SvTYPE(stash) == SVt_PVHV)) {
@@ -5552,16 +6851,15 @@ PP(pp_method_named)
     gv = gv_fetchmethod_sv_flags(stash, meth, GV_AUTOLOAD|GV_CROAK);
     assert(gv);
 
-    XPUSHs(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
-    RETURN;
+    rpp_xpush_1(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
+    return NORMAL;
 }
 
 PP(pp_method_super)
 {
-    dSP;
     GV* gv;
     HV* cache;
-    SV* const meth = cMETHOPx_meth(PL_op);
+    SV* const meth = cMETHOP_meth;
     HV* const stash = CopSTASH(PL_curcop);
     /* Actually, SUPER doesn't need real object's (or class') stash at all,
      * as it uses CopSTASH. However, we must ensure that object(class) is
@@ -5575,38 +6873,36 @@ PP(pp_method_super)
     gv = gv_fetchmethod_sv_flags(stash, meth, GV_AUTOLOAD|GV_CROAK|GV_SUPER);
     assert(gv);
 
-    XPUSHs(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
-    RETURN;
+    rpp_xpush_1(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
+    return NORMAL;
 }
 
 PP(pp_method_redir)
 {
-    dSP;
     GV* gv;
-    SV* const meth = cMETHOPx_meth(PL_op);
-    HV* stash = gv_stashsv(cMETHOPx_rclass(PL_op), 0);
+    SV* const meth = cMETHOP_meth;
+    HV* stash = gv_stashsv(cMETHOP_rclass, 0);
     opmethod_stash(meth); /* not used but needed for error checks */
 
     if (stash) { METHOD_CHECK_CACHE(stash, stash, meth); }
-    else stash = MUTABLE_HV(cMETHOPx_rclass(PL_op));
+    else stash = MUTABLE_HV(cMETHOP_rclass);
 
     gv = gv_fetchmethod_sv_flags(stash, meth, GV_AUTOLOAD|GV_CROAK);
     assert(gv);
 
-    XPUSHs(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
-    RETURN;
+    rpp_xpush_1(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
+    return NORMAL;
 }
 
 PP(pp_method_redir_super)
 {
-    dSP;
     GV* gv;
     HV* cache;
-    SV* const meth = cMETHOPx_meth(PL_op);
-    HV* stash = gv_stashsv(cMETHOPx_rclass(PL_op), 0);
+    SV* const meth = cMETHOP_meth;
+    HV* stash = gv_stashsv(cMETHOP_rclass, 0);
     opmethod_stash(meth); /* not used but needed for error checks */
 
-    if (UNLIKELY(!stash)) stash = MUTABLE_HV(cMETHOPx_rclass(PL_op));
+    if (UNLIKELY(!stash)) stash = MUTABLE_HV(cMETHOP_rclass);
     else if ((cache = HvMROMETA(stash)->super)) {
          METHOD_CHECK_CACHE(stash, cache, meth);
     }
@@ -5614,8 +6910,8 @@ PP(pp_method_redir_super)
     gv = gv_fetchmethod_sv_flags(stash, meth, GV_AUTOLOAD|GV_CROAK|GV_SUPER);
     assert(gv);
 
-    XPUSHs(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
-    RETURN;
+    rpp_xpush_1(isGV(gv) ? MUTABLE_SV(GvCV(gv)) : MUTABLE_SV(gv));
+    return NORMAL;
 }
 
 /*