Skip to content

Commit fc1cda2

Browse files
committed
[backport] Small refactoring to the closure optimizer
Introduces an extractor `LMFInvokeDynamic` that matches InvokeDynamic instructions that are LambdaMetaFactory calls. The case class `LambdaMetaFactoryCall` holds such an InvokeDynamic instruction. It also holds the bootstrap arguments (samMethodType, implMethod, instantiatedMethodType) so that they can be accessed without casting the indy.bsmArgs. The `closureInstantiations` map in the call graph now stores ClosureInstantiation objects instead of a tuple. This simplifies some code and gets rid of a few casts.
1 parent 8f272c0 commit fc1cda2

File tree

3 files changed

+151
-139
lines changed

3 files changed

+151
-139
lines changed

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

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package opt
99

1010
import scala.reflect.internal.util.{NoPosition, Position}
1111
import scala.tools.asm.tree.analysis.{Value, Analyzer, BasicInterpreter}
12-
import scala.tools.asm.{Opcodes, Type}
12+
import scala.tools.asm.{Opcodes, Type, Handle}
1313
import scala.tools.asm.tree._
1414
import scala.collection.concurrent
1515
import 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

Comments
 (0)