re-implement boolean context detection
authorDavid Mitchell <davem@iabyn.com>
Wed, 4 Jan 2017 20:27:55 +0000 (20:27 +0000)
committerDavid Mitchell <davem@iabyn.com>
Fri, 6 Jan 2017 16:28:27 +0000 (16:28 +0000)
commitb0e8c18f9f49fea18c28b17e25b09dc7e7244da8
treeab3a2c9a600b8297e4e38afb94d7594aed79eb2c
parent7adc03cc2c3385edc73d0522a46a24e1eeda3a27
re-implement boolean context detection

When certain ops are used in a boolean context (currently just PADHV and
RV2SV, implementing '%hash'), one of the private flags OPpTRUEBOOL or
OPpMAYBE_TRUEBOOL is set on the op to indicate this; at
runtime, the pp function can then just return a boolean value rather than
 a full scalar value (in the case of %hash, an element count).

However, the code which sets these flags has had a complex history, and is
a bit messy. It also sets the flags incorrectly (but safely) in many
cases: principally indicating boolean context when it's in fact void, or
scalar context when it's in fact boolean. Both these permutations make the
code potentially slower (but still correct).

[ As a side-note: in 5.25, a bare %hash in scalar context changed from
returning a bucket count etc, to just returning a key count, which is
quicker to calculate. So the boolean optimisation for %hash is not nearly
as important now: it's now just the overhead of creating a temp to return
a count verses returning &PL_sv_yes, rather than counting keys. However
the improved and generalised boolean context detection added by this
commit will be useful in future to apply boolean context to other ops. ]

In particular, this wasn't being optimised (i.e. a 'not' of a hash within
an if):

    if (!%hash) { ...}

This commit fixes all these cases (and uncomments a load of failing tests
in t/perf/optree.t which were added in the previous commit.)

It makes the code below nearly 3 times faster:

    my $c; my %h = 1..10;
    for my $i (1..10_000_000) { if (!%h) { $c++ }; }

It restructures the relevant code in rpeep() so that rather than switching
on logops like OP_OR, then seeing if that op is preceded by PADHV/RV2HV,
it instead switches on PADHV/RV2HV then sees if succeeding ops impose
boolean context on that op - that is to say, in all possible execution
paths after the PADHV/RV2HV pushes a scalar onto the stack, will that
scalar only ever be used for a boolean test? (*).

The scanning of succeeding ops is extracted out into a static function.
This will make it very easy in future to apply boolean context to other
ops too, or to expand the definition of boolean context (e.g. adding
'xor').

(*) Although in theory an expression like (A && B) can return A if A is
false, if A happens to be %hash, and as long as pp_padhv() etc return
a boolean false value that is also usable in scalar context (so it returns
0 rather than PL_sv_no), then we can pretend that OP_AND's LH arg is
never used as a scalar.
op.c
pp.c
pp_hot.c
t/perf/optree.t