Permalink
Browse files

Use a spesh plugin for $foo.Qual::Ified::meth()

This lets us optimize away the method resolution after the first time,
which since it has to search in the role structure was quite costly.
The "is the invocant a Qual::Ified" check also gets subsumed by the
guard.

The result is that the program:

    role R1 {
        method m() { 1 }
    }
    role R2 {
        method m() { 2 }
    }
    class C does R1 does R2 {
        method m() {
            self.R2::m()
        }
    }
    for ^10_000_000 {
        C.m
    }

Which used to run in 13.3s now runs in 1.07s, a factor of more than 12x
improvement. This construct appears 66 times in CORE.setting, so there
is a good chance we win something from it there as well as in user code
using it for role disambiguation.
  • Loading branch information...
jnthn committed Jun 8, 2018
1 parent 51ff83c commit f6f43d58e35ef0ba3ea92a47c7164429873977b2
Showing with 64 additions and 0 deletions.
  1. +45 −0 src/Perl6/Optimizer.nqp
  2. +19 −0 src/vm/moar/spesh-plugins.nqp
View
@@ -1394,6 +1394,9 @@ class Perl6::Optimizer {
# .= calls can be unpacked entirely
return self.optimize_dot_equals_method_call: $op;
}
elsif $op.name eq 'dispatch:<::>' {
return self.optimize_qual_method_call: $op;
}
}
if $op.op eq 'chain' {
@@ -2059,6 +2062,48 @@ class Perl6::Optimizer {
}
}
method optimize_qual_method_call($op) {
# Spesh plugins only available on MoarVM.
return $op unless nqp::getcomp('perl6').backend.name eq 'moar';
# We can only optimize if we have a compile-time-known name.
my $name_node := $op[1];
if nqp::istype($name_node, QAST::Want) && $name_node[1] eq 'Ss' {
$name_node := $name_node[2];
}
return $op unless nqp::istype($name_node, QAST::SVal);
# We need to evaluate the invocant only once, so will bind it into
# a temporary.
my $inv := $op.shift;
my $name := $op.shift;
my $type := $op.shift;
my @args;
while $op.list {
nqp::push(@args, $op.shift);
}
my $temp := QAST::Node.unique('inv_once');
$op.op('stmts');
$op.push(QAST::Op.new(
:op('bind'),
QAST::Var.new( :name($temp), :scope('local'), :decl('var') ),
$inv
));
$op.push(QAST::Op.new(
:op('call'),
QAST::Op.new(
:op('speshresolve'),
QAST::SVal.new( :value('qualmeth') ),
QAST::Var.new( :name($temp), :scope('local') ),
$name,
$type
),
QAST::Var.new( :name($temp), :scope('local') ),
|@args
));
return $op;
}
method optimize_for_range($op, $callee, $c2) {
my $code := $callee.ann('code_object');
my $count := $code.count;
@@ -5,3 +5,22 @@ nqp::speshreg('perl6', 'privmeth', -> $obj, str $name {
nqp::speshguardtype($obj, $obj.WHAT);
$obj.HOW.find_private_method($obj, $name)
});
# A resolution like `self.Foo::bar()` can have the resolution specialized. We
# fall back to the dispatch:<::> if there is an exception that'd need to be
# thrown.
nqp::speshreg('perl6', 'qualmeth', -> $obj, str $name, $type {
nqp::speshguardtype($obj, $obj.WHAT);
if nqp::istype($obj, $type) {
# Resolve to the correct qualified method.
nqp::speshguardtype($type, $type.WHAT);
$obj.HOW.find_method_qualified($obj, $type, $name)
}
else {
# We'll throw an exception; return a thunk that will delegate to the
# slow path implementation to do the throwing.
-> $inv, *@pos, *%named {
$inv.'dispatch:<::>'($name, $type, |@pos, |%named)
}
}
});

0 comments on commit f6f43d5

Please sign in to comment.