Father Chrysostomos via RT | 30 Jun 2012 07:34
Picon
Favicon

[perl #112312] perl5 version 5.14.2 coredumps during perl -c

On Mon Jun 25 14:50:38 2012, sprout wrote:
> On Mon Jun 25 14:41:06 2012, davem wrote:
> > That "we guessed we had a code block but it turns out we didn't" bit of
> > code was always a bit of hack, and now that I realise it leaves an op
> > allocated in the wrong CV, I like it even less.
> > 
> > I'm tempted to eliminate it altogether. Would doing this enable you to
> > simplify the slab code?
> 
> No, because I still have to take SAVEFREEOP into account. :-)  I could
> fiddle to get savestack items the right order, but what I have currently
> is far more robust than the alternative.
> 
> The three things I didn’t have working with my earlier (non-refcounted)
> system were:
> • smartmatch
> • SAVEFREEOP - I just made it a no-op to get tests passing, which leaked
> ops when there were no errors
> • re-evals

Attached is an early diff containing the alternative mentioned above,
which I am attaching here for posterity.

This was before the re-eval rewrite was merged, before newSTUB, and
before I had thought of the CVf_SLABBED flag.  The corresponding
workarounds are a twisted maze.  The only advantage was that freeing a
slab was faster, but probably less robust, in that some ops might not be
cleared and no check was done.

-- 

Father Chrysostomos

diff --git a/cop.h b/cop.h
index af98965..650ada4 100644
--- a/cop.h
+++ b/cop.h
 <at>  <at>  -719,6 +719,10  <at>  <at>  struct block_eval {
 	PL_eval_root = cx->blk_eval.old_eval_root;			\
 	if (cx->blk_eval.old_namesv)					\
 	    sv_2mortal(cx->blk_eval.old_namesv);			\
+	if (cx->blk_eval.cv) {						\
+	    assert(CvDEPTH(cx->blk_eval.cv) <= 1);			\
+	    CvDEPTH(cx->blk_eval.cv) = 0;				\
+	}								\
     } STMT_END

 /* loop context */
diff --git a/embed.fnc b/embed.fnc
index 594485d..238e89e 100644
--- a/embed.fnc
+++ b/embed.fnc
 <at>  <at>  -962,6 +962,9  <at>  <at>  p	|PerlIO*|nextargv	|NN GV* gv
 AnpP	|char*	|ninstr		|NN const char* big|NN const char* bigend \
 				|NN const char* little|NN const char* lend
 Ap	|void	|op_free	|NULLOK OP* arg
+#ifndef PL_OP_SLAB_ALLOC
+p	|void	|op_free_root	|NN OP* o
+#endif
 : Used in perly.y
 #ifdef PERL_MAD
 p	|OP*	|package	|NN OP* o
 <at>  <at>  -1770,10 +1773,12  <at>  <at>  s	|OP*	|ref_array_or_hash|NULLOK OP* cond
 s	|void	|process_special_blocks	|NN const char *const fullname\
 					|NN GV *const gv|NN CV *const cv
 #endif
-#if defined(PL_OP_SLAB_ALLOC)
-Apa	|void*	|Slab_Alloc	|size_t sz
-Ap	|void	|Slab_Free	|NN void *op
-#  if defined(PERL_DEBUG_READONLY_OPS)
+Xpa	|void*	|Slab_Alloc	|size_t sz
+Xp	|void	|Slab_Free	|NN void *op
+#ifndef PL_OP_SLAB_ALLOC
+p	|void	|Slab_Free_Slab	|NN OPSLAB *slab|bool fast
+#endif
+#if defined(PERL_DEBUG_READONLY_OPS)
 : Used in perl.c
 poxM	|void	|pending_Slabs_to_ro
 : Used in OpREFCNT_inc() in sv.c
 <at>  <at>  -1783,7 +1788,6  <at>  <at>  poxM	|PADOFFSET	|op_refcnt_dec	|NN OP *o
 #    if defined(PERL_IN_OP_C)
 s	|void	|Slab_to_rw	|NN void *op
 #    endif
-#  endif
 #endif

 #if defined(PERL_IN_PERL_C)
diff --git a/embed.h b/embed.h
index a980a87..a2e4ece 100644
--- a/embed.h
+++ b/embed.h
 <at>  <at>  -795,10 +795,6  <at>  <at> 
 #define newFORM(a,b,c)		Perl_newFORM(aTHX_ a,b,c)
 #define newMYSUB(a,b,c,d,e)	Perl_newMYSUB(aTHX_ a,b,c,d,e)
 #endif
-#if defined(PL_OP_SLAB_ALLOC)
-#define Slab_Alloc(a)		Perl_Slab_Alloc(aTHX_ a)
-#define Slab_Free(a)		Perl_Slab_Free(aTHX_ a)
-#endif
 #if defined(UNLINK_ALL_VERSIONS)
 #define unlnk(a)		Perl_unlnk(aTHX_ a)
 #endif
 <at>  <at>  -993,6 +989,8  <at>  <at> 
 #  endif
 #endif
 #ifdef PERL_CORE
+#define Slab_Alloc(a)		Perl_Slab_Alloc(aTHX_ a)
+#define Slab_Free(a)		Perl_Slab_Free(aTHX_ a)
 #define allocmy(a,b,c)		Perl_allocmy(aTHX_ a,b,c)
 #define amagic_is_enabled(a)	Perl_amagic_is_enabled(aTHX_ a)
 #define apply(a,b,c)		Perl_apply(aTHX_ a,b,c)
 <at>  <at>  -1265,6 +1263,10  <at>  <at> 
 #define utf16_textfilter(a,b,c)	S_utf16_textfilter(aTHX_ a,b,c)
 #    endif
 #  endif
+#  if !defined(PL_OP_SLAB_ALLOC)
+#define Slab_Free_Slab(a,b)	Perl_Slab_Free_Slab(aTHX_ a,b)
+#define op_free_root(a)		Perl_op_free_root(aTHX_ a)
+#  endif
 #  if !defined(WIN32)
 #define do_exec3(a,b,c)		Perl_do_exec3(aTHX_ a,b,c)
 #  endif
 <at>  <at>  -1307,9 +1309,7  <at>  <at> 
 #  endif
 #  if defined(PERL_DEBUG_READONLY_OPS)
 #    if defined(PERL_IN_OP_C)
-#      if defined(PL_OP_SLAB_ALLOC)
 #define Slab_to_rw(a)		S_Slab_to_rw(aTHX_ a)
-#      endif
 #    endif
 #  endif
 #  if defined(PERL_IN_AV_C)
diff --git a/op.c b/op.c
index 400291a..1cc3c59 100644
--- a/op.c
+++ b/op.c
 <at>  <at>  -297,6 +297,182  <at>  <at>  Perl_Slab_Free(pTHX_ void *op)
 	}
     }
 }
+#else /* !defined(PL_OP_SLAB_ALLOC) */
+
+/* See the explanatory comments above struct opslab in op.h. */
+
+# ifndef PERL_SLAB_SIZE
+#  define PERL_SLAB_SIZE 64
+# endif
+
+# define SIZE_TO_POINTERS(x)	(((x) + sizeof(I32 *) - 1)/sizeof(I32 *))
+# define DIFF(o,p)		((I32 **)(p) - (I32**)(o))
+# define NOT_FIRST_SLAB		(OP *)((STRLEN *)0 + 1)
+
+static OPSLAB *
+new_slab(size_t sz)
+{
+    OPSLAB *slab = PerlMemShared_calloc(sz, sizeof(I32 *));
+    slab->opslab_first = (OPSLOT *)((I32 **)slab + sz - 1);
+    slab->opslab_first->opslot_next = (OPSLOT *)slab;
+    return slab;
+}
+
+static OPSLAB *
+OpSLAB(OP *o)
+{
+if(!o->op_slabbed) Perl_warn_nocontext("op %p is not slabbed", o);
+    OPSLOT *slot = OpSLOT(o);
+    OPSLAB *slab;
+    while (slot->opslot_next > slot) slot = slot->opslot_next;
+    slab = (OPSLAB *)slot->opslot_next;
+    while (slab->opslab_freed == NOT_FIRST_SLAB) slab = slab->opslab_next;
+    return slab;
+}
+
+void *
+Perl_Slab_Alloc(pTHX_ size_t sz)
+{
+    dVAR;
+    OPSLAB *slab;
+    OPSLAB *slab2;
+    OPSLOT *slot;
+    OP *o;
+    size_t space;
+
+    assert(PL_compcv);
+    assert(!CvISXSUB(PL_compcv));
+DEBUG_U(if (CvROOT(PL_compcv)) { Perl_warn(aTHX_ "compcv %p root %p", PL_compcv,
CvROOT(PL_compcv)); Perl_sv_dump(aTHX_ (SV *)PL_compcv); });
+    assert(!CvROOT(PL_compcv));
+    if (!CvSTART(PL_compcv)) { /* sneak it in here */
+	CvSTART(PL_compcv) = (OP *)(slab = new_slab(PERL_SLAB_SIZE));
+	slab->opslab_next = slab;
+    }
+    else slab = (OPSLAB *)CvSTART(PL_compcv);
+
+/*    slab->opslab_refcnt++;*/
+
+    /*
+     * Round up the op size to the nearest pointer, and add one more
+     * pointer for opslot_next; convert to a pointer count in the process.
+     */
+    sz = SIZE_TO_POINTERS(sz) + 1;
+
+    if (slab->opslab_freed) {
+	OP **too = &slab->opslab_freed;
+	o = *too;
+	DEBUG_U(Perl_warn(aTHX_ "found free op at %p, slab %p", o, slab));
+	while (o && DIFF(OpSLOT(o), OpSLOT(o)->opslot_next) < sz)
+{	DEBUG_U(Perl_warn(aTHX_ "Alas! too small"));
+	    o = *(too = &o->op_next);
+	DEBUG_U(if(o) Perl_warn(aTHX_ "found another free op at %p", o));}
+	if (o) {
+	    *too = o->op_next;
+	    Zero(o, DIFF(OpSLOT(o), OpSLOT(o)->opslot_next)-1, I32 *);
+# ifdef DEBUGGING
+	    o->op_slabbed = 1;
+# endif
+	    return (void *)o;
+	}
+    }
+
+    slab2 = slab;
+    while (slab2->opslab_next != slab) slab2 = slab2->opslab_next;
+    if ((space = DIFF(&slab2->opslab_slots, slab2->opslab_first)) < sz) {
+	/* Remaining space is too small. */
+
+	OPSLAB *newslab;
+
+	/* If we can fit a BASEOP, add it to the free chain, so as not
+	   to waste it. */
+	if (space > SIZE_TO_POINTERS(sizeof(OP))) { /* not >= */
+	    slot = &slab2->opslab_slots;
+	    slot->opslot_next = slab2->opslab_first;
+	    slab2->opslab_first = slot;
+	    o = &slot->opslot_op;
+	    o->op_type = OP_FREED;
+# ifdef DEBUGGING
+	    o->op_slabbed = 1;
+# endif
+	    o->op_next = slab->opslab_freed;
+	    slab->opslab_freed = o;
+	}
+
+	/* Create a new slab.  Make this one twice as big. */
+	slot = slab2->opslab_first;
+	while (slot->opslot_next > (OPSLOT *)slab2)
+	    slot = slot->opslot_next;
+	newslab = new_slab(DIFF(slab2, slot)*2);
+	slab2->opslab_next = newslab;
+	newslab->opslab_next = slab;
+	newslab->opslab_freed = NOT_FIRST_SLAB;
+	slab2 = newslab;
+    }
+    assert(DIFF(&slab2->opslab_slots, slab2->opslab_first) >= sz);
+
+    /* Create a new op slot */
+    slot = (OPSLOT *)((I32 **)slab2->opslab_first - sz);
+    assert(slot >= &slab2->opslab_slots);
+    slot->opslot_next = slab2->opslab_first;
+    slab2->opslab_first = slot;
+    o = &slot->opslot_op;
+    DEBUG_U(Perl_warn(aTHX_ "allocating op at %p, slab %p", o, slab));
+# ifdef DEBUGGING
+    o->op_slabbed = 1;
+# endif
+    return (void *)o;
+}
+
+void
+Perl_Slab_Free(pTHX_ void *op)
+{
+    OP * const o = (OP *)op;
+    OPSLAB * const slab = OpSLAB(o);
+    PERL_ARGS_ASSERT_SLAB_FREE;
+    assert(o->op_slabbed);
+    o->op_type = OP_FREED;
+    o->op_next = slab->opslab_freed;
+/*    Perl_warn(aTHX_ "free op at %p, recorded in slab %p", o, slab);*/
+    slab->opslab_freed = o;
+/*    if (!--slab-≥opslab_refcnt) Slab_Free_Slab(slab, 1);*/
+}
+
+/* This cannot possibly be right, but it was copied from the old slab
+   allocator, to which it was originally added, without explanation, in
+   commit 083fcd5. */
+# ifdef NETWARE
+#    define PerlMemShared PerlMem
+# endif
+
+/* If fast is true, it is a promise that all ops have been freed. */
+
+void
+Perl_Slab_Free_Slab(pTHX_ OPSLAB *slab, bool fast) {
+    OPSLAB *slab2 = slab;
+    OPSLOT *slot;
+    PERL_ARGS_ASSERT_SLAB_FREE_SLAB;
+    assert(slab->opslab_freed != NOT_FIRST_SLAB);
+    DEBUG_U(Perl_warn(aTHX_ "freeing slab %p", slab));
+    if (!fast) {
+	do {
+	    for (slot = slab->opslab_first;
+		 slot->opslot_next > (OPSLOT *)slab;
+		 slot = slot->opslot_next) {
+		if (slot->opslot_op.op_type != OP_FREED)
+		    op_free(&slot->opslot_op);
+	    }
+	    /* Don’t free the slab yet, as ops in other slabs might still
+	       point to it. */
+	} while ((slab2 = slab2->opslab_next) != slab);
+    }
+    for (;;) {
+	OPSLAB *nextslab = slab2->opslab_next;
+	PerlMemShared_free(slab2);
+	if (nextslab == slab) break;
+	slab2 = nextslab;
+    }
+}
+
 #endif
 /*
  * In the following definition, the ", (OP*)0" is just to make the compiler
 <at>  <at>  -523,14 +699,13  <at>  <at>  S_op_destroy(pTHX_ OP *o)

 /* Destructor */

-void
-Perl_op_free(pTHX_ OP *o)
+static void
+S_op_free(pTHX_ OP *o, bool fast, bool is_root)
 {
     dVAR;
     OPCODE type;

-    if (!o)
-	return;
+    assert(o);
     if (o->op_latefreed) {
 	if (o->op_latefree)
 	    return;
 <at>  <at>  -573,7 +748,7  <at>  <at>  Perl_op_free(pTHX_ OP *o)
         register OP *kid, *nextkid;
 	for (kid = cUNOPo->op_first; kid; kid = nextkid) {
 	    nextkid = kid->op_sibling; /* Get before next freeing kid */
-	    op_free(kid);
+	    S_op_free(aTHX_ kid, fast, 0);
 	}
     }

 <at>  <at>  -599,13 +774,34  <at>  <at>  Perl_op_free(pTHX_ OP *o)
 	return;
     }
   do_free:
-    FreeOp(o);
 #ifdef DEBUG_LEAKING_SCALARS
     if (PL_op == o)
 	PL_op = NULL;
 #endif
+#ifndef PL_OP_SLAB_ALLOC
+    if (fast) {
+	if (is_root) Slab_Free_Slab(OpSLAB(o), 1);
+	return;
+    }
+#endif
+    FreeOp(o);
+}
+
+void
+Perl_op_free(pTHX_ OP *o)
+{
+    if (o) S_op_free(aTHX_ o, 0, 0);
 }

+#ifndef PL_OP_SLAB_ALLOC
+void
+Perl_op_free_root(pTHX_ OP *o)
+{
+    PERL_ARGS_ASSERT_OP_FREE_ROOT;
+    S_op_free(aTHX_ o, 1, 1);
+}
+#endif
+
 void
 Perl_op_clear(pTHX_ OP *o)
 {
 <at>  <at>  -2830,6 +3026,7  <at>  <at>  Perl_newPROG(pTHX_ OP *o)
 	PL_eval_root->op_private |= OPpREFCOUNTED;
 	OpREFCNT_set(PL_eval_root, 1);
 	PL_eval_root->op_next = 0;
+	CvROOT(PL_compcv) = PL_eval_root;
 	i = PL_savestack_ix;
 	SAVEFREEOP(o);
 	ENTER;
 <at>  <at>  -2853,6 +3050,8  <at>  <at>  Perl_newPROG(pTHX_ OP *o)
 	PL_main_root->op_next = 0;
 	CALL_PEEP(PL_main_start);
 	finalize_optree(PL_main_root);
+	/* Stop CvSTART from pointing to the op slab. */
+	CvSTART(PL_compcv) = NULL;
 	PL_compcv = 0;

 	/* Register with debugger */
 <at>  <at>  -4644,7 +4843,7  <at>  <at>  Perl_utilize(pTHX_ int aver, I32 floor, OP *version, OP *idop, OP *arg)
     OP *imop;
     OP *veop;
 #ifdef PERL_MAD
-    OP *pegop = newOP(OP_NULL,0);
+    OP *pegop = PL_madskills ? newOP(OP_NULL,0) : NULL;
 #endif
     SV *use_version = NULL;

 <at>  <at>  -4779,11 +4978,6  <at>  <at>  Perl_utilize(pTHX_ int aver, I32 floor, OP *version, OP *idop, OP *arg)
 	PL_cop_seqmax++;

 #ifdef PERL_MAD
-    if (!PL_madskills) {
-	/* FIXME - don't allocate pegop if !PL_madskills */
-	op_free(pegop);
-	return NULL;
-    }
     return pegop;
 #endif
 }
 <at>  <at>  -4840,10 +5034,23  <at>  <at>  Perl_vload_module(pTHX_ U32 flags, SV *name, SV *ver, va_list *args)
 {
     dVAR;
     OP *veop, *imop;
-    OP * const modname = newSVOP(OP_CONST, 0, name);
+    OP *modname;
+    I32 floor;

     PERL_ARGS_ASSERT_VLOAD_MODULE;

+    /* utilize() fakes up a BEGIN { require ..; import ... }, so make sure
+     * that it has a PL_parser to play with while doing that, and also
+     * that it doesn't mess with any existing parser, by creating a tmp
+     * new parser with lex_start(). This won't actually be used for much,
+     * since pp_require() will create another parser for the real work. */
+
+    ENTER;
+    SAVEVPTR(PL_curcop);
+    lex_start(NULL, NULL, LEX_START_SAME_FILTER);
+    floor = start_subparse(FALSE, 0);
+
+    modname = newSVOP(OP_CONST, 0, name);
     modname->op_private |= OPpCONST_BARE;
     if (ver) {
 	veop = newSVOP(OP_CONST, 0, ver);
 <at>  <at>  -4866,16 +5073,7  <at>  <at>  Perl_vload_module(pTHX_ U32 flags, SV *name, SV *ver, va_list *args)
 	}
     }

-    /* utilize() fakes up a BEGIN { require ..; import ... }, so make sure
-     * that it has a PL_parser to play with while doing that, and also
-     * that it doesn't mess with any existing parser, by creating a tmp
-     * new parser with lex_start(). This won't actually be used for much,
-     * since pp_require() will create another parser for the real work. */
-
-    ENTER;
-    SAVEVPTR(PL_curcop);
-    lex_start(NULL, NULL, LEX_START_SAME_FILTER);
-    utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(FALSE, 0),
+    utilize(!(flags & PERL_LOADMOD_DENY), floor,
 	    veop, modname, imop);
     LEAVE;
 }
 <at>  <at>  -6060,7 +6258,10  <at>  <at>  Perl_newFOROP(pTHX_ I32 flags, OP *sv, OP *expr, OP *block, OP *cont)
     /* for my  $x () sets OPpLVAL_INTRO;
      * for our $x () sets OPpOUR_INTRO */
     loop->op_private = (U8)iterpflags;
-#ifdef PL_OP_SLAB_ALLOC
+#ifndef PL_OP_SLAB_ALLOC
+    if (DIFF(OpSLOT(loop), OpSLOT(loop)->opslot_next)
+	 < SIZE_TO_POINTERS(sizeof(LOOP))+1)
+#endif
     {
 	LOOP *tmp;
 	NewOp(1234,tmp,1,LOOP);
 <at>  <at>  -6068,9 +6269,6  <at>  <at>  Perl_newFOROP(pTHX_ I32 flags, OP *sv, OP *expr, OP *block, OP *cont)
 	S_op_destroy(aTHX_ (OP*)loop);
 	loop = tmp;
     }
-#else
-    loop = (LOOP*)PerlMemShared_realloc(loop, sizeof(LOOP));
-#endif
     loop->op_targ = padoff;
     wop = newWHILEOP(flags, 1, loop, newOP(OP_ITER, 0), block, cont, 0);
     if (madsv)
 <at>  <at>  -6699,6 +6897,9  <at>  <at>  Perl_newATTRSUB_flags(pTHX_ I32 floor, OP *o, OP *proto, OP *attrs,
 	SvREFCNT_inc_simple_void_NN(const_sv);
 	if (cv) {
 	    assert(!CvROOT(cv) && !CvCONST(cv));
+#ifndef PL_OP_SLAB_ALLOC
+	    if (CvSTART(cv)) Slab_Free_Slab((OPSLAB *)CvSTART(cv), 0);
+#endif
 	    sv_setpvs(MUTABLE_SV(cv), "");  /* prototype is "" */
 	    CvXSUBANY(cv).any_ptr = const_sv;
 	    CvXSUB(cv) = const_sv_xsub;
 <at>  <at>  -6749,6 +6950,8  <at>  <at>  Perl_newATTRSUB_flags(pTHX_ I32 floor, OP *o, OP *proto, OP *attrs,
 	    CvPADLIST(cv) = CvPADLIST(PL_compcv);
 	    CvOUTSIDE(PL_compcv) = temp_cv;
 	    CvPADLIST(PL_compcv) = temp_av;
+	    CvSTART(cv) = CvSTART(PL_compcv);
+	    CvSTART(PL_compcv) = NULL;

 	    if (CvFILE(cv) && CvDYNFILE(cv)) {
 		Safefree(CvFILE(cv));
 <at>  <at>  -6837,15 +7040,26  <at>  <at>  Perl_newATTRSUB_flags(pTHX_ I32 floor, OP *o, OP *proto, OP *attrs,
 	    block = newblock;
     }
     else block->op_attached = 1;
-    CvROOT(cv) = CvLVALUE(cv)
+    block = CvLVALUE(cv)
 		   ? newUNOP(OP_LEAVESUBLV, 0,
 			     op_lvalue(scalarseq(block), OP_LEAVESUBLV))
 		   : newUNOP(OP_LEAVESUB, 0, scalarseq(block));
-    CvROOT(cv)->op_private |= OPpREFCOUNTED;
-    OpREFCNT_set(CvROOT(cv), 1);
-    CvSTART(cv) = LINKLIST(CvROOT(cv));
-    CvROOT(cv)->op_next = 0;
-    CALL_PEEP(CvSTART(cv));
+    block->op_private |= OPpREFCOUNTED;
+    OpREFCNT_set(block, 1);
+    o = LINKLIST(block);
+    block->op_next = 0;
+#ifdef PL_OP_SLAB_ALLOC
+    CvROOT(cv) = block;
+    CvSTART(cv) = o;
+#endif
+    CALL_PEEP(o);
+#ifndef PL_OP_SLAB_ALLOC
+    /* Do this after CALL_PEEP, as CALL_PEEP could create new ops, and
+       needs to see the slab in CvSTART(cv).  And CvROOT(cv) must be null
+       for CvSTART(cv) to contain the slab. */
+    CvROOT(cv) = block;
+    CvSTART(cv) = o;
+#endif
     finalize_optree(CvROOT(cv));

     /* now that optimizer has done its work, adjust pad values */
diff --git a/op.h b/op.h
index 6aa16f5..edfb9bd 100644
--- a/op.h
+++ b/op.h
 <at>  <at>  -28,8 +28,9  <at>  <at> 
  *			the op may be safely op_free()d multiple times
  *	op_latefreed	an op_latefree op has been op_free()d
  *	op_attached	this op (sub)tree has been attached to a CV
+ *	op_slabbed	allocated via opslab
  *
- *	op_spare	three spare bits!
+ *	op_spare	two spare bits!
  *	op_flags	Flags common to all operations.  See OPf_* below.
  *	op_private	Flags peculiar to a particular operation (BUT,
  *			by default, set to the number of children until
 <at>  <at>  -62,7 +63,8  <at>  <at>  typedef PERL_BITFIELD16 Optype;
     PERL_BITFIELD16 op_latefree:1;	\
     PERL_BITFIELD16 op_latefreed:1;	\
     PERL_BITFIELD16 op_attached:1;	\
-    PERL_BITFIELD16 op_spare:3;		\
+    PERL_BITFIELD16 op_slabbed:1;	\
+    PERL_BITFIELD16 op_spare:2;		\
     U8		op_flags;		\
     U8		op_private;
 #endif
 <at>  <at>  -579,6 +581,52  <at>  <at>  struct loop {
 #  define Nullop ((OP*)NULL)
 #endif

+/*
+ * The per-CV op slabs consist of a header (the opslab struct) and a bunch
+ * of space for allocating op slots, each of which consists of a pointer
+ * followed by an op.  Each pointer points to the next op slot.  At the
+ * end of the slab is a pointer back to the beginning, so that
+ * slot->opslot_next - slot can be used to determine the size of the op,
+ * and so that the beginning of the slab can be found by following the
+ * opslot_next pointers.
+ *
+ * Each CV can have multiple slabs; opslab_next points to the next slab, to
+ * form a chain.
+ *
+ * Freed ops are marked as freed and attached to the freed chain
+ * via op_next pointers.  Only the first slab uses opslab_freed and
+ * opslab_refcnt.
+ *
+ * The last slab in the slab chain is assumed to be the one with free space
+ * available.  It is used when allocating an op if there are no freed ops
+ * available.
+ */
+
+#if !defined(PL_OP_SLAB_ALLOC) && defined(PERL_CORE)
+struct opslot {
+    OPSLOT *	opslot_next;		/* next slot */
+    OP		opslot_op;		/* the op itself */
+};
+
+struct opslab {
+    OPSLOT *	opslab_first;		/* first op in this slab */
+    OPSLAB *	opslab_next;		/* next slab */
+    OP *	opslab_freed;		/* chain of freed ops */
+/*    size_t	opslab_refcnt;*/		/* number of ops */
+    OPSLOT	opslab_slots;		/* slots begin here */
+};
+
+/* First struct member used only by first slab */
+# define OPSLAB_UNUSED		opslot_freed
+
+# ifdef DEBUGGING
+#  define OpSLOT(o)		(assert(o->op_slabbed), \
+				 (OPSLOT *)(((I32 **)o)-1))
+# else
+#  define OpSLOT(o)		((OPSLOT *)(((I32 **)o)-1))
+# endif
+#endif
+
 /* Lowest byte of PL_opargs */
 #define OA_MARK 1
 #define OA_FOLDCONST 2
 <at>  <at>  -694,20 +742,11  <at>  <at>  least an C<UNOP>.
 #include "reentr.h"
 #endif

-#if defined(PL_OP_SLAB_ALLOC)
 #define NewOp(m,var,c,type)	\
 	(var = (type *) Perl_Slab_Alloc(aTHX_ c*sizeof(type)))
 #define NewOpSz(m,var,size)	\
 	(var = (OP *) Perl_Slab_Alloc(aTHX_ size))
 #define FreeOp(p) Perl_Slab_Free(aTHX_ p)
-#else
-#define NewOp(m, var, c, type)	\
-	(var = (MEM_WRAP_CHECK_(c,type) \
-	 (type*)PerlMemShared_calloc(c, sizeof(type))))
-#define NewOpSz(m, var, size)	\
-	(var = (OP*)PerlMemShared_calloc(1, size))
-#define FreeOp(p) PerlMemShared_free(p)
-#endif

 struct block_hooks {
     U32	    bhk_flags;
diff --git a/opnames.h b/opnames.h
index 8b6a39a..fd86d2a 100644
--- a/opnames.h
+++ b/opnames.h
 <at>  <at>  -392,6 +392,7  <at>  <at>  typedef enum opcode {
 } opcode;

 #define MAXO 374
+#define OP_FREED MAXO

 /* the OP_IS_* macros are optimized to a simple range check because
     all the member OPs are contiguous in regen/opcodes table.
diff --git a/pad.c b/pad.c
index 689a180..a1f42b4 100644
--- a/pad.c
+++ b/pad.c
 <at>  <at>  -346,17 +346,34  <at>  <at>  Perl_cv_undef(pTHX_ CV *cv)
     }
     CvFILE(cv) = NULL;

-    if (!CvISXSUB(cv) && CvROOT(cv)) {
+    if (!CvISXSUB(cv)) {
+      if (CvROOT(cv)) {
 	if (SvTYPE(cv) == SVt_PVCV && CvDEPTH(cv))
 	    Perl_croak(aTHX_ "Can't undef active subroutine");
 	ENTER;

 	PAD_SAVE_SETNULLPAD();

+#ifdef PL_OP_SLAB_ALLOC
 	op_free(CvROOT(cv));
+#else
+	op_free_root(CvROOT(cv));
+#endif
 	CvROOT(cv) = NULL;
 	CvSTART(cv) = NULL;
 	LEAVE;
+      }
+#ifndef PL_OP_SLAB_ALLOC
+      else if (CvSTART(cv)) {
+	ENTER;
+	PAD_SAVE_SETNULLPAD();
+
+	Slab_Free_Slab((OPSLAB *)CvSTART(cv), 0);
+	CvSTART(cv) = NULL;
+
+	LEAVE;
+      }
+#endif
     }
     SvPOK_off(MUTABLE_SV(cv));		/* forget prototype */
     CvGV_set(cv, NULL);
diff --git a/perl.c b/perl.c
index 79d15e2..04b58f2 100644
--- a/perl.c
+++ b/perl.c
 <at>  <at>  -747,7 +747,11  <at>  <at>  perl_destruct(pTHXx)
 	if (CvPADLIST(PL_main_cv)) {
 	    PAD_SET_CUR_NOSAVE(CvPADLIST(PL_main_cv), 1);
 	}
+#ifdef PL_OP_SLAB_ALLOC
 	op_free(PL_main_root);
+#else
+	op_free_root(PL_main_root);
+#endif
 	PL_main_root = NULL;
     }
     PL_main_start = NULL;
 <at>  <at>  -1616,7 +1620,11  <at>  <at>  perl_parse(pTHXx_ XSINIT_t xsinit, int argc, char **argv, char **env)
     }

     if (PL_main_root) {
+#ifdef PL_OP_SLAB_ALLOC
 	op_free(PL_main_root);
+#else
+	op_free_root(PL_main_root);
+#endif
 	PL_main_root = NULL;
     }
     PL_main_start = NULL;
diff --git a/perl.h b/perl.h
index 798e7b7..ffddee9 100644
--- a/perl.h
+++ b/perl.h
 <at>  <at>  -2418,6 +2418,11  <at>  <at>  typedef struct padop PADOP;
 typedef struct pvop PVOP;
 typedef struct loop LOOP;

+#if !defined(PL_OP_SLAB_ALLOC) && defined(PERL_CORE)
+typedef struct opslab OPSLAB;
+typedef struct opslot OPSLOT;
+#endif
+
 typedef struct block_hooks BHK;
 typedef struct custom_op XOP;

diff --git a/pp_ctl.c b/pp_ctl.c
index e196022..45afc70 100644
--- a/pp_ctl.c
+++ b/pp_ctl.c
 <at>  <at>  -3673,7 +3673,8  <at>  <at>  S_doeval(pTHX_ int gimme, OP** startop, CV* outside, U32 seq, HV *hh)
 	PL_op = saveop;
 	if (yystatus != 3) {
 	    if (PL_eval_root) {
-		op_free(PL_eval_root);
+assert(CvROOT(evalcv) == PL_eval_root);
+/*		op_free(PL_eval_root);*/
 		PL_eval_root = NULL;
 	    }
 	    SP = PL_stack_base + POPMARK;	/* pop original mark */
 <at>  <at>  -3724,10 +3725,12  <at>  <at>  S_doeval(pTHX_ int gimme, OP** startop, CV* outside, U32 seq, HV *hh)
     }
     else if (!startop) LEAVE_with_name("evalcomp");
     CopLINE_set(&PL_compiling, 0);
+    assert(CvROOT(evalcv) == PL_eval_root);
     if (startop) {
 	*startop = PL_eval_root;
-    } else
-	SAVEFREEOP(PL_eval_root);
+	CvROOT(evalcv) = NULL;
+	CvSTART(evalcv) = NULL; /* XXX This leaks a slab. */
+    }

     DEBUG_x(dump_eval());

 <at>  <at>  -4389,11 +4392,6  <at>  <at>  PP(pp_leaveeval)
 				gimme, SVs_TEMP);
     PL_curpm = newpm;	/* Don't pop $1 et al till now */

-#ifdef DEBUGGING
-    assert(CvDEPTH(evalcv) == 1);
-#endif
-    CvDEPTH(evalcv) = 0;
-
     if (optype == OP_REQUIRE &&
 	!(gimme == G_SCALAR ? SvTRUE(*SP) : SP > newsp))
     {
diff --git a/proto.h b/proto.h
index 02bc3cc..c65e9cd 100644
--- a/proto.h
+++ b/proto.h
 <at>  <at>  -23,6 +23,15  <at>  <at>  PERL_CALLCONV int	Perl_Gv_AMupdate(pTHX_ HV* stash, bool destructing)
 	assert(stash)

 PERL_CALLCONV const char *	Perl_PerlIO_context_layers(pTHX_ const char *mode);
+PERL_CALLCONV void*	Perl_Slab_Alloc(pTHX_ size_t sz)
+			__attribute__malloc__
+			__attribute__warn_unused_result__;
+
+PERL_CALLCONV void	Perl_Slab_Free(pTHX_ void *op)
+			__attribute__nonnull__(pTHX_1);
+#define PERL_ARGS_ASSERT_SLAB_FREE	\
+	assert(op)
+
 PERL_CALLCONV bool	Perl__is_utf8__perl_idstart(pTHX_ const U8 *p)
 			__attribute__warn_unused_result__
 			__attribute__nonnull__(pTHX_1);
 <at>  <at>  -4977,6 +4986,18  <at>  <at>  STATIC I32	S_utf16_textfilter(pTHX_ int idx, SV *sv, int maxlen)

 #  endif
 #endif
+#if !defined(PL_OP_SLAB_ALLOC)
+PERL_CALLCONV void	Perl_Slab_Free_Slab(pTHX_ OPSLAB *slab, bool fast)
+			__attribute__nonnull__(pTHX_1);
+#define PERL_ARGS_ASSERT_SLAB_FREE_SLAB	\
+	assert(slab)
+
+PERL_CALLCONV void	Perl_op_free_root(pTHX_ OP* o)
+			__attribute__nonnull__(pTHX_1);
+#define PERL_ARGS_ASSERT_OP_FREE_ROOT	\
+	assert(o)
+
+#endif
 #if !defined(SETUID_SCRIPTS_ARE_SECURE_NOW)
 #  if defined(PERL_IN_PERL_C)
 STATIC void	S_validate_suid(pTHX_ PerlIO *rsfp)
 <at>  <at>  -5248,16 +5269,6  <at>  <at>  STATIC void	S_strip_return(pTHX_ SV *sv)
 #  endif
 #endif
 #if defined(PERL_DEBUG_READONLY_OPS)
-#  if defined(PERL_IN_OP_C)
-#    if defined(PL_OP_SLAB_ALLOC)
-STATIC void	S_Slab_to_rw(pTHX_ void *op)
-			__attribute__nonnull__(pTHX_1);
-#define PERL_ARGS_ASSERT_SLAB_TO_RW	\
-	assert(op)
-
-#    endif
-#  endif
-#  if defined(PL_OP_SLAB_ALLOC)
 PERL_CALLCONV PADOFFSET	Perl_op_refcnt_dec(pTHX_ OP *o)
 			__attribute__nonnull__(pTHX_1);
 #define PERL_ARGS_ASSERT_OP_REFCNT_DEC	\
 <at>  <at>  -5265,6 +5276,12  <at>  <at>  PERL_CALLCONV PADOFFSET	Perl_op_refcnt_dec(pTHX_ OP *o)

 PERL_CALLCONV OP *	Perl_op_refcnt_inc(pTHX_ OP *o);
 PERL_CALLCONV void	Perl_pending_Slabs_to_ro(pTHX);
+#  if defined(PERL_IN_OP_C)
+STATIC void	S_Slab_to_rw(pTHX_ void *op)
+			__attribute__nonnull__(pTHX_1);
+#define PERL_ARGS_ASSERT_SLAB_TO_RW	\
+	assert(op)
+
 #  endif
 #endif
 #if defined(PERL_DEFAULT_DO_EXEC3_IMPLEMENTATION)
 <at>  <at>  -7456,17 +7473,6  <at>  <at>  PERL_CALLCONV SV*	Perl_sv_setsv_cow(pTHX_ SV* dstr, SV* sstr)
 #if defined(PERL_USES_PL_PIDSTATUS) && defined(PERL_IN_UTIL_C)
 STATIC void	S_pidgone(pTHX_ Pid_t pid, int status);
 #endif
-#if defined(PL_OP_SLAB_ALLOC)
-PERL_CALLCONV void*	Perl_Slab_Alloc(pTHX_ size_t sz)
-			__attribute__malloc__
-			__attribute__warn_unused_result__;
-
-PERL_CALLCONV void	Perl_Slab_Free(pTHX_ void *op)
-			__attribute__nonnull__(pTHX_1);
-#define PERL_ARGS_ASSERT_SLAB_FREE	\
-	assert(op)
-
-#endif
 #if defined(UNLINK_ALL_VERSIONS)
 PERL_CALLCONV I32	Perl_unlnk(pTHX_ const char* f)
 			__attribute__nonnull__(pTHX_1);
diff --git a/regen/opcode.pl b/regen/opcode.pl
index d8186cd..1c15edc 100755
--- a/regen/opcode.pl
+++ b/regen/opcode.pl
 <at>  <at>  -46,6 +46,8  <at>  <at>  while (<OPS>) {
     warn qq[Description "$desc" duplicates $seen{$desc}\n]
      if $seen{$desc} and $key ne "transr";
     die qq[Opcode "$key" duplicates $seen{$key}\n] if $seen{$key};
+    die qq[Opcode "freed" is reserved for the slab allocator\n]
+	if $key eq 'freed';
     $seen{$desc} = qq[description of opcode "$key"];
     $seen{$key} = qq[opcode "$key"];

 <at>  <at>  -189,6 +191,7  <at>  <at>  for ( <at> ops) {
 print $on "\t", tab(3,"OP_max"), "\n";
 print $on "} opcode;\n";
 print $on "\n#define MAXO ", scalar  <at> ops, "\n";
+print $on "#define OP_FREED MAXO\n";

 # Emit op names and descriptions.

diff --git a/scope.h b/scope.h
index 74ebed9..ec78b95 100644
--- a/scope.h
+++ b/scope.h
 <at>  <at>  -177,7 +177,11  <at>  <at>  scope has the given name. Name must be a literal string.
 #define SAVEPADSVANDMORTALIZE(s)	save_padsv_and_mortalize(s)
 #define SAVEFREESV(s)	save_freesv(MUTABLE_SV(s))
 #define SAVEMORTALIZESV(s)	save_mortalizesv(MUTABLE_SV(s))
-#define SAVEFREEOP(o)	save_freeop((OP*)(o))
+#ifdef PL_OP_SLAB_ALLOC
+# define SAVEFREEOP(o)	save_freeop((OP*)(o))
+#else
+# define SAVEFREEOP(o)	NOOP
+#endif
 #define SAVEFREEPV(p)	save_freepv((char*)(p))
 #define SAVECLEARSV(sv)	save_clearsv((SV**)&(sv))
 #define SAVEGENERICSV(s)	save_generic_svref((SV**)&(s))
diff --git a/sv.c b/sv.c
index fcd76a9..549cad0 100644
--- a/sv.c
+++ b/sv.c
 <at>  <at>  -9026,13 +9026,15  <at>  <at>  Perl_sv_2cv(pTHX_ SV *sv, HV **const st, GV **const gvp, const I32 lref)
 	*st = GvESTASH(gv);
 	if (lref & ~GV_ADDMG && !GvCVu(gv)) {
 	    SV *tmpsv;
+	    I32 floor;
 	    ENTER;
 	    tmpsv = newSV(0);
 	    gv_efullname3(tmpsv, gv, NULL);
 	    /* XXX this is probably not what they think they're getting.
 	     * It has the same effect as "sub name;", i.e. just a forward
 	     * declaration! */
-	    newSUB(start_subparse(FALSE, 0),
+	    floor = start_subparse(FALSE, 0);
+	    newSUB(floor,
 		   newSVOP(OP_CONST, 0, tmpsv),
 		   NULL, NULL);
 	    LEAVE;

Gmane