summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala448
1 files changed, 226 insertions, 222 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
index 1648a53ed8..b0dc6ead1b 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala
@@ -8,8 +8,9 @@ package backend.jvm
package opt
import scala.annotation.switch
+import scala.collection.immutable
import scala.reflect.internal.util.NoPosition
-import scala.tools.asm.{Handle, Type, Opcodes}
+import scala.tools.asm.{Type, Opcodes}
import scala.tools.asm.tree._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
import scala.tools.nsc.backend.jvm.analysis.ProdConsAnalyzer
@@ -23,90 +24,157 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
import btypes._
import callGraph._
+ /**
+ * If a closure is allocated and invoked within the same method, re-write the invocation to the
+ * closure body method.
+ *
+ * Note that the closure body method (generated by delambdafy:method) takes additional parameters
+ * for the values captured by the closure. The bytecode is transformed from
+ *
+ * [generate captured values]
+ * [closure init, capturing values]
+ * [...]
+ * [load closure object]
+ * [generate closure invocation arguments]
+ * [invoke closure.apply]
+ *
+ * to
+ *
+ * [generate captured values]
+ * [store captured values into new locals]
+ * [load the captured values from locals] // a future optimization will eliminate the closure
+ * [closure init, capturing values] // instantiation if the closure object becomes unused
+ * [...]
+ * [load closure object]
+ * [generate closure invocation arguments]
+ * [store argument values into new locals]
+ * [drop the closure object]
+ * [load captured values from locals]
+ * [load argument values from locals]
+ * [invoke the closure body method]
+ */
def rewriteClosureApplyInvocations(): Unit = {
- closureInstantiations foreach {
- case (indy, (methodNode, ownerClass)) =>
- val warnings = rewriteClosureApplyInvocations(indy, methodNode, ownerClass)
- warnings.foreach(w => backendReporting.inlinerWarning(w.pos, w.toString))
+ implicit object closureInitOrdering extends Ordering[ClosureInstantiation] {
+ override def compare(x: ClosureInstantiation, y: ClosureInstantiation): Int = {
+ val cls = x.ownerClass.internalName compareTo y.ownerClass.internalName
+ if (cls != 0) return cls
+
+ val mName = x.ownerMethod.name compareTo y.ownerMethod.name
+ if (mName != 0) return mName
+
+ val mDesc = x.ownerMethod.desc compareTo y.ownerMethod.desc
+ if (mDesc != 0) return mDesc
+
+ def pos(inst: ClosureInstantiation) = inst.ownerMethod.instructions.indexOf(inst.lambdaMetaFactoryCall.indy)
+ pos(x) - pos(y)
+ }
}
- }
- private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory"
+ // Grouping the closure instantiations by method allows running the ProdConsAnalyzer only once per
+ // method. Also sort the instantiations: If there are multiple closure instantiations in a method,
+ // closure invocations need to be re-written in a consistent order for bytecode stability. The local
+ // variable slots for storing captured values depends on the order of rewriting.
+ val closureInstantiationsByMethod: Map[MethodNode, immutable.TreeSet[ClosureInstantiation]] = {
+ closureInstantiations.values.groupBy(_.ownerMethod).mapValues(immutable.TreeSet.empty ++ _)
+ }
- private val metafactoryHandle = {
- val metafactoryMethodName: String = "metafactory"
- 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;"
- new Handle(H_INVOKESTATIC, lambdaMetaFactoryInternalName, metafactoryMethodName, metafactoryDesc)
- }
+ // For each closure instantiation, a list of callsites of the closure that can be re-written
+ // If a callsite cannot be rewritten, for example because the lambda body method is not accessible,
+ // a warning is returned instead.
+ val callsitesToRewrite: List[(ClosureInstantiation, List[Either[RewriteClosureApplyToClosureBodyFailed, (MethodInsnNode, Int)]])] = {
+ closureInstantiationsByMethod.iterator.flatMap({
+ case (methodNode, closureInits) =>
+ // A lazy val to ensure the analysis only runs if necessary (the value is passed by name to `closureCallsites`)
+ lazy val prodCons = new ProdConsAnalyzer(methodNode, closureInits.head.ownerClass.internalName)
+ closureInits.iterator.map(init => (init, closureCallsites(init, prodCons)))
+ }).toList // mapping to a list (not a map) to keep the sorting of closureInstantiationsByMethod
+ }
- private val altMetafactoryHandle = {
- val altMetafactoryMethodName: String = "altMetafactory"
- val altMetafactoryDesc: String = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"
- new Handle(H_INVOKESTATIC, lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc)
+ // Rewrite all closure callsites (or issue inliner warnings for those that cannot be rewritten)
+ for ((closureInit, callsites) <- callsitesToRewrite) {
+ // Local variables that hold the captured values and the closure invocation arguments.
+ // They are lazy vals to ensure that locals for captured values are only allocated if there's
+ // actually a callsite to rewrite (an not only warnings to be issued).
+ lazy val (localsForCapturedValues, argumentLocalsList) = localsForClosureRewrite(closureInit)
+ for (callsite <- callsites) callsite match {
+ case Left(warning) =>
+ backendReporting.inlinerWarning(warning.pos, warning.toString)
+
+ case Right((invocation, stackHeight)) =>
+ rewriteClosureApplyInvocation(closureInit, invocation, stackHeight, localsForCapturedValues, argumentLocalsList)
+ }
+ }
}
- def isClosureInstantiation(indy: InvokeDynamicInsnNode): Boolean = {
- (indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle) &&
- {
- indy.bsmArgs match {
- case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs @ _*) =>
- // LambdaMetaFactory performs a number of automatic adaptations when invoking the lambda
- // implementation method (casting, boxing, unboxing, and primitive widening, see Javadoc).
- //
- // The closure optimizer supports only one of those adaptations: it will cast arguments
- // to the correct type when re-writing a closure call to the body method. Example:
- //
- // val fun: String => String = l => l
- // val l = List("")
- // fun(l.head)
- //
- // The samMethodType of Function1 is `(Object)Object`, while the instantiatedMethodType
- // is `(String)String`. The return type of `List.head` is `Object`.
- //
- // The implMethod has the signature `C$anonfun(String)String`.
- //
- // At the closure callsite, we have an `INVOKEINTERFACE Function1.apply (Object)Object`,
- // so the object returned by `List.head` can be directly passed into the call (no cast).
- //
- // The closure object will cast the object to String before passing it to the implMethod.
- //
- // When re-writing the closure callsite to the implMethod, we have to insert a cast.
- //
- // The check below ensures that
- // (1) the implMethod type has the expected singature (captured types plus argument types
- // from instantiatedMethodType)
- // (2) the receiver of the implMethod matches the first captured type
- // (3) all parameters that are not the same in samMethodType and instantiatedMethodType
- // are reference types, so that we can insert casts to perform the same adaptation
- // that the closure object would.
-
- val isStatic = implMethod.getTag == H_INVOKESTATIC
- val indyParamTypes = Type.getArgumentTypes(indy.desc)
- val instantiatedMethodArgTypes = instantiatedMethodType.getArgumentTypes
- val expectedImplMethodType = {
- val paramTypes = (if (isStatic) indyParamTypes else indyParamTypes.tail) ++ instantiatedMethodArgTypes
- Type.getMethodType(instantiatedMethodType.getReturnType, paramTypes: _*)
- }
-
- {
- Type.getType(implMethod.getDesc) == expectedImplMethodType // (1)
- } && {
- isStatic || implMethod.getOwner == indyParamTypes(0).getInternalName // (2)
- } && {
- def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY
- (samMethodType.getArgumentTypes, instantiatedMethodArgTypes).zipped forall {
- case (samArgType, instArgType) =>
- samArgType == instArgType || isReference(samArgType) && isReference(instArgType) // (3)
- }
- }
-
+ /**
+ * Insert instructions to store the values captured by a closure instantiation into local variables,
+ * and load the values back to the stack.
+ *
+ * Returns the list of locals holding those captured values, and a list of locals that should be
+ * used at the closure invocation callsite to store the arguments passed to the closure invocation.
+ */
+ private def localsForClosureRewrite(closureInit: ClosureInstantiation): (LocalsList, LocalsList) = {
+ val ownerMethod = closureInit.ownerMethod
+ val captureLocals = storeCaptures(closureInit)
+
+ // allocate locals for storing the arguments of the closure apply callsites.
+ // if there are multiple callsites, the same locals are re-used.
+ val argTypes = closureInit.lambdaMetaFactoryCall.samMethodType.getArgumentTypes
+ val firstArgLocal = ownerMethod.maxLocals
+
+ // The comment in the unapply method of `LambdaMetaFactoryCall` explains why we have to introduce
+ // casts for arguments that have different types in samMethodType and instantiatedMethodType.
+ val castLoadTypes = {
+ val instantiatedMethodType = closureInit.lambdaMetaFactoryCall.instantiatedMethodType
+ (argTypes, instantiatedMethodType.getArgumentTypes).zipped map {
+ case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType =>
+ // the LambdaMetaFactoryCall extractor ensures that the two types are reference types,
+ // so we don't end up casting primitive values.
+ Some(instantiatedArgType)
case _ =>
- false
+ None
}
}
+ val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes, castLoadTypes)
+ ownerMethod.maxLocals = firstArgLocal + argLocals.size
+
+ (captureLocals, argLocals)
+ }
+
+ /**
+ * Find all callsites of a closure within the method where the closure is allocated.
+ */
+ private def closureCallsites(closureInit: ClosureInstantiation, prodCons: => ProdConsAnalyzer): List[Either[RewriteClosureApplyToClosureBodyFailed, (MethodInsnNode, Int)]] = {
+ val ownerMethod = closureInit.ownerMethod
+ val ownerClass = closureInit.ownerClass
+ val lambdaBodyHandle = closureInit.lambdaMetaFactoryCall.implMethod
+
+ ownerMethod.instructions.iterator.asScala.collect({
+ case invocation: MethodInsnNode if isSamInvocation(invocation, closureInit, prodCons) =>
+ // TODO: This is maybe over-cautious.
+ // We are checking if the closure body method is accessible at the closure callsite.
+ // If the closure allocation has access to the body method, then the callsite (in the same
+ // method as the alloction) should have access too.
+ val bodyAccessible: Either[OptimizerWarning, Boolean] = for {
+ (bodyMethodNode, declClass) <- byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)]
+ isAccessible <- inliner.memberIsAccessible(bodyMethodNode.access, classBTypeFromParsedClassfile(declClass), classBTypeFromParsedClassfile(lambdaBodyHandle.getOwner), ownerClass)
+ } yield {
+ isAccessible
+ }
+
+ def pos = callGraph.callsites.get(invocation).map(_.callsitePosition).getOrElse(NoPosition)
+ val stackSize: Either[RewriteClosureApplyToClosureBodyFailed, Int] = bodyAccessible match {
+ case Left(w) => Left(RewriteClosureAccessCheckFailed(pos, w))
+ case Right(false) => Left(RewriteClosureIllegalAccess(pos, ownerClass.internalName))
+ case _ => Right(prodCons.frameAt(invocation).getStackSize)
+ }
+
+ stackSize.right.map((invocation, _))
+ }).toList
}
- def isSamInvocation(invocation: MethodInsnNode, indy: InvokeDynamicInsnNode, prodCons: => ProdConsAnalyzer): Boolean = {
+ private def isSamInvocation(invocation: MethodInsnNode, closureInit: ClosureInstantiation, prodCons: => ProdConsAnalyzer): Boolean = {
+ val indy = closureInit.lambdaMetaFactoryCall.indy
if (invocation.getOpcode == INVOKESTATIC) false
else {
def closureIsReceiver = {
@@ -120,20 +188,95 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
}
invocation.name == indy.name && {
- val indySamMethodDesc = indy.bsmArgs(0).asInstanceOf[Type].getDescriptor // safe, checked in isClosureInstantiation
+ val indySamMethodDesc = closureInit.lambdaMetaFactoryCall.samMethodType.getDescriptor
indySamMethodDesc == invocation.desc
} &&
- closureIsReceiver // most expensive check last
+ closureIsReceiver // most expensive check last
+ }
+ }
+
+ private def rewriteClosureApplyInvocation(closureInit: ClosureInstantiation, invocation: MethodInsnNode, stackHeight: Int, localsForCapturedValues: LocalsList, argumentLocalsList: LocalsList): Unit = {
+ val ownerMethod = closureInit.ownerMethod
+ val lambdaBodyHandle = closureInit.lambdaMetaFactoryCall.implMethod
+
+ // store arguments
+ insertStoreOps(invocation, ownerMethod, argumentLocalsList)
+
+ // drop the closure from the stack
+ ownerMethod.instructions.insertBefore(invocation, new InsnNode(POP))
+
+ // load captured values and arguments
+ insertLoadOps(invocation, ownerMethod, localsForCapturedValues)
+ insertLoadOps(invocation, ownerMethod, argumentLocalsList)
+
+ // update maxStack
+ val capturesStackSize = localsForCapturedValues.size
+ val invocationStackHeight = stackHeight + capturesStackSize - 1 // -1 because the closure is gone
+ if (invocationStackHeight > ownerMethod.maxStack)
+ ownerMethod.maxStack = invocationStackHeight
+
+ // replace the callsite with a new call to the body method
+ val bodyOpcode = (lambdaBodyHandle.getTag: @switch) match {
+ case H_INVOKEVIRTUAL => INVOKEVIRTUAL
+ case H_INVOKESTATIC => INVOKESTATIC
+ case H_INVOKESPECIAL => INVOKESPECIAL
+ case H_INVOKEINTERFACE => INVOKEINTERFACE
+ case H_NEWINVOKESPECIAL =>
+ val insns = ownerMethod.instructions
+ insns.insertBefore(invocation, new TypeInsnNode(NEW, lambdaBodyHandle.getOwner))
+ insns.insertBefore(invocation, new InsnNode(DUP))
+ INVOKESPECIAL
}
+ val isInterface = bodyOpcode == INVOKEINTERFACE
+ val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface)
+ ownerMethod.instructions.insertBefore(invocation, bodyInvocation)
+
+ val returnType = Type.getReturnType(lambdaBodyHandle.getDesc)
+ fixLoadedNothingOrNullValue(returnType, bodyInvocation, ownerMethod, btypes) // see comment of that method
+
+ ownerMethod.instructions.remove(invocation)
+
+ // update the call graph
+ val originalCallsite = callGraph.callsites.remove(invocation)
+
+ // the method node is needed for building the call graph entry
+ val bodyMethod = byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc)
+ def bodyMethodIsBeingCompiled = byteCodeRepository.classNodeAndSource(lambdaBodyHandle.getOwner).map(_._2 == CompilationUnit).getOrElse(false)
+ val bodyMethodCallsite = Callsite(
+ callsiteInstruction = bodyInvocation,
+ callsiteMethod = ownerMethod,
+ callsiteClass = closureInit.ownerClass,
+ callee = bodyMethod.map({
+ case (bodyMethodNode, bodyMethodDeclClass) => Callee(
+ callee = bodyMethodNode,
+ calleeDeclarationClass = classBTypeFromParsedClassfile(bodyMethodDeclClass),
+ safeToInline = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled,
+ safeToRewrite = false, // the lambda body method is not a trait interface method
+ annotatedInline = false,
+ annotatedNoInline = false,
+ calleeInfoWarning = None)
+ }),
+ argInfos = Nil,
+ callsiteStackHeight = invocationStackHeight,
+ receiverKnownNotNull = true, // see below (*)
+ callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition)
+ )
+ // (*) The documentation in class LambdaMetafactory says:
+ // "if implMethod corresponds to an instance method, the first capture argument
+ // (corresponding to the receiver) must be non-null"
+ // Explanation: If the lambda body method is non-static, the receiver is a captured
+ // value. It can only be captured within some instance method, so we know it's non-null.
+ callGraph.callsites(bodyInvocation) = bodyMethodCallsite
}
/**
- * Stores the values captured by a closure creation into fresh local variables.
- * Returns the list of locals holding the captured values.
+ * Stores the values captured by a closure creation into fresh local variables, and loads the
+ * values back onto the stack. Returns the list of locals holding the captured values.
*/
- private def storeCaptures(indy: InvokeDynamicInsnNode, methodNode: MethodNode): LocalsList = {
+ private def storeCaptures(closureInit: ClosureInstantiation): LocalsList = {
+ val indy = closureInit.lambdaMetaFactoryCall.indy
val capturedTypes = Type.getArgumentTypes(indy.desc)
- val firstCaptureLocal = methodNode.maxLocals
+ val firstCaptureLocal = closureInit.ownerMethod.maxLocals
// This could be optimized: in many cases the captured values are produced by LOAD instructions.
// If the variable is not modified within the method, we could avoid introducing yet another
@@ -144,10 +287,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
// This is checked in `isClosureInstantiation`: the types of the captured variables in the indy
// instruction match exactly the corresponding parameter types in the body method.
val localsForCaptures = LocalsList.fromTypes(firstCaptureLocal, capturedTypes, castLoadTypes = _ => None)
- methodNode.maxLocals = firstCaptureLocal + localsForCaptures.size
+ closureInit.ownerMethod.maxLocals = firstCaptureLocal + localsForCaptures.size
- insertStoreOps(indy, methodNode, localsForCaptures)
- insertLoadOps(indy, methodNode, localsForCaptures)
+ insertStoreOps(indy, closureInit.ownerMethod, localsForCaptures)
+ insertLoadOps(indy, closureInit.ownerMethod, localsForCaptures)
localsForCaptures
}
@@ -184,145 +327,6 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
}
}
- def rewriteClosureApplyInvocations(indy: InvokeDynamicInsnNode, methodNode: MethodNode, ownerClass: ClassBType): List[RewriteClosureApplyToClosureBodyFailed] = {
- val lambdaBodyHandle = indy.bsmArgs(1).asInstanceOf[Handle] // safe, checked in isClosureInstantiation
-
- // Kept as a lazy val to make sure the analysis is only computed if it's actually needed.
- // ProdCons is used to identify closure body invocations (see isSamInvocation), but only if the
- // callsite has the right name and signature. If the method has no invcation instruction with
- // the right name and signature, the analysis is not executed.
- lazy val prodCons = new ProdConsAnalyzer(methodNode, ownerClass.internalName)
-
- // First collect all callsites without modifying the instructions list yet.
- // Once we start modifying the instruction list, prodCons becomes unusable.
-
- // A list of callsites and stack heights. If the invocation cannot be rewritten, a warning
- // message is stored in the stack height value.
- val invocationsToRewrite: List[(MethodInsnNode, Either[RewriteClosureApplyToClosureBodyFailed, Int])] = methodNode.instructions.iterator.asScala.collect({
- case invocation: MethodInsnNode if isSamInvocation(invocation, indy, prodCons) =>
- val bodyAccessible: Either[OptimizerWarning, Boolean] = for {
- (bodyMethodNode, declClass) <- byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)]
- isAccessible <- inliner.memberIsAccessible(bodyMethodNode.access, classBTypeFromParsedClassfile(declClass), classBTypeFromParsedClassfile(lambdaBodyHandle.getOwner), ownerClass)
- } yield {
- isAccessible
- }
-
- def pos = callGraph.callsites.get(invocation).map(_.callsitePosition).getOrElse(NoPosition)
- val stackSize: Either[RewriteClosureApplyToClosureBodyFailed, Int] = bodyAccessible match {
- case Left(w) => Left(RewriteClosureAccessCheckFailed(pos, w))
- case Right(false) => Left(RewriteClosureIllegalAccess(pos, ownerClass.internalName))
- case _ => Right(prodCons.frameAt(invocation).getStackSize)
- }
-
- (invocation, stackSize)
- }).toList
-
- if (invocationsToRewrite.isEmpty) Nil
- else {
- // lazy val to make sure locals for captures and arguments are only allocated if there's
- // effectively a callsite to rewrite.
- lazy val (localsForCapturedValues, argumentLocalsList) = {
- val captureLocals = storeCaptures(indy, methodNode)
-
- // allocate locals for storing the arguments of the closure apply callsites.
- // if there are multiple callsites, the same locals are re-used.
- val argTypes = indy.bsmArgs(0).asInstanceOf[Type].getArgumentTypes // safe, checked in isClosureInstantiation
- val firstArgLocal = methodNode.maxLocals
-
- // The comment in `isClosureInstantiation` explains why we have to introduce casts for
- // arguments that have different types in samMethodType and instantiatedMethodType.
- val castLoadTypes = {
- val instantiatedMethodType = indy.bsmArgs(2).asInstanceOf[Type]
- (argTypes, instantiatedMethodType.getArgumentTypes).zipped map {
- case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType =>
- // isClosureInstantiation ensures that the two types are reference types, so we don't
- // end up casting primitive values.
- Some(instantiatedArgType)
- case _ =>
- None
- }
- }
- val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes, castLoadTypes)
- methodNode.maxLocals = firstArgLocal + argLocals.size
-
- (captureLocals, argLocals)
- }
-
- val warnings = invocationsToRewrite flatMap {
- case (invocation, Left(warning)) => Some(warning)
-
- case (invocation, Right(stackHeight)) =>
- // store arguments
- insertStoreOps(invocation, methodNode, argumentLocalsList)
-
- // drop the closure from the stack
- methodNode.instructions.insertBefore(invocation, new InsnNode(POP))
-
- // load captured values and arguments
- insertLoadOps(invocation, methodNode, localsForCapturedValues)
- insertLoadOps(invocation, methodNode, argumentLocalsList)
-
- // update maxStack
- val capturesStackSize = localsForCapturedValues.size
- val invocationStackHeight = stackHeight + capturesStackSize - 1 // -1 because the closure is gone
- if (invocationStackHeight > methodNode.maxStack)
- methodNode.maxStack = invocationStackHeight
-
- // replace the callsite with a new call to the body method
- val bodyOpcode = (lambdaBodyHandle.getTag: @switch) match {
- case H_INVOKEVIRTUAL => INVOKEVIRTUAL
- case H_INVOKESTATIC => INVOKESTATIC
- case H_INVOKESPECIAL => INVOKESPECIAL
- case H_INVOKEINTERFACE => INVOKEINTERFACE
- case H_NEWINVOKESPECIAL =>
- val insns = methodNode.instructions
- insns.insertBefore(invocation, new TypeInsnNode(NEW, lambdaBodyHandle.getOwner))
- insns.insertBefore(invocation, new InsnNode(DUP))
- INVOKESPECIAL
- }
- val isInterface = bodyOpcode == INVOKEINTERFACE
- val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface)
- methodNode.instructions.insertBefore(invocation, bodyInvocation)
- methodNode.instructions.remove(invocation)
-
- // update the call graph
- val originalCallsite = callGraph.callsites.remove(invocation)
-
- // the method node is needed for building the call graph entry
- val bodyMethod = byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc)
- def bodyMethodIsBeingCompiled = byteCodeRepository.classNodeAndSource(lambdaBodyHandle.getOwner).map(_._2 == CompilationUnit).getOrElse(false)
- val bodyMethodCallsite = Callsite(
- callsiteInstruction = bodyInvocation,
- callsiteMethod = methodNode,
- callsiteClass = ownerClass,
- callee = bodyMethod.map({
- case (bodyMethodNode, bodyMethodDeclClass) => Callee(
- callee = bodyMethodNode,
- calleeDeclarationClass = classBTypeFromParsedClassfile(bodyMethodDeclClass),
- safeToInline = compilerSettings.YoptInlineGlobal || bodyMethodIsBeingCompiled,
- safeToRewrite = false, // the lambda body method is not a trait interface method
- annotatedInline = false,
- annotatedNoInline = false,
- calleeInfoWarning = None)
- }),
- argInfos = Nil,
- callsiteStackHeight = invocationStackHeight,
- receiverKnownNotNull = true, // see below (*)
- callsitePosition = originalCallsite.map(_.callsitePosition).getOrElse(NoPosition)
- )
- // (*) The documentation in class LambdaMetafactory says:
- // "if implMethod corresponds to an instance method, the first capture argument
- // (corresponding to the receiver) must be non-null"
- // Explanation: If the lambda body method is non-static, the receiver is a captured
- // value. It can only be captured within some instance method, so we know it's non-null.
- callGraph.callsites(bodyInvocation) = bodyMethodCallsite
- None
- }
-
- warnings.toList
- }
- }
-
/**
* A list of local variables. Each local stores information about its type, see class [[Local]].
*/
@@ -355,7 +359,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
}
/**
- * Stores a local varaible index the opcode offset required for operating on that variable.
+ * Stores a local variable index the opcode offset required for operating on that variable.
*
* The xLOAD / xSTORE opcodes are in the following sequence: I, L, F, D, A, so the offset for
* a local variable holding a reference (`A`) is 4. See also method `getOpcode` in [[scala.tools.asm.Type]].