develooper Front page | perl.perl5.porters | Postings from December 2021

Re: "no snails"; or having signatured subs complain about @_

Thread Previous | Thread Next
Nicholas Clark
December 5, 2021 20:39
Re: "no snails"; or having signatured subs complain about @_
Message ID:
On Mon, Nov 29, 2021 at 04:08:54PM +0000, Paul "LeoNerd" Evans wrote:
> The final part of the "signatures" feature still to be implemented, is
> getting rid of the @_ setup as part of OP_ENTERSUB when entering a
> signature'd sub, for the speed benefit it gives. I've been thinking
> about how to implement this.

Consistent with what I wrote in the other mail a few minutes ago, I don't
think that it needs to be fully *implemented*. The final fast version
needs to be designed (well enough), which tells us what the language level
restrictions need to be, but all that needs implementing are those

The goal here isn't to make it perfect for v5.36.0

The goal is to get something that won't regress, such that it's good enough
to leave experimental, and we can build on it.

> It seems to me we need four pieces. The first two of these should be
> quite simple:
>    1. Add a flag (maybe CvSIGNATURE(cv)) on CVs that is present when
>       the sub has a signature. The parser can easily turn this one on
>       at the time it creates the CV.
>    2. Get pp_entersub to check that flag, and skip setting up the
>       callee's @_ array if set. -- this is the core of the speedup.
>    3. {handwavy something} to ensure that OP_ARGCHECK and OP_ARGELEM
>       can look somewhere else to find the arguments, now they can no
>       longer use GvAV(PL_defgv).
>    4. {handwavy something else} to make accesses of @_ throw an error
>       at runtime.


5. pp_caller can still set up @DB::args if it needs to

I think that (2), (3) and (5) are all possible if pp_entersub operates in
two modes:

legacy:     set up @_, reset the stack pointer
signatures: leave the arguments on the stack

and we make sure that we store enough state that OP_ARGCHECK, OP_ARGLEN
and OP_CALLER all know where to look (and how many items to look for)

But that's the "fully implemented" version. For now, I think we take this

            AV *const av = MUTABLE_AV(PAD_SVl(0));
            SSize_t items;
            AV **defavp;

            defavp = &GvAV(PL_defgv);
            cx->blk_sub.savearray = *defavp;
            *defavp = MUTABLE_AV(SvREFCNT_inc_simple_NN(av));

            /* it's the responsibility of whoever leaves a sub to ensure
             * that a clean, empty AV is left in pad[0]. This is normally
             * done by cx_popsub() */
            assert(!AvREAL(av) && AvFILLp(av) == -1);

            items = SP - MARK;
            if (UNLIKELY(items - 1 > AvMAX(av))) {
                SV **ary = AvALLOC(av);
                Renew(ary, items, SV*);
                AvMAX(av) = items - 1;
                AvALLOC(av) = ary;
                AvARRAY(av) = ary;

            if (items)
            AvFILLp(av) = items - 1;

and keep it setting up everything *except* for the assignment here:

            *defavp = MUTABLE_AV(SvREFCNT_inc_simple_NN(av));

instead having GvAV(PL_defgv) pointing to a tied array singleton that is
built to croak.

and we change OP_ARGCHECK, OP_ARGLEN and OP_CALLER to look at PAD_SVl(0)
instead of GvAV(PL_defgv) to find the (hidden) @_

> Part 2 is the main reason *why* to do this. Part 1 is the logic to make
> it apply at the right time. Those are the easy bits.
> Far less clear is how to do parts 3 and 4. In particular, for part 3 I
> have the following thoughts on possible ways to implement it:
>    3a. Don't set up @_ but instead move the stack values over to some
>        other temporary place for the OP_ARG* ops to read from. Since
>        that has to be local'ised in case of recursion into another
>        signatured sub during argument unpack in practice this is going
>        to perform about the same amount of work as the current
>        GvAV(PL_defgv) implementation anyway, and thus be about the same
>        speed. We gain nothing.

Agree, but effectively I think we *should* take this approach as the first
step. Later (5.37.1 or 5.37.2) we should then eliminate that hidden @_ by
optimising to roughly this one:

>    3c. A variant of 3b, which involves adding a new interpreter pointer
>        like SP, which points at the start of the stack arguments. Lets
>        steal x86's naming and call it the "frame pointer". When
>        entering a CV with CvSIGNATURE(), pp_entersub would set
>        FP = TOPMARK and jump into the optree. Thereafter, the OP_ARG*
>        ops can use the frame pointer to work:
>        In OP_ARGCHECK:
>          argc = SP - FP + 1;
>        In OP_ARGELEM:
>          SV *arg = FP[argi];
>        This would be somewhat less fragile than 3b because it doesn't
>        rely on the TOPMARK being preserved. It lets us set aside a
>        storage area for passed arguments that should be much more
>        robust than in 3b, without needing to copy them aside as we do
>        now, or with 3a.
>        It is still one more pointer value that has to be saved/restored
>        around every function invocation (and likely stored on the
>        context stack), but it's nowhere near as much work to do that as
>        the current work around @_.

but I *think* we don't use SP or your FP. Offhand I don't know how much
of the stack state is stored in the various context structs, but

1) the stack has to be able to roll back when the subroutine exits by any
   means, implying that the saved stack pointer is known
2) we have PAD_SVl(0) to store something in :-)

so I think we can effectively get the two pointers we need that bound the
flat list of arguments on the stack. But they won't be called SP and FP.

> And finally I don't really have much thought on how to go about part 4,
> other than the vague thinking that adding a new AV flag just to put one
> on @_ in a signature'd sub, and then have every AV access check it, is
> probably going to be a bit slow as it makes every AV access slow.

I think a singleton AV with everything tied (implemented in XS) that croaks
would go a long way.

Also Aristotle suggested that under (what we are calling) `no snails`,
@_ should be disallowed under strict 'vars'. This message I think:

This seems quite elegant (from a user reporting sense) - code would do
something like:

    Global symbol "@_" requires explicit package name (did you forget to declare "my @_"?) at -e line 1.

but I realise that it does break other "rules":

$ perl -e 'use strict; my @_;'
Can't use global @_ in "my" at -e line 1, near "my @_"
Execution of -e aborted due to compilation errors.

All of $_, @_, %_ are deemed to be a global, and not permitted to be
lexicals. So to implementing it *exactly* like a strict vars violation
seems like adding more special cases to the Jenga tower.

Whereas "just" disallowing @_ (independent of strict, but implemented with/
hacked into the same code as strict vars checking) might be good enough.

But I'm feeling more unsure on this part than the first part of this mail -
I think the way forward is 3a now, 3c next release.

Nicholas Clark

Thread Previous | Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About