summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2015-06-22 15:08:32 +0200
committerLukas Rytz <lukas.rytz@gmail.com>2015-06-22 17:49:19 +0200
commit5be0722abc913deea1a0bc3e433f1bf4c29f4e09 (patch)
tree6f6f5d090531ab29ea60debc851a8f995d2e9d31 /src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
parentd159f1420e51fb17e38c0de3690c5d27c870e9ac (diff)
downloadscala-5be0722abc913deea1a0bc3e433f1bf4c29f4e09.tar.gz
scala-5be0722abc913deea1a0bc3e433f1bf4c29f4e09.tar.bz2
scala-5be0722abc913deea1a0bc3e433f1bf4c29f4e09.zip
Rewrite closure invocations to the lambda body method
When an indylambda closure is allocated and invoked within the same method, rewrite the invocation to the implementation method. This works for any indylambda / SAM type, not only Scala functions. However, the Scala compiler (under -Xexperimental) currently desugars function literals for non-FunctionN types to an anonymous class during typer. No testing yet, waiting for FunctionN to become SAMs first. The feature requires scala-java8-compat to be on the classpath and a number of compiler flags: -Ydelambdafy:method -Ybackend:GenBCode -Yopt:closure-elimination -target:jvm-1.8 ➜ scala git:(opt/closureInlining) ant -Dscala-java8-compat.package=1 -Dlocker.skip=1 ➜ scala git:(opt/closureInlining) cd sandbox ➜ sandbox git:(opt/closureInlining) cat Fun.java public interface Fun<T> { T apply(T x); } ➜ sandbox git:(opt/closureInlining) javac Fun.java ➜ sandbox git:(opt/closureInlining) cat Test.scala class C { val z = "too" def f = { val kap = "me! me!" val f: Tuple2[String, String] => String = (o => z + kap + o.toString) f(("a", "b")) } def g = { val f: Int => String = x => x.toString f(10) } def h = { val f: Fun[Int] = x => x + 100 // Java SAM, requires -Xexperimental, will create an anonymous class in typer f(10) } def i = { val l = 10l val f: (Long, String) => String = (x, s) => s + l + z + x f(20l, "n") } def j = { val f: Int => Int = x => x + 101 // specialized f(33) } } ➜ sandbox git:(opt/closureInlining) ../build/quick/bin/scalac -target:jvm-1.8 -Yopt:closure-elimination -Ydelambdafy:method -Ybackend:GenBCode -Xexperimental -cp ../build/quick/scala-java8-compat:. Test.scala ➜ sandbox git:(opt/closureInlining) asm -a C.class ➜ sandbox git:(opt/closureInlining) cat C.asm [...] public g()Ljava/lang/String; L0 INVOKEDYNAMIC apply()Lscala/compat/java8/JFunction1; [ // handle kind 0x6 : INVOKESTATIC java/lang/invoke/LambdaMetafactory.altMetafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; // arguments: (Ljava/lang/Object;)Ljava/lang/Object;, // handle kind 0x6 : INVOKESTATIC C.C$$$anonfun$2$adapted(Ljava/lang/Object;)Ljava/lang/String;, (Ljava/lang/Object;)Ljava/lang/String;, 3, 1, Lscala/Serializable;.class, 0 ] CHECKCAST scala/Function1 L1 ASTORE 1 L2 ALOAD 1 BIPUSH 10 INVOKESTATIC scala/runtime/BoxesRunTime.boxToInteger (I)Ljava/lang/Integer; ASTORE 2 POP ALOAD 2 INVOKESTATIC C.C$$$anonfun$2$adapted (Ljava/lang/Object;)Ljava/lang/String; CHECKCAST java/lang/String L3 ARETURN [...]
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala38
1 files changed, 30 insertions, 8 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
index 0932564b1f..8abecdb261 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala
@@ -11,6 +11,7 @@ import scala.reflect.internal.util.{NoPosition, Position}
import scala.tools.asm.tree.analysis.{Value, Analyzer, BasicInterpreter}
import scala.tools.asm.{Opcodes, Type}
import scala.tools.asm.tree._
+import scala.collection.concurrent
import scala.collection.convert.decorateAsScala._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.tools.nsc.backend.jvm.BackendReporting._
@@ -21,14 +22,25 @@ import BytecodeUtils._
class CallGraph[BT <: BTypes](val btypes: BT) {
import btypes._
- val callsites: collection.concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(collection.concurrent.TrieMap.empty[MethodInsnNode, Callsite])
+ val callsites: concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(concurrent.TrieMap.empty)
+
+ val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, (MethodNode, ClassBType)] = recordPerRunCache(concurrent.TrieMap.empty)
def addClass(classNode: ClassNode): Unit = {
- for (m <- classNode.methods.asScala; callsite <- analyzeCallsites(m, classBTypeFromClassNode(classNode)))
- callsites(callsite.callsiteInstruction) = callsite
+ val classType = classBTypeFromClassNode(classNode)
+ for {
+ m <- classNode.methods.asScala
+ (calls, closureInits) = analyzeCallsites(m, classType)
+ } {
+ calls foreach (callsite => callsites(callsite.callsiteInstruction) = callsite)
+ closureInits foreach (indy => closureInstantiations(indy) = (m, classType))
+ }
}
- def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = {
+ /**
+ * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions.
+ */
+ def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[InvokeDynamicInsnNode]) = {
case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean,
annotatedInline: Boolean, annotatedNoInline: Boolean,
@@ -116,7 +128,10 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
case _ => false
}
- methodNode.instructions.iterator.asScala.collect({
+ val callsites = new collection.mutable.ListBuffer[Callsite]
+ val closureInstantiations = new collection.mutable.ListBuffer[InvokeDynamicInsnNode]
+
+ methodNode.instructions.iterator.asScala foreach {
case call: MethodInsnNode =>
val callee: Either[OptimizerWarning, Callee] = for {
(method, declarationClass) <- byteCodeRepository.methodNode(call.owner, call.name, call.desc): Either[OptimizerWarning, (MethodNode, InternalName)]
@@ -147,7 +162,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
receiverNotNullByAnalysis(call, numArgs)
}
- Callsite(
+ callsites += Callsite(
callsiteInstruction = call,
callsiteMethod = methodNode,
callsiteClass = definingClass,
@@ -157,7 +172,14 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
receiverKnownNotNull = receiverNotNull,
callsitePosition = callsitePositions.getOrElse(call, NoPosition)
)
- }).toList
+
+ case indy: InvokeDynamicInsnNode =>
+ if (closureOptimizer.isClosureInstantiation(indy)) closureInstantiations += indy
+
+ case _ =>
+ }
+
+ (callsites.toList, closureInstantiations.toList)
}
/**
@@ -201,7 +223,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
* @param calleeDeclarationClass The class in which the callee is declared
* @param safeToInline True if the callee can be safely inlined: it cannot be overridden,
* and the inliner settings (project / global) allow inlining it.
- * @param safeToRewrite True if the callee the interface method of a concrete trait method
+ * @param safeToRewrite True if the callee is the interface method of a concrete trait method
* that can be safely re-written to the static implementation method.
* @param annotatedInline True if the callee is annotated @inline
* @param annotatedNoInline True if the callee is annotated @noinline