Skip to content

Commit 7d3c8a6

Browse files
committed
Optimization: Remove needless list/pushmark pairs from the OP execution
This is an optimization for OP trees that involve list OPs in list context. In list context, the list OP's first child, a pushmark, will do what its name claims and push a mark to the mark stack, indicating the start of a list of parameters to another OP. Then the list's other child OPs will do their stack pushing. Finally, the list OP will be executed and do nothing but undo what the pushmark has done. This is because the main effect of the list OP only really kicks in if it's not in array context (actually, it should probably only kick in if it's in scalar context, but I don't know of any valid examples of list OPs in void contexts). This optimization is quite a measurable speed-up for array or hash slicing and some other situations. Another (contrived) example is that (1,2,(3,4)) now actually is the same, performance-wise as (1,2,3,4), albeit that's rarely relevant. The price to pay for this is a slightly convoluted (by standards other than the perl core) bit of optimization logic that has to do minor look-ahead on certain OPs in the peephole optimizer. A number of tests failed after the first attack on this problem. The failures were in two categories: a) Tests that are sensitive to details of the OP tree structure and did verbatim text comparisons of B::Concise output (ouch). These are just patched according to the new red in this commit. b) Test that validly failed because certain conditions in op.c were expecting OP_LISTs where there are now OP_NULLs (with op_targ=OP_LIST). For these, the respective conditions in op.c were adjusted. The change includes modifying B::Deparse to handle the new OP tree structure in the face of nulled OP_LISTs.
1 parent 11ee9dd commit 7d3c8a6

File tree

7 files changed

+133
-58
lines changed

7 files changed

+133
-58
lines changed

embed.fnc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1870,6 +1870,7 @@ sR |OP* |newDEFSVOP
18701870
sR |OP* |search_const |NN OP *o
18711871
sR |OP* |new_logop |I32 type|I32 flags|NN OP **firstp|NN OP **otherp
18721872
s |void |simplify_sort |NN OP *o
1873+
s |void |null_listop_in_list_context |NN OP* o
18731874
s |SV* |gv_ename |NN GV *gv
18741875
sRn |bool |scalar_mod_type|NULLOK const OP *o|I32 type
18751876
s |OP * |my_kid |NULLOK OP *o|NULLOK OP *attrs|NN OP **imopsp

embed.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,7 @@
14781478
#define new_logop(a,b,c,d) S_new_logop(aTHX_ a,b,c,d)
14791479
#define no_bareword_allowed(a) S_no_bareword_allowed(aTHX_ a)
14801480
#define no_fh_allowed(a) S_no_fh_allowed(aTHX_ a)
1481+
#define null_listop_in_list_context(a) S_null_listop_in_list_context(aTHX_ a)
14811482
#define op_integerize(a) S_op_integerize(aTHX_ a)
14821483
#define op_std_init(a) S_op_std_init(aTHX_ a)
14831484
#define pmtrans(a,b,c) S_pmtrans(aTHX_ a,b,c)

ext/B/t/f_map.t

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,18 @@ checkOptree(note => q{},
101101
# 8 <0> enter l
102102
# 9 <;> nextstate(main 475 (eval 10):1) v:{
103103
# a <0> pushmark s
104-
# b <0> pushmark s
105-
# c <#> gvsv[*_] s
106-
# d <#> gv[*getkey] s/EARLYCV
107-
# e <1> entersub[t5] lKS/TARG
108-
# f <#> gvsv[*_] s
109-
# g <@> list lK
110-
# h <@> leave lKP
104+
# b <#> gvsv[*_] s
105+
# c <#> gv[*getkey] s/EARLYCV
106+
# d <1> entersub[t5] lKS/TARG
107+
# e <#> gvsv[*_] s
108+
# f <@> leave lKP
111109
# goto 7
112-
# i <0> pushmark s
113-
# j <#> gv[*hash] s
114-
# k <1> rv2hv[t2] lKRM*/1 < 5.019006
115-
# k <1> rv2hv lKRM*/1 >=5.019006
116-
# l <2> aassign[t10] KS/COMMON
117-
# m <1> leavesub[1 ref] K/REFC,1
110+
# g <0> pushmark s
111+
# h <#> gv[*hash] s
112+
# i <1> rv2hv[t2] lKRM*/1 < 5.019006
113+
# i <1> rv2hv lKRM*/1 >=5.019006
114+
# j <2> aassign[t10] KS/COMMON
115+
# k <1> leavesub[1 ref] K/REFC,1
118116
EOT_EOT
119117
# 1 <;> nextstate(main 560 (eval 15):1) v:{
120118
# 2 <0> pushmark s
@@ -127,20 +125,18 @@ EOT_EOT
127125
# 8 <0> enter l
128126
# 9 <;> nextstate(main 559 (eval 15):1) v:{
129127
# a <0> pushmark s
130-
# b <0> pushmark s
131-
# c <$> gvsv(*_) s
132-
# d <$> gv(*getkey) s/EARLYCV
133-
# e <1> entersub[t2] lKS/TARG
134-
# f <$> gvsv(*_) s
135-
# g <@> list lK
136-
# h <@> leave lKP
128+
# b <$> gvsv(*_) s
129+
# c <$> gv(*getkey) s/EARLYCV
130+
# d <1> entersub[t2] lKS/TARG
131+
# e <$> gvsv(*_) s
132+
# f <@> leave lKP
137133
# goto 7
138-
# i <0> pushmark s
139-
# j <$> gv(*hash) s
140-
# k <1> rv2hv[t1] lKRM*/1 < 5.019006
141-
# k <1> rv2hv lKRM*/1 >=5.019006
142-
# l <2> aassign[t5] KS/COMMON
143-
# m <1> leavesub[1 ref] K/REFC,1
134+
# g <0> pushmark s
135+
# h <$> gv(*hash) s
136+
# i <1> rv2hv[t1] lKRM*/1 < 5.019006
137+
# i <1> rv2hv lKRM*/1 >=5.019006
138+
# j <2> aassign[t5] KS/COMMON
139+
# k <1> leavesub[1 ref] K/REFC,1
144140
EONT_EONT
145141

146142

ext/B/t/optree_samples.t

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -475,20 +475,18 @@ checkOptree ( name => '%h = map { getkey($_) => $_ } @a',
475475
# 8 <0> enter l
476476
# 9 <;> nextstate(main 500 (eval 22):1) v:{
477477
# a <0> pushmark s
478-
# b <0> pushmark s
479-
# c <#> gvsv[*_] s
480-
# d <#> gv[*getkey] s/EARLYCV
481-
# e <1> entersub[t5] lKS/TARG
482-
# f <#> gvsv[*_] s
483-
# g <@> list lK
484-
# h <@> leave lKP
478+
# b <#> gvsv[*_] s
479+
# c <#> gv[*getkey] s/EARLYCV
480+
# d <1> entersub[t5] lKS/TARG
481+
# e <#> gvsv[*_] s
482+
# f <@> leave lKP
485483
# goto 7
486-
# i <0> pushmark s
487-
# j <#> gv[*h] s
488-
# k <1> rv2hv[t2] lKRM*/1 < 5.019006
489-
# k <1> rv2hv lKRM*/1 >=5.019006
490-
# l <2> aassign[t10] KS/COMMON
491-
# m <1> leavesub[1 ref] K/REFC,1
484+
# g <0> pushmark s
485+
# h <#> gv[*h] s
486+
# i <1> rv2hv[t2] lKRM*/1 < 5.019006
487+
# i <1> rv2hv lKRM*/1 >=5.019006
488+
# j <2> aassign[t10] KS/COMMON
489+
# k <1> leavesub[1 ref] K/REFC,1
492490
EOT_EOT
493491
# 1 <;> nextstate(main 501 (eval 22):1) v:{
494492
# 2 <0> pushmark s
@@ -501,20 +499,18 @@ EOT_EOT
501499
# 8 <0> enter l
502500
# 9 <;> nextstate(main 500 (eval 22):1) v:{
503501
# a <0> pushmark s
504-
# b <0> pushmark s
505-
# c <$> gvsv(*_) s
506-
# d <$> gv(*getkey) s/EARLYCV
507-
# e <1> entersub[t2] lKS/TARG
508-
# f <$> gvsv(*_) s
509-
# g <@> list lK
510-
# h <@> leave lKP
502+
# b <$> gvsv(*_) s
503+
# c <$> gv(*getkey) s/EARLYCV
504+
# d <1> entersub[t2] lKS/TARG
505+
# e <$> gvsv(*_) s
506+
# f <@> leave lKP
511507
# goto 7
512-
# i <0> pushmark s
513-
# j <$> gv(*h) s
514-
# k <1> rv2hv[t1] lKRM*/1 < 5.019006
515-
# k <1> rv2hv lKRM*/1 >=5.019006
516-
# l <2> aassign[t5] KS/COMMON
517-
# m <1> leavesub[1 ref] K/REFC,1
508+
# g <0> pushmark s
509+
# h <$> gv(*h) s
510+
# i <1> rv2hv[t1] lKRM*/1 < 5.019006
511+
# i <1> rv2hv lKRM*/1 >=5.019006
512+
# j <2> aassign[t5] KS/COMMON
513+
# k <1> leavesub[1 ref] K/REFC,1
518514
EONT_EONT
519515

520516
checkOptree ( name => '%h=(); for $_(@a){$h{getkey($_)} = $_}',

lib/B/Deparse.pm

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3212,13 +3212,23 @@ sub pp_leavetry {
32123212
return "eval {\n\t" . $self->pp_leave(@_) . "\n\b}";
32133213
}
32143214

3215+
sub _op_is_or_was {
3216+
my ($op, $expect_type) = @_;
3217+
my $type = $op->type;
3218+
return($type == $expect_type
3219+
|| ($type == OP_NULL && $op->targ == $expect_type));
3220+
}
3221+
32153222
sub pp_null {
32163223
my $self = shift;
32173224
my($op, $cx) = @_;
32183225
if (class($op) eq "OP") {
32193226
# old value is lost
32203227
return $self->{'ex_const'} if $op->targ == OP_CONST;
3221-
} elsif ($op->first->name eq "pushmark") {
3228+
} elsif ($op->first->name eq 'pushmark'
3229+
or $op->first->name eq 'null'
3230+
&& $op->first->targ == OP_PUSHMARK
3231+
&& _op_is_or_was($op, OP_LIST)) {
32223232
return $self->pp_list($op, $cx);
32233233
} elsif ($op->first->name eq "enter") {
32243234
return $self->pp_leave($op, $cx);

op.c

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,11 @@ S_scalar_slice_warning(pTHX_ const OP *o)
12281228
case OP_RVALUES:
12291229
return;
12301230
}
1231+
1232+
/* Don't warn if we have a nulled list either. */
1233+
if (kid->op_type == OP_NULL && kid->op_targ == OP_LIST)
1234+
return;
1235+
12311236
assert(kid->op_sibling);
12321237
name = S_op_varname(aTHX_ kid->op_sibling);
12331238
if (!name) /* XS module fiddling with the op tree */
@@ -1953,10 +1958,13 @@ S_finalize_op(pTHX_ OP* o)
19531958
S_scalar_slice_warning(aTHX_ o);
19541959

19551960
case OP_KVHSLICE:
1961+
kid = cLISTOPo->op_first->op_sibling;
19561962
if (/* I bet there's always a pushmark... */
1957-
(kid = cLISTOPo->op_first->op_sibling)->op_type != OP_LIST
1958-
&& kid->op_type != OP_CONST)
1963+
OP_TYPE_ISNT_AND_WASNT_NN(kid, OP_LIST)
1964+
&& OP_TYPE_ISNT_NN(kid, OP_CONST))
1965+
{
19591966
break;
1967+
}
19601968

19611969
key_op = (SVOP*)(kid->op_type == OP_CONST
19621970
? kid
@@ -5803,7 +5811,7 @@ Perl_newASSIGNOP(pTHX_ I32 flags, OP *left, I32 optype, OP *right)
58035811
(state $a, my $b, our $c, $d, undef) = ... */
58045812
}
58055813
} else if (lop->op_type == OP_UNDEF ||
5806-
lop->op_type == OP_PUSHMARK) {
5814+
OP_TYPE_IS_OR_WAS(lop, OP_PUSHMARK)) {
58075815
/* undef may be interesting in
58085816
(state $a, undef, state $c) */
58095817
} else {
@@ -9661,7 +9669,7 @@ Perl_ck_sassign(pTHX_ OP *o)
96619669
/* For state variable assignment, kkid is a list op whose op_last
96629670
is a padsv. */
96639671
if ((kkid->op_type == OP_PADSV ||
9664-
(kkid->op_type == OP_LIST &&
9672+
(OP_TYPE_IS_OR_WAS(kkid, OP_LIST) &&
96659673
(kkid = cLISTOPx(kkid)->op_last)->op_type == OP_PADSV
96669674
)
96679675
)
@@ -11144,6 +11152,26 @@ S_inplace_aassign(pTHX_ OP *o) {
1114411152
#define IS_AND_OP(o) (o->op_type == OP_AND)
1114511153
#define IS_OR_OP(o) (o->op_type == OP_OR)
1114611154

11155+
STATIC void
11156+
S_null_listop_in_list_context(pTHX_ OP *o)
11157+
{
11158+
PERL_ARGS_ASSERT_NULL_LISTOP_IN_LIST_CONTEXT;
11159+
11160+
/* This is an OP_LIST in list context. That means we
11161+
* can ditch the OP_LIST and the OP_PUSHMARK within. */
11162+
11163+
OP *kid = cLISTOPo->op_first;
11164+
/* Find the end of the chain of OPs executed within the OP_LIST. */
11165+
while (kid->op_next != o) {
11166+
assert(kid);
11167+
kid = kid->op_next;
11168+
}
11169+
11170+
kid->op_next = o->op_next; /* patch list out of exec chain */
11171+
op_null(cUNOPo->op_first); /* NULL the pushmark */
11172+
op_null(o); /* NULL the list */
11173+
}
11174+
1114711175
/* A peephole optimizer. We visit the ops in the order they're to execute.
1114811176
* See the comments at the top of this file for more details about when
1114911177
* peep() is called */
@@ -11176,6 +11204,44 @@ Perl_rpeep(pTHX_ OP *o)
1117611204
clear this again. */
1117711205
o->op_opt = 1;
1117811206
PL_op = o;
11207+
11208+
11209+
/* The following will have the OP_LIST and OP_PUSHMARK
11210+
* patched out later IF the OP_LIST is in list context.
11211+
* So in that case, we can set the this OP's op_next
11212+
* to skip to after the OP_PUSHMARK:
11213+
* a THIS -> b
11214+
* d list -> e
11215+
* b pushmark -> c
11216+
* c whatever -> d
11217+
* e whatever
11218+
* will eventually become:
11219+
* a THIS -> c
11220+
* - ex-list -> -
11221+
* - ex-pushmark -> -
11222+
* c whatever -> e
11223+
* e whatever
11224+
*/
11225+
{
11226+
OP *sibling;
11227+
OP *other_pushmark;
11228+
if (OP_TYPE_IS(o->op_next, OP_PUSHMARK)
11229+
&& (sibling = o->op_sibling)
11230+
&& sibling->op_type == OP_LIST
11231+
/* This KIDS check is likely superfluous since OP_LIST
11232+
* would otherwise be an OP_STUB. */
11233+
&& sibling->op_flags & OPf_KIDS
11234+
&& (sibling->op_flags & OPf_WANT) == OPf_WANT_LIST
11235+
&& (other_pushmark = cLISTOPx(sibling)->op_first)
11236+
/* Pointer equality also effectively checks that it's a
11237+
* pushmark. */
11238+
&& other_pushmark == o->op_next)
11239+
{
11240+
o->op_next = other_pushmark->op_next;
11241+
null_listop_in_list_context(sibling);
11242+
}
11243+
}
11244+
1117911245
switch (o->op_type) {
1118011246
case OP_DBSTATE:
1118111247
PL_curcop = ((COP*)o); /* for warnings */
@@ -11538,7 +11604,7 @@ Perl_rpeep(pTHX_ OP *o)
1153811604
*/
1153911605
assert(followop);
1154011606
if (gimme == OPf_WANT_VOID) {
11541-
if (followop->op_type == OP_LIST
11607+
if (OP_TYPE_IS_OR_WAS(followop, OP_LIST)
1154211608
&& gimme == (followop->op_flags & OPf_WANT)
1154311609
&& ( followop->op_next->op_type == OP_NEXTSTATE
1154411610
|| followop->op_next->op_type == OP_DBSTATE))

proto.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6123,6 +6123,11 @@ STATIC OP* S_no_fh_allowed(pTHX_ OP *o)
61236123
#define PERL_ARGS_ASSERT_NO_FH_ALLOWED \
61246124
assert(o)
61256125

6126+
STATIC void S_null_listop_in_list_context(pTHX_ OP* o)
6127+
__attribute__nonnull__(pTHX_1);
6128+
#define PERL_ARGS_ASSERT_NULL_LISTOP_IN_LIST_CONTEXT \
6129+
assert(o)
6130+
61266131
PERL_STATIC_INLINE OP* S_op_integerize(pTHX_ OP *o)
61276132
__attribute__nonnull__(pTHX_1);
61286133
#define PERL_ARGS_ASSERT_OP_INTEGERIZE \

0 commit comments

Comments
 (0)