develooper Front page | perl.perl5.porters | Postings from March 2015

Re: OP_SIGNATURE

Thread Previous | Thread Next
From:
Dave Mitchell
Date:
March 10, 2015 18:54
Subject:
Re: OP_SIGNATURE
Message ID:
20150310185350.GK28599@iabyn.com
Ok everyone, how about this compromise proposal:

I'm thinking in terms of accommodating the suggestion that the 'pre-peep'
API should be 'firmer' than the post-peep API, with funky ops like
OP_SIGNATURE only emitted at the peephole stage.

Suppose we create some new "API" ops, whose individual actions are short
and constrained, and typically act on only a single parameter. Things
along the lines of what was suggested earlier, e.g. ops to check the arity
of @_, assign an arg with an integer default, slurp an array etc. (exact
details of the ops still to be designed).

The signature parsing code in Perl_parse_subsignature() would emit these
intermediate-level ops, rather than the low-level ops of the existing
implementation or the high-level OP_SIGNATURE op of my branch.
Since such ops will be generally emitted per param (with the exception of
checkarity), that still allows a hypothetical sig parser hook to be be
called at the param level rather than the sig level.

Once the peephole optimiser is called, if it sees a series of these
mid-level ops that conform to something OP_SIGNATURE can handle more
efficiently, it replaces them with a single OP_SIGNATURE op. Similarly,
a series of lower-level ops that look like my (...) = @_ get replaced
with a single OP_SIGNATURE.

In addition, peep() will convert otherwise unoptimised short sequences of
low-level ops into those mid-range ones where appropriate. So for
argument's sake, $x = shift // 1 and similar get converted.

Also, it occurred to me that OP_SIGNATURE could probably be easily
enhanced with a "shift" action that shifts @_ N places; it could then
handle these common types of param processing too:

    my $self = shift; my (...rest...) = @_;

    my $a = shift; my $b = shift; my $c = shift;


So in summary for various classes of param handling:

    sub f (.....) {}

        Initially compiled as lots of OP_CHECKARITY, OP_SETPARAM etc
        ops; converted during peepholing into a single OP_SIGNATURE if
        possible, otherwise left as lots of OP_CHECKARITY etc.

    my ($a, $b, $c) = @_;

    my $self = shift; my (...rest...) = @_;

    my $a = shift; my $b = shift; my $c = shift;

    etc

        Initially compiled as lots of pushmark, padsv, shift ops etc.
        Converted during peepholing  into OP_SIGNATURE where possible.

    roll-your-own default handling, e.g.
    
    my $a = shift // 1;

        Initially compiled as lots of pushmark, padsv, shift ops etc.
        Where not converted into OP_SIGNATURE as above, partially
        consolidated during peepholing  into a small number of
        OP_SETPARAM etc ops.



Some thoughts and considerations:

In terms of the design of these new ops, it seems to me that there are two
main categories: ops that implicitly use @_, and more general ops. The
former will be more compact and efficient, but will more specialised in
that they would typically only optimise constructs likely to be found near
the start of a sub.  The latter would likely have to have extra helper ops
to push the more general AV onto the stack first; for example either an
existing gv(*foo) + rv2av pair, or a hypothetical new gvav op, so would be
more general purpose but less efficient.

We need to decide whether we want ops of the 'retrieve param N' type, or
of the 'shift @_ 1 place' type (or both).

People should remember that there is relatively little information that
you can attach to an individual op. Typically, 1 lexical target, one SV
pointer (or on threaded builds, an index to an SV stored in the pad), plus
8 bits in op_private to use for flags and/or a small integer.  Thus an op
that does something like my $x = $_[3] // "foo" could probably just about
work by having op_targ point to $x, op_sv point to the const "foo" SV, and
the index 3 stored in op_private (assuming its < 256 and that you don't
need any other flags). But anything more fancy will require more ops.

This op scheme might preclude a later option of skipping setting up and
breaking down @_. This is a considerable part of the overhead of
pp_entersub / pp_leavesub, and I could envisage (either specified by new
syntax, or by fiat) situations where we would like to enter the sub with
the args still on the stack rather than being put into @_.  With
OP_SIGNATURE,handling this would be trivial.

I've also given a lot of thought to whether OP_SIGNATURE can be used in
places other than at the start of the sub, and whether it could use arrays
other than @_.  I've come to the conclusion that it can't.  The basic
issue is that if OP_SIGNATURE is expanded to handle all the extra cases
that pp_aassign() etc handles (such as @slurpy not being empty, @_ being
tied, common vars on both side etc - see my recent OPpASSIGN_COMMON
thread) then it will become as slow and bloated as pp_aassign(), which I
note, compiles to more than twice the size of pp_signature().

This is not to say that OP_SIGNATURE must be exactly positioned as
CvSTART(cv)->op_next and CvROOT(cv)->op_first->op_first->op_sibling
in the op-tree (I intend to fix Deparse to not assume this), but that any
code which is injected ahead of the op must be very limited in what it
does (e.g. not calling a closure that populates any of the param
lexicals). In fact I would assume that the peepholer would avoid
converting to OP_SIGNATURE unless the candidate code *was* at the start of
the sub.

But note that this just means that hooks etc that mess with the start of
the sub's optree just causes OP_SIGNATURE generation to be skipped (rather
than OP_SIGNATURE precluding hooks).

Note that I've only just come up with this idea and haven't thought it
through fully (note the handwaving about new op types). I reserve the right
to withdraw this proposal if I come to the conclusion it has more
downsides than I initially realised.

PS:

I hope that this post demonstrates that various strawmen written about me
are false; specifically:

*   That I have a monomania about speed and don't care about good
    architecture design etc. I do care about both, its just that my
    bias is somewhat more towards speed, and am prepared to accept some
    ugliness if the speed tradeoff is good enough and general enough.
    (Which is probably a good summary of about 90% of the perl core code.)

    For example at one point I wrote

        Yes, I'm not keen on them [SIGNATURE and MULTIDEREF], but in a
        few select cases they can make a major performance difference not
        achievable by any other method.

    which was (bizzarely) interpreted to mean that I wanted to convert
    most of the core to similar such ops.

*   That I don't care in the slightest about API compatibility, and want
    to get rid of B etc: I don't like B and think that perl5 would have
    overall been better off without it, but since we're lumbered with it,
    I fully support and maintain it: I have over 50 commits under ext/B/
    and lib/B to my name. I don't gratuitously break things in the optree,
    but I am prepared to change the details of OPs and optrees if the gain
    seems worth it, and regard it as a cost that must to be borne by
    people who write modules that directly mess with optree internals.


-- 
You're only as old as you look.

Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About