@@ -9,7 +9,7 @@ package opt
99
1010import scala .reflect .internal .util .{NoPosition , Position }
1111import scala .tools .asm .tree .analysis .{Value , Analyzer , BasicInterpreter }
12- import scala .tools .asm .{Opcodes , Type }
12+ import scala .tools .asm .{Opcodes , Type , Handle }
1313import scala .tools .asm .tree ._
1414import scala .collection .concurrent
1515import scala .collection .convert .decorateAsScala ._
@@ -24,7 +24,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
2424
2525 val callsites : concurrent.Map [MethodInsnNode , Callsite ] = recordPerRunCache(concurrent.TrieMap .empty)
2626
27- val closureInstantiations : concurrent.Map [InvokeDynamicInsnNode , ( MethodNode , ClassBType ) ] = recordPerRunCache(concurrent.TrieMap .empty)
27+ val closureInstantiations : concurrent.Map [InvokeDynamicInsnNode , ClosureInstantiation ] = recordPerRunCache(concurrent.TrieMap .empty)
2828
2929 def addClass (classNode : ClassNode ): Unit = {
3030 val classType = classBTypeFromClassNode(classNode)
@@ -33,14 +33,14 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
3333 (calls, closureInits) = analyzeCallsites(m, classType)
3434 } {
3535 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))
3737 }
3838 }
3939
4040 /**
4141 * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions.
4242 */
43- def analyzeCallsites (methodNode : MethodNode , definingClass : ClassBType ): (List [Callsite ], List [InvokeDynamicInsnNode ]) = {
43+ def analyzeCallsites (methodNode : MethodNode , definingClass : ClassBType ): (List [Callsite ], List [LambdaMetaFactoryCall ]) = {
4444
4545 case class CallsiteInfo (safeToInline : Boolean , safeToRewrite : Boolean ,
4646 annotatedInline : Boolean , annotatedNoInline : Boolean ,
@@ -129,7 +129,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
129129 }
130130
131131 val callsites = new collection.mutable.ListBuffer [Callsite ]
132- val closureInstantiations = new collection.mutable.ListBuffer [InvokeDynamicInsnNode ]
132+ val closureInstantiations = new collection.mutable.ListBuffer [LambdaMetaFactoryCall ]
133133
134134 methodNode.instructions.iterator.asScala foreach {
135135 case call : MethodInsnNode =>
@@ -173,8 +173,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
173173 callsitePosition = callsitePositions.getOrElse(call, NoPosition )
174174 )
175175
176- case indy : InvokeDynamicInsnNode =>
177- if (closureOptimizer.isClosureInstantiation(indy)) closureInstantiations += indy
176+ case LMFInvokeDynamic (lmf) =>
177+ closureInstantiations += lmf
178178
179179 case _ =>
180180 }
@@ -236,4 +236,91 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
236236 calleeInfoWarning : Option [CalleeInfoWarning ]) {
237237 assert(! (safeToInline && safeToRewrite), s " A callee of ${callee.name} can be either safeToInline or safeToRewrite, but not both. " )
238238 }
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+ }
239326}
0 commit comments