@@ -9,7 +9,7 @@ package opt
9
9
10
10
import scala .reflect .internal .util .{NoPosition , Position }
11
11
import scala .tools .asm .tree .analysis .{Value , Analyzer , BasicInterpreter }
12
- import scala .tools .asm .{Opcodes , Type }
12
+ import scala .tools .asm .{Opcodes , Type , Handle }
13
13
import scala .tools .asm .tree ._
14
14
import scala .collection .concurrent
15
15
import scala .collection .convert .decorateAsScala ._
@@ -24,7 +24,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
24
24
25
25
val callsites : concurrent.Map [MethodInsnNode , Callsite ] = recordPerRunCache(concurrent.TrieMap .empty)
26
26
27
- val closureInstantiations : concurrent.Map [InvokeDynamicInsnNode , ( MethodNode , ClassBType ) ] = recordPerRunCache(concurrent.TrieMap .empty)
27
+ val closureInstantiations : concurrent.Map [InvokeDynamicInsnNode , ClosureInstantiation ] = recordPerRunCache(concurrent.TrieMap .empty)
28
28
29
29
def addClass (classNode : ClassNode ): Unit = {
30
30
val classType = classBTypeFromClassNode(classNode)
@@ -33,14 +33,14 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
33
33
(calls, closureInits) = analyzeCallsites(m, classType)
34
34
} {
35
35
calls foreach (callsite => callsites(callsite.callsiteInstruction) = callsite)
36
- closureInits foreach (indy => closureInstantiations(indy) = ( m, classType))
36
+ closureInits foreach (lmf => closureInstantiations(lmf. indy) = ClosureInstantiation (lmf, m, classType))
37
37
}
38
38
}
39
39
40
40
/**
41
41
* Returns a list of callsites in the method, plus a list of closure instantiation indy instructions.
42
42
*/
43
- def analyzeCallsites (methodNode : MethodNode , definingClass : ClassBType ): (List [Callsite ], List [InvokeDynamicInsnNode ]) = {
43
+ def analyzeCallsites (methodNode : MethodNode , definingClass : ClassBType ): (List [Callsite ], List [LambdaMetaFactoryCall ]) = {
44
44
45
45
case class CallsiteInfo (safeToInline : Boolean , safeToRewrite : Boolean ,
46
46
annotatedInline : Boolean , annotatedNoInline : Boolean ,
@@ -129,7 +129,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
129
129
}
130
130
131
131
val callsites = new collection.mutable.ListBuffer [Callsite ]
132
- val closureInstantiations = new collection.mutable.ListBuffer [InvokeDynamicInsnNode ]
132
+ val closureInstantiations = new collection.mutable.ListBuffer [LambdaMetaFactoryCall ]
133
133
134
134
methodNode.instructions.iterator.asScala foreach {
135
135
case call : MethodInsnNode =>
@@ -173,8 +173,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
173
173
callsitePosition = callsitePositions.getOrElse(call, NoPosition )
174
174
)
175
175
176
- case indy : InvokeDynamicInsnNode =>
177
- if (closureOptimizer.isClosureInstantiation(indy)) closureInstantiations += indy
176
+ case LMFInvokeDynamic (lmf) =>
177
+ closureInstantiations += lmf
178
178
179
179
case _ =>
180
180
}
@@ -236,4 +236,91 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
236
236
calleeInfoWarning : Option [CalleeInfoWarning ]) {
237
237
assert(! (safeToInline && safeToRewrite), s " A callee of ${callee.name} can be either safeToInline or safeToRewrite, but not both. " )
238
238
}
239
+
240
+ final case class ClosureInstantiation (lambdaMetaFactoryCall : LambdaMetaFactoryCall , ownerMethod : MethodNode , ownerClass : ClassBType ) {
241
+ override def toString = s " ClosureInstantiation( $lambdaMetaFactoryCall, ${ownerMethod.name + ownerMethod.desc}, $ownerClass) "
242
+ }
243
+ final case class LambdaMetaFactoryCall (indy : InvokeDynamicInsnNode , samMethodType : Type , implMethod : Handle , instantiatedMethodType : Type )
244
+
245
+ object LMFInvokeDynamic {
246
+ private val lambdaMetaFactoryInternalName : InternalName = " java/lang/invoke/LambdaMetafactory"
247
+
248
+ private val metafactoryHandle = {
249
+ val metafactoryMethodName : String = " metafactory"
250
+ val metafactoryDesc : String = " (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"
251
+ new Handle (Opcodes .H_INVOKESTATIC , lambdaMetaFactoryInternalName, metafactoryMethodName, metafactoryDesc)
252
+ }
253
+
254
+ private val altMetafactoryHandle = {
255
+ val altMetafactoryMethodName : String = " altMetafactory"
256
+ val altMetafactoryDesc : String = " (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"
257
+ new Handle (Opcodes .H_INVOKESTATIC , lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc)
258
+ }
259
+
260
+ private def extractLambdaMetaFactoryCall (indy : InvokeDynamicInsnNode ) = {
261
+ if (indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle) indy.bsmArgs match {
262
+ case Array (samMethodType : Type , implMethod : Handle , instantiatedMethodType : Type , xs@_* ) =>
263
+ // LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
264
+ // implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc).
265
+ //
266
+ // The closure optimizer supports only one of those adaptations: it will cast arguments
267
+ // to the correct type when re-writing a closure call to the body method. Example:
268
+ //
269
+ // val fun: String => String = l => l
270
+ // val l = List("")
271
+ // fun(l.head)
272
+ //
273
+ // The samMethodType of Function1 is `(Object)Object`, while the instantiatedMethodType
274
+ // is `(String)String`. The return type of `List.head` is `Object`.
275
+ //
276
+ // The implMethod has the signature `C$anonfun(String)String`.
277
+ //
278
+ // At the closure callsite, we have an `INVOKEINTERFACE Function1.apply (Object)Object`,
279
+ // so the object returned by `List.head` can be directly passed into the call (no cast).
280
+ //
281
+ // The closure object will cast the object to String before passing it to the implMethod.
282
+ //
283
+ // When re-writing the closure callsite to the implMethod, we have to insert a cast.
284
+ //
285
+ // The check below ensures that
286
+ // (1) the implMethod type has the expected singature (captured types plus argument types
287
+ // from instantiatedMethodType)
288
+ // (2) the receiver of the implMethod matches the first captured type
289
+ // (3) all parameters that are not the same in samMethodType and instantiatedMethodType
290
+ // are reference types, so that we can insert casts to perform the same adaptation
291
+ // that the closure object would.
292
+
293
+ val isStatic = implMethod.getTag == Opcodes .H_INVOKESTATIC
294
+ val indyParamTypes = Type .getArgumentTypes(indy.desc)
295
+ val instantiatedMethodArgTypes = instantiatedMethodType.getArgumentTypes
296
+ val expectedImplMethodType = {
297
+ val paramTypes = (if (isStatic) indyParamTypes else indyParamTypes.tail) ++ instantiatedMethodArgTypes
298
+ Type .getMethodType(instantiatedMethodType.getReturnType, paramTypes : _* )
299
+ }
300
+
301
+ val isIndyLambda = {
302
+ Type .getType(implMethod.getDesc) == expectedImplMethodType // (1)
303
+ } && {
304
+ isStatic || implMethod.getOwner == indyParamTypes(0 ).getInternalName // (2)
305
+ } && {
306
+ def isReference (t : Type ) = t.getSort == Type .OBJECT || t.getSort == Type .ARRAY
307
+ (samMethodType.getArgumentTypes, instantiatedMethodArgTypes).zipped forall {
308
+ case (samArgType, instArgType) =>
309
+ samArgType == instArgType || isReference(samArgType) && isReference(instArgType) // (3)
310
+ }
311
+ }
312
+
313
+ if (isIndyLambda) Some (LambdaMetaFactoryCall (indy, samMethodType, implMethod, instantiatedMethodType))
314
+ else None
315
+
316
+ case _ => None
317
+ }
318
+ else None
319
+ }
320
+
321
+ def unapply (insn : AbstractInsnNode ): Option [LambdaMetaFactoryCall ] = insn match {
322
+ case indy : InvokeDynamicInsnNode => extractLambdaMetaFactoryCall(indy)
323
+ case _ => None
324
+ }
325
+ }
239
326
}
0 commit comments