OP_SORT: store start of block in null->op_next
authorDavid Mitchell <davem@iabyn.com>
Wed, 5 Mar 2014 14:44:41 +0000 (14:44 +0000)
committerDavid Mitchell <davem@iabyn.com>
Sun, 16 Mar 2014 18:34:37 +0000 (18:34 +0000)
When a sort with a code block, like sort { BLOCK } arg, ...
is compiled, it comes out like

     sort
        pushmark
        null
           scope
              BLOCK
        arg
        ...

(The 'scope' may be instead be 'ex-leave' depending on circumstances).

At run time, pp_sort() navigates its way down from the sort op to find the
start op of the BLOCK. We can shorten this process slightly by storing the
start of BLOCK in the otherwise unused op_next field of the OP_NULL.
Effectively we are using the null->op_next field as a surrogate op_other
field for the op_sort (which doesn't have a spare field we could store
the pointer in).

The main point of this commit however is not the slight speed up from
skipping a couple of pointer follows at run-time; rather that it will
shortly allow us to trim any null ops from the beginning of the BLOCK. We
can't do this directly, as that would involve changing the scope->op_first
pointer, which might confuse B:: type modules.

op.c
pp_sort.c

diff --git a/op.c b/op.c
index 10d7da0..4f3c570 100644 (file)
--- a/op.c
+++ b/op.c
@@ -11899,11 +11899,25 @@ Perl_rpeep(pTHX_ OP *o)
            if (o->op_flags & OPf_SPECIAL) {
                 /* first arg is a code block */
                OP * const nullop = cLISTOP->op_first->op_sibling;
-                OP * const kid    = cUNOPx(nullop)->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));
-                DEFER(kLISTOP->op_first);
+                /* 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_type == OP_STUB
+                    || kid->op_type == OP_ENTER);
+                nullop->op_next = kLISTOP->op_next;
+                DEFER(nullop->op_next);
            }
 
            /* check that RHS of sort is a single plain array */
index 4741d71..0fe0411 100644 (file)
--- a/pp_sort.c
+++ b/pp_sort.c
@@ -1512,10 +1512,9 @@ PP(pp_sort)
     SAVEVPTR(PL_sortcop);
     if (flags & OPf_STACKED) {
        if (flags & OPf_SPECIAL) {
-           OP *kid = cLISTOP->op_first->op_sibling;    /* pass pushmark */
-           kid = kUNOP->op_first;                      /* pass rv2gv */
-           kid = kUNOP->op_first;                      /* pass leave */
-           PL_sortcop = kid->op_next;
+           OP *nullop = cLISTOP->op_first->op_sibling; /* pass pushmark */
+            assert(nullop->op_type == OP_NULL);
+           PL_sortcop = nullop->op_next;
        }
        else {
            GV *autogv = NULL;