Skip to content

Commit 41edbe6

Browse files
committed
Merge pull request scala#4607 from lrytz/inlineIndy
Accessibility checks for methods with an InvokeDynamic instruction
2 parents bac96a3 + 0e98c59 commit 41edbe6

File tree

16 files changed

+368
-149
lines changed

16 files changed

+368
-149
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package scala.tools.nsc
22
package backend.jvm
33

4-
import scala.tools.asm.tree.{AbstractInsnNode, MethodNode}
4+
import scala.tools.asm.tree.{InvokeDynamicInsnNode, AbstractInsnNode, MethodNode}
55
import scala.tools.nsc.backend.jvm.BTypes.InternalName
66
import scala.reflect.internal.util.Position
77
import scala.tools.nsc.settings.ScalaSettings
@@ -246,6 +246,11 @@ object BackendReporting {
246246
case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String,
247247
callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning
248248

249+
case object UnknownInvokeDynamicInstruction extends OptimizerWarning {
250+
override def toString = "The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory)."
251+
def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarningEmitAtInlineFailed
252+
}
253+
249254
/**
250255
* Used in `rewriteClosureApplyInvocations` when a closure apply callsite cannot be rewritten
251256
* to the closure body method.

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: 85 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 LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) =>
177+
closureInstantiations += LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType)
178178

179179
case _ =>
180180
}
@@ -236,4 +236,82 @@ 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 LambdaMetaFactoryCall {
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+
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: _*)
300+
}
301+
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+
)
308+
309+
if (isIndyLambda) Some((indy, samMethodType, implMethod, instantiatedMethodType))
310+
else None
311+
312+
case _ => None
313+
}
314+
case _ => None
315+
}
316+
}
239317
}

0 commit comments

Comments
 (0)