Skip to content

Commit 41b99e2

Browse files
adriaanmlrytz
authored andcommitted
[backport] Integrate the LMFInvokeDynamic extractor into LambdaMetaFactoryCall
1 parent fc1cda2 commit 41b99e2

File tree

3 files changed

+64
-70
lines changed

3 files changed

+64
-70
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ object BytecodeUtils {
104104

105105
def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STRICT) != 0
106106

107+
def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY
108+
107109
def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = {
108110
var result = instruction
109111
do { result = result.getNext }

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

Lines changed: 53 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
173173
callsitePosition = callsitePositions.getOrElse(call, NoPosition)
174174
)
175175

176-
case LMFInvokeDynamic(lmf) =>
177-
closureInstantiations += lmf
176+
case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) =>
177+
closureInstantiations += LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType)
178178

179179
case _ =>
180180
}
@@ -242,7 +242,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
242242
}
243243
final case class LambdaMetaFactoryCall(indy: InvokeDynamicInsnNode, samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type)
244244

245-
object LMFInvokeDynamic {
245+
object LambdaMetaFactoryCall {
246246
private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory"
247247

248248
private val metafactoryHandle = {
@@ -257,69 +257,60 @@ class CallGraph[BT <: BTypes](val btypes: BT) {
257257
new Handle(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc)
258258
}
259259

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)
260+
def unapply(insn: AbstractInsnNode): Option[(InvokeDynamicInsnNode, Type, Handle, Type)] = insn match {
261+
case indy: InvokeDynamicInsnNode if indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle =>
262+
indy.bsmArgs match {
263+
case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs@_*) => // xs binding because IntelliJ gets confused about _@_*
264+
// LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
265+
// implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc).
266+
//
267+
// The closure optimizer supports only one of those adaptations: it will cast arguments
268+
// to the correct type when re-writing a closure call to the body method. Example:
269+
//
270+
// val fun: String => String = l => l
271+
// val l = List("")
272+
// fun(l.head)
273+
//
274+
// The samMethodType of Function1 is `(Object)Object`, while the instantiatedMethodType
275+
// is `(String)String`. The return type of `List.head` is `Object`.
276+
//
277+
// The implMethod has the signature `C$anonfun(String)String`.
278+
//
279+
// At the closure callsite, we have an `INVOKEINTERFACE Function1.apply (Object)Object`,
280+
// so the object returned by `List.head` can be directly passed into the call (no cast).
281+
//
282+
// The closure object will cast the object to String before passing it to the implMethod.
283+
//
284+
// When re-writing the closure callsite to the implMethod, we have to insert a cast.
285+
//
286+
// The check below ensures that
287+
// (1) the implMethod type has the expected singature (captured types plus argument types
288+
// from instantiatedMethodType)
289+
// (2) the receiver of the implMethod matches the first captured type
290+
// (3) all parameters that are not the same in samMethodType and instantiatedMethodType
291+
// are reference types, so that we can insert casts to perform the same adaptation
292+
// that the closure object would.
293+
294+
val isStatic = implMethod.getTag == Opcodes.H_INVOKESTATIC
295+
val indyParamTypes = Type.getArgumentTypes(indy.desc)
296+
val instantiatedMethodArgTypes = instantiatedMethodType.getArgumentTypes
297+
val expectedImplMethodType = {
298+
val paramTypes = (if (isStatic) indyParamTypes else indyParamTypes.tail) ++ instantiatedMethodArgTypes
299+
Type.getMethodType(instantiatedMethodType.getReturnType, paramTypes: _*)
310300
}
311-
}
312301

313-
if (isIndyLambda) Some(LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType))
314-
else None
302+
val isIndyLambda = (
303+
Type.getType(implMethod.getDesc) == expectedImplMethodType // (1)
304+
&& (isStatic || implMethod.getOwner == indyParamTypes(0).getInternalName) // (2)
305+
&& samMethodType.getArgumentTypes.corresponds(instantiatedMethodArgTypes)((samArgType, instArgType) =>
306+
samArgType == instArgType || isReference(samArgType) && isReference(instArgType)) // (3)
307+
)
315308

316-
case _ => None
317-
}
318-
else None
319-
}
309+
if (isIndyLambda) Some((indy, samMethodType, implMethod, instantiatedMethodType))
310+
else None
320311

321-
def unapply(insn: AbstractInsnNode): Option[LambdaMetaFactoryCall] = insn match {
322-
case indy: InvokeDynamicInsnNode => extractLambdaMetaFactoryCall(indy)
312+
case _ => None
313+
}
323314
case _ => None
324315
}
325316
}

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,12 @@ class Inliner[BT <: BTypes](val btypes: BT) {
688688
}
689689
}
690690

691-
case LMFInvokeDynamic(lmf) =>
691+
case _: InvokeDynamicInsnNode if destinationClass == calleeDeclarationClass =>
692+
// within the same class, any indy instruction can be inlined
693+
Right(true)
694+
695+
// does the InvokeDynamicInsnNode call LambdaMetaFactory?
696+
case LambdaMetaFactoryCall(_, _, implMethod, _) =>
692697
// an indy instr points to a "call site specifier" (CSP) [1]
693698
// - a reference to a bootstrap method [2]
694699
// - bootstrap method name
@@ -734,20 +739,16 @@ class Inliner[BT <: BTypes](val btypes: BT) {
734739
// the implMethod is public, lambdaMetaFactory doesn't use the Lookup object's extended
735740
// capability, and we can safely inline the instruction into a different class.
736741

737-
val methodRefClass = classBTypeFromParsedClassfile(lmf.implMethod.getOwner)
742+
val methodRefClass = classBTypeFromParsedClassfile(implMethod.getOwner)
738743
for {
739-
(methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, lmf.implMethod.getName, lmf.implMethod.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)]
744+
(methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, implMethod.getName, implMethod.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)]
740745
methodDeclClass = classBTypeFromParsedClassfile(methodDeclClassNode)
741746
res <- memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass, destinationClass)
742747
} yield {
743748
res
744749
}
745750

746-
case indy: InvokeDynamicInsnNode =>
747-
if (destinationClass == calleeDeclarationClass)
748-
Right(true) // within the same class, any indy instruction can be inlined
749-
else
750-
Left(UnknownInvokeDynamicInstruction)
751+
case _: InvokeDynamicInsnNode => Left(UnknownInvokeDynamicInstruction)
751752

752753
case ci: LdcInsnNode => ci.cst match {
753754
case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName), destinationClass)

0 commit comments

Comments
 (0)