The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
    context marshalling massively pessimizes extensions built for threaded perls e.g. Cygwin.

    define PERL_CORE rather than PERL_NO_GET_CONTEXT (see perlguts) because a) PERL_NO_GET_CONTEXT still incurs the
    overhead of an extra function call for each interpreter variable; and b) this is a drop-in replacement for a
    core op.
*/

#define PERL_CORE

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

#include "ptable.h"

static PTABLE_t *AUTOBOX_OP_MAP = NULL;
static U32 AUTOBOX_SCOPE_DEPTH = 0;

OP * autobox_ck_method_named(pTHX_ OP *o);
OP * autobox_ck_subr(pTHX_ OP *o);
OP * autobox_method_named(pTHX);

/* the original is a no-op, so simply return o rather than delegating */
OP * autobox_ck_method_named(pTHX_ OP *o) {
    char *meth  = SvPVX(((SVOP *)o)->op_sv);
    /*
     * workaround %^H scoping bug by checking that PL_hints (which is properly scoped) & 0x100000 is true
     *
     * the bareword flag is not set on the receivers of the import, unimport
     * and VERSION messages faked up by use and no, so exempt them
     */
    /* Perl_warn(aTHX_ "Perl_hints: 0x%x", PL_hints); */
    if (((PL_hints & 0x120000) == 0x120000) && strNE(meth, "import") && strNE(meth, "unimport") && strNE(meth, "VERSION")) {
	HV *table = GvHV(PL_hintgv);
	SV **svp;

	if (table && (svp = hv_fetch(table, "autobox", 7, FALSE)) && *svp && SvOK(*svp)) {
	    PTABLE_store(AUTOBOX_OP_MAP, o, INT2PTR(void *, SvIVX(*svp)));
	    /* autoboxing has been disabled (by prematurely setting OPf_SPECIAL) because the receiver is a bareword */
	    if (o->op_flags & OPf_SPECIAL) {
		o->op_flags &= ~OPf_SPECIAL;
	    } else {
		o->op_flags |= OPf_SPECIAL;
		o->op_ppaddr = MEMBER_TO_FPTR(autobox_method_named);
	    }
	}
    }
    return o;
}

/* handle barewords before delegating to the original check handler */
OP * autobox_ck_subr(pTHX_ OP *o) {
    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);

    if ((cvop->op_type == OP_METHOD_NAMED) && (o2->op_private & OPpCONST_BARE)) {
	cvop->op_flags |= OPf_SPECIAL;
    }

    return Perl_ck_subr(aTHX_ o);
}

OP* autobox_method_named(pTHX) {
    dSP;
    SV * meth = cSVOP_sv;
    U32 hash = PTR2UV(meth);
    SV * sv;
    GV * gv = NULL;
    HV * stash;
    char * name;
    STRLEN namelen;
    char * packname = 0;
    SV  * packsv = Nullsv;
    STRLEN packlen;
    HE *he;

    name = SvPV(meth, namelen);
    sv = *(PL_stack_base + TOPMARK + 1);

    if (SvGMAGICAL(sv))
	mg_get(sv);

    if ((PL_op->op_flags & OPf_SPECIAL) && !(SvOBJECT(SvROK(sv) ? SvRV(sv) : sv))) {
	HV * autobox_handlers = (HV *)(PTABLE_fetch(AUTOBOX_OP_MAP, PL_op)); /* maps datatypes to package names */

	if (autobox_handlers) {
	    char *reftype; /* autobox_handlers key */
	    SV **svp; /* pointer to autobox_handlers value */

	    reftype = SvOK(sv) ? sv_reftype((SvROK(sv) ? SvRV(sv) : sv), 0) : "UNDEF";
	    svp = hv_fetch(autobox_handlers, reftype, strlen(reftype), 0);

	    if (svp && SvOK(*svp)) {
		packsv = *svp;
		packname = SvPVX(packsv); /* fake the package name */
		packlen = strlen(packname);
		stash = gv_stashpvn(packname, packlen, FALSE);

#ifdef PL_stashcache /* not defined in 5.6.1 */
		if (stash) {
		    /* ref (no underscore) appears to be reserved as of 5.9.3 */
		    SV * _ref = newSViv(PTR2IV(stash));
		    hv_store(PL_stashcache, packname, packlen, _ref, 0); /* cache the stash */
		}
#endif

		/* NOTE: stash may be null, hope hv_fetch_ent and gv_fetchmethod can cope (it seems they can) */
		he = hv_fetch_ent(stash, meth, 0, hash); /* shortcut for simple names */

		if (he) {
		    gv = (GV*)HeVAL(he);
		    if (isGV(gv) && GvCV(gv) && (!GvCVGEN(gv) || GvCVGEN(gv) == PL_sub_generation)) {
			XPUSHs((SV*)GvCV(gv));
			RETURN;
		    }
		}

		gv = gv_fetchmethod(stash ? stash : (HV*)packsv, name);
	    }
	}
    }

    if (gv) {
	XPUSHs(isGV(gv) ? (SV*)GvCV(gv) : (SV*)gv);
	RETURN;
    } else {
	return Perl_pp_method_named(aTHX);
    }
}

MODULE = autobox		PACKAGE = Autobox

PROTOTYPES: ENABLE

BOOT:
AUTOBOX_OP_MAP = PTABLE_new(); if (!AUTOBOX_OP_MAP) Perl_croak(aTHX_ "Can't initialize op map");

void
enterscope()
    PROTOTYPE:
    CODE: 
	if (AUTOBOX_SCOPE_DEPTH > 0) {
	    ++AUTOBOX_SCOPE_DEPTH;
	} else {
	    AUTOBOX_SCOPE_DEPTH = 1;
	    /* Perl_warn("inside autobox::enterscope\n"); */
	    PL_check[OP_METHOD_NAMED] = MEMBER_TO_FPTR(autobox_ck_method_named);
	    PL_check[OP_ENTERSUB] = MEMBER_TO_FPTR(autobox_ck_subr);
	}

void
leavescope()
    PROTOTYPE:
    CODE: 
	if (AUTOBOX_SCOPE_DEPTH > 1) {
	    --AUTOBOX_SCOPE_DEPTH;
	} else {
	    AUTOBOX_SCOPE_DEPTH = 0;
	    /* Perl_warn("inside autobox::leavescope\n"); */
	    PL_check[OP_METHOD_NAMED] = MEMBER_TO_FPTR(Perl_ck_null);
	    PL_check[OP_ENTERSUB] = MEMBER_TO_FPTR(Perl_ck_subr);
	}

void
END()
    PROTOTYPE:
    CODE: 
	/* Perl_warn("inside autobox::cleanup\n"); */
	PL_check[OP_METHOD_NAMED] = MEMBER_TO_FPTR(Perl_ck_null);
	PL_check[OP_ENTERSUB] = MEMBER_TO_FPTR(Perl_ck_subr);
	PTABLE_free(AUTOBOX_OP_MAP);
	AUTOBOX_OP_MAP = NULL;
	AUTOBOX_SCOPE_DEPTH = 0;