Prototypes and the call checker

Perl prototypes are fascinating. They enable: making argument count enforceable at compile time; adding an implicit argument; changing how a list of arguments are parsed; changing how individual arguments are parsed; and even allowing Perl to optimize away a call to that function. Unfortunately, it's also impossible to count on any of these things; those changes only take effect if the sub can be reliably looked up when the call to it is being compiled, and since calling a sub with a & specifically prevents doing that look up in the first place.

That's relatively common knowledge. There's another way to disable prototype handling, or rather, to replace prototype handling: the call checker. The prototype handler is the default call checker for any sub when its created, and its the only one that comes built into Perl, but it's possible to provide a replacement and attach it to individual subs of your choosing. The call checker takes affect when determining what to do when each sub is called - subject to the existing limitations of prototypes - and that enables a new class of optimizations.

The last example given for prototypes above, is generally thought of a way to give a name to a constant, but what it's actually doing is declaring a sub that's constrained enough that it's legal for it to be inlined. In other words, because sub CONSTANT() { 5; } has a prototype that makes it illegal to give it arguments, and because the body of the sub is just a single constant, there's no way for that sub to have any side-effects; and since it can't have any side effects, it's possible to skip calling the sub altogether and replace any calls to it with the number 5.

The core limits that optimization to the case of an empty prototype and a sub with only a constant (roughly...) in its call checker, but since that call checker can be overridden, it's possible for a CPAN module to do the same thing but with a more expansive definition for what can be inlined. I'm working on a module to do this now and will post more on it as it develops.

It also enables having something like sub debug { print STDERR ... }, where all calls to debug are not just no-ops, but don't even exist in the compiled version of the program that's being executed. I don't know of a CPAN module for this, and would be more than happy if someone could point me to one that does this in the comments!

Examples

  1. sub foo($$){} foo 1, 2, 3; # Errors during compilation
  2. sub foo(){} foo; # Equivalent to foo($); <
  3. sub foo($) foo bar, baz; # Equivalent to foo(bar), baz();
  4. sub foo(\@\@){} foo @bar, @baz; # Equivalent to foo(\@bar, \@baz){} instead of push @arglist, @foo; push @arglist, @bar; foo(@arglist){}
  5. sub foo(){ 5;} $bar = foo; # Equivalent to writing "$bar = 5"

7 Comments

I had a stab at something with a similar end result, but very different implementation a while back. It's on a GitHub Gist.

I might have another go using Keyword::Simple instead of Keyword::Parse. The call checker is probably a saner place to do it though.

With Debuggit, calls debug statements don't end up in the compiled version of the program, when you turn DEBUG off: "any conditional based on the DEBUG constant will actually be removed during compile-time if the debugging level isn't high enough" (from Debuggit::Manual).

The link to ex5 is broken.

Another typo in §2: "takes affect" => "takes effect"

It sounds like you're on the cusp of achieving what I've been trying to do for years with Debuggit. As mascip mentions, Debuggit properly advertises that you can achieve this via:

debuggit("foo is", $foo) if DEBUG >= 2;

but that's just using standard constant folding. What I really want is for this:

debuggit(2 => "foo is", $foo);

to likewise get expunged during the compilation phase. But I haven't seen any way to do it outside source filtering/Devel::Declare magic/new keyword API, none of which seemed worth the hassle. But if you can create a new call checker ...

Definitely keep us posted. I will totally leap on that for Debuggit once it's available. :-)

Leave a comment

About Peter Martini

user-pic I like thinking about machines, especially virtual machines like Perl's VM, the Java VM, and kvm/qemu