diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2015-09-24 12:54:19 +1000 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2015-09-24 12:54:19 +1000 |
commit | 58a5c5a57c1ada086c4e728586537d64dca765ef (patch) | |
tree | 54bf3c3c75a1911f7a570ab16831d71c3fbd4ef4 | |
parent | df704ef7389ff6265afc1278783489ee97ee4ce7 (diff) | |
parent | b0b5b09f90b84696695538a42e7b7ff36555c0f9 (diff) | |
download | scala-58a5c5a57c1ada086c4e728586537d64dca765ef.tar.gz scala-58a5c5a57c1ada086c4e728586537d64dca765ef.tar.bz2 scala-58a5c5a57c1ada086c4e728586537d64dca765ef.zip |
Merge pull request #4763 from lrytz/inlineDeserializeLambda
Add $deserializeLambda$ when inlining an indyLambda into a class
17 files changed, 203 insertions, 151 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index e5eb0b79d5..a42332f7f2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -11,6 +11,7 @@ package jvm import scala.annotation.switch import scala.reflect.internal.Flags +import java.lang.invoke.LambdaMetafactory import scala.tools.asm import GenBCode._ @@ -1303,7 +1304,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val samName = sam.name.toString val samMethodType = asmMethodType(sam).toASMType - val flags = 3 // TODO 2.12.x Replace with LambdaMetafactory.FLAG_SERIALIZABLE | LambdaMetafactory.FLAG_MARKERS + val flags = LambdaMetafactory.FLAG_SERIALIZABLE | LambdaMetafactory.FLAG_MARKERS val ScalaSerializable = classBTypeFromSymbol(definitions.SerializableClass).toASMType bc.jmethod.visitInvokeDynamicInsn(samName, invokedType, lambdaMetaFactoryBootstrapHandle, @@ -1315,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/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 0f381a4325..bc3bdfc6ba 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -683,60 +683,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { new java.lang.Long(id) ).visitEnd() } - - /** - * Add: - * private static java.util.Map $deserializeLambdaCache$ = null - * private static Object $deserializeLambda$(SerializedLambda l) { - * var cache = $deserializeLambdaCache$ - * if (cache eq null) { - * cache = new java.util.HashMap() - * $deserializeLambdaCache$ = cache - * } - * return scala.runtime.LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), cache, l); - * } - */ - def addLambdaDeserialize(clazz: Symbol, jclass: asm.ClassVisitor): Unit = { - val cw = jclass - import scala.tools.asm.Opcodes._ - - // Need to force creation of BTypes for these as `getCommonSuperClass` is called on - // automatically computing the max stack size (`visitMaxs`) during method writing. - javaUtilHashMapReference - javaUtilMapReference - - cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC + ACC_FINAL + ACC_STATIC) - - { - val fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambdaCache$", "Ljava/util/Map;", null, null) - fv.visitEnd() - } - - { - val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", null, null) - mv.visitCode() - // javaBinaryName returns the internal name of a class. Also used in BTypesFromsymbols.classBTypeFromSymbol. - mv.visitFieldInsn(GETSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;") - mv.visitVarInsn(ASTORE, 1) - mv.visitVarInsn(ALOAD, 1) - val l0 = new asm.Label() - mv.visitJumpInsn(IFNONNULL, l0) - mv.visitTypeInsn(NEW, "java/util/HashMap") - mv.visitInsn(DUP) - mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false) - mv.visitVarInsn(ASTORE, 1) - mv.visitVarInsn(ALOAD, 1) - mv.visitFieldInsn(PUTSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;") - mv.visitLabel(l0) - mv.visitFieldInsn(GETSTATIC, "scala/runtime/LambdaDeserializer$", "MODULE$", "Lscala/runtime/LambdaDeserializer$;") - mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false) - mv.visitVarInsn(ALOAD, 1) - mv.visitVarInsn(ALOAD, 0) - mv.visitMethodInsn(INVOKEVIRTUAL, "scala/runtime/LambdaDeserializer$", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false) - mv.visitInsn(ARETURN) - mv.visitEnd() - } - } } // end of trait BCClassGen /* functionality for building plain and mirror classes */ diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index 9875ade113..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,10 +125,10 @@ 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) - addLambdaDeserialize(claszSymbol, cnode) + backendUtils.addLambdaDeserialize(cnode) addInnerClassesASM(cnode, innerClassBufferASM.toList) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index aff2d2d8c9..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 @@ -14,7 +15,7 @@ import asm.Opcodes import scala.tools.asm.tree._ import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} import scala.tools.nsc.backend.jvm.BackendReporting._ -import scala.tools.nsc.backend.jvm.analysis.Analyzers +import scala.tools.nsc.backend.jvm.analysis.BackendUtils import scala.tools.nsc.backend.jvm.opt._ import scala.collection.convert.decorateAsScala._ import scala.tools.nsc.settings.ScalaSettings @@ -30,6 +31,8 @@ import scala.tools.nsc.settings.ScalaSettings abstract class BTypes { import BTypes.InternalName + val backendUtils: BackendUtils[this.type] + // Some core BTypes are required here, in class BType, where no Global instance is available. // The Global is only available in the subclass BTypesFromSymbols. We cannot depend on the actual // implementation (CoreBTypesProxy) here because it has members that refer to global.Symbol. @@ -51,8 +54,6 @@ abstract class BTypes { val callGraph: CallGraph[this.type] - val analyzers: Analyzers[this.type] - val backendReporting: BackendReporting // Allows to define per-run caches here and in the CallGraph component, which don't have a global @@ -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/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index df34326bbf..79b5e6a2fb 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -7,7 +7,7 @@ package scala.tools.nsc package backend.jvm import scala.tools.asm -import scala.tools.nsc.backend.jvm.analysis.Analyzers +import scala.tools.nsc.backend.jvm.analysis.BackendUtils import scala.tools.nsc.backend.jvm.opt._ import scala.tools.nsc.backend.jvm.BTypes._ import BackendReporting._ @@ -33,6 +33,8 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val bCodeAsmCommon: BCodeAsmCommon[global.type] = new BCodeAsmCommon(global) import bCodeAsmCommon._ + val backendUtils: BackendUtils[this.type] = new BackendUtils(this) + // Why the proxy, see documentation of class [[CoreBTypes]]. val coreBTypes = new CoreBTypesProxy[this.type](this) import coreBTypes._ @@ -49,8 +51,6 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { val callGraph: CallGraph[this.type] = new CallGraph(this) - val analyzers: Analyzers[this.type] = new Analyzers(this) - val backendReporting: BackendReporting = new BackendReportingImpl(global) final def initializeCoreBTypes(): Unit = { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 00ca096e59..48e43c74f4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -206,12 +206,14 @@ trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] { def boxedClasses: Set[ClassBType] - def RT_NOTHING : ClassBType - def RT_NULL : ClassBType - - def ObjectReference : ClassBType - def jlCloneableReference : ClassBType - def jioSerializableReference : ClassBType + def RT_NOTHING: ClassBType + def RT_NULL : ClassBType + + def ObjectReference : ClassBType + def jlCloneableReference : ClassBType + def jioSerializableReference: ClassBType + def javaUtilHashMapReference: ClassBType + def javaUtilMapReference : ClassBType } /** diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/Analyzers.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/Analyzers.scala deleted file mode 100644 index bb5c6e3820..0000000000 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/Analyzers.scala +++ /dev/null @@ -1,48 +0,0 @@ -package scala.tools.nsc -package backend.jvm -package analysis - -import scala.tools.asm.tree.{AbstractInsnNode, MethodNode} -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._ - -/** - * This component hosts tools for running ASM analyzers that require access to a `BTypes` instance. - * In particular, the AsmAnalyzer class runs `computeMaxLocalsMaxStack` on the methodNode to be - * analyzed. This method in turn lives inside the BTypes assembly because it queries the per-run - * cache `maxLocalsMaxStackComputed` defined in there. - */ -class Analyzers[BT <: BTypes](val btypes: BT) { - import btypes._ - - /** - * A wrapper to make ASM's Analyzer a bit easier to use. - */ - class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, val analyzer: Analyzer[V] = new Analyzer(new BasicInterpreter)) { - localOpt.computeMaxLocalsMaxStack(methodNode) - analyzer.analyze(classInternalName, methodNode) - def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) - } - - /** - * See the doc comment on package object `analysis` for a discussion on performance. - */ - object AsmAnalyzer { - // jvm limit is 65535 for both number of instructions and number of locals - - private def size(method: MethodNode) = method.instructions.size.toLong * method.maxLocals * method.maxLocals - - // with the limits below, analysis should not take more than one second - - private val nullnessSizeLimit = 5000l * 600l * 600l // 5000 insns, 600 locals - private val basicValueSizeLimit = 9000l * 1000l * 1000l - private val sourceValueSizeLimit = 8000l * 950l * 950l - - def sizeOKForNullness(method: MethodNode): Boolean = size(method) < nullnessSizeLimit - def sizeOKForBasicValue(method: MethodNode): Boolean = size(method) < basicValueSizeLimit - def sizeOKForSourceValue(method: MethodNode): Boolean = size(method) < sourceValueSizeLimit - } - - class ProdConsAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new InitialProducerSourceInterpreter)) with ProdConsAnalyzerImpl -} diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala new file mode 100644 index 0000000000..793235c131 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala @@ -0,0 +1,136 @@ +package scala.tools.nsc +package backend.jvm +package analysis + +import scala.tools.asm.Label +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` + * instance. + * + * One example is the AsmAnalyzer class, which runs `computeMaxLocalsMaxStack` on the methodNode to + * be analyzed. This method in turn lives inside the BTypes assembly because it queries the per-run + * cache `maxLocalsMaxStackComputed` defined in there. + */ +class BackendUtils[BT <: BTypes](val btypes: BT) { + import btypes._ + + /** + * A wrapper to make ASM's Analyzer a bit easier to use. + */ + class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, val analyzer: Analyzer[V] = new Analyzer(new BasicInterpreter)) { + localOpt.computeMaxLocalsMaxStack(methodNode) + analyzer.analyze(classInternalName, methodNode) + def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode) + } + + /** + * See the doc comment on package object `analysis` for a discussion on performance. + */ + object AsmAnalyzer { + // jvm limit is 65535 for both number of instructions and number of locals + + private def size(method: MethodNode) = method.instructions.size.toLong * method.maxLocals * method.maxLocals + + // with the limits below, analysis should not take more than one second + + private val nullnessSizeLimit = 5000l * 600l * 600l // 5000 insns, 600 locals + private val basicValueSizeLimit = 9000l * 1000l * 1000l + private val sourceValueSizeLimit = 8000l * 950l * 950l + + def sizeOKForNullness(method: MethodNode): Boolean = size(method) < nullnessSizeLimit + def sizeOKForBasicValue(method: MethodNode): Boolean = size(method) < basicValueSizeLimit + def sizeOKForSourceValue(method: MethodNode): Boolean = size(method) < sourceValueSizeLimit + } + + class ProdConsAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new InitialProducerSourceInterpreter)) with ProdConsAnalyzerImpl + + /** + * Add: + * private static java.util.Map $deserializeLambdaCache$ = null + * private static Object $deserializeLambda$(SerializedLambda l) { + * var cache = $deserializeLambdaCache$ + * if (cache eq null) { + * cache = new java.util.HashMap() + * $deserializeLambdaCache$ = cache + * } + * return scala.runtime.LambdaDeserializer.deserializeLambda(MethodHandles.lookup(), cache, l); + * } + */ + def addLambdaDeserialize(classNode: ClassNode): Unit = { + val cw = classNode + import scala.tools.asm.Opcodes._ + + // Need to force creation of BTypes for these as `getCommonSuperClass` is called on + // automatically computing the max stack size (`visitMaxs`) during method writing. + 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) + + { + val fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambdaCache$", "Ljava/util/Map;", null, null) + fv.visitEnd() + } + + { + val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", null, null) + mv.visitCode() + // javaBinaryName returns the internal name of a class. Also used in BTypesFromsymbols.classBTypeFromSymbol. + mv.visitFieldInsn(GETSTATIC, classNode.name, "$deserializeLambdaCache$", "Ljava/util/Map;") + mv.visitVarInsn(ASTORE, 1) + mv.visitVarInsn(ALOAD, 1) + val l0 = new Label() + mv.visitJumpInsn(IFNONNULL, l0) + mv.visitTypeInsn(NEW, "java/util/HashMap") + mv.visitInsn(DUP) + mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false) + mv.visitVarInsn(ASTORE, 1) + mv.visitVarInsn(ALOAD, 1) + mv.visitFieldInsn(PUTSTATIC, classNode.name, "$deserializeLambdaCache$", "Ljava/util/Map;") + mv.visitLabel(l0) + mv.visitFieldInsn(GETSTATIC, "scala/runtime/LambdaDeserializer$", "MODULE$", "Lscala/runtime/LambdaDeserializer$;") + mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false) + mv.visitVarInsn(ALOAD, 1) + mv.visitVarInsn(ALOAD, 0) + mv.visitMethodInsn(INVOKEVIRTUAL, "scala/runtime/LambdaDeserializer$", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false) + mv.visitInsn(ARETURN) + 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/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala index b9788c3f56..32eaf07080 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -21,7 +21,7 @@ import BytecodeUtils._ class CallGraph[BT <: BTypes](val btypes: BT) { import btypes._ - import analyzers._ + import backendUtils._ /** * The call graph contains the callsites in the program being compiled. 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 fb7dd16909..a7c4c27a97 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -23,7 +23,7 @@ import scala.collection.convert.decorateAsScala._ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { import btypes._ import callGraph._ - import analyzers._ + import backendUtils._ /** * If a closure is allocated and invoked within the same method, re-write the invocation to the 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 baa747492f..f88c131e8d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -24,7 +24,7 @@ class Inliner[BT <: BTypes](val btypes: BT) { import btypes._ import callGraph._ import inlinerHeuristics._ - import analyzers._ + import backendUtils._ def eliminateUnreachableCodeAndUpdateCallGraph(methodNode: MethodNode, definingClass: InternalName): Unit = { localOpt.minimalRemoveUnreachableCode(methodNode, definingClass) foreach { @@ -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/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala index 1e7b46012e..38f3c51892 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -48,7 +48,7 @@ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ class LocalOpt[BT <: BTypes](val btypes: BT) { import LocalOptImpls._ import btypes._ - import analyzers._ + import backendUtils._ /** * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM 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[_, _]]) +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala index f78d450db1..a7d1dc168a 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala @@ -31,7 +31,7 @@ object NullnessAnalyzerTest extends ClearAfterClass.Clearable { class NullnessAnalyzerTest extends ClearAfterClass { ClearAfterClass.stateToClear = NullnessAnalyzerTest val noOptCompiler = NullnessAnalyzerTest.noOptCompiler - import noOptCompiler.genBCode.bTypes.analyzers._ + import noOptCompiler.genBCode.bTypes.backendUtils._ def newNullnessAnalyzer(methodNode: MethodNode, classInternalName: InternalName = "C") = new AsmAnalyzer(methodNode, classInternalName, new NullnessAnalyzer) diff --git a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala index 155e0a6017..4835eb3cdf 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala @@ -26,7 +26,7 @@ object ProdConsAnalyzerTest extends ClearAfterClass.Clearable { class ProdConsAnalyzerTest extends ClearAfterClass { ClearAfterClass.stateToClear = ProdConsAnalyzerTest val noOptCompiler = ProdConsAnalyzerTest.noOptCompiler - import noOptCompiler.genBCode.bTypes.analyzers._ + import noOptCompiler.genBCode.bTypes.backendUtils._ def prodToString(producer: AbstractInsnNode) = producer match { case p: InitialProducer => p.toString diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala index 8429a583b5..1108a37266 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -68,7 +68,7 @@ class InlinerTest extends ClearAfterClass { val compiler = InlinerTest.compiler import compiler.genBCode.bTypes._ - import compiler.genBCode.bTypes.analyzers._ + import compiler.genBCode.bTypes.backendUtils._ def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = { InlinerTest.notPerRun.foreach(_.clear()) |