The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define PERL_NO_GET_CONTEXT 1
#include "EXTERN.h"
#include "perl.h"
#include "callchecker0.h"
#include "XSUB.h"

static OP *mypp_readonly_or_assign(pTHX)
{
	dSP;
	return SvREADONLY(TOPs) ? PL_op->op_next : cLOGOP->op_other;
}

static OP *mypp_sassign_memo(pTHX)
{
	dSP;
	SV *val, *var;
	val = POPs;
	var = TOPs;
	PUTBACK;
	if(!SvREADONLY(var)) {
		sv_setsv(var, val);
		SvREADONLY_on(var);
	}
	return PL_op->op_next;
}

static OP *myck_entersub_once(pTHX_ OP *entersubop, GV *namegv, SV *protosv)
{
	OP *pushop, *thunkop, *storeop, *assignop, *memoop, *oraop;
	entersubop = ck_entersub_args_proto(entersubop, namegv, protosv);
	pushop = cUNOPx(entersubop)->op_first;
	if(!pushop->op_sibling) pushop = cUNOPx(pushop)->op_first;
	thunkop = pushop->op_sibling;
	if(!thunkop || !thunkop->op_sibling || thunkop->op_sibling->op_sibling)
		return entersubop;
	pushop->op_sibling = thunkop->op_sibling;
	thunkop->op_sibling = NULL;
	op_free(entersubop);
	storeop = newSVREF(newSVOP(OP_CONST, 0, newRV_noinc(newSV(0))));
	assignop = newUNOP(OP_SASSIGN, 0, thunkop);
	assignop->op_ppaddr = mypp_sassign_memo;
	memoop = newLOGOP(OP_ORASSIGN, 0, storeop, assignop);
	oraop = memoop->op_type == OP_ORASSIGN ? memoop :
		cUNOPx(memoop)->op_first;
	oraop->op_ppaddr = mypp_readonly_or_assign;
	return memoop;
}

MODULE = Memoize::Once PACKAGE = Memoize::Once

PROTOTYPES: DISABLE

BOOT:
{
	CV *once_cv = get_cv("Memoize::Once::once", 0);
	cv_set_call_checker(once_cv, myck_entersub_once, (SV*)once_cv);
}

void
once(...)
PROTOTYPE: $
CODE:
	PERL_UNUSED_VAR(items);
	croak("once called as a function");