summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala2
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala4
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala20
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala33
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala18
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala7
-rw-r--r--test/files/run/inlineAddDeserializeLambda.scala20
7 files changed, 75 insertions, 29 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
index 98e8fae51a..a42332f7f2 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala
@@ -1316,7 +1316,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
/* markerInterfaces[0] = */ ScalaSerializable,
/* bridgeCount = */ 0.asInstanceOf[AnyRef]
)
- indyLambdaHosts += this.claszSymbol
+ indyLambdaHosts += cnode.name
}
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
index 8d8ffe31d2..b22fdf7c2c 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala
@@ -67,8 +67,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
var isCZStaticModule = false
var isCZRemote = false
- protected val indyLambdaHosts = collection.mutable.Set[Symbol]()
-
/* ---------------- idiomatic way to ask questions to typer ---------------- */
def paramTKs(app: Apply): List[BType] = {
@@ -127,7 +125,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
val shouldAddLambdaDeserialize = (
settings.target.value == "jvm-1.8"
&& settings.Ydelambdafy.value == "method"
- && indyLambdaHosts.contains(claszSymbol))
+ && indyLambdaHosts.contains(cnode.name))
if (shouldAddLambdaDeserialize)
backendUtils.addLambdaDeserialize(cnode)
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
index 518c808488..f1b515910f 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala
@@ -7,6 +7,7 @@ package scala.tools.nsc
package backend.jvm
import scala.annotation.switch
+import scala.collection.{mutable, concurrent}
import scala.collection.concurrent.TrieMap
import scala.reflect.internal.util.Position
import scala.tools.asm
@@ -72,19 +73,19 @@ abstract class BTypes {
* Concurrent because stack map frames are computed when in the class writer, which might run
* on multiple classes concurrently.
*/
- val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
+ val classBTypeFromInternalName: concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty)
/**
* Store the position of every MethodInsnNode during code generation. This allows each callsite
* in the call graph to remember its source position, which is required for inliner warnings.
*/
- val callsitePositions: collection.concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty)
+ val callsitePositions: concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty)
/**
* Contains the internal names of all classes that are defined in Java source files of the current
* compilation run (mixed compilation). Used for more detailed error reporting.
*/
- val javaDefinedClasses: collection.mutable.Set[InternalName] = recordPerRunCache(collection.mutable.Set.empty)
+ val javaDefinedClasses: mutable.Set[InternalName] = recordPerRunCache(mutable.Set.empty)
/**
* Cache, contains methods whose unreachable instructions are eliminated.
@@ -96,14 +97,23 @@ abstract class BTypes {
* This cache allows running dead code elimination whenever an analyzer is used. If the method
* is already optimized, DCE can return early.
*/
- val unreachableCodeEliminated: collection.mutable.Set[MethodNode] = recordPerRunCache(collection.mutable.Set.empty)
+ val unreachableCodeEliminated: mutable.Set[MethodNode] = recordPerRunCache(mutable.Set.empty)
/**
* Cache of methods which have correct `maxLocals` / `maxStack` values assigned. This allows
* invoking `computeMaxLocalsMaxStack` whenever running an analyzer but performing the actual
* computation only when necessary.
*/
- val maxLocalsMaxStackComputed: collection.mutable.Set[MethodNode] = recordPerRunCache(collection.mutable.Set.empty)
+ val maxLocalsMaxStackComputed: mutable.Set[MethodNode] = recordPerRunCache(mutable.Set.empty)
+
+ /**
+ * Classes with indyLambda closure instantiations where the SAM type is serializable (e.g. Scala's
+ * FunctionN) need a `$deserializeLambda$` method. This map contains classes for which such a
+ * method has been generated. It is used during ordinary code generation, as well as during
+ * inlining: when inlining an indyLambda instruction into a class, we need to make sure the class
+ * has the method.
+ */
+ val indyLambdaHosts: mutable.Set[InternalName] = recordPerRunCache(mutable.Set.empty)
/**
* Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
index 4e0d7a4c44..793235c131 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
@@ -3,10 +3,13 @@ package backend.jvm
package analysis
import scala.tools.asm.Label
-import scala.tools.asm.tree.{ClassNode, AbstractInsnNode, MethodNode}
+import scala.tools.asm.tree._
import scala.tools.asm.tree.analysis.{Frame, BasicInterpreter, Analyzer, Value}
import scala.tools.nsc.backend.jvm.BTypes._
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+import java.lang.invoke.LambdaMetafactory
+import scala.collection.convert.decorateAsJava._
+import scala.collection.convert.decorateAsScala._
/**
* This component hosts tools and utilities used in the backend that require access to a `BTypes`
@@ -70,6 +73,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
btypes.coreBTypes.javaUtilHashMapReference
btypes.coreBTypes.javaUtilMapReference
+ // This is fine, even if `visitInnerClass` was called before for MethodHandles.Lookup: duplicates are not emitted.
cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC + ACC_FINAL + ACC_STATIC)
{
@@ -102,4 +106,31 @@ class BackendUtils[BT <: BTypes](val btypes: BT) {
mv.visitEnd()
}
}
+
+ /**
+ * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to
+ * the `labelMap`. Returns the new instruction list and a map from old to new instructions, and
+ * a boolean indicating if the instruction list contains an instantiation of a serializable SAM
+ * type.
+ */
+ def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): (InsnList, Map[AbstractInsnNode, AbstractInsnNode], Boolean) = {
+ val javaLabelMap = labelMap.asJava
+ val result = new InsnList
+ var map = Map.empty[AbstractInsnNode, AbstractInsnNode]
+ var hasSerializableClosureInstantiation = false
+ for (ins <- methodNode.instructions.iterator.asScala) {
+ if (!hasSerializableClosureInstantiation) ins match {
+ case callGraph.LambdaMetaFactoryCall(indy, _, _, _) => indy.bsmArgs match {
+ case Array(_, _, _, flags: Integer, xs@_*) if (flags.intValue & LambdaMetafactory.FLAG_SERIALIZABLE) != 0 =>
+ hasSerializableClosureInstantiation = true
+ case _ =>
+ }
+ case _ =>
+ }
+ val cloned = ins.clone(javaLabelMap)
+ result add cloned
+ map += ((ins, cloned))
+ }
+ (result, map, hasSerializableClosureInstantiation)
+ }
}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
index ea186f9a1b..5ff356b704 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala
@@ -16,8 +16,6 @@ import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes, Type}
import scala.tools.asm.tree._
import GenBCode._
import scala.collection.convert.decorateAsScala._
-import scala.collection.convert.decorateAsJava._
-import scala.tools.nsc.backend.jvm.BTypes._
object BytecodeUtils {
@@ -271,22 +269,6 @@ object BytecodeUtils {
}
/**
- * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to
- * the `labelMap`. Returns the new instruction list and a map from old to new instructions.
- */
- def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode]): (InsnList, Map[AbstractInsnNode, AbstractInsnNode]) = {
- val javaLabelMap = labelMap.asJava
- val result = new InsnList
- var map = Map.empty[AbstractInsnNode, AbstractInsnNode]
- for (ins <- methodNode.instructions.iterator.asScala) {
- val cloned = ins.clone(javaLabelMap)
- result add cloned
- map += ((ins, cloned))
- }
- (result, map)
- }
-
- /**
* Clone the local variable descriptors of `methodNode` and map their `start` and `end` labels
* according to the `labelMap`.
*/
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
index a2ee4f8ad4..f88c131e8d 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala
@@ -299,7 +299,7 @@ class Inliner[BT <: BTypes](val btypes: BT) {
// New labels for the cloned instructions
val labelsMap = cloneLabels(callee)
- val (clonedInstructions, instructionMap) = cloneInstructions(callee, labelsMap)
+ val (clonedInstructions, instructionMap, hasSerializableClosureInstantiation) = cloneInstructions(callee, labelsMap)
val keepLineNumbers = callsiteClass == calleeDeclarationClass
if (!keepLineNumbers) {
removeLineNumberNodes(clonedInstructions)
@@ -431,6 +431,11 @@ class Inliner[BT <: BTypes](val btypes: BT) {
callsiteMethod.maxStack = math.max(callsiteMethod.maxStack, math.max(stackHeightAtNullCheck, maxStackOfInlinedCode))
+ if (hasSerializableClosureInstantiation && !indyLambdaHosts(callsiteClass.internalName)) {
+ indyLambdaHosts += callsiteClass.internalName
+ addLambdaDeserialize(byteCodeRepository.classNode(callsiteClass.internalName).get)
+ }
+
callGraph.addIfMissing(callee, calleeDeclarationClass)
def mapArgInfo(argInfo: (Int, ArgInfo)): Option[(Int, ArgInfo)] = argInfo match {
diff --git a/test/files/run/inlineAddDeserializeLambda.scala b/test/files/run/inlineAddDeserializeLambda.scala
new file mode 100644
index 0000000000..a6bafd0f49
--- /dev/null
+++ b/test/files/run/inlineAddDeserializeLambda.scala
@@ -0,0 +1,20 @@
+class C { @inline final def f: Int => Int = (x: Int) => x + 1 }
+
+object Test extends App {
+ import java.io._
+
+ def serialize(obj: AnyRef): Array[Byte] = {
+ val buffer = new ByteArrayOutputStream
+ val out = new ObjectOutputStream(buffer)
+ out.writeObject(obj)
+ buffer.toByteArray
+ }
+ def deserialize(a: Array[Byte]): AnyRef = {
+ val in = new ObjectInputStream(new ByteArrayInputStream(a))
+ in.readObject
+ }
+
+ def serializeDeserialize[T <: AnyRef](obj: T) = deserialize(serialize(obj)).asInstanceOf[T]
+
+ assert(serializeDeserialize((new C).f).isInstanceOf[Function1[_, _]])
+}