diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2015-09-23 15:11:01 +0200 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2015-09-23 15:12:29 +0200 |
commit | b0b5b09f90b84696695538a42e7b7ff36555c0f9 (patch) | |
tree | 54bf3c3c75a1911f7a570ab16831d71c3fbd4ef4 /src | |
parent | f9dbade4dbc3a888f927983c164b83ef5d500af9 (diff) | |
download | scala-b0b5b09f90b84696695538a42e7b7ff36555c0f9.tar.gz scala-b0b5b09f90b84696695538a42e7b7ff36555c0f9.tar.bz2 scala-b0b5b09f90b84696695538a42e7b7ff36555c0f9.zip |
Add $deserializeLambda$ when inlining an indyLambda into a class
Fixes https://github.com/scala/scala-dev/issues/39
When inlining an indyLambda closure instantiation into a class, and
the closure type is serializable, make sure that the target class has
the synthetic `$deserializeLambda$` method.
Diffstat (limited to 'src')
6 files changed, 55 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 { |