/*    peep.c
 *
 *    Copyright (C) 1991-2022 by Larry Wall and others
 *
 *    You may distribute under the terms of either the GNU General Public
 *    License or the Artistic License, as specified in the README file.
 *
 */

/*
 * Aragorn sped on up the hill. Every now and again he bent to the ground.
 * Hobbits go light, and their footprints are not easy even for a Ranger to
 * read, but not far from the top a spring crossed the path, and in the wet
 * earth he saw what he was seeking.
 * 'I read the signs aright,' he said to himself. 'Frodo ran to the hill-top.
 * I wonder what he saw there? But he returned by the same way, and went down
 * the hill again.'
 */

/* This file contains functions for optimizing and finalizing the OP
 * structures that hold a compiled perl program
 */

#include "EXTERN.h"
#define PERL_IN_PEEP_C
#include "perl.h"


#define CALL_RPEEP(o) PL_rpeepp(aTHX_ o)


static void
S_scalar_slice_warning(pTHX_ const OP *o)
{
    OP *kid;
    const bool is_hash = o->op_type == OP_HSLICE
                || (o->op_type == OP_NULL && o->op_targ == OP_HSLICE);
    SV *name;

    if (!(o->op_private & OPpSLICEWARNING))
        return;
    if (PL_parser && PL_parser->error_count)
        /* This warning can be nonsensical when there is a syntax error. */
        return;

    kid = cLISTOPo->op_first;
    kid = OpSIBLING(kid); /* get past pushmark */
    /* weed out false positives: any ops that can return lists */
    switch (kid->op_type) {
    case OP_BACKTICK:
    case OP_GLOB:
    case OP_READLINE:
    case OP_MATCH:
    case OP_RV2AV:
    case OP_EACH:
    case OP_VALUES:
    case OP_KEYS:
    case OP_SPLIT:
    case OP_LIST:
    case OP_SORT:
    case OP_REVERSE:
    case OP_ENTERSUB:
    case OP_CALLER:
    case OP_LSTAT:
    case OP_STAT:
    case OP_READDIR:
    case OP_SYSTEM:
    case OP_TMS:
    case OP_LOCALTIME:
    case OP_GMTIME:
    case OP_ENTEREVAL:
        return;
    }

    /* Don't warn if we have a nulled list either. */
    if (kid->op_type == OP_NULL && kid->op_targ == OP_LIST)
        return;

    assert(OpSIBLING(kid));
    name = op_varname(OpSIBLING(kid));
    if (!name) /* XS module fiddling with the op tree */
        return;
    warn_elem_scalar_context(kid, name, is_hash, true);
}


/* info returned by S_sprintf_is_multiconcatable() */

struct sprintf_ismc_info {
    SSize_t nargs;    /* num of args to sprintf (not including the format) */
    char  *start;     /* start of raw format string */
    char  *end;       /* bytes after end of raw format string */
    STRLEN total_len; /* total length (in bytes) of format string, not
                         including '%s' and  half of '%%' */
    STRLEN variant;   /* number of bytes by which total_len_p would grow
                         if upgraded to utf8 */
    bool   utf8;      /* whether the format is utf8 */
};

/* is the OP_SPRINTF o suitable for converting into a multiconcat op?
 * i.e. its format argument is a const string with only '%s' and '%%'
 * formats, and the number of args is known, e.g.
 *    sprintf "a=%s f=%s", $a[0], scalar(f());
 * but not
 *    sprintf "i=%d a=%s f=%s", $i, @a, f();
 *
 * If successful, the sprintf_ismc_info struct pointed to by info will be
 * populated.
 */

STATIC bool
S_sprintf_is_multiconcatable(pTHX_ OP *o,struct sprintf_ismc_info *info)
{
    OP    *pm, *constop, *kid;
    SV    *sv;
    char  *s, *e, *p;
    SSize_t nargs, nformats;
    STRLEN cur, total_len, variant;
    bool   utf8;

    /* if sprintf's behaviour changes, die here so that someone
     * can decide whether to enhance this function or skip optimising
     * under those new circumstances */
    assert(!(o->op_flags & OPf_STACKED));
    assert(!(PL_opargs[OP_SPRINTF] & OA_TARGLEX));
    assert(!(o->op_private & ~OPpARG4_MASK));

    pm = cUNOPo->op_first;
    if (pm->op_type != OP_PUSHMARK) /* weird coreargs stuff */
        return FALSE;
    constop = OpSIBLING(pm);
    if (!constop || constop->op_type != OP_CONST)
        return FALSE;
    sv = cSVOPx_sv(constop);
    if (SvMAGICAL(sv) || !SvPOK(sv))
        return FALSE;

    s = SvPV(sv, cur);
    e = s + cur;

    /* Scan format for %% and %s and work out how many %s there are.
     * Abandon if other format types are found.
     */

    nformats  = 0;
    total_len = 0;
    variant   = 0;

    for (p = s; p < e; p++) {
        if (*p != '%') {
            total_len++;
            if (!UTF8_IS_INVARIANT(*p))
                variant++;
            continue;
        }
        p++;
        if (p >= e)
            return FALSE; /* lone % at end gives "Invalid conversion" */
        if (*p == '%')
            total_len++;
        else if (*p == 's')
            nformats++;
        else
            return FALSE;
    }

    if (!nformats || nformats > PERL_MULTICONCAT_MAXARG)
        return FALSE;

    utf8 = cBOOL(SvUTF8(sv));
    if (utf8)
        variant = 0;

    /* scan args; they must all be in scalar cxt */

    nargs = 0;
    kid = OpSIBLING(constop);

    while (kid) {
        if ((kid->op_flags & OPf_WANT) != OPf_WANT_SCALAR)
            return FALSE;
        nargs++;
        kid = OpSIBLING(kid);
    }

    if (nargs != nformats)
        return FALSE; /* e.g. sprintf("%s%s", $a); */


    info->nargs      = nargs;
    info->start      = s;
    info->end        = e;
    info->total_len  = total_len;
    info->variant    = variant;
    info->utf8       = utf8;

    return TRUE;
}

/* S_maybe_multiconcat():
 *
 * given an OP_STRINGIFY, OP_SASSIGN, OP_CONCAT or OP_SPRINTF op, possibly
 * convert it (and its children) into an OP_MULTICONCAT. See the code
 * comments just before pp_multiconcat() for the full details of what
 * OP_MULTICONCAT supports.
 *
 * Basically we're looking for an optree with a chain of OP_CONCATS down
 * the LHS (or an OP_SPRINTF), with possibly an OP_SASSIGN, and/or
 * OP_STRINGIFY, and/or OP_CONCAT acting as '.=' at its head, e.g.
 *
 *      $x = "$a$b-$c"
 *
 *  looks like
 *
 *      SASSIGN
 *         |
 *      STRINGIFY   -- PADSV[$x]
 *         |
 *         |
 *      ex-PUSHMARK -- CONCAT/S
 *                        |
 *                     CONCAT/S  -- PADSV[$d]
 *                        |
 *                     CONCAT    -- CONST["-"]
 *                        |
 *                     PADSV[$a] -- PADSV[$b]
 *
 * Note that at this stage the OP_SASSIGN may have already been optimised
 * away with OPpTARGET_MY set on the OP_STRINGIFY or OP_CONCAT.
 */

STATIC void
S_maybe_multiconcat(pTHX_ OP *o)
{
    OP *lastkidop;   /* the right-most of any kids unshifted onto o */
    OP *topop;       /* the top-most op in the concat tree (often equals o,
                        unless there are assign/stringify ops above it */
    OP *parentop;    /* the parent op of topop (or itself if no parent) */
    OP *targmyop;    /* the op (if any) with the OPpTARGET_MY flag */
    OP *targetop;    /* the op corresponding to target=... or target.=... */
    OP *stringop;    /* the OP_STRINGIFY op, if any */
    OP *nextop;      /* used for recreating the op_next chain without consts */
    OP *kid;         /* general-purpose op pointer */
    UNOP_AUX_item *aux;
    UNOP_AUX_item *lenp;
    char *const_str, *p;
    struct sprintf_ismc_info sprintf_info;

                     /* store info about each arg in args[];
                      * toparg is the highest used slot; argp is a general
                      * pointer to args[] slots */
    struct {
        void *p;      /* initially points to const sv (or null for op);
                         later, set to SvPV(constsv), with ... */
        STRLEN len;   /* ... len set to SvPV(..., len) */
    } *argp, *toparg, args[PERL_MULTICONCAT_MAXARG*2 + 1];

    SSize_t nargs  = 0;
    SSize_t nconst = 0;
    SSize_t nadjconst  = 0; /* adjacent consts - may be demoted to args */
    STRLEN variant;
    bool utf8 = FALSE;
    bool kid_is_last = FALSE; /* most args will be the RHS kid of a concat op;
                                 the last-processed arg will the LHS of one,
                                 as args are processed in reverse order */
    U8   stacked_last = 0;   /* whether the last seen concat op was STACKED */
    STRLEN total_len  = 0;   /* sum of the lengths of the const segments */
    U8 flags          = 0;   /* what will become the op_flags and ... */
    U8 private_flags  = 0;   /* ... op_private of the multiconcat op */
    bool is_sprintf = FALSE; /* we're optimising an sprintf */
    bool is_targable  = FALSE; /* targetop is an OPpTARGET_MY candidate */
    bool prev_was_const = FALSE; /* previous arg was a const */

    /* -----------------------------------------------------------------
     * Phase 1:
     *
     * Examine the optree non-destructively to determine whether it's
     * suitable to be converted into an OP_MULTICONCAT. Accumulate
     * information about the optree in args[].
     */

    argp     = args;
    targmyop = NULL;
    targetop = NULL;
    stringop = NULL;
    topop    = o;
    parentop = o;

    assert(   o->op_type == OP_SASSIGN
           || o->op_type == OP_CONCAT
           || o->op_type == OP_SPRINTF
           || o->op_type == OP_STRINGIFY);

    Zero(&sprintf_info, 1, struct sprintf_ismc_info);

    /* first see if, at the top of the tree, there is an assign,
     * append and/or stringify */

    if (topop->op_type == OP_SASSIGN) {
        /* expr = ..... */
        if (o->op_ppaddr != PL_ppaddr[OP_SASSIGN])
            return;
        if (o->op_private & (OPpASSIGN_BACKWARDS|OPpASSIGN_CV_TO_GV))
            return;
        assert(!(o->op_private & ~OPpARG2_MASK)); /* barf on unknown flags */

        parentop = topop;
        topop = cBINOPo->op_first;
        targetop = OpSIBLING(topop);
        if (!targetop) /* probably some sort of syntax error */
            return;

        /* don't optimise away assign in 'local $foo = ....' */
        if (   (targetop->op_private & OPpLVAL_INTRO)
            /* these are the common ops which do 'local', but
             * not all */
            && (   targetop->op_type == OP_GVSV
                || targetop->op_type == OP_RV2SV
                || targetop->op_type == OP_AELEM
                || targetop->op_type == OP_HELEM
                )
        )
            return;
    }
    else if (   topop->op_type == OP_CONCAT
             && (topop->op_flags & OPf_STACKED)
             && (!(topop->op_private & OPpCONCAT_NESTED))
            )
    {
        /* expr .= ..... */

        /* OPpTARGET_MY shouldn't be able to be set here. If it is,
         * decide what to do about it */
        assert(!(o->op_private & OPpTARGET_MY));

        /* barf on unknown flags */
        assert(!(o->op_private & ~(OPpARG2_MASK|OPpTARGET_MY)));
        private_flags |= OPpMULTICONCAT_APPEND;
        targetop = cBINOPo->op_first;
        parentop = topop;
        topop    = OpSIBLING(targetop);

        /* $x .= <FOO> gets optimised to rcatline instead */
        if (topop->op_type == OP_READLINE)
            return;
    }

    if (targetop) {
        /* Can targetop (the LHS) if it's a padsv, be optimised
         * away and use OPpTARGET_MY instead?
         */
        if (    (targetop->op_type == OP_PADSV)
            && !(targetop->op_private & OPpDEREF)
            && !(targetop->op_private & OPpPAD_STATE)
               /* we don't support 'my $x .= ...' */
            && (   o->op_type == OP_SASSIGN
                || !(targetop->op_private & OPpLVAL_INTRO))
        )
            is_targable = TRUE;
    }

    if (topop->op_type == OP_STRINGIFY) {
        if (topop->op_ppaddr != PL_ppaddr[OP_STRINGIFY])
            return;
        stringop = topop;

        /* barf on unknown flags */
        assert(!(o->op_private & ~(OPpARG4_MASK|OPpTARGET_MY)));

        if ((topop->op_private & OPpTARGET_MY)) {
            if (o->op_type == OP_SASSIGN)
                return; /* can't have two assigns */
            targmyop = topop;
        }

        private_flags |= OPpMULTICONCAT_STRINGIFY;
        parentop = topop;
        topop = cBINOPx(topop)->op_first;
        assert(OP_TYPE_IS_OR_WAS_NN(topop, OP_PUSHMARK));
        topop = OpSIBLING(topop);
    }

    if (topop->op_type == OP_SPRINTF) {
        if (topop->op_ppaddr != PL_ppaddr[OP_SPRINTF])
            return;
        if (S_sprintf_is_multiconcatable(aTHX_ topop, &sprintf_info)) {
            nargs     = sprintf_info.nargs;
            total_len = sprintf_info.total_len;
            variant   = sprintf_info.variant;
            utf8      = sprintf_info.utf8;
            is_sprintf = TRUE;
            private_flags |= OPpMULTICONCAT_FAKE;
            toparg = argp;
            /* we have an sprintf op rather than a concat optree.
             * Skip most of the code below which is associated with
             * processing that optree. We also skip phase 2, determining
             * whether its cost effective to optimise, since for sprintf,
             * multiconcat is *always* faster */
            goto create_aux;
        }
        /* note that even if the sprintf itself isn't multiconcatable,
         * the expression as a whole may be, e.g. in
         *    $x .= sprintf("%d",...)
         * the sprintf op will be left as-is, but the concat/S op may
         * be upgraded to multiconcat
         */
    }
    else if (topop->op_type == OP_CONCAT) {
        if (topop->op_ppaddr != PL_ppaddr[OP_CONCAT])
            return;

        if ((topop->op_private & OPpTARGET_MY)) {
            if (o->op_type == OP_SASSIGN || targmyop)
                return; /* can't have two assigns */
            targmyop = topop;
        }
    }

    /* Is it safe to convert a sassign/stringify/concat op into
     * a multiconcat? */
    assert((PL_opargs[OP_SASSIGN]   & OA_CLASS_MASK) == OA_BINOP);
    assert((PL_opargs[OP_CONCAT]    & OA_CLASS_MASK) == OA_BINOP);
    assert((PL_opargs[OP_STRINGIFY] & OA_CLASS_MASK) == OA_LISTOP);
    assert((PL_opargs[OP_SPRINTF]   & OA_CLASS_MASK) == OA_LISTOP);
    STATIC_ASSERT_STMT(   STRUCT_OFFSET(BINOP,    op_last)
                       == STRUCT_OFFSET(UNOP_AUX, op_aux));
    STATIC_ASSERT_STMT(   STRUCT_OFFSET(LISTOP,   op_last)
                       == STRUCT_OFFSET(UNOP_AUX, op_aux));

    /* Now scan the down the tree looking for a series of
     * CONCAT/OPf_STACKED ops on the LHS (with the last one not
     * stacked). For example this tree:
     *
     *     |
     *   CONCAT/STACKED
     *     |
     *   CONCAT/STACKED -- EXPR5
     *     |
     *   CONCAT/STACKED -- EXPR4
     *     |
     *   CONCAT -- EXPR3
     *     |
     *   EXPR1  -- EXPR2
     *
     * corresponds to an expression like
     *
     *   (EXPR1 . EXPR2 . EXPR3 . EXPR4 . EXPR5)
     *
     * Record info about each EXPR in args[]: in particular, whether it is
     * a stringifiable OP_CONST and if so what the const sv is.
     *
     * The reason why the last concat can't be STACKED is the difference
     * between
     *
     *    ((($a .= $a) .= $a) .= $a) .= $a
     *
     * and
     *    $a . $a . $a . $a . $a
     *
     * The main difference between the optrees for those two constructs
     * is the presence of the last STACKED. As well as modifying $a,
     * the former sees the changed $a between each concat, so if $s is
     * initially 'a', the first returns 'a' x 16, while the latter returns
     * 'a' x 5. And pp_multiconcat can't handle that kind of thing.
     */

    kid = topop;

    for (;;) {
        OP *argop;
        SV *sv;
        bool last = FALSE;

        if (    kid->op_type == OP_CONCAT
            && !kid_is_last
        ) {
            OP *k1, *k2;
            k1 = cUNOPx(kid)->op_first;
            k2 = OpSIBLING(k1);
            /* shouldn't happen except maybe after compile err? */
            if (!k2)
                return;

            /* avoid turning (A . B . ($lex = C) ...)  into  (A . B . C ...) */
            if (kid->op_private & OPpTARGET_MY)
                kid_is_last = TRUE;

            stacked_last = (kid->op_flags & OPf_STACKED);
            if (!stacked_last)
                kid_is_last = TRUE;

            kid   = k1;
            argop = k2;
        }
        else {
            argop = kid;
            last = TRUE;
        }

        if (   nargs + nadjconst  >  PERL_MULTICONCAT_MAXARG        - 2
            || (argp - args + 1)  > (PERL_MULTICONCAT_MAXARG*2 + 1) - 2)
        {
            /* At least two spare slots are needed to decompose both
             * concat args. If there are no slots left, continue to
             * examine the rest of the optree, but don't push new values
             * on args[]. If the optree as a whole is legal for conversion
             * (in particular that the last concat isn't STACKED), then
             * the first PERL_MULTICONCAT_MAXARG elements of the optree
             * can be converted into an OP_MULTICONCAT now, with the first
             * child of that op being the remainder of the optree -
             * which may itself later be converted to a multiconcat op
             * too.
             */
            if (last) {
                /* the last arg is the rest of the optree */
                argp++->p = NULL;
                nargs++;
            }
        }
        else if (   argop->op_type == OP_CONST
            && ((sv = cSVOPx_sv(argop)))
            /* defer stringification until runtime of 'constant'
             * things that might stringify variantly, e.g. the radix
             * point of NVs, or overloaded RVs */
            && (SvPOK(sv) || SvIOK(sv))
            && (!SvGMAGICAL(sv))
        ) {
            if (argop->op_private & OPpCONST_STRICT)
                no_bareword_allowed(argop);
            argp++->p = sv;
            utf8   |= cBOOL(SvUTF8(sv));
            nconst++;
            if (prev_was_const)
                /* this const may be demoted back to a plain arg later;
                 * make sure we have enough arg slots left */
                nadjconst++;
            prev_was_const = !prev_was_const;
        }
        else {
            argp++->p = NULL;
            nargs++;
            prev_was_const = FALSE;
        }

        if (last)
            break;
    }

    toparg = argp - 1;

    if (stacked_last)
        return; /* we don't support ((A.=B).=C)...) */

    /* look for two adjacent consts and don't fold them together:
     *     $o . "a" . "b"
     * should do
     *     $o->concat("a")->concat("b")
     * rather than
     *     $o->concat("ab")
     * (but $o .=  "a" . "b" should still fold)
     */
    {
        bool seen_nonconst = FALSE;
        for (argp = toparg; argp >= args; argp--) {
            if (argp->p == NULL) {
                seen_nonconst = TRUE;
                continue;
            }
            if (!seen_nonconst)
                continue;
            if (argp[1].p) {
                /* both previous and current arg were constants;
                 * leave the current OP_CONST as-is */
                argp->p = NULL;
                nconst--;
                nargs++;
            }
        }
    }

    /* -----------------------------------------------------------------
     * Phase 2:
     *
     * At this point we have determined that the optree *can* be converted
     * into a multiconcat. Having gathered all the evidence, we now decide
     * whether it *should*.
     */


    /* we need at least one concat action, e.g.:
     *
     *  Y . Z
     *  X = Y . Z
     *  X .= Y
     *
     * otherwise we could be doing something like $x = "foo", which
     * if treated as a concat, would fail to COW.
     */
    if (nargs + nconst + cBOOL(private_flags & OPpMULTICONCAT_APPEND) < 2)
        return;

    /* Benchmarking seems to indicate that we gain if:
     * * we optimise at least two actions into a single multiconcat
     *    (e.g., concat+concat, sassign+concat);
     * * or if we can eliminate at least 1 OP_CONST;
     * * or if we can eliminate a padsv via OPpTARGET_MY
     */

    if (
           /* eliminated at least one OP_CONST */
           nconst >= 1
           /* eliminated an OP_SASSIGN */
        || o->op_type == OP_SASSIGN
           /* eliminated an OP_PADSV */
        || (!targmyop && is_targable)
    )
        /* definitely a net gain to optimise */
        goto optimise;

    /* ... if not, what else? */

    /* special-case '$lex1 = expr . $lex1' (where expr isn't lex1):
     * multiconcat is faster (due to not creating a temporary copy of
     * $lex1), whereas for a general $lex1 = $lex2 . $lex3, concat is
     * faster.
     */
    if (   nconst == 0
         && nargs == 2
         && targmyop
         && topop->op_type == OP_CONCAT
    ) {
        PADOFFSET t = targmyop->op_targ;
        OP *k1 = cBINOPx(topop)->op_first;
        OP *k2 = cBINOPx(topop)->op_last;
        if (   k2->op_type == OP_PADSV
            && k2->op_targ == t
            && (   k1->op_type != OP_PADSV
                || k1->op_targ != t)
        )
            goto optimise;
    }

    /* need at least two concats */
    if (nargs + nconst + cBOOL(private_flags & OPpMULTICONCAT_APPEND) < 3)
        return;



    /* -----------------------------------------------------------------
     * Phase 3:
     *
     * At this point the optree has been verified as ok to be optimised
     * into an OP_MULTICONCAT. Now start changing things.
     */

   optimise:

    /* stringify all const args and determine utf8ness */

    variant = 0;
    for (argp = args; argp <= toparg; argp++) {
        SV *sv = (SV*)argp->p;
        if (!sv)
            continue; /* not a const op */
        if (utf8 && !SvUTF8(sv))
            sv_utf8_upgrade_nomg(sv);
        argp->p = SvPV_nomg(sv, argp->len);
        total_len += argp->len;

        /* see if any strings would grow if converted to utf8 */
        if (!utf8) {
            variant += variant_under_utf8_count((U8 *) argp->p,
                                                (U8 *) argp->p + argp->len);
        }
    }

    /* create and populate aux struct */

  create_aux:

    aux = (UNOP_AUX_item*)PerlMemShared_malloc(
                    sizeof(UNOP_AUX_item)
                    *  (
                           PERL_MULTICONCAT_HEADER_SIZE
                         + ((nargs + 1) * (variant ? 2 : 1))
                        )
                    );
    const_str = (char *)PerlMemShared_malloc(total_len ? total_len : 1);

    /* Extract all the non-const expressions from the concat tree then
     * dispose of the old tree, e.g. convert the tree from this:
     *
     *  o => SASSIGN
     *         |
     *       STRINGIFY   -- TARGET
     *         |
     *       ex-PUSHMARK -- CONCAT
     *                        |
     *                      CONCAT -- EXPR5
     *                        |
     *                      CONCAT -- EXPR4
     *                        |
     *                      CONCAT -- EXPR3
     *                        |
     *                      EXPR1  -- EXPR2
     *
     *
     * to:
     *
     *  o => MULTICONCAT
     *         |
     *       ex-PUSHMARK -- EXPR1 -- EXPR2 -- EXPR3 -- EXPR4 -- EXPR5 -- TARGET
     *
     * except that if EXPRi is an OP_CONST, it's discarded.
     *
     * During the conversion process, EXPR ops are stripped from the tree
     * and unshifted onto o. Finally, any of o's remaining original
     * children are discarded and o is converted into an OP_MULTICONCAT.
     *
     * In this middle of this, o may contain both: unshifted args on the
     * left, and some remaining original args on the right. lastkidop
     * is set to point to the right-most unshifted arg to delineate
     * between the two sets.
     */


    if (is_sprintf) {
        /* create a copy of the format with the %'s removed, and record
         * the sizes of the const string segments in the aux struct */
        char *q, *oldq;
        lenp = aux + PERL_MULTICONCAT_IX_LENGTHS;

        p    = sprintf_info.start;
        q    = const_str;
        oldq = q;
        for (; p < sprintf_info.end; p++) {
            if (*p == '%') {
                p++;
                if (*p != '%') {
                    (lenp++)->ssize = q - oldq;
                    oldq = q;
                    continue;
                }
            }
            *q++ = *p;
        }
        lenp->ssize = q - oldq;
        assert((STRLEN)(q - const_str) == total_len);

        /* Attach all the args (i.e. the kids of the sprintf) to o (which
         * may or may not be topop) The pushmark and const ops need to be
         * kept in case they're an op_next entry point.
         */
        lastkidop = cLISTOPx(topop)->op_last;
        kid = cUNOPx(topop)->op_first; /* pushmark */
        op_null(kid);
        op_null(OpSIBLING(kid));       /* const */
        if (o != topop) {
            kid = op_sibling_splice(topop, NULL, -1, NULL); /* cut all args */
            op_sibling_splice(o, NULL, 0, kid); /* and attach to o */
            lastkidop->op_next = o;
        }
    }
    else {
        p = const_str;
        lenp = aux + PERL_MULTICONCAT_IX_LENGTHS;

        lenp->ssize = -1;

        /* Concatenate all const strings into const_str.
         * Note that args[] contains the RHS args in reverse order, so
         * we scan args[] from top to bottom to get constant strings
         * in L-R order
         */
        for (argp = toparg; argp >= args; argp--) {
            if (!argp->p)
                /* not a const op */
                (++lenp)->ssize = -1;
            else {
                STRLEN l = argp->len;
                Copy(argp->p, p, l, char);
                p += l;
                if (lenp->ssize == -1)
                    lenp->ssize = l;
                else
                    lenp->ssize += l;
            }
        }

        kid = topop;
        nextop = o;
        lastkidop = NULL;

        for (argp = args; argp <= toparg; argp++) {
            /* only keep non-const args, except keep the first-in-next-chain
             * arg no matter what it is (but nulled if OP_CONST), because it
             * may be the entry point to this subtree from the previous
             * op_next.
             */
            bool last = (argp == toparg);
            OP *prev;

            /* set prev to the sibling *before* the arg to be cut out,
             * e.g. when cutting EXPR:
             *
             *         |
             * kid=  CONCAT
             *         |
             * prev= CONCAT -- EXPR
             *         |
             */
            if (argp == args && kid->op_type != OP_CONCAT) {
                /* in e.g. '$x .= f(1)' there's no RHS concat tree
                 * so the expression to be cut isn't kid->op_last but
                 * kid itself */
                OP *o1, *o2;
                /* find the op before kid */
                o1 = NULL;
                o2 = cUNOPx(parentop)->op_first;
                while (o2 && o2 != kid) {
                    o1 = o2;
                    o2 = OpSIBLING(o2);
                }
                assert(o2 == kid);
                prev = o1;
                kid  = parentop;
            }
            else if (kid == o && lastkidop)
                prev = last ? lastkidop : OpSIBLING(lastkidop);
            else
                prev = last ? NULL : cUNOPx(kid)->op_first;

            if (!argp->p || last) {
                /* cut RH op */
                OP *aop = op_sibling_splice(kid, prev, 1, NULL);
                /* and unshift to front of o */
                op_sibling_splice(o, NULL, 0, aop);
                /* record the right-most op added to o: later we will
                 * free anything to the right of it */
                if (!lastkidop)
                    lastkidop = aop;
                aop->op_next = nextop;
                if (last) {
                    if (argp->p)
                        /* null the const at start of op_next chain */
                        op_null(aop);
                }
                else if (prev)
                    nextop = prev->op_next;
            }

            /* the last two arguments are both attached to the same concat op */
            if (argp < toparg - 1)
                kid = prev;
        }
    }

    /* Populate the aux struct */

    aux[PERL_MULTICONCAT_IX_NARGS].ssize     = nargs;
    aux[PERL_MULTICONCAT_IX_PLAIN_PV].pv    = utf8 ? NULL : const_str;
    aux[PERL_MULTICONCAT_IX_PLAIN_LEN].ssize = utf8 ?    0 : total_len;
    aux[PERL_MULTICONCAT_IX_UTF8_PV].pv     = const_str;
    aux[PERL_MULTICONCAT_IX_UTF8_LEN].ssize  = total_len;

    /* if variant > 0, calculate a variant const string and lengths where
     * the utf8 version of the string will take 'variant' more bytes than
     * the plain one. */

    if (variant) {
        char              *p = const_str;
        STRLEN          ulen = total_len + variant;
        UNOP_AUX_item  *lens = aux + PERL_MULTICONCAT_IX_LENGTHS;
        UNOP_AUX_item *ulens = lens + (nargs + 1);
        char             *up = (char*)PerlMemShared_malloc(ulen);
        SSize_t            n;

        aux[PERL_MULTICONCAT_IX_UTF8_PV].pv    = up;
        aux[PERL_MULTICONCAT_IX_UTF8_LEN].ssize = ulen;

        for (n = 0; n < (nargs + 1); n++) {
            SSize_t i;
            char * orig_up = up;
            for (i = (lens++)->ssize; i > 0; i--) {
                U8 c = *p++;
                append_utf8_from_native_byte(c, (U8**)&up);
            }
            (ulens++)->ssize = (i < 0) ? i : up - orig_up;
        }
    }

    if (stringop) {
        /* if there was a top(ish)-level OP_STRINGIFY, we need to keep
         * that op's first child - an ex-PUSHMARK - because the op_next of
         * the previous op may point to it (i.e. it's the entry point for
         * the o optree)
         */
        OP *pmop =
            (stringop == o)
                ? op_sibling_splice(o, lastkidop, 1, NULL)
                : op_sibling_splice(stringop, NULL, 1, NULL);
        assert(OP_TYPE_IS_OR_WAS_NN(pmop, OP_PUSHMARK));
        op_sibling_splice(o, NULL, 0, pmop);
        if (!lastkidop)
            lastkidop = pmop;
    }

    /* Optimise
     *    target  = A.B.C...
     *    target .= A.B.C...
     */

    if (targetop) {
        assert(!targmyop);

        if (o->op_type == OP_SASSIGN) {
            /* Move the target subtree from being the last of o's children
             * to being the last of o's preserved children.
             * Note the difference between 'target = ...' and 'target .= ...':
             * for the former, target is executed last; for the latter,
             * first.
             */
            kid = OpSIBLING(lastkidop);
            op_sibling_splice(o, kid, 1, NULL); /* cut target op */
            op_sibling_splice(o, lastkidop, 0, targetop); /* and paste */
            lastkidop->op_next = kid->op_next;
            lastkidop = targetop;
        }
        else {
            /* Move the target subtree from being the first of o's
             * original children to being the first of *all* o's children.
             */
            if (lastkidop) {
                op_sibling_splice(o, lastkidop, 1, NULL); /* cut target op */
                op_sibling_splice(o, NULL, 0, targetop);  /* and paste*/
            }
            else {
                /* if the RHS of .= doesn't contain a concat (e.g.
                 * $x .= "foo"), it gets missed by the "strip ops from the
                 * tree and add to o" loop earlier */
                assert(topop->op_type != OP_CONCAT);
                if (stringop) {
                    /* in e.g. $x .= "$y", move the $y expression
                     * from being a child of OP_STRINGIFY to being the
                     * second child of the OP_CONCAT
                     */
                    assert(cUNOPx(stringop)->op_first == topop);
                    op_sibling_splice(stringop, NULL, 1, NULL);
                    op_sibling_splice(o, cUNOPo->op_first, 0, topop);
                }
                assert(topop == OpSIBLING(cBINOPo->op_first));
                if (toparg->p)
                    op_null(topop);
                lastkidop = topop;
            }
        }

        if (is_targable) {
            /* optimise
             *  my $lex  = A.B.C...
             *     $lex  = A.B.C...
             *     $lex .= A.B.C...
             * The original padsv op is kept but nulled in case it's the
             * entry point for the optree (which it will be for
             * '$lex .=  ... '
             */
            private_flags |= OPpTARGET_MY;
            private_flags |= (targetop->op_private & OPpLVAL_INTRO);
            o->op_targ = targetop->op_targ;
            targetop->op_targ = 0;
            op_null(targetop);
        }
        else
            flags |= OPf_STACKED;
    }
    else if (targmyop) {
        private_flags |= OPpTARGET_MY;
        if (o != targmyop) {
            o->op_targ = targmyop->op_targ;
            targmyop->op_targ = 0;
        }
    }

    /* detach the emaciated husk of the sprintf/concat optree and free it */
    for (;;) {
        kid = op_sibling_splice(o, lastkidop, 1, NULL);
        if (!kid)
            break;
        op_free(kid);
    }

    /* and convert o into a multiconcat */

    o->op_flags        = (flags|OPf_KIDS|stacked_last
                         |(o->op_flags & (OPf_WANT|OPf_PARENS)));
    o->op_private      = private_flags;
    o->op_type         = OP_MULTICONCAT;
    o->op_ppaddr       = PL_ppaddr[OP_MULTICONCAT];
    cUNOP_AUXo->op_aux = aux;


    /* add some PADTMPs, as needed, for the 'fallback to OP_CONCAT
     * behaviour if magic / overloaded etc present' code path */

    /* general PADTMP for the target of each concat */
    aux[PERL_MULTICONCAT_IX_PADTMP0].pad_offset =
                            pad_alloc(OP_MULTICONCAT, SVs_PADTMP);

    /* PADTMP for recreating OP_CONST return values */
    aux[PERL_MULTICONCAT_IX_PADTMP1].pad_offset =
        (is_sprintf || nconst) ? pad_alloc(OP_MULTICONCAT, SVs_PADTMP) : 0;

    /* PADTMP for stringifying the result */
    aux[PERL_MULTICONCAT_IX_PADTMP2].pad_offset =
    (o->op_private &OPpMULTICONCAT_STRINGIFY)
            ? pad_alloc(OP_MULTICONCAT, SVs_PADTMP) : 0;
}


/*
=for apidoc_section $optree_manipulation

=for apidoc optimize_optree

This function applies some optimisations to the optree in top-down order.
It is called before the peephole optimizer, which processes ops in
execution order. Note that finalize_optree() also does a top-down scan,
but is called *after* the peephole optimizer.

=cut
*/

void
Perl_optimize_optree(pTHX_ OP* o)
{
    PERL_ARGS_ASSERT_OPTIMIZE_OPTREE;

    ENTER;
    SAVEVPTR(PL_curcop);

    optimize_op(o);

    LEAVE;
}


#define warn_implicit_snail_cvsig(o)  S_warn_implicit_snail_cvsig(aTHX_ o)
static void
S_warn_implicit_snail_cvsig(pTHX_ OP *o)
{
    CV *cv = PL_compcv;
    while(cv && CvEVAL(cv))
        cv = CvOUTSIDE(cv);

    if(cv && CvSIGNATURE(cv))
        Perl_ck_warner_d(aTHX_ packWARN(WARN_EXPERIMENTAL__ARGS_ARRAY_WITH_SIGNATURES),
            "Implicit use of @_ in %s with signatured subroutine is experimental", OP_DESC(o));
}


#define OP_ZOOM(o)  (OP_TYPE_IS(o, OP_NULL) ? cUNOPx(o)->op_first : (o))

/* helper for optimize_optree() which optimises one op then recurses
 * to optimise any children.
 */

STATIC void
S_optimize_op(pTHX_ OP* o)
{
    OP *top_op = o;

    PERL_ARGS_ASSERT_OPTIMIZE_OP;

    while (1) {
        OP * next_kid = NULL;

        assert(o->op_type != OP_FREED);

        switch (o->op_type) {
        case OP_NEXTSTATE:
        case OP_DBSTATE:
            PL_curcop = ((COP*)o);		/* for warnings */
            break;


        case OP_CONCAT:
        case OP_SASSIGN:
        case OP_STRINGIFY:
        case OP_SPRINTF:
            S_maybe_multiconcat(aTHX_ o);
            break;

        case OP_SUBST:
            if (cPMOPo->op_pmreplrootu.op_pmreplroot) {
                /* we can't assume that op_pmreplroot->op_sibparent == o
                 * and that it is thus possible to walk back up the tree
                 * past op_pmreplroot. So, although we try to avoid
                 * recursing through op trees, do it here. After all,
                 * there are unlikely to be many nested s///e's within
                 * the replacement part of a s///e.
                 */
                optimize_op(cPMOPo->op_pmreplrootu.op_pmreplroot);
            }
            break;

        case OP_RV2AV:
        {
            OP *first = (o->op_flags & OPf_KIDS) ? cUNOPo->op_first : NULL;
            CV *cv = PL_compcv;
            while(cv && CvEVAL(cv))
                cv = CvOUTSIDE(cv);

            if(cv && CvSIGNATURE(cv) &&
                    OP_TYPE_IS(first, OP_GV) && cGVOPx_gv(first) == PL_defgv) {
                OP *parent = op_parent(o);
                while(OP_TYPE_IS(parent, OP_NULL))
                    parent = op_parent(parent);

                Perl_ck_warner_d(aTHX_ packWARN(WARN_EXPERIMENTAL__ARGS_ARRAY_WITH_SIGNATURES),
                    "Use of @_ in %s with signatured subroutine is experimental", OP_DESC(parent));
            }
            break;
        }

        case OP_SHIFT:
        case OP_POP:
            if(!CvUNIQUE(PL_compcv) && !(o->op_flags & OPf_KIDS))
                warn_implicit_snail_cvsig(o);
            break;

        case OP_ENTERSUB:
            if(!(o->op_flags & OPf_STACKED))
                warn_implicit_snail_cvsig(o);
            break;

        case OP_GOTO:
        {
            OP *first = (o->op_flags & OPf_KIDS) ? cUNOPo->op_first : NULL;
            OP *ffirst;
            if(OP_TYPE_IS(first, OP_SREFGEN) &&
                    (ffirst = OP_ZOOM(cUNOPx(first)->op_first)) &&
                    OP_TYPE_IS(ffirst, OP_RV2CV))
                warn_implicit_snail_cvsig(o);
            break;
        }

        default:
            break;
        }

        if (o->op_flags & OPf_KIDS)
            next_kid = cUNOPo->op_first;

        /* if a kid hasn't been nominated to process, continue with the
         * next sibling, or if no siblings left, go back to the parent's
         * siblings and so on
         */
        while (!next_kid) {
            if (o == top_op)
                return; /* at top; no parents/siblings to try */
            if (OpHAS_SIBLING(o))
                next_kid = o->op_sibparent;
            else
                o = o->op_sibparent; /*try parent's next sibling */
        }

      /* this label not yet used. Goto here if any code above sets
       * next-kid
       get_next_op:
       */
        o = next_kid;
    }
}

/*
=for apidoc finalize_optree

This function finalizes the optree.  Should be called directly after
the complete optree is built.  It does some additional
checking which can't be done in the normal C<ck_>xxx functions and makes
the tree thread-safe.

=cut
*/

void
Perl_finalize_optree(pTHX_ OP* o)
{
    PERL_ARGS_ASSERT_FINALIZE_OPTREE;

    ENTER;
    SAVEVPTR(PL_curcop);

    finalize_op(o);

    LEAVE;
}


/*
=for apidoc traverse_op_tree

Return the next op in a depth-first traversal of the op tree,
returning NULL when the traversal is complete.

The initial call must supply the root of the tree as both top and o.

For now it's static, but it may be exposed to the API in the future.

=cut
*/

STATIC OP*
S_traverse_op_tree(pTHX_ OP *top, OP *o) {
    OP *sib;

    PERL_ARGS_ASSERT_TRAVERSE_OP_TREE;

    if ((o->op_flags & OPf_KIDS) && cUNOPo->op_first) {
        return cUNOPo->op_first;
    }
    else if ((sib = OpSIBLING(o))) {
        return sib;
    }
    else {
        OP *parent = o->op_sibparent;
        assert(!(o->op_moresib));
        while (parent && parent != top) {
            OP *sib = OpSIBLING(parent);
            if (sib)
                return sib;
            parent = parent->op_sibparent;
        }

        return NULL;
    }
}

STATIC void
S_finalize_op(pTHX_ OP* o)
{
    OP * const top = o;
    PERL_ARGS_ASSERT_FINALIZE_OP;

    do {
        assert(o->op_type != OP_FREED);

        switch (o->op_type) {
        case OP_NEXTSTATE:
        case OP_DBSTATE:
            PL_curcop = ((COP*)o);		/* for warnings */
            break;
        case OP_EXEC:
            if (OpHAS_SIBLING(o)) {
                OP *sib = OpSIBLING(o);
                if ((  sib->op_type == OP_NEXTSTATE || sib->op_type == OP_DBSTATE)
                    && ckWARN(WARN_EXEC)
                    && OpHAS_SIBLING(sib))
                {
                    const OPCODE type = OpSIBLING(sib)->op_type;
                    if (type != OP_EXIT && type != OP_WARN && type != OP_DIE) {
                        const line_t oldline = CopLINE(PL_curcop);
                        CopLINE_set(PL_curcop, CopLINE((COP*)sib));
                        Perl_warner(aTHX_ packWARN(WARN_EXEC),
                            "Statement unlikely to be reached");
                        Perl_warner(aTHX_ packWARN(WARN_EXEC),
                            "\t(Maybe you meant system() when you said exec()?)\n");
                        CopLINE_set(PL_curcop, oldline);
                    }
                }
            }
            break;

        case OP_GV:
            if ((o->op_private & OPpEARLY_CV) && ckWARN(WARN_PROTOTYPE)) {
                GV * const gv = cGVOPo_gv;
                if (SvTYPE(gv) == SVt_PVGV && GvCV(gv) && SvPVX_const(GvCV(gv))) {
                    /* XXX could check prototype here instead of just carping */
                    SV * const sv = sv_newmortal();
                    gv_efullname3(sv, gv, NULL);
                    Perl_warner(aTHX_ packWARN(WARN_PROTOTYPE),
                                "%" SVf "() called too early to check prototype",
                                SVfARG(sv));
                }
            }
            break;

        case OP_CONST:
            if (cSVOPo->op_private & OPpCONST_STRICT)
                no_bareword_allowed(o);
#ifdef USE_ITHREADS
            /* FALLTHROUGH */
        case OP_HINTSEVAL:
            op_relocate_sv(&cSVOPo->op_sv, &o->op_targ);
#endif
            break;

#ifdef USE_ITHREADS
            /* Relocate all the METHOP's SVs to the pad for thread safety. */
        case OP_METHOD_NAMED:
        case OP_METHOD_SUPER:
        case OP_METHOD_REDIR:
        case OP_METHOD_REDIR_SUPER:
            op_relocate_sv(&cMETHOPo->op_u.op_meth_sv, &o->op_targ);
            break;
#endif

        case OP_HELEM: {
            UNOP *rop;
            SVOP *key_op;
            OP *kid;

            if ((key_op = cSVOPx(cBINOPo->op_last))->op_type != OP_CONST)
                break;

            rop = cUNOPx(cBINOPo->op_first);

            goto check_keys;

            case OP_HSLICE:
                S_scalar_slice_warning(aTHX_ o);
                /* FALLTHROUGH */

            case OP_KVHSLICE:
                kid = OpSIBLING(cLISTOPo->op_first);
            if (/* I bet there's always a pushmark... */
                OP_TYPE_ISNT_AND_WASNT_NN(kid, OP_LIST)
                && OP_TYPE_ISNT_NN(kid, OP_CONST))
            {
                break;
            }

            key_op = cSVOPx(kid->op_type == OP_CONST
                             ? kid
                             : OpSIBLING(kLISTOP->op_first));

            rop = cUNOPx(cLISTOPo->op_last);

        check_keys:
            if (o->op_private & OPpLVAL_INTRO || rop->op_type != OP_RV2HV)
                rop = NULL;
            check_hash_fields_and_hekify(rop, key_op, 1);
            break;
        }
        case OP_NULL:
            if (o->op_targ != OP_HSLICE && o->op_targ != OP_ASLICE)
                break;
            /* FALLTHROUGH */
        case OP_ASLICE:
            S_scalar_slice_warning(aTHX_ o);
            break;

        case OP_SUBST: {
            if (cPMOPo->op_pmreplrootu.op_pmreplroot)
                finalize_op(cPMOPo->op_pmreplrootu.op_pmreplroot);
            break;
        }
        default:
            break;
        }

#ifdef DEBUGGING
        if (o->op_flags & OPf_KIDS) {
            OP *kid;

            /* check that op_last points to the last sibling, and that
             * the last op_sibling/op_sibparent field points back to the
             * parent, and that the only ops with KIDS are those which are
             * entitled to them */
            U32 type = o->op_type;
            U32 family;
            bool has_last;

            if (type == OP_NULL) {
                type = o->op_targ;
                /* ck_glob creates a null UNOP with ex-type GLOB
                 * (which is a list op. So pretend it wasn't a listop */
                if (type == OP_GLOB)
                    type = OP_NULL;
            }
            family = PL_opargs[type] & OA_CLASS_MASK;

            has_last = (   family == OA_BINOP
                        || family == OA_LISTOP
                        || family == OA_PMOP
                        || family == OA_LOOP
                       );
            assert(  has_last /* has op_first and op_last, or ...
                  ... has (or may have) op_first: */
                  || family == OA_UNOP
                  || family == OA_UNOP_AUX
                  || family == OA_LOGOP
                  || family == OA_BASEOP_OR_UNOP
                  || family == OA_FILESTATOP
                  || family == OA_LOOPEXOP
                  || family == OA_METHOP
                  || type == OP_CUSTOM
                  || type == OP_NULL /* new_logop does this */
                  );

            for (kid = cUNOPo->op_first; kid; kid = OpSIBLING(kid)) {
                if (!OpHAS_SIBLING(kid)) {
                    if (has_last)
                        assert(kid == cLISTOPo->op_last);
                    assert(kid->op_sibparent == o);
                }
            }
        }
#endif
    } while (( o = traverse_op_tree(top, o)) != NULL);
}


/*
   ---------------------------------------------------------

   Common vars in list assignment

   There now follows some enums and static functions for detecting
   common variables in list assignments. Here is a little essay I wrote
   for myself when trying to get my head around this. DAPM.

   ----

   First some random observations:

   * If a lexical var is an alias of something else, e.g.
       for my $x ($lex, $pkg, $a[0]) {...}
     then the act of aliasing will increase the reference count of the SV

   * If a package var is an alias of something else, it may still have a
     reference count of 1, depending on how the alias was created, e.g.
     in *a = *b, $a may have a refcount of 1 since the GP is shared
     with a single GvSV pointer to the SV. So If it's an alias of another
     package var, then RC may be 1; if it's an alias of another scalar, e.g.
     a lexical var or an array element, then it will have RC > 1.

   * There are many ways to create a package alias; ultimately, XS code
     may quite legally do GvSV(gv) = SvREFCNT_inc(sv) for example, so
     run-time tracing mechanisms are unlikely to be able to catch all cases.

   * When the LHS is all my declarations, the same vars can't appear directly
     on the RHS, but they can indirectly via closures, aliasing and lvalue
     subs. But those techniques all involve an increase in the lexical
     scalar's ref count.

   * When the LHS is all lexical vars (but not necessarily my declarations),
     it is possible for the same lexicals to appear directly on the RHS, and
     without an increased ref count, since the stack isn't refcounted.
     This case can be detected at compile time by scanning for common lex
     vars with PL_generation.

   * lvalue subs defeat common var detection, but they do at least
     return vars with a temporary ref count increment. Also, you can't
     tell at compile time whether a sub call is lvalue.


   So...

   A: There are a few circumstances where there definitely can't be any
     commonality:

       LHS empty:  () = (...);
       RHS empty:  (....) = ();
       RHS contains only constants or other 'can't possibly be shared'
           elements (e.g. ops that return PADTMPs):  (...) = (1,2, length)
           i.e. they only contain ops not marked as dangerous, whose children
           are also not dangerous;
       LHS ditto;
       LHS contains a single scalar element: e.g. ($x) = (....); because
           after $x has been modified, it won't be used again on the RHS;
       RHS contains a single element with no aggregate on LHS: e.g.
           ($a,$b,$c)  = ($x); again, once $a has been modified, its value
           won't be used again.

   B: If LHS are all 'my' lexical var declarations (or safe ops, which
     we can ignore):

       my ($a, $b, @c) = ...;

       Due to closure and goto tricks, these vars may already have content.
       For the same reason, an element on the RHS may be a lexical or package
       alias of one of the vars on the left, or share common elements, for
       example:

           my ($x,$y) = f(); # $x and $y on both sides
           sub f : lvalue { ($x,$y) = (1,2); $y, $x }

       and

           my $ra = f();
           my @a = @$ra;  # elements of @a on both sides
           sub f { @a = 1..4; \@a }


       First, just consider scalar vars on LHS:

           RHS is safe only if (A), or in addition,
               * contains only lexical *scalar* vars, where neither side's
                 lexicals have been flagged as aliases

           If RHS is not safe, then it's always legal to check LHS vars for
           RC==1, since the only RHS aliases will always be associated
           with an RC bump.

           Note that in particular, RHS is not safe if:

               * it contains package scalar vars; e.g.:

                   f();
                   my ($x, $y) = (2, $x_alias);
                   sub f { $x = 1; *x_alias = \$x; }

               * It contains other general elements, such as flattened or
               * spliced or single array or hash elements, e.g.

                   f();
                   my ($x,$y) = @a; # or $a[0] or @a{@b} etc

                   sub f {
                       ($x, $y) = (1,2);
                       use feature 'refaliasing';
                       \($a[0], $a[1]) = \($y,$x);
                   }

                 It doesn't matter if the array/hash is lexical or package.

               * it contains a function call that happens to be an lvalue
                 sub which returns one or more of the above, e.g.

                   f();
                   my ($x,$y) = f();

                   sub f : lvalue {
                       ($x, $y) = (1,2);
                       *x1 = \$x;
                       $y, $x1;
                   }

                   (so a sub call on the RHS should be treated the same
                   as having a package var on the RHS).

               * any other "dangerous" thing, such an op or built-in that
                 returns one of the above, e.g. pp_preinc


           If RHS is not safe, what we can do however is at compile time flag
           that the LHS are all my declarations, and at run time check whether
           all the LHS have RC == 1, and if so skip the full scan.

       Now consider array and hash vars on LHS: e.g. my (...,@a) = ...;

           Here the issue is whether there can be elements of @a on the RHS
           which will get prematurely freed when @a is cleared prior to
           assignment. This is only a problem if the aliasing mechanism
           is one which doesn't increase the refcount - only if RC == 1
           will the RHS element be prematurely freed.

           Because the array/hash is being INTROed, it or its elements
           can't directly appear on the RHS:

               my (@a) = ($a[0], @a, etc) # NOT POSSIBLE

           but can indirectly, e.g.:

               my $r = f();
               my (@a) = @$r;
               sub f { @a = 1..3; \@a }

           So if the RHS isn't safe as defined by (A), we must always
           mortalise and bump the ref count of any remaining RHS elements
           when assigning to a non-empty LHS aggregate.

           Lexical scalars on the RHS aren't safe if they've been involved in
           aliasing, e.g.

               use feature 'refaliasing';

               f();
               \(my $lex) = \$pkg;
               my @a = ($lex,3); # equivalent to ($a[0],3)

               sub f {
                   @a = (1,2);
                   \$pkg = \$a[0];
               }

           Similarly with lexical arrays and hashes on the RHS:

               f();
               my @b;
               my @a = (@b);

               sub f {
                   @a = (1,2);
                   \$b[0] = \$a[1];
                   \$b[1] = \$a[0];
               }



   C: As (B), but in addition the LHS may contain non-intro lexicals, e.g.
       my $a; ($a, my $b) = (....);

       The difference between (B) and (C) is that it is now physically
       possible for the LHS vars to appear on the RHS too, where they
       are not reference counted; but in this case, the compile-time
       PL_generation sweep will detect such common vars.

       So the rules for (C) differ from (B) in that if common vars are
       detected, the runtime "test RC==1" optimisation can no longer be used,
       and a full mark and sweep is required

   D: As (C), but in addition the LHS may contain package vars.

       Since package vars can be aliased without a corresponding refcount
       increase, all bets are off. It's only safe if (A). E.g.

           my ($x, $y) = (1,2);

           for $x_alias ($x) {
               ($x_alias, $y) = (3, $x); # whoops
           }

       Ditto for LHS aggregate package vars.

   E: Any other dangerous ops on LHS, e.g.
           (f(), $a[0], @$r) = (...);

       this is similar to (E) in that all bets are off. In addition, it's
       impossible to determine at compile time whether the LHS
       contains a scalar or an aggregate, e.g.

           sub f : lvalue { @a }
           (f()) = 1..3;

* ---------------------------------------------------------
*/

/* A set of bit flags returned by S_aassign_scan(). Each flag indicates
 * that at least one of the things flagged was seen.
 */

enum {
    AAS_MY_SCALAR       = 0x001, /* my $scalar */
    AAS_MY_AGG          = 0x002, /* aggregate: my @array or my %hash */
    AAS_LEX_SCALAR      = 0x004, /* $lexical */
    AAS_LEX_AGG         = 0x008, /* @lexical or %lexical aggregate */
    AAS_LEX_SCALAR_COMM = 0x010, /* $lexical seen on both sides */
    AAS_PKG_SCALAR      = 0x020, /* $scalar (where $scalar is pkg var) */
    AAS_PKG_AGG         = 0x040, /* package @array or %hash aggregate */
    AAS_DANGEROUS       = 0x080, /* an op (other than the above)
                                         that's flagged OA_DANGEROUS */
    AAS_SAFE_SCALAR     = 0x100, /* produces at least one scalar SV that's
                                        not in any of the categories above */
    AAS_DEFAV           = 0x200  /* contains just a single '@_' on RHS */
};

/* helper function for S_aassign_scan().
 * check a PAD-related op for commonality and/or set its generation number.
 * Returns a boolean indicating whether its shared */

static bool
S_aassign_padcheck(pTHX_ OP* o, bool rhs)
{
    if (PAD_COMPNAME_GEN(o->op_targ) == PERL_INT_MAX)
        /* lexical used in aliasing */
        return TRUE;

    if (rhs)
        return cBOOL(PAD_COMPNAME_GEN(o->op_targ) == (STRLEN)PL_generation);
    else
        PAD_COMPNAME_GEN_set(o->op_targ, PL_generation);

    return FALSE;
}

/*
  Helper function for OPpASSIGN_COMMON* detection in rpeep().
  It scans the left or right hand subtree of the aassign op, and returns a
  set of flags indicating what sorts of things it found there.
  'rhs' indicates whether we're scanning the LHS or RHS. If the former, we
  set PL_generation on lexical vars; if the latter, we see if
  PL_generation matches.
  'scalars_p' is a pointer to a counter of the number of scalar SVs seen.
  This fn will increment it by the number seen. It's not intended to
  be an accurate count (especially as many ops can push a variable
  number of SVs onto the stack); rather it's used as to test whether there
  can be at most 1 SV pushed; so it's only meanings are "0, 1, many".
*/

static int
S_aassign_scan(pTHX_ OP* o, bool rhs, int *scalars_p)
{
    OP *top_op           = o;
    OP *effective_top_op = o;
    int all_flags = 0;

    while (1) {
        bool top = o == effective_top_op;
        int flags = 0;
        OP* next_kid = NULL;

        /* first, look for a solitary @_ on the RHS */
        if (   rhs
            && top
            && (o->op_flags & OPf_KIDS)
            && OP_TYPE_IS_OR_WAS(o, OP_LIST)
        ) {
            OP *kid = cUNOPo->op_first;
            if (   (   kid->op_type == OP_PUSHMARK
                    || kid->op_type == OP_PADRANGE) /* ex-pushmark */
                && ((kid = OpSIBLING(kid)))
                && !OpHAS_SIBLING(kid)
                && kid->op_type == OP_RV2AV
                && !(kid->op_flags & OPf_REF)
                && !(kid->op_private & (OPpLVAL_INTRO|OPpMAYBE_LVSUB))
                && ((kid->op_flags & OPf_WANT) == OPf_WANT_LIST)
                && ((kid = cUNOPx(kid)->op_first))
                && kid->op_type == OP_GV
                && cGVOPx_gv(kid) == PL_defgv
            )
                flags = AAS_DEFAV;
        }

        switch (o->op_type) {
        case OP_GVSV:
            (*scalars_p)++;
            all_flags |= AAS_PKG_SCALAR;
            goto do_next;

        case OP_PADAV:
        case OP_PADHV:
            (*scalars_p) += 2;
            /* if !top, could be e.g. @a[0,1] */
            all_flags |=  (top && (o->op_flags & OPf_REF))
                            ? ((o->op_private & OPpLVAL_INTRO)
                                ? AAS_MY_AGG : AAS_LEX_AGG)
                            : AAS_DANGEROUS;
            goto do_next;

        case OP_PADSV:
            {
                int comm = S_aassign_padcheck(aTHX_ o, rhs)
                            ?  AAS_LEX_SCALAR_COMM : 0;
                (*scalars_p)++;
                all_flags |= (o->op_private & OPpLVAL_INTRO)
                    ? (AAS_MY_SCALAR|comm) : (AAS_LEX_SCALAR|comm);
                goto do_next;

            }

        case OP_RV2AV:
        case OP_RV2HV:
            (*scalars_p) += 2;
            if (cUNOPx(o)->op_first->op_type != OP_GV)
                all_flags |= AAS_DANGEROUS; /* @{expr}, %{expr} */
            /* @pkg, %pkg */
            /* if !top, could be e.g. @a[0,1] */
            else if (top && (o->op_flags & OPf_REF))
                all_flags |= AAS_PKG_AGG;
            else
                all_flags |= AAS_DANGEROUS;
            goto do_next;

        case OP_RV2SV:
            (*scalars_p)++;
            if (cUNOPx(o)->op_first->op_type != OP_GV) {
                (*scalars_p) += 2;
                all_flags |= AAS_DANGEROUS; /* ${expr} */
            }
            else
                all_flags |= AAS_PKG_SCALAR; /* $pkg */
            goto do_next;

        case OP_SPLIT:
            if (o->op_private & OPpSPLIT_ASSIGN) {
                /* the assign in @a = split() has been optimised away
                 * and the @a attached directly to the split op
                 * Treat the array as appearing on the RHS, i.e.
                 *    ... = (@a = split)
                 * is treated like
                 *    ... = @a;
                 */

                if (o->op_flags & OPf_STACKED) {
                    /* @{expr} = split() - the array expression is tacked
                     * on as an extra child to split - process kid */
                    next_kid = cLISTOPo->op_last;
                    goto do_next;
                }

                /* ... else array is directly attached to split op */
                (*scalars_p) += 2;
                all_flags |= (PL_op->op_private & OPpSPLIT_LEX)
                                ? ((o->op_private & OPpLVAL_INTRO)
                                    ? AAS_MY_AGG : AAS_LEX_AGG)
                                : AAS_PKG_AGG;
                goto do_next;
            }
            (*scalars_p)++;
            /* other args of split can't be returned */
            all_flags |= AAS_SAFE_SCALAR;
            goto do_next;

        case OP_UNDEF:
            /* undef on LHS following a var is significant, e.g.
             *    my $x = 1;
             *    @a = (($x, undef) = (2 => $x));
             *    # @a shoul be (2,1) not (2,2)
             *
             * undef on RHS counts as a scalar:
             *   ($x, $y)    = (undef, $x); # 2 scalars on RHS: unsafe
             */
            if ((!rhs && *scalars_p) || rhs)
                (*scalars_p)++;
            flags = AAS_SAFE_SCALAR;
            break;

        case OP_PUSHMARK:
        case OP_STUB:
            /* these are all no-ops; they don't push a potentially common SV
             * onto the stack, so they are neither AAS_DANGEROUS nor
             * AAS_SAFE_SCALAR */
            goto do_next;

        case OP_PADRANGE: /* Ignore padrange; checking its siblings is enough */
            break;

        case OP_NULL:
        case OP_LIST:
            /* these do nothing, but may have children */
            break;

        default:
            if (PL_opargs[o->op_type] & OA_DANGEROUS) {
                (*scalars_p) += 2;
                flags = AAS_DANGEROUS;
                break;
            }

            if (   (PL_opargs[o->op_type] & OA_TARGLEX)
                && (o->op_private & OPpTARGET_MY))
            {
                (*scalars_p)++;
                all_flags |= S_aassign_padcheck(aTHX_ o, rhs)
                                ? AAS_LEX_SCALAR_COMM : AAS_LEX_SCALAR;
                goto do_next;
            }

            /* if its an unrecognised, non-dangerous op, assume that it
             * is the cause of at least one safe scalar */
            (*scalars_p)++;
            flags = AAS_SAFE_SCALAR;
            break;
        }

        all_flags |= flags;

        /* by default, process all kids next
         * XXX this assumes that all other ops are "transparent" - i.e. that
         * they can return some of their children. While this true for e.g.
         * sort and grep, it's not true for e.g. map. We really need a
         * 'transparent' flag added to regen/opcodes
         */
        if (o->op_flags & OPf_KIDS) {
            next_kid = cUNOPo->op_first;
            /* these ops do nothing but may have children; but their
             * children should also be treated as top-level */
            if (   o == effective_top_op
                && (o->op_type == OP_NULL || o->op_type == OP_LIST)
            )
                effective_top_op = next_kid;
        }


        /* If next_kid is set, someone in the code above wanted us to process
         * that kid and all its remaining siblings.  Otherwise, work our way
         * back up the tree */
      do_next:
        while (!next_kid) {
            if (o == top_op)
                return all_flags; /* at top; no parents/siblings to try */
            if (OpHAS_SIBLING(o)) {
                next_kid = o->op_sibparent;
                if (o == effective_top_op)
                    effective_top_op = next_kid;
            }
            else if (o == effective_top_op)
              effective_top_op = o->op_sibparent;
            o = o->op_sibparent; /* try parent's next sibling */
        }
        o = next_kid;
    } /* while */
}

/* S_maybe_multideref(): given an op_next chain of ops beginning at 'start'
 * that potentially represent a series of one or more aggregate derefs
 * (such as $a->[1]{$key}), examine the chain, and if appropriate, convert
 * the whole chain to a single OP_MULTIDEREF op (maybe with a few
 * additional ops left in too).
 *
 * The caller will have already verified that the first few ops in the
 * chain following 'start' indicate a multideref candidate, and will have
 * set 'orig_o' to the point further on in the chain where the first index
 * expression (if any) begins.  'orig_action' specifies what type of
 * beginning has already been determined by the ops between start..orig_o
 * (e.g.  $lex_ary[], $pkg_ary->{}, expr->[], etc).
 *
 * 'hints' contains any hints flags that need adding (currently just
 * OPpHINT_STRICT_REFS) as found in any rv2av/hv skipped by the caller.
 */

STATIC void
S_maybe_multideref(pTHX_ OP *start, OP *orig_o, UV orig_action, U8 hints)
{
    int pass;
    UNOP_AUX_item *arg_buf = NULL;
    bool reset_start_targ  = FALSE; /* start->op_targ needs zeroing */
    int index_skip         = -1;    /* don't output index arg on this action */

    /* similar to regex compiling, do two passes; the first pass
     * determines whether the op chain is convertible and calculates the
     * buffer size; the second pass populates the buffer and makes any
     * changes necessary to ops (such as moving consts to the pad on
     * threaded builds).
     *
     * NB: for things like Coverity, note that both passes take the same
     * path through the logic tree (except for 'if (pass)' bits), since
     * both passes are following the same op_next chain; and in
     * particular, if it would return early on the second pass, it would
     * already have returned early on the first pass.
     */
    for (pass = 0; pass < 2; pass++) {
        OP *o                = orig_o;
        UV action            = orig_action;
        OP *first_elem_op    = NULL;  /* first seen aelem/helem */
        OP *top_op           = NULL;  /* highest [ah]elem/exists/del/rv2[ah]v */
        int action_count     = 0;     /* number of actions seen so far */
        int action_ix        = 0;     /* action_count % (actions per IV) */
        bool next_is_hash    = FALSE; /* is the next lookup to be a hash? */
        bool is_last         = FALSE; /* no more derefs to follow */
        bool maybe_aelemfast = FALSE; /* we can replace with aelemfast? */
        UV action_word       = 0;     /* all actions so far */
        size_t argi          = 0;
        UNOP_AUX_item *action_ptr = arg_buf;

        argi++; /* reserve slot for first action word */

        switch (action) {
        case MDEREF_HV_gvsv_vivify_rv2hv_helem:
        case MDEREF_HV_gvhv_helem:
            next_is_hash = TRUE;
            /* FALLTHROUGH */
        case MDEREF_AV_gvsv_vivify_rv2av_aelem:
        case MDEREF_AV_gvav_aelem:
            if (pass) {
#ifdef USE_ITHREADS
                arg_buf[argi].pad_offset = cPADOPx(start)->op_padix;
                /* stop it being swiped when nulled */
                cPADOPx(start)->op_padix = 0;
#else
                arg_buf[argi].sv = cSVOPx(start)->op_sv;
                cSVOPx(start)->op_sv = NULL;
#endif
            }
            argi++;
            break;

        case MDEREF_HV_padhv_helem:
        case MDEREF_HV_padsv_vivify_rv2hv_helem:
            next_is_hash = TRUE;
            /* FALLTHROUGH */
        case MDEREF_AV_padav_aelem:
        case MDEREF_AV_padsv_vivify_rv2av_aelem:
            if (pass) {
                arg_buf[argi].pad_offset = start->op_targ;
                /* we skip setting op_targ = 0 for now, since the intact
                 * OP_PADXV is needed by check_hash_fields_and_hekify */
                reset_start_targ = TRUE;
            }
            argi++;
            break;

        case MDEREF_HV_pop_rv2hv_helem:
            next_is_hash = TRUE;
            /* FALLTHROUGH */
        case MDEREF_AV_pop_rv2av_aelem:
            break;

        default:
            NOT_REACHED; /* NOTREACHED */
            return;
        }

        while (!is_last) {
            /* look for another (rv2av/hv; get index;
             * aelem/helem/exists/delele) sequence */

            OP *kid;
            bool is_deref;
            bool ok;
            UV index_type = MDEREF_INDEX_none;

            if (action_count) {
                /* if this is not the first lookup, consume the rv2av/hv  */

                /* for N levels of aggregate lookup, we normally expect
                 * that the first N-1 [ah]elem ops will be flagged as
                 * /DEREF (so they autovivify if necessary), and the last
                 * lookup op not to be.
                 * For other things (like @{$h{k1}{k2}}) extra scope or
                 * leave ops can appear, so abandon the effort in that
                 * case */
                if (o->op_type != OP_RV2AV && o->op_type != OP_RV2HV)
                    return;

                /* rv2av or rv2hv sKR/1 */

                ASSUME(!(o->op_flags & ~(OPf_WANT|OPf_KIDS|OPf_PARENS
                                            |OPf_REF|OPf_MOD|OPf_SPECIAL)));
                if (o->op_flags != (OPf_WANT_SCALAR|OPf_KIDS|OPf_REF))
                    return;

                /* at this point, we wouldn't expect any of these
                 * possible private flags:
                 * OPpMAYBE_LVSUB, OPpOUR_INTRO, OPpLVAL_INTRO
                 * OPpTRUEBOOL, OPpMAYBE_TRUEBOOL (rv2hv only)
                 */
                ASSUME(!(o->op_private &
                    ~(OPpHINT_STRICT_REFS|OPpARG1_MASK|OPpSLICEWARNING)));

                hints = (o->op_private & OPpHINT_STRICT_REFS);

                /* make sure the type of the previous /DEREF matches the
                 * type of the next lookup */
                ASSUME(o->op_type == (next_is_hash ? OP_RV2HV : OP_RV2AV));
                top_op = o;

                action = next_is_hash
                            ? MDEREF_HV_vivify_rv2hv_helem
                            : MDEREF_AV_vivify_rv2av_aelem;
                o = o->op_next;
            }

            /* if this is the second pass, and we're at the depth where
             * previously we encountered a non-simple index expression,
             * stop processing the index at this point */
            if (action_count != index_skip) {

                /* look for one or more simple ops that return an array
                 * index or hash key */

                switch (o->op_type) {
                case OP_PADSV:
                    /* it may be a lexical var index */
                    ASSUME(!(o->op_flags & ~(OPf_WANT|OPf_PARENS
                                            |OPf_REF|OPf_MOD|OPf_SPECIAL)));
                    ASSUME(!(o->op_private &
                            ~(OPpPAD_STATE|OPpDEREF|OPpLVAL_INTRO)));

                    if (   OP_GIMME(o,0) == G_SCALAR
                        && !(o->op_flags & (OPf_REF|OPf_MOD))
                        && o->op_private == 0)
                    {
                        if (pass)
                            arg_buf[argi].pad_offset = o->op_targ;
                        argi++;
                        index_type = MDEREF_INDEX_padsv;
                        o = o->op_next;
                    }
                    break;

                case OP_CONST:
                    if (next_is_hash) {
                        /* it's a constant hash index */
                        if (!(SvFLAGS(cSVOPo_sv) & (SVf_IOK|SVf_NOK|SVf_POK)))
                            /* "use constant foo => FOO; $h{+foo}" for
                             * some weird FOO, can leave you with constants
                             * that aren't simple strings. It's not worth
                             * the extra hassle for those edge cases */
                            break;

                        {
                            UNOP *rop = NULL;
                            OP * helem_op = o->op_next;

                            ASSUME(   helem_op->op_type == OP_HELEM
                                   || helem_op->op_type == OP_NULL
                                   || pass == 0);
                            if (helem_op->op_type == OP_HELEM) {
                                rop = cUNOPx(cBINOPx(helem_op)->op_first);
                                if (   helem_op->op_private & OPpLVAL_INTRO
                                    || rop->op_type != OP_RV2HV
                                )
                                    rop = NULL;
                            }
                            /* on first pass just check; on second pass
                             * hekify */
                            check_hash_fields_and_hekify(rop, cSVOPo, pass);
                        }

                        if (pass) {
#ifdef USE_ITHREADS
                            /* Relocate sv to the pad for thread safety */
                            op_relocate_sv(&cSVOPo->op_sv, &o->op_targ);
                            arg_buf[argi].pad_offset = o->op_targ;
                            o->op_targ = 0;
#else
                            arg_buf[argi].sv = cSVOPx_sv(o);
#endif
                        }
                    }
                    else {
                        /* it's a constant array index */
                        IV iv;
                        SV *ix_sv = cSVOPo->op_sv;
                        if (!SvIOK(ix_sv))
                            break;
                        iv = SvIV(ix_sv);

                        if (   action_count == 0
                            && iv >= -128
                            && iv <= 127
                            && (   action == MDEREF_AV_padav_aelem
                                || action == MDEREF_AV_gvav_aelem)
                        )
                            maybe_aelemfast = TRUE;

                        if (pass) {
                            arg_buf[argi].iv = iv;
                            SvREFCNT_dec_NN(cSVOPo->op_sv);
                        }
                    }
                    if (pass)
                        /* we've taken ownership of the SV */
                        cSVOPo->op_sv = NULL;
                    argi++;
                    index_type = MDEREF_INDEX_const;
                    o = o->op_next;
                    break;

                case OP_GV:
                    /* it may be a package var index */

                    ASSUME(!(o->op_flags & ~(OPf_WANT|OPf_PARENS|OPf_SPECIAL)));
                    ASSUME(!(o->op_private & ~(OPpEARLY_CV)));
                    if (  (o->op_flags & ~(OPf_PARENS|OPf_SPECIAL)) != OPf_WANT_SCALAR
                        || o->op_private != 0
                    )
                        break;

                    kid = o->op_next;
                    if (kid->op_type != OP_RV2SV)
                        break;

                    ASSUME(!(kid->op_flags &
                            ~(OPf_WANT|OPf_KIDS|OPf_MOD|OPf_REF
                             |OPf_SPECIAL|OPf_PARENS)));
                    ASSUME(!(kid->op_private &
                                    ~(OPpARG1_MASK
                                     |OPpHINT_STRICT_REFS|OPpOUR_INTRO
                                     |OPpDEREF|OPpLVAL_INTRO)));
                    if(   (kid->op_flags &~ OPf_PARENS)
                            != (OPf_WANT_SCALAR|OPf_KIDS)
                       || (kid->op_private & ~(OPpARG1_MASK|HINT_STRICT_REFS))
                    )
                        break;

                    if (pass) {
#ifdef USE_ITHREADS
                        arg_buf[argi].pad_offset = cPADOPx(o)->op_padix;
                        /* stop it being swiped when nulled */
                        cPADOPx(o)->op_padix = 0;
#else
                        arg_buf[argi].sv = cSVOPx(o)->op_sv;
                        cSVOPo->op_sv = NULL;
#endif
                    }
                    argi++;
                    index_type = MDEREF_INDEX_gvsv;
                    o = kid->op_next;
                    break;

                } /* switch */
            } /* action_count != index_skip */

            action |= index_type;


            /* at this point we have either:
             *   * detected what looks like a simple index expression,
             *     and expect the next op to be an [ah]elem, or
             *     an nulled  [ah]elem followed by a delete or exists;
             *  * found a more complex expression, so something other
             *    than the above follows.
             */

            /* possibly an optimised away [ah]elem (where op_next is
             * exists or delete) */
            if (o->op_type == OP_NULL)
                o = o->op_next;

            /* at this point we're looking for an OP_AELEM, OP_HELEM,
             * OP_EXISTS or OP_DELETE */

            /* if a custom array/hash access checker is in scope,
             * abandon optimisation attempt */
            if (  (o->op_type == OP_AELEM || o->op_type == OP_HELEM)
               && PL_check[o->op_type] != Perl_ck_null)
                return;
            /* similarly for customised exists and delete */
            if (  (o->op_type == OP_EXISTS)
               && PL_check[o->op_type] != Perl_ck_exists)
                return;
            if (  (o->op_type == OP_DELETE)
               && PL_check[o->op_type] != Perl_ck_delete)
                return;

            if (   o->op_type != OP_AELEM
                || (o->op_private &
                      (OPpLVAL_INTRO|OPpLVAL_DEFER|OPpDEREF|OPpMAYBE_LVSUB))
                )
                maybe_aelemfast = FALSE;

            /* look for aelem/helem/exists/delete. If it's not the last elem
             * lookup, it *must* have OPpDEREF_AV/HV, but not many other
             * flags; if it's the last, then it mustn't have
             * OPpDEREF_AV/HV, but may have lots of other flags, like
             * OPpLVAL_INTRO etc
             */

            if (   index_type == MDEREF_INDEX_none
                || (   o->op_type != OP_AELEM  && o->op_type != OP_HELEM
                    && o->op_type != OP_EXISTS && o->op_type != OP_DELETE)
            )
                ok = FALSE;
            else {
                /* we have aelem/helem/exists/delete with valid simple index */

                is_deref =    (o->op_type == OP_AELEM || o->op_type == OP_HELEM)
                           && (   (o->op_private & OPpDEREF) == OPpDEREF_AV
                               || (o->op_private & OPpDEREF) == OPpDEREF_HV);

                /* This doesn't make much sense but is legal:
                 *    @{ local $x[0][0] } = 1
                 * Since scope exit will undo the autovivification,
                 * don't bother in the first place. The OP_LEAVE
                 * assertion is in case there are other cases of both
                 * OPpLVAL_INTRO and OPpDEREF which don't include a scope
                 * exit that would undo the local - in which case this
                 * block of code would need rethinking.
                 */
                if (is_deref && (o->op_private & OPpLVAL_INTRO)) {
#ifdef DEBUGGING
                    OP *n = o->op_next;
                    while (n && (  n->op_type == OP_NULL
                                || n->op_type == OP_LIST
                                || n->op_type == OP_SCALAR))
                        n = n->op_next;
                    assert(n && n->op_type == OP_LEAVE);
#endif
                    o->op_private &= ~OPpDEREF;
                    is_deref = FALSE;
                }

                if (is_deref) {
                    ASSUME(!(o->op_flags &
                                 ~(OPf_WANT|OPf_KIDS|OPf_MOD|OPf_PARENS)));
                    ASSUME(!(o->op_private & ~(OPpARG2_MASK|OPpDEREF)));

                    ok =    (o->op_flags &~ OPf_PARENS)
                               == (OPf_WANT_SCALAR|OPf_KIDS|OPf_MOD)
                         && !(o->op_private & ~(OPpDEREF|OPpARG2_MASK));
                }
                else if (o->op_type == OP_EXISTS) {
                    ASSUME(!(o->op_flags & ~(OPf_WANT|OPf_KIDS|OPf_PARENS
                                |OPf_REF|OPf_MOD|OPf_SPECIAL)));
                    ASSUME(!(o->op_private & ~(OPpARG1_MASK|OPpEXISTS_SUB)));
                    ok =  !(o->op_private & ~OPpARG1_MASK);
                }
                else if (o->op_type == OP_DELETE) {
                    ASSUME(!(o->op_flags & ~(OPf_WANT|OPf_KIDS|OPf_PARENS
                                |OPf_REF|OPf_MOD|OPf_SPECIAL)));
                    ASSUME(!(o->op_private &
                                    ~(OPpARG1_MASK|OPpSLICE|OPpLVAL_INTRO)));
                    /* don't handle slices or 'local delete'; the latter
                     * is fairly rare, and has a complex runtime */
                    ok =  !(o->op_private & ~OPpARG1_MASK);
                    if (OP_TYPE_IS_OR_WAS(cUNOPo->op_first, OP_AELEM))
                        /* skip handling run-tome error */
                        ok = (ok && cBOOL(o->op_flags & OPf_SPECIAL));
                }
                else {
                    ASSUME(o->op_type == OP_AELEM || o->op_type == OP_HELEM);
                    ASSUME(!(o->op_flags & ~(OPf_WANT|OPf_KIDS|OPf_MOD
                                            |OPf_PARENS|OPf_REF|OPf_SPECIAL)));
                    ASSUME(!(o->op_private & ~(OPpARG2_MASK|OPpMAYBE_LVSUB
                                    |OPpLVAL_DEFER|OPpDEREF|OPpLVAL_INTRO)));
                    ok = (o->op_private & OPpDEREF) != OPpDEREF_SV;
                }
            }

            if (ok) {
                if (!first_elem_op)
                    first_elem_op = o;
                top_op = o;
                if (is_deref) {
                    next_is_hash = cBOOL((o->op_private & OPpDEREF) == OPpDEREF_HV);
                    o = o->op_next;
                }
                else {
                    is_last = TRUE;
                    action |= MDEREF_FLAG_last;
                }
            }
            else {
                /* at this point we have something that started
                 * promisingly enough (with rv2av or whatever), but failed
                 * to find a simple index followed by an
                 * aelem/helem/exists/delete. If this is the first action,
                 * give up; but if we've already seen at least one
                 * aelem/helem, then keep them and add a new action with
                 * MDEREF_INDEX_none, which causes it to do the vivify
                 * from the end of the previous lookup, and do the deref,
                 * but stop at that point. So $a[0][expr] will do one
                 * av_fetch, vivify and deref, then continue executing at
                 * expr */
                if (!action_count)
                    return;
                is_last = TRUE;
                index_skip = action_count;
                action |= MDEREF_FLAG_last;
                if (index_type != MDEREF_INDEX_none)
                    argi--;
            }

            action_word |= (action << (action_ix * MDEREF_SHIFT));
            action_ix++;
            action_count++;
            /* if there's no space for the next action, reserve a new slot
             * for it *before* we start adding args for that action */
            if ((action_ix + 1) * MDEREF_SHIFT > UVSIZE*8) {
                if (pass) {
                    action_ptr->uv = action_word;
                    action_ptr = arg_buf + argi;
                }
                action_word = 0;
                argi++;
                action_ix = 0;
            }
        } /* while !is_last */

        /* success! */

        if (!action_ix)
            /* slot reserved for next action word not now needed */
            argi--;
        else if (pass)
            action_ptr->uv = action_word;

        if (pass) {
            OP *mderef;
            OP *p, *q;

            mderef = newUNOP_AUX(OP_MULTIDEREF, 0, NULL, arg_buf);
            if (index_skip == -1) {
                mderef->op_flags = o->op_flags
                        & (OPf_WANT|OPf_MOD|(next_is_hash ? OPf_SPECIAL : 0));
                if (o->op_type == OP_EXISTS)
                    mderef->op_private = OPpMULTIDEREF_EXISTS;
                else if (o->op_type == OP_DELETE)
                    mderef->op_private = OPpMULTIDEREF_DELETE;
                else
                    mderef->op_private = o->op_private
                        & (OPpMAYBE_LVSUB|OPpLVAL_DEFER|OPpLVAL_INTRO);
            }
            /* accumulate strictness from every level (although I don't think
             * they can actually vary) */
            mderef->op_private |= hints;

            /* integrate the new multideref op into the optree and the
             * op_next chain.
             *
             * In general an op like aelem or helem has two child
             * sub-trees: the aggregate expression (a_expr) and the
             * index expression (i_expr):
             *
             *     aelem
             *       |
             *     a_expr - i_expr
             *
             * The a_expr returns an AV or HV, while the i-expr returns an
             * index. In general a multideref replaces most or all of a
             * multi-level tree, e.g.
             *
             *     exists
             *       |
             *     ex-aelem
             *       |
             *     rv2av  - i_expr1
             *       |
             *     helem
             *       |
             *     rv2hv  - i_expr2
             *       |
             *     aelem
             *       |
             *     a_expr - i_expr3
             *
             * With multideref, all the i_exprs will be simple vars or
             * constants, except that i_expr1 may be arbitrary in the case
             * of MDEREF_INDEX_none.
             *
             * The bottom-most a_expr will be either:
             *   1) a simple var (so padXv or gv+rv2Xv);
             *   2) a simple scalar var dereferenced (e.g. $r->[0]):
             *      so a simple var with an extra rv2Xv;
             *   3) or an arbitrary expression.
             *
             * 'start', the first op in the execution chain, will point to
             *   1),2): the padXv or gv op;
             *   3):    the rv2Xv which forms the last op in the a_expr
             *          execution chain, and the top-most op in the a_expr
             *          subtree.
             *
             * For all cases, the 'start' node is no longer required,
             * but we can't free it since one or more external nodes
             * may point to it. E.g. consider
             *     $h{foo} = $a ? $b : $c
             * Here, both the op_next and op_other branches of the
             * cond_expr point to the gv[*h] of the hash expression, so
             * we can't free the 'start' op.
             *
             * For expr->[...], we need to save the subtree containing the
             * expression; for the other cases, we just need to save the
             * start node.
             * So in all cases, we null the start op and keep it around by
             * making it the child of the multideref op; for the expr->
             * case, the expr will be a subtree of the start node.
             *
             * So in the simple 1,2 case the  optree above changes to
             *
             *     ex-exists
             *       |
             *     multideref
             *       |
             *     ex-gv (or ex-padxv)
             *
             *  with the op_next chain being
             *
             *  -> ex-gv -> multideref -> op-following-ex-exists ->
             *
             *  In the 3 case, we have
             *
             *     ex-exists
             *       |
             *     multideref
             *       |
             *     ex-rv2xv
             *       |
             *    rest-of-a_expr
             *      subtree
             *
             *  and
             *
             *  -> rest-of-a_expr subtree ->
             *    ex-rv2xv -> multideref -> op-following-ex-exists ->
             *
             *
             * Where the last i_expr is non-simple (i.e. MDEREF_INDEX_none,
             * e.g. $a[0]{foo}[$x+1], the next rv2xv is nulled and the
             * multideref attached as the child, e.g.
             *
             *     exists
             *       |
             *     ex-aelem
             *       |
             *     ex-rv2av  - i_expr1
             *       |
             *     multideref
             *       |
             *     ex-whatever
             *
             */

            /* if we free this op, don't free the pad entry */
            if (reset_start_targ)
                start->op_targ = 0;


            /* Cut the bit we need to save out of the tree and attach to
             * the multideref op, then free the rest of the tree */

            /* find parent of node to be detached (for use by splice) */
            p = first_elem_op;
            if (   orig_action == MDEREF_AV_pop_rv2av_aelem
                || orig_action == MDEREF_HV_pop_rv2hv_helem)
            {
                /* there is an arbitrary expression preceding us, e.g.
                 * expr->[..]? so we need to save the 'expr' subtree */
                if (p->op_type == OP_EXISTS || p->op_type == OP_DELETE)
                    p = cUNOPx(p)->op_first;
                ASSUME(   start->op_type == OP_RV2AV
                       || start->op_type == OP_RV2HV);
            }
            else {
                /* either a padXv or rv2Xv+gv, maybe with an ex-Xelem
                 * above for exists/delete. */
                while (   (p->op_flags & OPf_KIDS)
                       && cUNOPx(p)->op_first != start
                )
                    p = cUNOPx(p)->op_first;
            }
            ASSUME(cUNOPx(p)->op_first == start);

            /* detach from main tree, and re-attach under the multideref */
            op_sibling_splice(mderef, NULL, 0,
                    op_sibling_splice(p, NULL, 1, NULL));
            op_null(start);

            start->op_next = mderef;

            mderef->op_next = index_skip == -1 ? o->op_next : o;

            /* excise and free the original tree, and replace with
             * the multideref op */
            p = op_sibling_splice(top_op, NULL, -1, mderef);
            while (p) {
                q = OpSIBLING(p);
                op_free(p);
                p = q;
            }
            op_null(top_op);
        }
        else {
            Size_t size = argi;

            if (maybe_aelemfast && action_count == 1)
                return;

            arg_buf = (UNOP_AUX_item*)PerlMemShared_malloc(
                                sizeof(UNOP_AUX_item) * (size + 1));
            /* for dumping etc: store the length in a hidden first slot;
             * we set the op_aux pointer to the second slot */
            arg_buf->uv = size;
            arg_buf++;
        }
    } /* for (pass = ...) */
}

/* See if the ops following o are such that o will always be executed in
 * boolean context: that is, the SV which o pushes onto the stack will
 * only ever be consumed by later ops via SvTRUE(sv) or similar.
 * If so, set a suitable private flag on o. Normally this will be
 * bool_flag; but see below why maybe_flag is needed too.
 *
 * Typically the two flags you pass will be the generic OPpTRUEBOOL and
 * OPpMAYBE_TRUEBOOL, buts it's possible that for some ops those bits may
 * already be taken, so you'll have to give that op two different flags.
 *
 * More explanation of 'maybe_flag' and 'safe_and' parameters.
 * The binary logical ops &&, ||, // (plus 'if' and 'unless' which use
 * those underlying ops) short-circuit, which means that rather than
 * necessarily returning a truth value, they may return the LH argument,
 * which may not be boolean. For example in $x = (keys %h || -1), keys
 * should return a key count rather than a boolean, even though its
 * sort-of being used in boolean context.
 *
 * So we only consider such logical ops to provide boolean context to
 * their LH argument if they themselves are in void or boolean context.
 * However, sometimes the context isn't known until run-time. In this
 * case the op is marked with the maybe_flag flag it.
 *
 * Consider the following.
 *
 *     sub f { ....;  if (%h) { .... } }
 *
 * This is actually compiled as
 *
 *     sub f { ....;  %h && do { .... } }
 *
 * Here we won't know until runtime whether the final statement (and hence
 * the &&) is in void context and so is safe to return a boolean value.
 * So mark o with maybe_flag rather than the bool_flag.
 * Note that there is cost associated with determining context at runtime
 * (e.g. a call to block_gimme()), so it may not be worth setting (at
 * compile time) and testing (at runtime) maybe_flag if the scalar verses
 * boolean costs savings are marginal.
 *
 * However, we can do slightly better with && (compared to || and //):
 * this op only returns its LH argument when that argument is false. In
 * this case, as long as the op promises to return a false value which is
 * valid in both boolean and scalar contexts, we can mark an op consumed
 * by && with bool_flag rather than maybe_flag.
 * For example as long as pp_padhv and pp_rv2hv return &PL_sv_zero rather
 * than &PL_sv_no for a false result in boolean context, then it's safe. An
 * op which promises to handle this case is indicated by setting safe_and
 * to true.
 */

static void
S_check_for_bool_cxt(OP*o, bool safe_and, U8 bool_flag, U8 maybe_flag)
{
    OP *lop;
    U8 flag = 0;

    assert((o->op_flags & OPf_WANT) == OPf_WANT_SCALAR);

    /* OPpTARGET_MY and boolean context probably don't mix well.
     * If someone finds a valid use case, maybe add an extra flag to this
     * function which indicates its safe to do so for this op? */
    assert(!(   (PL_opargs[o->op_type] & OA_TARGLEX)
             && (o->op_private & OPpTARGET_MY)));

    lop = o->op_next;

    while (lop) {
        switch (lop->op_type) {
        case OP_NULL:
        case OP_SCALAR:
            break;

        /* these two consume the stack argument in the scalar case,
         * and treat it as a boolean in the non linenumber case */
        case OP_FLIP:
        case OP_FLOP:
            if (   ((lop->op_flags & OPf_WANT) == OPf_WANT_LIST)
                || (lop->op_private & OPpFLIP_LINENUM))
            {
                lop = NULL;
                break;
            }
            /* FALLTHROUGH */
        /* these never leave the original value on the stack */
        case OP_NOT:
        case OP_XOR:
        case OP_COND_EXPR:
        case OP_GREPWHILE:
            flag = bool_flag;
            lop = NULL;
            break;

        /* OR DOR and AND evaluate their arg as a boolean, but then may
         * leave the original scalar value on the stack when following the
         * op_next route. If not in void context, we need to ensure
         * that whatever follows consumes the arg only in boolean context
         * too.
         */
        case OP_AND:
            if (safe_and) {
                flag = bool_flag;
                lop = NULL;
                break;
            }
            /* FALLTHROUGH */
        case OP_OR:
        case OP_DOR:
            if ((lop->op_flags & OPf_WANT) == OPf_WANT_VOID) {
                flag = bool_flag;
                lop = NULL;
            }
            else if (!(lop->op_flags & OPf_WANT)) {
                /* unknown context - decide at runtime */
                flag = maybe_flag;
                lop = NULL;
            }
            break;

        default:
            lop = NULL;
            break;
        }

        if (lop)
            lop = lop->op_next;
    }

    o->op_private |= flag;
}

/* mechanism for deferring recursion in rpeep() */

#define MAX_DEFERRED 4

#define DEFER(o) \
  STMT_START { \
    if (defer_ix == (MAX_DEFERRED-1)) { \
        OP **defer = defer_queue[defer_base]; \
        CALL_RPEEP(*defer); \
        op_prune_chain_head(defer); \
        defer_base = (defer_base + 1) % MAX_DEFERRED; \
        defer_ix--; \
    } \
    defer_queue[(defer_base + ++defer_ix) % MAX_DEFERRED] = &(o); \
  } STMT_END

#define IS_AND_OP(o)   (o->op_type == OP_AND)
#define IS_OR_OP(o)    (o->op_type == OP_OR)

/* A peephole optimizer.  We visit the ops in the order they're to execute.
 * See the comments at the top of this file for more details about when
 * peep() is called */

void
Perl_rpeep(pTHX_ OP *o)
{
    OP* oldop = NULL;
    OP* oldoldop = NULL;
    OP** defer_queue[MAX_DEFERRED] = { NULL }; /* small queue of deferred branches */
    int defer_base = 0;
    int defer_ix = -1;

    if (!o || o->op_opt)
        return;

    assert(o->op_type != OP_FREED);

    ENTER;
    SAVEOP();
    SAVEVPTR(PL_curcop);
    for (;; o = o->op_next) {
        if (o && o->op_opt)
            o = NULL;
        if (!o) {
            while (defer_ix >= 0) {
                OP **defer =
                        defer_queue[(defer_base + defer_ix--) % MAX_DEFERRED];
                CALL_RPEEP(*defer);
                op_prune_chain_head(defer);
            }
            break;
        }

      redo:

        /* oldoldop -> oldop -> o should be a chain of 3 adjacent ops */
        assert(!oldoldop || oldoldop->op_next == oldop);
        assert(!oldop    || oldop->op_next    == o);

        /* By default, this op has now been optimised. A couple of cases below
           clear this again.  */
        o->op_opt = 1;
        PL_op = o;

        /* look for a series of 1 or more aggregate derefs, e.g.
         *   $a[1]{foo}[$i]{$k}
         * and replace with a single OP_MULTIDEREF op.
         * Each index must be either a const, or a simple variable,
         *
         * First, look for likely combinations of starting ops,
         * corresponding to (global and lexical variants of)
         *     $a[...]   $h{...}
         *     $r->[...] $r->{...}
         *     (preceding expression)->[...]
         *     (preceding expression)->{...}
         * and if so, call maybe_multideref() to do a full inspection
         * of the op chain and if appropriate, replace with an
         * OP_MULTIDEREF
         */
        {
            UV action;
            OP *o2 = o;
            U8 hints = 0;

            switch (o2->op_type) {
            case OP_GV:
                /* $pkg[..]   :   gv[*pkg]
                 * $pkg->[...]:   gv[*pkg]; rv2sv sKM/DREFAV */

                /* Fail if there are new op flag combinations that we're
                 * not aware of, rather than:
                 *  * silently failing to optimise, or
                 *  * silently optimising the flag away.
                 * If this ASSUME starts failing, examine what new flag
                 * has been added to the op, and decide whether the
                 * optimisation should still occur with that flag, then
                 * update the code accordingly. This applies to all the
                 * other ASSUMEs in the block of code too.
                 */
                ASSUME(!(o2->op_flags &
                            ~(OPf_WANT|OPf_MOD|OPf_PARENS|OPf_SPECIAL)));
                ASSUME(!(o2->op_private & ~OPpEARLY_CV));

                o2 = o2->op_next;

                if (o2->op_type == OP_RV2AV) {
                    action = MDEREF_AV_gvav_aelem;
                    goto do_deref;
                }

                if (o2->op_type == OP_RV2HV) {
                    action = MDEREF_HV_gvhv_helem;
                    goto do_deref;
                }

                if (o2->op_type != OP_RV2SV)
                    break;

                /* at this point we've seen gv,rv2sv, so the only valid
                 * construct left is $pkg->[] or $pkg->{} */

                ASSUME(!(o2->op_flags & OPf_STACKED));
                if ((o2->op_flags & (OPf_WANT|OPf_REF|OPf_MOD|OPf_SPECIAL))
                            != (OPf_WANT_SCALAR|OPf_MOD))
                    break;

                ASSUME(!(o2->op_private & ~(OPpARG1_MASK|HINT_STRICT_REFS
                                    |OPpOUR_INTRO|OPpDEREF|OPpLVAL_INTRO)));
                if (o2->op_private & (OPpOUR_INTRO|OPpLVAL_INTRO))
                    break;
                if (   (o2->op_private & OPpDEREF) != OPpDEREF_AV
                    && (o2->op_private & OPpDEREF) != OPpDEREF_HV)
                    break;

                o2 = o2->op_next;
                if (o2->op_type == OP_RV2AV) {
                    action = MDEREF_AV_gvsv_vivify_rv2av_aelem;
                    goto do_deref;
                }
                if (o2->op_type == OP_RV2HV) {
                    action = MDEREF_HV_gvsv_vivify_rv2hv_helem;
                    goto do_deref;
                }
                break;

            case OP_PADSV:
                /* $lex->[...]: padsv[$lex] sM/DREFAV */

                ASSUME(!(o2->op_flags &
                    ~(OPf_WANT|OPf_PARENS|OPf_REF|OPf_MOD|OPf_SPECIAL)));
                if ((o2->op_flags &
                        (OPf_WANT|OPf_REF|OPf_MOD|OPf_SPECIAL))
                     != (OPf_WANT_SCALAR|OPf_MOD))
                    break;

                ASSUME(!(o2->op_private &
                                ~(OPpPAD_STATE|OPpDEREF|OPpLVAL_INTRO)));
                /* skip if state or intro, or not a deref */
                if (      o2->op_private != OPpDEREF_AV
                       && o2->op_private != OPpDEREF_HV)
                    break;

                o2 = o2->op_next;
                if (o2->op_type == OP_RV2AV) {
                    action = MDEREF_AV_padsv_vivify_rv2av_aelem;
                    goto do_deref;
                }
                if (o2->op_type == OP_RV2HV) {
                    action = MDEREF_HV_padsv_vivify_rv2hv_helem;
                    goto do_deref;
                }
                break;

            case OP_PADAV:
            case OP_PADHV:
                /*    $lex[..]:  padav[@lex:1,2] sR *
                 * or $lex{..}:  padhv[%lex:1,2] sR */
                ASSUME(!(o2->op_flags & ~(OPf_WANT|OPf_MOD|OPf_PARENS|
                                            OPf_REF|OPf_SPECIAL)));
                if ((o2->op_flags &
                        (OPf_WANT|OPf_REF|OPf_MOD|OPf_SPECIAL))
                     != (OPf_WANT_SCALAR|OPf_REF))
                    break;
                if (o2->op_flags != (OPf_WANT_SCALAR|OPf_REF))
                    break;
                /* OPf_PARENS isn't currently used in this case;
                 * if that changes, let us know! */
                ASSUME(!(o2->op_flags & OPf_PARENS));

                /* at this point, we wouldn't expect any of the remaining
                 * possible private flags:
                 * OPpPAD_STATE, OPpLVAL_INTRO, OPpTRUEBOOL,
                 * OPpMAYBE_TRUEBOOL, OPpMAYBE_LVSUB
                 *
                 * OPpSLICEWARNING shouldn't affect runtime
                 */
                ASSUME(!(o2->op_private & ~(OPpSLICEWARNING)));

                action = o2->op_type == OP_PADAV
                            ? MDEREF_AV_padav_aelem
                            : MDEREF_HV_padhv_helem;
                o2 = o2->op_next;
                S_maybe_multideref(aTHX_ o, o2, action, 0);
                break;


            case OP_RV2AV:
            case OP_RV2HV:
                action = o2->op_type == OP_RV2AV
                            ? MDEREF_AV_pop_rv2av_aelem
                            : MDEREF_HV_pop_rv2hv_helem;
                /* FALLTHROUGH */
            do_deref:
                /* (expr)->[...]:  rv2av sKR/1;
                 * (expr)->{...}:  rv2hv sKR/1; */

                ASSUME(o2->op_type == OP_RV2AV || o2->op_type == OP_RV2HV);

                ASSUME(!(o2->op_flags & ~(OPf_WANT|OPf_KIDS|OPf_PARENS
                                |OPf_REF|OPf_MOD|OPf_STACKED|OPf_SPECIAL)));
                if (o2->op_flags != (OPf_WANT_SCALAR|OPf_KIDS|OPf_REF))
                    break;

                /* at this point, we wouldn't expect any of these
                 * possible private flags:
                 * OPpMAYBE_LVSUB, OPpLVAL_INTRO
                 * OPpTRUEBOOL, OPpMAYBE_TRUEBOOL, (rv2hv only)
                 */
                ASSUME(!(o2->op_private &
                    ~(OPpHINT_STRICT_REFS|OPpARG1_MASK|OPpSLICEWARNING
                     |OPpOUR_INTRO)));
                hints |= (o2->op_private & OPpHINT_STRICT_REFS);

                o2 = o2->op_next;

                S_maybe_multideref(aTHX_ o, o2, action, hints);
                break;

            default:
                break;
            }
        }


        switch (o->op_type) {
        case OP_DBSTATE:
            PL_curcop = ((COP*)o);		/* for warnings */
            break;
        case OP_NEXTSTATE:
            PL_curcop = ((COP*)o);		/* for warnings */

            /* Optimise a "return ..." at the end of a sub to just be "...".
             * This saves 2 ops. Before:
             * 1  <;> nextstate(main 1 -e:1) v ->2
             * 4  <@> return K ->5
             * 2    <0> pushmark s ->3
             * -    <1> ex-rv2sv sK/1 ->4
             * 3      <#> gvsv[*cat] s ->4
             *
             * After:
             * -  <@> return K ->-
             * -    <0> pushmark s ->2
             * -    <1> ex-rv2sv sK/1 ->-
             * 2      <$> gvsv(*cat) s ->3
             */
            {
                OP *next = o->op_next;
                OP *sibling = OpSIBLING(o);
                if (   OP_TYPE_IS(next, OP_PUSHMARK)
                    && OP_TYPE_IS(sibling, OP_RETURN)
                    && OP_TYPE_IS(sibling->op_next, OP_LINESEQ)
                    && ( OP_TYPE_IS(sibling->op_next->op_next, OP_LEAVESUB)
                       ||OP_TYPE_IS(sibling->op_next->op_next,
                                    OP_LEAVESUBLV))
                    && cUNOPx(sibling)->op_first == next
                    && OpHAS_SIBLING(next) && OpSIBLING(next)->op_next
                    && next->op_next
                ) {
                    /* Look through the PUSHMARK's siblings for one that
                     * points to the RETURN */
                    OP *top = OpSIBLING(next);
                    while (top && top->op_next) {
                        if (top->op_next == sibling) {
                            top->op_next = sibling->op_next;
                            o->op_next = next->op_next;
                            break;
                        }
                        top = OpSIBLING(top);
                    }
                }
            }

            /* Optimise 'my $x; my $y;' into 'my ($x, $y);'
             *
             * This latter form is then suitable for conversion into padrange
             * later on. Convert:
             *
             *   nextstate1 -> padop1 -> nextstate2 -> padop2 -> nextstate3
             *
             * into:
             *
             *   nextstate1 ->     listop     -> nextstate3
             *                 /            \
             *         pushmark -> padop1 -> padop2
             */
            if (o->op_next && (
                    o->op_next->op_type == OP_PADSV
                 || o->op_next->op_type == OP_PADAV
                 || o->op_next->op_type == OP_PADHV
                )
                && !(o->op_next->op_private & ~OPpLVAL_INTRO)
                && o->op_next->op_next && o->op_next->op_next->op_type == OP_NEXTSTATE
                && o->op_next->op_next->op_next && (
                    o->op_next->op_next->op_next->op_type == OP_PADSV
                 || o->op_next->op_next->op_next->op_type == OP_PADAV
                 || o->op_next->op_next->op_next->op_type == OP_PADHV
                )
                && !(o->op_next->op_next->op_next->op_private & ~OPpLVAL_INTRO)
                && o->op_next->op_next->op_next->op_next && o->op_next->op_next->op_next->op_next->op_type == OP_NEXTSTATE
                && (!CopLABEL((COP*)o)) /* Don't mess with labels */
                && (!CopLABEL((COP*)o->op_next->op_next)) /* ... */
            ) {
                OP *pad1, *ns2, *pad2, *ns3, *newop, *newpm;

                pad1 =    o->op_next;
                ns2  = pad1->op_next;
                pad2 =  ns2->op_next;
                ns3  = pad2->op_next;

                /* we assume here that the op_next chain is the same as
                 * the op_sibling chain */
                assert(OpSIBLING(o)    == pad1);
                assert(OpSIBLING(pad1) == ns2);
                assert(OpSIBLING(ns2)  == pad2);
                assert(OpSIBLING(pad2) == ns3);

                /* excise and delete ns2 */
                op_sibling_splice(NULL, pad1, 1, NULL);
                op_free(ns2);

                /* excise pad1 and pad2 */
                op_sibling_splice(NULL, o, 2, NULL);

                /* create new listop, with children consisting of:
                 * a new pushmark, pad1, pad2. */
                newop = newLISTOP(OP_LIST, 0, pad1, pad2);
                newop->op_flags |= OPf_PARENS;
                newop->op_flags = (newop->op_flags & ~OPf_WANT) | OPf_WANT_VOID;

                /* insert newop between o and ns3 */
                op_sibling_splice(NULL, o, 0, newop);

                /*fixup op_next chain */
                newpm = cUNOPx(newop)->op_first; /* pushmark */
                o    ->op_next = newpm;
                newpm->op_next = pad1;
                pad1 ->op_next = pad2;
                pad2 ->op_next = newop; /* listop */
                newop->op_next = ns3;

                /* Ensure pushmark has this flag if padops do */
                if (pad1->op_flags & OPf_MOD && pad2->op_flags & OPf_MOD) {
                    newpm->op_flags |= OPf_MOD;
                }

                break;
            }

            /* Two NEXTSTATEs in a row serve no purpose. Except if they happen
               to carry two labels. For now, take the easier option, and skip
               this optimisation if the first NEXTSTATE has a label.
               Yves asked what about if they have different hints or features?
               Tony thinks that as we remove the first of the pair it should
               be fine.
            */
            if (!CopLABEL((COP*)o) && !PERLDB_NOOPT) {
                OP *nextop = o->op_next;
                while (nextop) {
                    switch (nextop->op_type) {
                        case OP_NULL:
                        case OP_SCALAR:
                        case OP_LINESEQ:
                        case OP_SCOPE:
                            nextop = nextop->op_next;
                            continue;
                    }
                    break;
                }

                if (nextop && (nextop->op_type == OP_NEXTSTATE)) {
                    op_null(o);
                    if (oldop)
                        oldop->op_next = nextop;
                    o = nextop;
                    /* Skip (old)oldop assignment since the current oldop's
                       op_next already points to the next op.  */
                    goto redo;
                }
            }
            break;

        case OP_CONCAT:
            if (o->op_next && o->op_next->op_type == OP_STRINGIFY) {
                if (o->op_next->op_private & OPpTARGET_MY) {
                    if (o->op_flags & OPf_STACKED) /* chained concats */
                        break; /* ignore_optimization */
                    else {
                        /* assert(PL_opargs[o->op_type] & OA_TARGLEX); */
                        o->op_targ = o->op_next->op_targ;
                        o->op_next->op_targ = 0;
                        o->op_private |= OPpTARGET_MY;
                    }
                }
                op_null(o->op_next);
            }
            break;
        case OP_STUB:
            if ((o->op_flags & OPf_WANT) != OPf_WANT_LIST) {
                break; /* Scalar stub must produce undef.  List stub is noop */
            }
            goto nothin;
        case OP_NULL:
            if (o->op_targ == OP_NEXTSTATE
                || o->op_targ == OP_DBSTATE)
            {
                PL_curcop = ((COP*)o);
            }
            /* XXX: We avoid setting op_seq here to prevent later calls
               to rpeep() from mistakenly concluding that optimisation
               has already occurred. This doesn't fix the real problem,
               though (See 20010220.007 (#5874)). AMS 20010719 */
            /* op_seq functionality is now replaced by op_opt */
            o->op_opt = 0;
            /* FALLTHROUGH */
        case OP_SCALAR:
        case OP_LINESEQ:
        case OP_SCOPE:
        nothin:
            if (oldop) {
                oldop->op_next = o->op_next;
                o->op_opt = 0;
                continue;
            }
            break;

        case OP_PUSHMARK:

            /* Given
                 5 repeat/DOLIST
                 3   ex-list
                 1     pushmark
                 2     scalar or const
                 4   const[0]
               convert repeat into a stub with no kids.
             */
            if (o->op_next->op_type == OP_CONST
             || (  o->op_next->op_type == OP_PADSV
                && !(o->op_next->op_private & OPpLVAL_INTRO))
             || (  o->op_next->op_type == OP_GV
                && o->op_next->op_next->op_type == OP_RV2SV
                && !(o->op_next->op_next->op_private
                        & (OPpLVAL_INTRO|OPpOUR_INTRO))))
            {
                const OP *kid = o->op_next->op_next;
                if (o->op_next->op_type == OP_GV)
                   kid = kid->op_next;
                /* kid is now the ex-list.  */
                if (kid->op_type == OP_NULL
                 && (kid = kid->op_next)->op_type == OP_CONST
                    /* kid is now the repeat count.  */
                 && kid->op_next->op_type == OP_REPEAT
                 && kid->op_next->op_private & OPpREPEAT_DOLIST
                 && (kid->op_next->op_flags & OPf_WANT) == OPf_WANT_LIST
                 && SvIOK(kSVOP_sv) && SvIVX(kSVOP_sv) == 0
                 && oldop)
                {
                    o = kid->op_next; /* repeat */
                    oldop->op_next = o;
                    op_free(cBINOPo->op_first);
                    op_free(cBINOPo->op_last );
                    o->op_flags &=~ OPf_KIDS;
                    /* stub is a baseop; repeat is a binop */
                    STATIC_ASSERT_STMT(sizeof(OP) <= sizeof(BINOP));
                    OpTYPE_set(o, OP_STUB);
                    o->op_private = 0;
                    break;
                }
            }

            /* Convert a series of PAD ops for my vars plus support into a
             * single padrange op. Basically
             *
             *    pushmark -> pad[ahs]v -> pad[ahs]?v -> ... -> (list) -> rest
             *
             * becomes, depending on circumstances, one of
             *
             *    padrange  ----------------------------------> (list) -> rest
             *    padrange  --------------------------------------------> rest
             *
             * where all the pad indexes are sequential and of the same type
             * (INTRO or not).
             * We convert the pushmark into a padrange op, then skip
             * any other pad ops, and possibly some trailing ops.
             * Note that we don't null() the skipped ops, to make it
             * easier for Deparse to undo this optimisation (and none of
             * the skipped ops are holding any resources). It also makes
             * it easier for find_uninit_var(), as it can just ignore
             * padrange, and examine the original pad ops.
             */
        {
            OP *p;
            OP *followop = NULL; /* the op that will follow the padrange op */
            U8 count = 0;
            U8 intro = 0;
            PADOFFSET base = 0; /* init only to stop compiler whining */
            bool gvoid = 0;     /* init only to stop compiler whining */
            bool defav = 0;  /* seen (...) = @_ */
            bool reuse = 0;  /* reuse an existing padrange op */

            /* look for a pushmark -> gv[_] -> rv2av */

            {
                OP *rv2av, *q;
                p = o->op_next;
                if (   p->op_type == OP_GV
                    && cGVOPx_gv(p) == PL_defgv
                    && (rv2av = p->op_next)
                    && rv2av->op_type == OP_RV2AV
                    && !(rv2av->op_flags & OPf_REF)
                    && !(rv2av->op_private & (OPpLVAL_INTRO|OPpMAYBE_LVSUB))
                    && ((rv2av->op_flags & OPf_WANT) == OPf_WANT_LIST)
                ) {
                    q = rv2av->op_next;
                    if (q->op_type == OP_NULL)
                        q = q->op_next;
                    if (q->op_type == OP_PUSHMARK) {
                        defav = 1;
                        p = q;
                    }
                }
            }
            if (!defav) {
                p = o;
            }

            /* scan for PAD ops */

            for (p = p->op_next; p; p = p->op_next) {
                if (p->op_type == OP_NULL)
                    continue;

                if ((     p->op_type != OP_PADSV
                       && p->op_type != OP_PADAV
                       && p->op_type != OP_PADHV
                    )
                      /* any private flag other than INTRO? e.g. STATE */
                   || (p->op_private & ~OPpLVAL_INTRO)
                )
                    break;

                /* let $a[N] potentially be optimised into AELEMFAST_LEX
                 * instead */
                if (   p->op_type == OP_PADAV
                    && p->op_next
                    && p->op_next->op_type == OP_CONST
                    && p->op_next->op_next
                    && p->op_next->op_next->op_type == OP_AELEM
                )
                    break;

                /* for 1st padop, note what type it is and the range
                 * start; for the others, check that it's the same type
                 * and that the targs are contiguous */
                if (count == 0) {
                    intro = (p->op_private & OPpLVAL_INTRO);
                    base = p->op_targ;
                    gvoid = OP_GIMME(p,0) == G_VOID;
                }
                else {
                    if ((p->op_private & OPpLVAL_INTRO) != intro)
                        break;
                    /* Note that you'd normally  expect targs to be
                     * contiguous in my($a,$b,$c), but that's not the case
                     * when external modules start doing things, e.g.
                     * Function::Parameters */
                    if (p->op_targ != base + count)
                        break;
                    assert(p->op_targ == base + count);
                    /* Either all the padops or none of the padops should
                       be in void context.  Since we only do the optimisa-
                       tion for av/hv when the aggregate itself is pushed
                       on to the stack (one item), there is no need to dis-
                       tinguish list from scalar context.  */
                    if (gvoid != (OP_GIMME(p,0) == G_VOID))
                        break;
                }

                /* for AV, HV, only when we're not flattening */
                if (   p->op_type != OP_PADSV
                    && !gvoid
                    && !(p->op_flags & OPf_REF)
                )
                    break;

                if (count >= OPpPADRANGE_COUNTMASK)
                    break;

                /* there's a biggest base we can fit into a
                 * SAVEt_CLEARPADRANGE in pp_padrange.
                 * (The sizeof() stuff will be constant-folded, and is
                 * intended to avoid getting "comparison is always false"
                 * compiler warnings. See the comments above
                 * MEM_WRAP_CHECK for more explanation on why we do this
                 * in a weird way to avoid compiler warnings.)
                 */
                if (   intro
                    && (8*sizeof(base) >
                        8*sizeof(UV)-OPpPADRANGE_COUNTSHIFT-SAVE_TIGHT_SHIFT
                        ? (Size_t)base
                        : (UV_MAX >> (OPpPADRANGE_COUNTSHIFT+SAVE_TIGHT_SHIFT))
                        ) >
                        (UV_MAX >> (OPpPADRANGE_COUNTSHIFT+SAVE_TIGHT_SHIFT))
                )
                    break;

                /* Success! We've got another valid pad op to optimise away */
                count++;
                followop = p->op_next;
            }

            if (count < 1 || (count == 1 && !defav))
                break;

            /* pp_padrange in specifically compile-time void context
             * skips pushing a mark and lexicals; in all other contexts
             * (including unknown till runtime) it pushes a mark and the
             * lexicals. We must be very careful then, that the ops we
             * optimise away would have exactly the same effect as the
             * padrange.
             * In particular in void context, we can only optimise to
             * a padrange if we see the complete sequence
             *     pushmark, pad*v, ...., list
             * which has the net effect of leaving the markstack as it
             * was.  Not pushing onto the stack (whereas padsv does touch
             * the stack) makes no difference in void context.
             */
            assert(followop);
            if (gvoid) {
                if (followop->op_type == OP_LIST
                        && OP_GIMME(followop,0) == G_VOID
                   )
                {
                    followop = followop->op_next; /* skip OP_LIST */

                    /* consolidate two successive my(...);'s */

                    if (   oldoldop
                        && oldoldop->op_type == OP_PADRANGE
                        && (oldoldop->op_flags & OPf_WANT) == OPf_WANT_VOID
                        && (oldoldop->op_private & OPpLVAL_INTRO) == intro
                        && !(oldoldop->op_flags & OPf_SPECIAL)
                    ) {
                        U8 old_count;
                        assert(oldoldop->op_next == oldop);
                        assert(   oldop->op_type == OP_NEXTSTATE
                               || oldop->op_type == OP_DBSTATE);
                        assert(oldop->op_next == o);

                        old_count
                            = (oldoldop->op_private & OPpPADRANGE_COUNTMASK);

                       /* Do not assume pad offsets for $c and $d are con-
                          tiguous in
                            my ($a,$b,$c);
                            my ($d,$e,$f);
                        */
                        if (  oldoldop->op_targ + old_count == base
                           && old_count < OPpPADRANGE_COUNTMASK - count) {
                            base = oldoldop->op_targ;
                            count += old_count;
                            reuse = 1;
                        }
                    }

                    /* if there's any immediately following singleton
                     * my var's; then swallow them and the associated
                     * nextstates; i.e.
                     *    my ($a,$b); my $c; my $d;
                     * is treated as
                     *    my ($a,$b,$c,$d);
                     */

                    while (    ((p = followop->op_next))
                            && (  p->op_type == OP_PADSV
                               || p->op_type == OP_PADAV
                               || p->op_type == OP_PADHV)
                            && (p->op_flags & OPf_WANT) == OPf_WANT_VOID
                            && (p->op_private & OPpLVAL_INTRO) == intro
                            && !(p->op_private & ~OPpLVAL_INTRO)
                            && p->op_next
                            && (   p->op_next->op_type == OP_NEXTSTATE
                                || p->op_next->op_type == OP_DBSTATE)
                            && count < OPpPADRANGE_COUNTMASK
                            && base + count == p->op_targ
                    ) {
                        count++;
                        followop = p->op_next;
                    }
                }
                else
                    break;
            }

            if (reuse) {
                assert(oldoldop->op_type == OP_PADRANGE);
                oldoldop->op_next = followop;
                oldoldop->op_private = (intro | count);
                o = oldoldop;
                oldop = NULL;
                oldoldop = NULL;
            }
            else {
                /* Convert the pushmark into a padrange.
                 * To make Deparse easier, we guarantee that a padrange was
                 * *always* formerly a pushmark */
                assert(o->op_type == OP_PUSHMARK);
                o->op_next = followop;
                OpTYPE_set(o, OP_PADRANGE);
                o->op_targ = base;
                /* bit 7: INTRO; bit 6..0: count */
                o->op_private = (intro | count);
                o->op_flags = ((o->op_flags & ~(OPf_WANT|OPf_SPECIAL))
                              | gvoid * OPf_WANT_VOID
                              | (defav ? OPf_SPECIAL : 0));
            }
            break;
        }

        case OP_RV2AV:
            if ((o->op_flags & OPf_WANT) == OPf_WANT_SCALAR)
                S_check_for_bool_cxt(o, 1, OPpTRUEBOOL, 0);
            break;

        case OP_RV2HV:
        case OP_PADHV:
            /*'keys %h' in void or scalar context: skip the OP_KEYS
             * and perform the functionality directly in the RV2HV/PADHV
             * op
             */
            if (o->op_flags & OPf_REF) {
                OP *k = o->op_next;
                U8 want = (k->op_flags & OPf_WANT);
                if (   k
                    && k->op_type == OP_KEYS
                    && (   want == OPf_WANT_VOID
                        || want == OPf_WANT_SCALAR)
                    && !(k->op_private & OPpMAYBE_LVSUB)
                    && !(k->op_flags & OPf_MOD)
                ) {
                    o->op_next     = k->op_next;
                    o->op_flags   &= ~(OPf_REF|OPf_WANT);
                    o->op_flags   |= want;
                    o->op_private |= (o->op_type == OP_PADHV ?
                                      OPpPADHV_ISKEYS : OPpRV2HV_ISKEYS);
                    /* for keys(%lex), hold onto the OP_KEYS's targ
                     * since padhv doesn't have its own targ to return
                     * an int with */
                    if (!(o->op_type ==OP_PADHV && want == OPf_WANT_SCALAR))
                        op_null(k);
                }
            }

            /* see if %h is used in boolean context */
            if ((o->op_flags & OPf_WANT) == OPf_WANT_SCALAR)
                S_check_for_bool_cxt(o, 1, OPpTRUEBOOL, OPpMAYBE_TRUEBOOL);


            if (o->op_type != OP_PADHV)
                break;
            /* FALLTHROUGH */
        case OP_PADAV:
            if (   o->op_type == OP_PADAV
                && (o->op_flags & OPf_WANT) == OPf_WANT_SCALAR
            )
                S_check_for_bool_cxt(o, 1, OPpTRUEBOOL, 0);
            /* FALLTHROUGH */
        case OP_PADSV:
            /* Skip over state($x) in void context.  */
            if (oldop && o->op_private == (OPpPAD_STATE|OPpLVAL_INTRO)
             && (o->op_flags & OPf_WANT) == OPf_WANT_VOID)
            {
                oldop->op_next = o->op_next;
                goto redo_nextstate;
            }
            if (o->op_type != OP_PADAV)
                break;
            /* FALLTHROUGH */
        case OP_GV:
            if (o->op_type == OP_PADAV || o->op_next->op_type == OP_RV2AV) {
                OP* const pop = (o->op_type == OP_PADAV) ?
                            o->op_next : o->op_next->op_next;
                IV i;
                if (pop && pop->op_type == OP_CONST &&
                    ((PL_op = pop->op_next)) &&
                    pop->op_next->op_type == OP_AELEM &&
                    !(pop->op_next->op_private &
                      (OPpLVAL_INTRO|OPpLVAL_DEFER|OPpDEREF|OPpMAYBE_LVSUB)) &&
                    (i = SvIV(cSVOPx(pop)->op_sv)) >= -128 && i <= 127)
                {
                    GV *gv;
                    if (cSVOPx(pop)->op_private & OPpCONST_STRICT)
                        no_bareword_allowed(pop);
                    if (o->op_type == OP_GV)
                        op_null(o->op_next);
                    op_null(pop->op_next);
                    op_null(pop);
                    o->op_flags |= pop->op_next->op_flags & OPf_MOD;
                    o->op_next = pop->op_next->op_next;
                    o->op_ppaddr = PL_ppaddr[OP_AELEMFAST];
                    o->op_private = (U8)i;
                    if (o->op_type == OP_GV) {
                        gv = cGVOPo_gv;
                        GvAVn(gv);
                        o->op_type = OP_AELEMFAST;
                    }
                    else
                        o->op_type = OP_AELEMFAST_LEX;
                }
                if (o->op_type != OP_GV)
                    break;
            }

            /* Remove $foo from the op_next chain in void context.  */
            if (oldop
             && (  o->op_next->op_type == OP_RV2SV
                || o->op_next->op_type == OP_RV2AV
                || o->op_next->op_type == OP_RV2HV  )
             && (o->op_next->op_flags & OPf_WANT) == OPf_WANT_VOID
             && !(o->op_next->op_private & OPpLVAL_INTRO))
            {
                oldop->op_next = o->op_next->op_next;
                /* Reprocess the previous op if it is a nextstate, to
                   allow double-nextstate optimisation.  */
              redo_nextstate:
                if (oldop->op_type == OP_NEXTSTATE) {
                    oldop->op_opt = 0;
                    o = oldop;
                    oldop = oldoldop;
                    oldoldop = NULL;
                    goto redo;
                }
                o = oldop->op_next;
                goto redo;
            }
            else if (o->op_next->op_type == OP_RV2SV) {
                if (!(o->op_next->op_private & OPpDEREF)) {
                    op_null(o->op_next);
                    o->op_private |= o->op_next->op_private & (OPpLVAL_INTRO
                                                               | OPpOUR_INTRO);
                    o->op_next = o->op_next->op_next;
                    OpTYPE_set(o, OP_GVSV);
                }
            }
            else if (o->op_next->op_type == OP_READLINE
                    && o->op_next->op_next->op_type == OP_CONCAT
                    && (o->op_next->op_next->op_flags & OPf_STACKED))
            {
                /* Turn "$a .= <FH>" into an OP_RCATLINE. AMS 20010917 */
                OpTYPE_set(o, OP_RCATLINE);
                o->op_flags |= OPf_STACKED;
                op_null(o->op_next->op_next);
                op_null(o->op_next);
            }

            break;

        case OP_NOT:
            break;

        case OP_AND:
        case OP_OR:
        case OP_DOR:
        case OP_CMPCHAIN_AND:
        case OP_PUSHDEFER:
            while (cLOGOP->op_other->op_type == OP_NULL)
                cLOGOP->op_other = cLOGOP->op_other->op_next;
            while (o->op_next && (   o->op_type == o->op_next->op_type
                                  || o->op_next->op_type == OP_NULL))
                o->op_next = o->op_next->op_next;

            /* If we're an OR and our next is an AND in void context, we'll
               follow its op_other on short circuit, same for reverse.
               We can't do this with OP_DOR since if it's true, its return
               value is the underlying value which must be evaluated
               by the next op. */
            if (o->op_next &&
                (
                    (IS_AND_OP(o) && IS_OR_OP(o->op_next))
                 || (IS_OR_OP(o) && IS_AND_OP(o->op_next))
                )
                && (o->op_next->op_flags & OPf_WANT) == OPf_WANT_VOID
            ) {
                o->op_next = cLOGOPx(o->op_next)->op_other;
            }
            DEFER(cLOGOP->op_other);
            o->op_opt = 1;
            break;

        case OP_GREPWHILE:
            if ((o->op_flags & OPf_WANT) == OPf_WANT_SCALAR)
                S_check_for_bool_cxt(o, 1, OPpTRUEBOOL, 0);
            /* FALLTHROUGH */
        case OP_COND_EXPR:
        case OP_MAPWHILE:
        case OP_ANDASSIGN:
        case OP_ORASSIGN:
        case OP_DORASSIGN:
        case OP_RANGE:
        case OP_ONCE:
        case OP_ARGDEFELEM:
            while (cLOGOP->op_other->op_type == OP_NULL)
                cLOGOP->op_other = cLOGOP->op_other->op_next;
            DEFER(cLOGOP->op_other);
            break;

        case OP_ENTERLOOP:
        case OP_ENTERITER:
            while (cLOOP->op_redoop->op_type == OP_NULL)
                cLOOP->op_redoop = cLOOP->op_redoop->op_next;
            while (cLOOP->op_nextop->op_type == OP_NULL)
                cLOOP->op_nextop = cLOOP->op_nextop->op_next;
            while (cLOOP->op_lastop->op_type == OP_NULL)
                cLOOP->op_lastop = cLOOP->op_lastop->op_next;
            /* a while(1) loop doesn't have an op_next that escapes the
             * loop, so we have to explicitly follow the op_lastop to
             * process the rest of the code */
            DEFER(cLOOP->op_lastop);
            break;

        case OP_ENTERTRY:
            assert(cLOGOPo->op_other->op_type == OP_LEAVETRY);
            DEFER(cLOGOPo->op_other);
            break;

        case OP_ENTERTRYCATCH:
            assert(cLOGOPo->op_other->op_type == OP_CATCH);
            /* catch body is the ->op_other of the OP_CATCH */
            DEFER(cLOGOPx(cLOGOPo->op_other)->op_other);
            break;

        case OP_SUBST:
            if ((o->op_flags & OPf_WANT) == OPf_WANT_SCALAR)
                S_check_for_bool_cxt(o, 1, OPpTRUEBOOL, 0);
            assert(!(cPMOP->op_pmflags & PMf_ONCE));
            while (cPMOP->op_pmstashstartu.op_pmreplstart &&
                   cPMOP->op_pmstashstartu.op_pmreplstart->op_type == OP_NULL)
                cPMOP->op_pmstashstartu.op_pmreplstart
                    = cPMOP->op_pmstashstartu.op_pmreplstart->op_next;
            DEFER(cPMOP->op_pmstashstartu.op_pmreplstart);
            break;

        case OP_SORT: {
            OP *oright;

            if (o->op_flags & OPf_SPECIAL) {
                /* first arg is a code block */
                OP * const nullop = OpSIBLING(cLISTOP->op_first);
                OP * kid          = cUNOPx(nullop)->op_first;

                assert(nullop->op_type == OP_NULL);
                assert(kid->op_type == OP_SCOPE
                 || (kid->op_type == OP_NULL && kid->op_targ == OP_LEAVE));
                /* since OP_SORT doesn't have a handy op_other-style
                 * field that can point directly to the start of the code
                 * block, store it in the otherwise-unused op_next field
                 * of the top-level OP_NULL. This will be quicker at
                 * run-time, and it will also allow us to remove leading
                 * OP_NULLs by just messing with op_nexts without
                 * altering the basic op_first/op_sibling layout. */
                kid = kLISTOP->op_first;
                assert(
                      (kid->op_type == OP_NULL
                      && (  kid->op_targ == OP_NEXTSTATE
                         || kid->op_targ == OP_DBSTATE  ))
                    || kid->op_type == OP_STUB
                    || kid->op_type == OP_ENTER
                    || (PL_parser && PL_parser->error_count));
                nullop->op_next = kid->op_next;
                DEFER(nullop->op_next);
            }

            /* check that RHS of sort is a single plain array */
            oright = cUNOPo->op_first;
            if (!oright || oright->op_type != OP_PUSHMARK)
                break;

            if (o->op_private & OPpSORT_INPLACE)
                break;

            /* reverse sort ... can be optimised.  */
            if (!OpHAS_SIBLING(cUNOPo)) {
                /* Nothing follows us on the list. */
                OP * const reverse = o->op_next;

                if (reverse->op_type == OP_REVERSE &&
                    (reverse->op_flags & OPf_WANT) == OPf_WANT_LIST) {
                    OP * const pushmark = cUNOPx(reverse)->op_first;
                    if (pushmark && (pushmark->op_type == OP_PUSHMARK)
                        && (OpSIBLING(cUNOPx(pushmark)) == o)) {
                        /* reverse -> pushmark -> sort */
                        o->op_private |= OPpSORT_REVERSE;
                        op_null(reverse);
                        pushmark->op_next = oright->op_next;
                        op_null(oright);
                    }
                }
            }

            break;
        }

        case OP_REVERSE: {
            OP *ourmark, *theirmark, *ourlast, *iter, *expushmark, *rv2av;
            OP *gvop = NULL;
            LISTOP *enter, *exlist;

            if (o->op_private & OPpSORT_INPLACE)
                break;

            enter = cLISTOPx(o->op_next);
            if (!enter)
                break;
            if (enter->op_type == OP_NULL) {
                enter = cLISTOPx(enter->op_next);
                if (!enter)
                    break;
            }
            /* for $a (...) will have OP_GV then OP_RV2GV here.
               for (...) just has an OP_GV.  */
            if (enter->op_type == OP_GV) {
                gvop = (OP *) enter;
                enter = cLISTOPx(enter->op_next);
                if (!enter)
                    break;
                if (enter->op_type == OP_RV2GV) {
                  enter = cLISTOPx(enter->op_next);
                  if (!enter)
                    break;
                }
            }

            if (enter->op_type != OP_ENTERITER)
                break;

            iter = enter->op_next;
            if (!iter || iter->op_type != OP_ITER)
                break;

            expushmark = enter->op_first;
            if (!expushmark || expushmark->op_type != OP_NULL
                || expushmark->op_targ != OP_PUSHMARK)
                break;

            exlist = cLISTOPx(OpSIBLING(expushmark));
            if (!exlist || exlist->op_type != OP_NULL
                || exlist->op_targ != OP_LIST)
                break;

            if (exlist->op_last != o) {
                /* Mmm. Was expecting to point back to this op.  */
                break;
            }
            theirmark = exlist->op_first;
            if (!theirmark || theirmark->op_type != OP_PUSHMARK)
                break;

            if (OpSIBLING(theirmark) != o) {
                /* There's something between the mark and the reverse, eg
                   for (1, reverse (...))
                   so no go.  */
                break;
            }

            ourmark = cLISTOPo->op_first;
            if (!ourmark || ourmark->op_type != OP_PUSHMARK)
                break;

            ourlast = cLISTOPo->op_last;
            if (!ourlast || ourlast->op_next != o)
                break;

            rv2av = OpSIBLING(ourmark);
            if (rv2av && rv2av->op_type == OP_RV2AV && !OpHAS_SIBLING(rv2av)
                && rv2av->op_flags == (OPf_WANT_LIST | OPf_KIDS)) {
                /* We're just reversing a single array.  */
                rv2av->op_flags = OPf_WANT_SCALAR | OPf_KIDS | OPf_REF;
                enter->op_flags |= OPf_STACKED;
            }

            /* We don't have control over who points to theirmark, so sacrifice
               ours.  */
            theirmark->op_next = ourmark->op_next;
            theirmark->op_flags = ourmark->op_flags;
            ourlast->op_next = gvop ? gvop : (OP *) enter;
            op_null(ourmark);
            op_null(o);
            enter->op_private |= OPpITER_REVERSED;
            iter->op_private |= OPpITER_REVERSED;

            oldoldop = NULL;
            oldop    = ourlast;
            o        = oldop->op_next;
            goto redo;
            NOT_REACHED; /* NOTREACHED */
            break;
        }

        case OP_UNDEF:
            if ((o->op_flags & OPf_KIDS) &&
                (cUNOPx(o)->op_first->op_type == OP_PADSV)) {

                /* Convert:
                 *     undef
                 *       padsv[$x]
                 * to:
                 *     undef[$x]
                 */

                OP * padsv = cUNOPx(o)->op_first;
                o->op_private = OPpTARGET_MY |
                        (padsv->op_private & (OPpLVAL_INTRO|OPpPAD_STATE));
                o->op_targ = padsv->op_targ; padsv->op_targ = 0;
                op_null(padsv);
                /* Optimizer does NOT seem to fix up the padsv op_next ptr */
                if (oldoldop)
                    oldoldop->op_next = o;
                oldop = oldoldop;
                oldoldop = NULL;

            } else if (o->op_next->op_type == OP_PADSV) {
                OP * padsv = o->op_next;
                OP * sassign = (padsv->op_next &&
                        padsv->op_next->op_type == OP_SASSIGN) ?
                        padsv->op_next : NULL;
                if (sassign && cBINOPx(sassign)->op_first == o) {
                    /* Convert:
                     *     sassign
                     *       undef
                     *       padsv[$x]
                     * to:
                     *     undef[$x]
                     * NOTE: undef does not have the "T" flag set in
                     *       regen/opcodes, as this would cause
                     *       S_maybe_targlex to do the optimization.
                     *       Seems easier to keep it all here, rather
                     *       than have an undef-specific branch in
                     *       S_maybe_targlex just to add the
                     *       OPpUNDEF_KEEP_PV flag.
                     */
                     o->op_private = OPpTARGET_MY | OPpUNDEF_KEEP_PV |
                         (padsv->op_private & (OPpLVAL_INTRO|OPpPAD_STATE));
                     o->op_targ = padsv->op_targ; padsv->op_targ = 0;
                     op_null(padsv);
                     op_null(sassign);
                     /* Optimizer DOES seems to fix up the op_next ptrs */
                }
            }
            break;

        case OP_QR:
        case OP_MATCH:
            if (!(cPMOP->op_pmflags & PMf_ONCE)) {
                assert (!cPMOP->op_pmstashstartu.op_pmreplstart);
            }
            break;

        case OP_RUNCV:
            if (!(o->op_private & OPpOFFBYONE) && !CvCLONE(PL_compcv)
             && (!CvANON(PL_compcv) || (!PL_cv_has_eval && !PL_perldb)))
            {
                SV *sv;
                if (CvEVAL(PL_compcv)) sv = &PL_sv_undef;
                else {
                    sv = newRV((SV *)PL_compcv);
                    sv_rvweaken(sv);
                    SvREADONLY_on(sv);
                }
                OpTYPE_set(o, OP_CONST);
                o->op_flags |= OPf_SPECIAL;
                cSVOPo->op_sv = sv;
            }
            break;

        case OP_SASSIGN: {
            if (OP_GIMME(o,0) == G_VOID
             || (  o->op_next->op_type == OP_LINESEQ
                && (  o->op_next->op_next->op_type == OP_LEAVESUB
                   || (  o->op_next->op_next->op_type == OP_RETURN
                      && !CvLVALUE(PL_compcv)))))
            {
                OP *right = cBINOP->op_first;
                if (right) {
                    /*   sassign
                    *      RIGHT
                    *      substr
                    *         pushmark
                    *         arg1
                    *         arg2
                    *         ...
                    * becomes
                    *
                    *  ex-sassign
                    *     substr
                    *        pushmark
                    *        RIGHT
                    *        arg1
                    *        arg2
                    *        ...
                    */
                    OP *left = OpSIBLING(right);
                    if (left->op_type == OP_SUBSTR
                         && (left->op_private & 7) < 4) {
                        op_null(o);
                        /* cut out right */
                        op_sibling_splice(o, NULL, 1, NULL);
                        /* and insert it as second child of OP_SUBSTR */
                        op_sibling_splice(left, cBINOPx(left)->op_first, 0,
                                    right);
                        left->op_private |= OPpSUBSTR_REPL_FIRST;
                        left->op_flags =
                            (o->op_flags & ~OPf_WANT) | OPf_WANT_VOID;
                    }
                }
            }
            OP* rhs = cBINOPx(o)->op_first;
            OP* lval = cBINOPx(o)->op_last;

            /* Combine a simple SASSIGN OP with a PADSV lvalue child OP
             * into a single OP. */

            /* This optimization covers arbitrarily complicated RHS OP
             * trees. Separate optimizations may exist for specific,
             * single RHS OPs, such as:
             * "my $foo = undef;" or "my $bar = $other_padsv;" */

            if (!(o->op_private & (OPpASSIGN_BACKWARDS|OPpASSIGN_CV_TO_GV))
                 && lval && (lval->op_type == OP_PADSV) &&
                !(lval->op_private & OPpDEREF)
                 /* skip if padrange has already gazumped the padsv */
                 && (lval == oldop)
                 /* Memoize::Once produces a non-standard SASSIGN that
                  * doesn't actually point to pp_sassign, has only one
                  * child (PADSV), and gets to it via op_other rather
                  * than op_next. Don't try to optimize this. */
                 && (lval != rhs)
                 /* For efficiency, pp_padsv_store() doesn't push its
                  * result onto the stack. For the relatively rare case of
                  * the $lex assignment not in void context, we just do it
                  * the old slow way. */
                 && OP_GIMME(o,0) == G_VOID
               ) {
                /* SASSIGN's bitfield flags, such as op_moresib and
                 * op_slabbed, will be carried over unchanged. */
                OpTYPE_set(o, OP_PADSV_STORE);

                /* Explicitly craft the new OP's op_flags, carrying
                 * some bits over from the SASSIGN */
                o->op_flags = (
                    OPf_KIDS | OPf_STACKED |
                    (o->op_flags & (OPf_WANT|OPf_PARENS))
                );

                /* Reset op_private flags, taking relevant private flags
                 * from the PADSV */
                o->op_private = (lval->op_private &
                                (OPpLVAL_INTRO|OPpPAD_STATE|OPpDEREF));

                /* Steal the targ from the PADSV */
                o->op_targ = lval->op_targ; lval->op_targ = 0;

                /* Fixup op_next ptrs */
                assert(oldop->op_type == OP_PADSV);
                /* oldoldop can be arbitrarily deep in the RHS OP tree */
                oldoldop->op_next = o;

                /* Even when (rhs != oldoldop), rhs might still have a
                 * relevant op_next ptr to lval. This is definitely true
                 * when rhs is OP_NULL with a LOGOP kid (e.g. orassign).
                 * There may be other cases. */
                if (rhs->op_next == lval)
                    rhs->op_next = o;

                /* Now null-out the PADSV */
                op_null(lval);

                /* NULL the previous op ptrs, so rpeep can continue */
                oldoldop = NULL; oldop = NULL;
            }

            /* Combine a simple SASSIGN OP with an AELEMFAST_LEX lvalue
             * into a single OP. This optimization covers arbitrarily
             * complicated RHS OP trees. */

            if (!(o->op_private & (OPpASSIGN_BACKWARDS|OPpASSIGN_CV_TO_GV))
                && (lval->op_type == OP_NULL) && (lval->op_private == 2) &&
                (cBINOPx(lval)->op_first->op_type == OP_AELEMFAST_LEX)
                 /* For efficiency, pp_aelemfastlex_store() doesn't push its
                  * result onto the stack. For the relatively rare case of
                  * the array assignment not in void context, we just do it
                  * the old slow way. */
                 && OP_GIMME(o,0) == G_VOID
            ) {
                OP * lex = cBINOPx(lval)->op_first;
                /* SASSIGN's bitfield flags, such as op_moresib and
                 * op_slabbed, will be carried over unchanged. */
                OpTYPE_set(o, OP_AELEMFASTLEX_STORE);

                /* Explicitly craft the new OP's op_flags, carrying
                 * some bits over from the SASSIGN */
                o->op_flags = (
                    OPf_KIDS | OPf_STACKED |
                    (o->op_flags & (OPf_WANT|OPf_PARENS))
                );

                /* Copy the AELEMFAST_LEX op->private, which contains
                 * the key index. */
                o->op_private = lex->op_private;

                /* Take the targ from the AELEMFAST_LEX */
                o->op_targ = lex->op_targ; lex->op_targ = 0;

                assert(oldop->op_type == OP_AELEMFAST_LEX);
                /* oldoldop can be arbitrarily deep in the RHS OP tree */
                oldoldop->op_next = o;

                /* Even when (rhs != oldoldop), rhs might still have a
                 * relevant op_next ptr to lex. (Updating it here can
                 * also cause other ops in the RHS to get the desired
                 * op_next pointer, presumably thanks to the finalizer.)
                 * This is definitely truewhen rhs is OP_NULL with a
                 * LOGOP kid (e.g. orassign). There may be other cases. */
                if (rhs->op_next == lex)
                    rhs->op_next = o;

                /* Now null-out the AELEMFAST_LEX */
                op_null(lex);

                /* NULL the previous op ptrs, so rpeep can continue */
                oldop = oldoldop; oldoldop = NULL;
            }

            break;
        }

        case OP_AASSIGN: {
            int l, r, lr, lscalars, rscalars;

            /* handle common vars detection, e.g. ($a,$b) = ($b,$a).
               Note that we do this now rather than in newASSIGNOP(),
               since only by now are aliased lexicals flagged as such

               See the essay "Common vars in list assignment" above for
               the full details of the rationale behind all the conditions
               below.

               PL_generation sorcery:
               To detect whether there are common vars, the global var
               PL_generation is incremented for each assign op we scan.
               Then we run through all the lexical variables on the LHS,
               of the assignment, setting a spare slot in each of them to
               PL_generation.  Then we scan the RHS, and if any lexicals
               already have that value, we know we've got commonality.
               Also, if the generation number is already set to
               PERL_INT_MAX, then the variable is involved in aliasing, so
               we also have potential commonality in that case.
             */

            PL_generation++;
            /* scan LHS */
            lscalars = 0;
            l = S_aassign_scan(aTHX_ cLISTOPo->op_last,  FALSE, &lscalars);
            /* scan RHS */
            rscalars = 0;
            r = S_aassign_scan(aTHX_ cLISTOPo->op_first, TRUE, &rscalars);
            lr = (l|r);


            /* After looking for things which are *always* safe, this main
             * if/else chain selects primarily based on the type of the
             * LHS, gradually working its way down from the more dangerous
             * to the more restrictive and thus safer cases */

            if (   !l                      /* () = ....; */
                || !r                      /* .... = (); */
                || !(l & ~AAS_SAFE_SCALAR) /* (undef, pos()) = ...; */
                || !(r & ~AAS_SAFE_SCALAR) /* ... = (1,2,length,undef); */
                || (lscalars < 2)          /* (undef, $x) = ... */
            ) {
                NOOP; /* always safe */
            }
            else if (l & AAS_DANGEROUS) {
                /* always dangerous */
                o->op_private |= OPpASSIGN_COMMON_SCALAR;
                o->op_private |= OPpASSIGN_COMMON_AGG;
            }
            else if (l & (AAS_PKG_SCALAR|AAS_PKG_AGG)) {
                /* package vars are always dangerous - too many
                 * aliasing possibilities */
                if (l & AAS_PKG_SCALAR)
                    o->op_private |= OPpASSIGN_COMMON_SCALAR;
                if (l & AAS_PKG_AGG)
                    o->op_private |= OPpASSIGN_COMMON_AGG;
            }
            else if (l & ( AAS_MY_SCALAR|AAS_MY_AGG
                          |AAS_LEX_SCALAR|AAS_LEX_AGG))
            {
                /* LHS contains only lexicals and safe ops */

                if (l & (AAS_MY_AGG|AAS_LEX_AGG))
                    o->op_private |= OPpASSIGN_COMMON_AGG;

                if (l & (AAS_MY_SCALAR|AAS_LEX_SCALAR)) {
                    if (lr & AAS_LEX_SCALAR_COMM)
                        o->op_private |= OPpASSIGN_COMMON_SCALAR;
                    else if (   !(l & AAS_LEX_SCALAR)
                             && (r & AAS_DEFAV))
                    {
                        /* falsely mark
                         *    my (...) = @_
                         * as scalar-safe for performance reasons.
                         * (it will still have been marked _AGG if necessary */
                        NOOP;
                    }
                    else if (r  & (AAS_PKG_SCALAR|AAS_PKG_AGG|AAS_DANGEROUS))
                        /* if there are only lexicals on the LHS and no
                         * common ones on the RHS, then we assume that the
                         * only way those lexicals could also get
                         * on the RHS is via some sort of dereffing or
                         * closure, e.g.
                         *    $r = \$lex;
                         *    ($lex, $x) = (1, $$r)
                         * and in this case we assume the var must have
                         *  a bumped ref count. So if its ref count is 1,
                         *  it must only be on the LHS.
                         */
                        o->op_private |= OPpASSIGN_COMMON_RC1;
                }
            }

            /* ... = ($x)
             * may have to handle aggregate on LHS, but we can't
             * have common scalars. */
            if (rscalars < 2)
                o->op_private &=
                        ~(OPpASSIGN_COMMON_SCALAR|OPpASSIGN_COMMON_RC1);

            if ((o->op_flags & OPf_WANT) == OPf_WANT_SCALAR)
                S_check_for_bool_cxt(o, 1, OPpASSIGN_TRUEBOOL, 0);
            break;
        }

        case OP_REF:
        case OP_BLESSED:
            /* if the op is used in boolean context, set the TRUEBOOL flag
             * which enables an optimisation at runtime which avoids creating
             * a stack temporary for known-true package names */
            if ((o->op_flags & OPf_WANT) == OPf_WANT_SCALAR)
                S_check_for_bool_cxt(o, 1, OPpTRUEBOOL, OPpMAYBE_TRUEBOOL);
            break;

        case OP_LENGTH:
            /* see if the op is used in known boolean context,
             * but not if OA_TARGLEX optimisation is enabled */
            if (   (o->op_flags & OPf_WANT) == OPf_WANT_SCALAR
                && !(o->op_private & OPpTARGET_MY)
            )
                S_check_for_bool_cxt(o, 1, OPpTRUEBOOL, 0);
            break;

        case OP_POS:
            /* see if the op is used in known boolean context */
            if ((o->op_flags & OPf_WANT) == OPf_WANT_SCALAR)
                S_check_for_bool_cxt(o, 1, OPpTRUEBOOL, 0);
            break;

        case OP_CUSTOM: {
            Perl_cpeep_t cpeep =
                XopENTRYCUSTOM(o, xop_peep);
            if (cpeep)
                cpeep(aTHX_ o, oldop);
            break;
        }

        }
        /* did we just null the current op? If so, re-process it to handle
         * eliding "empty" ops from the chain */
        if (o->op_type == OP_NULL && oldop && oldop->op_next == o) {
            o->op_opt = 0;
            o = oldop;
        }
        else {
            oldoldop = oldop;
            oldop = o;
        }
    }
    LEAVE;
}

void
Perl_peep(pTHX_ OP *o)
{
    CALL_RPEEP(o);
}

/*
 * ex: set ts=8 sts=4 sw=4 et:
 */
