The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* ex: set sw=4 et: */

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include "hook_op_check_entersubforcv.h"



#ifndef AvREIFY_only
#define AvREIFY_only(av)	(AvREAL_off(av), AvREIFY_on(av))
#endif

static OP *recur (pTHX) {
    dVAR; dSP; dMARK; dITEMS;

    IV cxix = cxstack_ix;
    PERL_CONTEXT *cx = NULL;

    while ( cxix > 0 ) {
        if ( CxTYPE(&cxstack[cxix]) == CXt_SUB ) {
            cx = &cxstack[cxix];
            break;
        } else {
            cxix--;
        }
    }
    
    if (cx == NULL) {
        DIE(aTHX_ "Can't recur outside a subroutine");
    } else {
        CV *cv = cx->blk_sub.cv;
        I32 gimme = cx->blk_gimme;
        AV *av = cx->blk_sub.argarray;

        // POPs; PUTBACK; /* discard the GV that was added for entersub */

        /* undwind to top level */
        if ( cxix < cxstack_ix )
            dounwind(cxix);

        /* abandon @_ if it got reified */
        if (AvREAL(av)) {
            SvREFCNT_dec(av);
            av = newAV();
            AvREIFY_only(av);

            cx->blk_sub.argarray = av;
            PAD_SVl(0) = (SV *)av;
        }

        ++MARK;

        av_extend(av, items-1);

        Copy(MARK,AvARRAY(av),items,SV*);
        AvFILLp(av) = items - 1;

        while (MARK <= SP) {
            if (*SP) {
                if ( SvTEMP(*SP) || SvPADMY(*SP) ) {
                    I32 key;

                    key = AvMAX(av) + 1;
                    while (key > AvFILLp(av) + 1)
                        AvARRAY(av)[--key] = &PL_sv_undef;
                    while (key) {
                        SV * const sv = AvARRAY(av)[--key];
                        assert(sv);
                        if (sv != &PL_sv_undef)
                            SvREFCNT_inc_simple_void_NN(sv);
                    }
                    key = AvARRAY(av) - AvALLOC(av);
                    while (key)
                        AvALLOC(av)[--key] = &PL_sv_undef;
                    AvREIFY_off(av);
                    AvREAL_on(av);

                    break;
                }
            }
            SP--;
        }

        PUTBACK;

        LEAVE;
        FREETMPS;
        ENTER;

        SAVECOMPPAD();
        PAD_SET_CUR_NOSAVE(CvPADLIST(cv), CvDEPTH(cv));


        RETURNOP(CvSTART(cv));
    }
}

STATIC OP *install_recur_op (pTHX_ OP *o, CV *cv, void *user_data) {

    OP *prev = ((cUNOPo->op_first->op_sibling) ? cUNOPo : ((UNOP*)cUNOPo->op_first))->op_first;
    OP *o2 = prev->op_sibling;
    OP *cvop;

    for (cvop = o2; cvop->op_sibling; cvop = cvop->op_sibling);

    /* disable the cv push op, so that we don't need to pop the GV for recur
     * itself */
    cvop->op_ppaddr = PL_ppaddr[OP_NULL];
    ((UNOP *)cvop)->op_first->op_ppaddr = PL_ppaddr[OP_NULL];

    o->op_ppaddr = recur;

    return o;
}

MODULE = Sub::Call::Recur        PACKAGE = Sub::Call::Recur
PROTOTYPES: disable

BOOT:
{
    hook_op_check_entersubforcv(get_cv("Sub::Call::Recur::recur", TRUE), install_recur_op, NULL);
}