To recap, the new backend supports Java 6 and 7. In an experimental branch (based itself on the experimental backend) there's now support for MethodHandles, building upon "Late Closure Classes", ie the ability of the new backend to materialize anonymous closure classes as it emits bytecode.
And now the MethodHandles story. For backward compatibility the prototype assumes we want to still pass FunctionX objects, which now wrap a MethodHandle instance. That MH is invoked whenever apply() is called. For that to work, the values captured by the anon-closures should be available to the MH instance, via partial-application ("bindTo" in terms of JSR-292 API). Finally, as before, an anon-closure-class is instantiated each time an FunctionX object is passed; the difference is that before a dedicated custom class existed for each anon-closure, while now there's scala.runtime.MHAbsFun0 to 22 , and it's one of those that is instantiated. Each MHAbsFunX takes a single constructor argument (the MH in question).
Besides anon-closures, other candidates areas for MHs (not covered by the prototype) are (1) specialization; (2) updating typer to special-case MH.invoke and MH.invokeExact; among others.
Unlike for the new backend, documentation is lacking. The rest of this post discusses implementation aspects.
How to grab the branch with MethodHandles support:
git clone git://github.com/magarciaEPFL/scala.git GenMHv1
cd GenMHv1
git checkout -b GenMHv1 origin/GenMHv1
ant all.clean && antFor perspective, the delta with respect to the non-MethodHandles backend is:
https://github.com/magarciaEPFL/scala/compare/magarciaEPFL:GenRefactored5...magarciaEPFL:GenMHv1So far results are a mixed bag:
The good:
(a.1) not emitting anon-closure-classes shrinks (compiler + reflect) into 8 MB territory, previously 13 MB (and that's with "Lean Closure Classes", which already improves on the 16 MB that the current, "traditional", lowering of anon-closures produces).
The bad: No performance gain, in fact a performance loss (to be honest I wasn't expecting a perf gain on JDK7, however the perf degradation was larger than expected).
Ways to improve performance:
(b.1) use JDK8 (actually any serious effort about MH should be on JDK8 anyway). But beware build.xml won't let you build on JDK8.
(b.2) currently the newly added scala.runtime.MHAbsFunX (the counterpart to AbstractFunctionX) doesn't use MH.invokeExact , incurring heavy boxing overhead. That can be solved (more on this in a moment)
(b.3) "partial application" ie binding of (closure-captured) arguments to a MethodHandle also involves boxing.
Each of the items above is amenable to a localized solution, however they aren't addressed in this version because experience has shown that compilation speed can be improved noticeably via old fashioned good engineering. Besides, we're not in a hurry to support JDK 8.
Coming back to emitting faster code for MethodHandles, four approaches that make sense (so much sense that probably they should have been followed from the start):
(c.1) invoke instead of invokeWithArguments
For expediency the MH-based counterparts to AbstractFunctionX are emitted using the same tools as for AbstractFunctionX (ie genprod.scala and from there the compiler build does the rest). For example, the source of MHAbsFun2 is:
final class MHAbsFun2[
@specialized(scala.Int, scala.Long, scala.Double) -T1,
@specialized(scala.Int, scala.Long, scala.Double) -T2,
@specialized(scala.Unit, scala.Boolean, scala.Int, scala.Float, scala.Long, scala.Double) +R
](target: _root_.java.lang.invoke.MethodHandle)
extends AbstractFunction2[T1, T2, R] {
def apply(v1: T1, v2: T2): R = {
val args = new _root_.java.util.ArrayList[Any]()
args.add(v1)
args.add(v2)
target.invokeWithArguments(args).asInstanceOf[R]
}
}A (way) more efficient version would use invokeExact (or plain invoke for that matter) specialized to the actual arg types, ie inside each of the following methods:
javap -classpath build/pack/lib/scala-library.jar scala.runtime.MHAbsFun2
Compiled from "AbstractFunction2.scala"
public class scala.runtime.MHAbsFun2<T1, T2, R> extends scala.runtime.AbstractFunction2<T1, T2, R> {
public final java.lang.invoke.MethodHandle scala$runtime$MHAbsFun2$$target;
public R apply(T1, T2);
public boolean apply$mcZDD$sp(double, double);
public double apply$mcDDD$sp(double, double);
public float apply$mcFDD$sp(double, double);
public int apply$mcIDD$sp(double, double);
public long apply$mcJDD$sp(double, double);
public void apply$mcVDD$sp(double, double);
public boolean apply$mcZDI$sp(double, int);
public double apply$mcDDI$sp(double, int);
public float apply$mcFDI$sp(double, int);
. . .
(c.2) more efficient "partial application". Currently done by boxing each bound argument individually. For example the following code:
def mA(captured: String) {
(1 to 10) foreach { i => println(captured + i) }
}For the sake of discussion, let's forget for a moment about -neo:o2 (which, er, would optimize away the closure above, and deliver a while loop).
The anon-closure passed to Range.foreach() captures a String value, "captured". The bytecode below shows the MH instance passed as argument to the constructor of MHAbsFun1, after binding "captured" via MHUtil.bindAt() where boxing occurs. Binding without boxing would already be an improvement, as well as binding values in block.
15: new #47 // class scala/runtime/MHAbsFun1
18: dup
19: aload_1 // load "captured" value
// a method-pointer for the "closure body" is loaded onto the operand stack,
// the method holding that body goes by the name of dlgt$27b8b21$1
20: ldc #52 // MethodHandle invokevirtual Test$.dlgt$27b8b21$1:(ILjava/lang/String;)V
22: ldc #53 // int 2
24: invokestatic #59 // Method scala/runtime/MHUtil.bindAt:(Ljava/lang/Object;Ljava/lang/invoke/MethodHandle;I)Ljava/lang/invoke/MethodHandle;
27: getstatic #61 // Field MODULE$:LTest$;
30: invokevirtual #67 // Method java/lang/invoke/MethodHandle.bindTo:(Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;
33: invokespecial #70 // Method scala/runtime/MHAbsFun1."<init>":(Ljava/lang/invoke/MethodHandle;)V
36: invokevirtual #74 // Method scala/collection/immutable/Range.foreach$mVc$sp:(Lscala/Function1;)V(c.3) "invokedynamic" At the end of the day, *some code* has to run behind the curtain of "invokedynamic", code that should make things faster. If you're suggesting using invokedynamic, it would be great to know what that code should look like.
(c.4) currently LDC MH-constant binds invokevirtual-style (for a non-static LCC endpoint) but it could bind invokespecial-style.
That's the current status, comments are welcome!
Miguel
http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/