Skip to content
This repository was archived by the owner on Sep 1, 2020. It is now read-only.

Commit 5be0722

Browse files
committed
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 [...]
1 parent d159f14 commit 5be0722

File tree

10 files changed

+489
-108
lines changed

10 files changed

+489
-108
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
140140
if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
141141
AsmUtils.traceClass(cnode)
142142

143-
if (settings.YoptInlinerEnabled) {
143+
if (settings.YoptAddToBytecodeRepository) {
144144
// The inliner needs to find all classes in the code repo, also those being compiled
145145
byteCodeRepository.add(cnode, ByteCodeRepository.CompilationUnit)
146146
}

src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ abstract class BTypes {
4444

4545
val inliner: Inliner[this.type]
4646

47+
val closureOptimizer: ClosureOptimizer[this.type]
48+
4749
val callGraph: CallGraph[this.type]
4850

4951
val backendReporting: BackendReporting

src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package scala.tools.nsc
77
package backend.jvm
88

99
import scala.tools.asm
10-
import scala.tools.nsc.backend.jvm.opt.{LocalOpt, CallGraph, Inliner, ByteCodeRepository}
10+
import scala.tools.nsc.backend.jvm.opt._
1111
import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo, InternalName}
1212
import BackendReporting._
1313
import scala.tools.nsc.settings.ScalaSettings
@@ -42,6 +42,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
4242

4343
val inliner: Inliner[this.type] = new Inliner(this)
4444

45+
val closureOptimizer: ClosureOptimizer[this.type] = new ClosureOptimizer(this)
46+
4547
val callGraph: CallGraph[this.type] = new CallGraph(this)
4648

4749
val backendReporting: BackendReporting = new BackendReportingImpl(global)

src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,28 @@ object BackendReporting {
246246
case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String,
247247
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
248248

249+
/**
250+
* Used in `rewriteClosureApplyInvocations` when a closure apply callsite cannot be rewritten
251+
* to the closure body method.
252+
*/
253+
trait RewriteClosureApplyToClosureBodyFailed extends OptimizerWarning {
254+
def pos: Position
255+
256+
override def emitWarning(settings: ScalaSettings): Boolean = this match {
257+
case RewriteClosureAccessCheckFailed(_, cause) => cause.emitWarning(settings)
258+
case RewriteClosureIllegalAccess(_, _) => settings.YoptWarningEmitAtInlineFailed
259+
}
260+
261+
override def toString: String = this match {
262+
case RewriteClosureAccessCheckFailed(_, cause) =>
263+
s"Failed to rewrite the closure invocation to its implementation method:\n" + cause
264+
case RewriteClosureIllegalAccess(_, callsiteClass) =>
265+
s"The closure body invocation cannot be rewritten because the target method is not accessible in class $callsiteClass."
266+
}
267+
}
268+
case class RewriteClosureAccessCheckFailed(pos: Position, cause: OptimizerWarning) extends RewriteClosureApplyToClosureBodyFailed
269+
case class RewriteClosureIllegalAccess(pos: Position, callsiteClass: InternalName) extends RewriteClosureApplyToClosureBodyFailed
270+
249271
/**
250272
* Used in the InlineInfo of a ClassBType, when some issue occurred obtaining the inline information.
251273
*/

src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,20 +216,25 @@ abstract class GenBCode extends BCodeSyncAndTry {
216216
class Worker2 {
217217
def runGlobalOptimizations(): Unit = {
218218
import scala.collection.convert.decorateAsScala._
219-
q2.asScala foreach {
220-
case Item2(_, _, plain, _, _) =>
221-
// skip mirror / bean: wd don't inline into tem, and they are not used in the plain class
222-
if (plain != null) callGraph.addClass(plain)
219+
if (settings.YoptBuildCallGraph) {
220+
q2.asScala foreach {
221+
case Item2(_, _, plain, _, _) =>
222+
// skip mirror / bean: wd don't inline into tem, and they are not used in the plain class
223+
if (plain != null) callGraph.addClass(plain)
224+
}
223225
}
224-
bTypes.inliner.runInliner()
226+
if (settings.YoptInlinerEnabled)
227+
bTypes.inliner.runInliner()
228+
if (settings.YoptClosureElimination)
229+
closureOptimizer.rewriteClosureApplyInvocations()
225230
}
226231

227232
def localOptimizations(classNode: ClassNode): Unit = {
228233
BackendStats.timed(BackendStats.methodOptTimer)(localOpt.methodOptimizations(classNode))
229234
}
230235

231236
def run() {
232-
if (settings.YoptInlinerEnabled) runGlobalOptimizations()
237+
runGlobalOptimizations()
233238

234239
while (true) {
235240
val item = q2.poll

src/compiler/scala/tools/nsc/backend/jvm/opt/ByteCodeRepository.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class ByteCodeRepository(val classPath: ClassFileLookup[AbstractFile], val isJav
102102
}
103103

104104
/**
105-
* The method node for a method matching `name` and `descriptor`, accessed in class `classInternalName`.
105+
* The method node for a method matching `name` and `descriptor`, accessed in class `ownerInternalNameOrArrayDescriptor`.
106106
* The declaration of the method may be in one of the parents.
107107
*
108108
* @return The [[MethodNode]] of the requested method and the [[InternalName]] of its declaring

src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import scala.reflect.internal.util.{NoPosition, Position}
1111
import scala.tools.asm.tree.analysis.{Value, Analyzer, BasicInterpreter}
1212
import scala.tools.asm.{Opcodes, Type}
1313
import scala.tools.asm.tree._
14+
import scala.collection.concurrent
1415
import scala.collection.convert.decorateAsScala._
1516
import scala.tools.nsc.backend.jvm.BTypes.InternalName
1617
import scala.tools.nsc.backend.jvm.BackendReporting._
@@ -21,14 +22,25 @@ import BytecodeUtils._
2122
class CallGraph[BT <: BTypes](val btypes: BT) {
2223
import btypes._
2324

24-
val callsites: collection.concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(collection.concurrent.TrieMap.empty[MethodInsnNode, Callsite])
25+
val callsites: concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(concurrent.TrieMap.empty)
26+
27+
val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, (MethodNode, ClassBType)] = recordPerRunCache(concurrent.TrieMap.empty)
2528

2629
def addClass(classNode: ClassNode): Unit = {
27-
for (m <- classNode.methods.asScala; callsite <- analyzeCallsites(m, classBTypeFromClassNode(classNode)))
28-
callsites(callsite.callsiteInstruction) = callsite
30+
val classType = classBTypeFromClassNode(classNode)
31+
for {
32+
m <- classNode.methods.asScala
33+
(calls, closureInits) = analyzeCallsites(m, classType)
34+
} {
35+
calls foreach (callsite => callsites(callsite.callsiteInstruction) = callsite)
36+
closureInits foreach (indy => closureInstantiations(indy) = (m, classType))
37+
}
2938
}
3039

31-
def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): List[Callsite] = {
40+
/**
41+
* Returns a list of callsites in the method, plus a list of closure instantiation indy instructions.
42+
*/
43+
def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[InvokeDynamicInsnNode]) = {
3244

3345
case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean,
3446
annotatedInline: Boolean, annotatedNoInline: Boolean,
@@ -116,7 +128,10 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
116128
case _ => false
117129
}
118130

119-
methodNode.instructions.iterator.asScala.collect({
131+
val callsites = new collection.mutable.ListBuffer[Callsite]
132+
val closureInstantiations = new collection.mutable.ListBuffer[InvokeDynamicInsnNode]
133+
134+
methodNode.instructions.iterator.asScala foreach {
120135
case call: MethodInsnNode =>
121136
val callee: Either[OptimizerWarning, Callee] = for {
122137
(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) {
147162
receiverNotNullByAnalysis(call, numArgs)
148163
}
149164

150-
Callsite(
165+
callsites += Callsite(
151166
callsiteInstruction = call,
152167
callsiteMethod = methodNode,
153168
callsiteClass = definingClass,
@@ -157,7 +172,14 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
157172
receiverKnownNotNull = receiverNotNull,
158173
callsitePosition = callsitePositions.getOrElse(call, NoPosition)
159174
)
160-
}).toList
175+
176+
case indy: InvokeDynamicInsnNode =>
177+
if (closureOptimizer.isClosureInstantiation(indy)) closureInstantiations += indy
178+
179+
case _ =>
180+
}
181+
182+
(callsites.toList, closureInstantiations.toList)
161183
}
162184

163185
/**
@@ -201,7 +223,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
201223
* @param calleeDeclarationClass The class in which the callee is declared
202224
* @param safeToInline True if the callee can be safely inlined: it cannot be overridden,
203225
* and the inliner settings (project / global) allow inlining it.
204-
* @param safeToRewrite True if the callee the interface method of a concrete trait method
226+
* @param safeToRewrite True if the callee is the interface method of a concrete trait method
205227
* that can be safely re-written to the static implementation method.
206228
* @param annotatedInline True if the callee is annotated @inline
207229
* @param annotatedNoInline True if the callee is annotated @noinline

0 commit comments

Comments
 (0)