diff options
82 files changed, 767 insertions, 272 deletions
diff --git a/build-ant-macros.xml b/build-ant-macros.xml index 868fb41cce..cfdb1ce70e 100644 --- a/build-ant-macros.xml +++ b/build-ant-macros.xml @@ -6,7 +6,7 @@ <attribute name="name"/> <sequential> <antcall target="@{name}"> - <param name="scalac.args.optimise" value="-optimise"/> + <param name="scalac.args.optimise" value="-Yopt:l:classpath"/> </antcall> </sequential> </macrodef> @@ -19,7 +19,7 @@ ant $antArgs $scalacArgs $targets antArgs tend to be: -Darchives.skipxz=true - -Dscalac.args.optimise=-optimise + -Dscalac.args.optimise=-Yopt:l:classpath scalacArgs examples: "-Dscalac.args=\"-Yrangepos\" -Dpartest.scalac_opts=\"-Yrangepos\"" @@ -79,13 +79,13 @@ TODO: <target name="publish-opt-nodocs" description="Publishes Scala (optimized) without generating docs/testing (library/reflect/compiler/swing)."> <antcall target="publish"> <param name="docs.skip" value="1"/> - <param name="scalac.args.optimise" value="-optimise"/> + <param name="scalac.args.optimise" value="-Yopt:l:classpath"/> </antcall> </target> <target name="publish-core-opt-nodocs" description="Builds an untested, undocumented optimised core (library/reflect/compiler) and publishes to maven."> <antcall target="publish-core"> <param name="docs.skip" value="1"/> - <param name="scalac.args.optimise" value="-optimise"/> + <param name="scalac.args.optimise" value="-Yopt:l:classpath"/> </antcall> </target> <target name="publish-core-local-nodocs" description="Builds an untested, undocumented core (library/reflect/compiler) and locally publishes to maven"> @@ -1042,7 +1042,7 @@ TODO: <antcall target="publish-core-local"> <param name="maven.version.suffix" value="-STARR-${git.commit.sha}-SNAPSHOT"/> <param name="docs.skip" value="1"/> - <param name="scalac.args.optimise" value="-optimise"/> + <param name="scalac.args.optimise" value="-Yopt:l:classpath"/> <param name="update.starr.version" value="alright then"/> </antcall> </target> diff --git a/scripts/jobs/integrate/bootstrap b/scripts/jobs/integrate/bootstrap index ccc62c0d45..c6935df936 100755 --- a/scripts/jobs/integrate/bootstrap +++ b/scripts/jobs/integrate/bootstrap @@ -217,7 +217,8 @@ buildXML() { then echo "Found scala-xml $XML_VER; not building." else update scala scala-xml "$XML_REF" && gfxd - sbtBuild 'set version := "'$XML_VER'-DOC"' $clean doc 'set version := "'$XML_VER'"' test "${buildTasks[@]}" + # TODO: compile under 2.12 source level once `scala.xml.XMLTest.escape` is fixed (see #4451) + sbtBuild 'set scalacOptions += "-Xsource:2.11"' 'set version := "'$XML_VER'-DOC"' $clean doc 'set version := "'$XML_VER'"' test "${buildTasks[@]}" XML_BUILT="yes" # ensure the module is built and published when buildXML is invoked for the second time, see comment above fi } @@ -493,7 +494,7 @@ bootstrap() { -Dremote.snapshot.repository=NOPE\ -Dremote.release.repository=$releaseTempRepoUrl\ -Drepository.credentials.id=$releaseTempRepoCred\ - -Dscalac.args.optimise=-optimise\ + -Dscalac.args.optimise=-Yopt:l:classpath\ -Ddocs.skip=1\ -Dlocker.skip=1\ $publishLockerPrivateTask >> $baseDir/logs/builds 2>&1 @@ -533,7 +534,7 @@ bootstrap() { -Dremote.snapshot.repository=NOPE\ -Dremote.release.repository=$releaseTempRepoUrl\ -Drepository.credentials.id=$releaseTempRepoCred\ - -Dscalac.args.optimise=-optimise\ + -Dscalac.args.optimise=-Yopt:l:classpath\ $antBuildTask $publishPrivateTask # clear ivy cache (and to be sure, local as well), so the next round of sbt builds sees the fresh scala diff --git a/scripts/jobs/validate/test b/scripts/jobs/validate/test index bedef2e458..d63d39c65d 100755 --- a/scripts/jobs/validate/test +++ b/scripts/jobs/validate/test @@ -10,7 +10,7 @@ case $prDryRun in # build quick using STARR built upstream, as specified by scalaVersion # (in that sense it's locker, since it was built with starr by that upstream job) ant -Dstarr.version=$scalaVersion \ - -Dscalac.args.optimise=-optimise \ + -Dscalac.args.optimise=-Yopt:l:classpath \ -Dlocker.skip=1 -Dextra.repo.url=$prRepoUrl \ $testExtraArgs ${testTarget-test.core docs.done} ;; diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 965f984367..5cb31c1b64 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -1373,7 +1373,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) unitbuf += unit compiledFiles += unit.source.file.path } - private def checkDeprecatedSettings(unit: CompilationUnit) { + private def warnDeprecatedAndConflictingSettings(unit: CompilationUnit) { // issue warnings for any usage of deprecated settings settings.userSetSettings filter (_.isDeprecated) foreach { s => currentRun.reporting.deprecationWarning(NoPosition, s.name + " is deprecated: " + s.deprecationMessage.get) @@ -1383,6 +1383,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) currentRun.reporting.deprecationWarning(NoPosition, settings.target.name + ":" + settings.target.value + " is deprecated and has no effect, setting to " + supportedTarget) settings.target.value = supportedTarget } + settings.conflictWarning.foreach(reporter.warning(NoPosition, _)) } /* An iterator returning all the units being compiled in this run */ @@ -1473,7 +1474,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) def compileSources(sources: List[SourceFile]) = if (!reporter.hasErrors) { def checkDeprecations() = { - checkDeprecatedSettings(newCompilationUnit("")) + warnDeprecatedAndConflictingSettings(newCompilationUnit("")) reporting.summarizeErrors() } @@ -1495,7 +1496,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val startTime = currentTime reporter.reset() - checkDeprecatedSettings(unitbuf.head) + warnDeprecatedAndConflictingSettings(unitbuf.head) globalPhase = fromPhase while (globalPhase.hasNext && !reporter.hasErrors) { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index 0df1b2029d..cd7e0b83e8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -10,6 +10,7 @@ import java.io.{StringWriter, PrintWriter} import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier} import scala.tools.asm.{ClassWriter, Attribute, ClassReader} import scala.collection.convert.decorateAsScala._ +import scala.tools.nsc.backend.jvm.analysis.InitialProducer import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype object AsmUtils { @@ -81,13 +82,16 @@ object AsmUtils { /** * Returns a human-readable representation of the given instruction. */ - def textify(insn: AbstractInsnNode): String = { - val trace = new TraceMethodVisitor(new Textifier) - insn.accept(trace) - val sw = new StringWriter - val pw = new PrintWriter(sw) - trace.p.print(pw) - sw.toString.trim + def textify(insn: AbstractInsnNode): String = insn match { + case _: InitialProducer => + insn.toString + case _ => + val trace = new TraceMethodVisitor(new Textifier) + insn.accept(trace) + val sw = new StringWriter + val pw = new PrintWriter(sw) + trace.p.print(pw) + sw.toString.trim } /** diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index c3f71969f6..22ac8f84d4 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -632,10 +632,11 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case _ => abort(s"Cannot instantiate $tpt of kind: $generatedType") } - case Apply(_, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] => + case Apply(fun, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] => val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get genLoadArguments(args, paramTKs(app)) genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface) + generatedType = asmMethodType(fun.symbol).returnType case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => val nativeKind = tpeTK(expr) @@ -843,7 +844,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * * New (http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1) * - Requires consistent stack map frames. GenBCode always generates stack frames. - * or higher. * - In practice: the ASM library computes stack map frames for us (ClassWriter). Emitting * correct frames after an ATHROW is probably complex, so ASM uses the following strategy: * - Every time when generating an ATHROW, a new basic block is started. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index c6db591c5f..0f381a4325 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -728,11 +728,11 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { mv.visitVarInsn(ALOAD, 1) mv.visitFieldInsn(PUTSTATIC, clazz.javaBinaryName.toString, "$deserializeLambdaCache$", "Ljava/util/Map;") mv.visitLabel(l0) - mv.visitFrame(asm.Opcodes.F_APPEND,1, Array("java/util/Map"), 0, null) + 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(INVOKESTATIC, "scala/runtime/LambdaDeserializer", "deserializeLambda", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/util/Map;Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;", false) + 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() } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 8720da84e8..9bae63f1fc 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -841,16 +841,17 @@ abstract class BTypes { assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") - assert( - if (info.get.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } - else if (isInterface.get) isJLO(info.get.superClass.get) - else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get), - s"Invalid superClass in $this: ${info.get.superClass}" - ) - assert( - info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)), - s"Invalid interfaces in $this: ${info.get.interfaces}" - ) + // TODO bring these back in a way that doesn't trip pos/t9393 + // assert( + // if (info.get.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } + // else if (isInterface.get) isJLO(info.get.superClass.get) + // else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get), + // s"Invalid superClass in $this: ${info.get.superClass}" + // ) + // assert( + // info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)), + // s"Invalid interfaces in $this: ${info.get.interfaces}" + // ) assert(info.get.nestedClasses.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala index 4fc05cafdc..4eb24d13e3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BackendReporting.scala @@ -1,7 +1,7 @@ package scala.tools.nsc package backend.jvm -import scala.tools.asm.tree.{AbstractInsnNode, MethodNode} +import scala.tools.asm.tree.{InvokeDynamicInsnNode, AbstractInsnNode, MethodNode} import scala.tools.nsc.backend.jvm.BTypes.InternalName import scala.reflect.internal.util.Position import scala.tools.nsc.settings.ScalaSettings @@ -246,6 +246,11 @@ object BackendReporting { case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: String, descriptor: String, callsiteClass: InternalName, callsiteName: String, callsiteDesc: String) extends CannotInlineWarning + case object UnknownInvokeDynamicInstruction extends OptimizerWarning { + override def toString = "The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory)." + def emitWarning(settings: ScalaSettings): Boolean = settings.YoptWarningEmitAtInlineFailed + } + /** * Used in `rewriteClosureApplyInvocations` when a closure apply callsite cannot be rewritten * to the closure body method. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala index ad75363102..5d56861ffc 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala @@ -103,7 +103,11 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) def initialProducersForValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = { def initialProducers(insn: AbstractInsnNode, producedSlot: Int): Set[AbstractInsnNode] = { if (isCopyOperation(insn)) { - _initialProducersCache.getOrElseUpdate((insn, producedSlot), { + val key = (insn, producedSlot) + _initialProducersCache.getOrElseUpdate(key, { + // prevent infinite recursion if an instruction is its own producer or consumer + // see cyclicProdCons in ProdConsAnalyzerTest + _initialProducersCache(key) = Set.empty val (sourceValue, sourceValueSlot) = copyOperationSourceValue(insn, producedSlot) sourceValue.insns.iterator.asScala.flatMap(initialProducers(_, sourceValueSlot)).toSet }) @@ -121,7 +125,11 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName) def ultimateConsumersOfValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = { def ultimateConsumers(insn: AbstractInsnNode, consumedSlot: Int): Set[AbstractInsnNode] = { if (isCopyOperation(insn)) { - _ultimateConsumersCache.getOrElseUpdate((insn, consumedSlot), { + val key = (insn, consumedSlot) + _ultimateConsumersCache.getOrElseUpdate(key, { + // prevent infinite recursion if an instruction is its own producer or consumer + // see cyclicProdCons in ProdConsAnalyzerTest + _ultimateConsumersCache(key) = Set.empty for { producedSlot <- copyOperationProducedValueSlots(insn, consumedSlot) consumer <- consumersOfValueAt(insn.getNext, producedSlot) 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 0ec550981a..df8dcc690a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -12,7 +12,7 @@ import scala.collection.mutable import scala.reflect.internal.util.Collections._ import scala.tools.asm.commons.CodeSizeEvaluator import scala.tools.asm.tree.analysis._ -import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes} +import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes, Type} import scala.tools.asm.tree._ import GenBCode._ import scala.collection.convert.decorateAsScala._ @@ -104,6 +104,8 @@ object BytecodeUtils { def isStrictfpMethod(methodNode: MethodNode): Boolean = (methodNode.access & Opcodes.ACC_STRICT) != 0 + def isReference(t: Type) = t.getSort == Type.OBJECT || t.getSort == Type.ARRAY + def nextExecutableInstruction(instruction: AbstractInsnNode, alsoKeep: AbstractInsnNode => Boolean = Set()): Option[AbstractInsnNode] = { var result = instruction do { result = result.getNext } @@ -331,6 +333,26 @@ object BytecodeUtils { } /** + * This method is used by optimizer components to eliminate phantom values of instruction + * that load a value of type `Nothing$` or `Null$`. Such values on the stack don't interact well + * with stack map frames. + * + * For example, `opt.getOrElse(throw e)` is re-written to an invocation of the lambda body, a + * method with return type `Nothing$`. Similarly for `opt.getOrElse(null)` and `Null$`. + * + * During bytecode generation this is handled by BCodeBodyBuilder.adapt. See the comment in that + * method which explains the issue with such phantom values. + */ + def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = { + if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) { + methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW)) + } else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) { + methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL)) + methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP)) + } + } + + /** * A wrapper to make ASM's Analyzer a bit easier to use. */ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, interpreter: Interpreter[V] = new BasicInterpreter) { 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 8abecdb261..96455c0e38 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -9,7 +9,7 @@ package opt import scala.reflect.internal.util.{NoPosition, Position} import scala.tools.asm.tree.analysis.{Value, Analyzer, BasicInterpreter} -import scala.tools.asm.{Opcodes, Type} +import scala.tools.asm.{Opcodes, Type, Handle} import scala.tools.asm.tree._ import scala.collection.concurrent import scala.collection.convert.decorateAsScala._ @@ -24,7 +24,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { val callsites: concurrent.Map[MethodInsnNode, Callsite] = recordPerRunCache(concurrent.TrieMap.empty) - val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, (MethodNode, ClassBType)] = recordPerRunCache(concurrent.TrieMap.empty) + val closureInstantiations: concurrent.Map[InvokeDynamicInsnNode, ClosureInstantiation] = recordPerRunCache(concurrent.TrieMap.empty) def addClass(classNode: ClassNode): Unit = { val classType = classBTypeFromClassNode(classNode) @@ -33,14 +33,14 @@ class CallGraph[BT <: BTypes](val btypes: BT) { (calls, closureInits) = analyzeCallsites(m, classType) } { calls foreach (callsite => callsites(callsite.callsiteInstruction) = callsite) - closureInits foreach (indy => closureInstantiations(indy) = (m, classType)) + closureInits foreach (lmf => closureInstantiations(lmf.indy) = ClosureInstantiation(lmf, m, classType)) } } /** * Returns a list of callsites in the method, plus a list of closure instantiation indy instructions. */ - def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[InvokeDynamicInsnNode]) = { + def analyzeCallsites(methodNode: MethodNode, definingClass: ClassBType): (List[Callsite], List[LambdaMetaFactoryCall]) = { case class CallsiteInfo(safeToInline: Boolean, safeToRewrite: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean, @@ -129,7 +129,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { } val callsites = new collection.mutable.ListBuffer[Callsite] - val closureInstantiations = new collection.mutable.ListBuffer[InvokeDynamicInsnNode] + val closureInstantiations = new collection.mutable.ListBuffer[LambdaMetaFactoryCall] methodNode.instructions.iterator.asScala foreach { case call: MethodInsnNode => @@ -173,8 +173,8 @@ class CallGraph[BT <: BTypes](val btypes: BT) { callsitePosition = callsitePositions.getOrElse(call, NoPosition) ) - case indy: InvokeDynamicInsnNode => - if (closureOptimizer.isClosureInstantiation(indy)) closureInstantiations += indy + case LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) => + closureInstantiations += LambdaMetaFactoryCall(indy, samMethodType, implMethod, instantiatedMethodType) case _ => } @@ -236,4 +236,82 @@ class CallGraph[BT <: BTypes](val btypes: BT) { calleeInfoWarning: Option[CalleeInfoWarning]) { assert(!(safeToInline && safeToRewrite), s"A callee of ${callee.name} can be either safeToInline or safeToRewrite, but not both.") } + + final case class ClosureInstantiation(lambdaMetaFactoryCall: LambdaMetaFactoryCall, ownerMethod: MethodNode, ownerClass: ClassBType) { + override def toString = s"ClosureInstantiation($lambdaMetaFactoryCall, ${ownerMethod.name + ownerMethod.desc}, $ownerClass)" + } + final case class LambdaMetaFactoryCall(indy: InvokeDynamicInsnNode, samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type) + + object LambdaMetaFactoryCall { + private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory" + + 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(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, metafactoryMethodName, metafactoryDesc) + } + + 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(Opcodes.H_INVOKESTATIC, lambdaMetaFactoryInternalName, altMetafactoryMethodName, altMetafactoryDesc) + } + + def unapply(insn: AbstractInsnNode): Option[(InvokeDynamicInsnNode, Type, Handle, Type)] = insn match { + case indy: InvokeDynamicInsnNode if indy.bsm == metafactoryHandle || indy.bsm == altMetafactoryHandle => + indy.bsmArgs match { + case Array(samMethodType: Type, implMethod: Handle, instantiatedMethodType: Type, xs@_*) => // xs binding because IntelliJ gets confused about _@_* + // 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 == Opcodes.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: _*) + } + + val isIndyLambda = ( + Type.getType(implMethod.getDesc) == expectedImplMethodType // (1) + && (isStatic || implMethod.getOwner == indyParamTypes(0).getInternalName) // (2) + && samMethodType.getArgumentTypes.corresponds(instantiatedMethodArgTypes)((samArgType, instArgType) => + samArgType == instArgType || isReference(samArgType) && isReference(instArgType)) // (3) + ) + + if (isIndyLambda) Some((indy, samMethodType, implMethod, instantiatedMethodType)) + else None + + case _ => None + } + case _ => None + } + } } 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..86536ff0d2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -8,6 +8,7 @@ package backend.jvm package opt import scala.annotation.switch +import scala.collection.mutable import scala.reflect.internal.util.NoPosition import scala.tools.asm.{Handle, Type, Opcodes} import scala.tools.asm.tree._ @@ -24,85 +25,28 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { import callGraph._ def rewriteClosureApplyInvocations(): Unit = { - closureInstantiations foreach { - case (indy, (methodNode, ownerClass)) => - val warnings = rewriteClosureApplyInvocations(indy, methodNode, ownerClass) - warnings.foreach(w => backendReporting.inlinerWarning(w.pos, w.toString)) - } - } - - private val lambdaMetaFactoryInternalName: InternalName = "java/lang/invoke/LambdaMetafactory" + 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 - 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) - } + val mName = x.ownerMethod.name compareTo y.ownerMethod.name + if (mName != 0) return mName - 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) - } + val mDesc = x.ownerMethod.desc compareTo y.ownerMethod.desc + if (mDesc != 0) return mDesc - 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: _*) - } + def pos(inst: ClosureInstantiation) = inst.ownerMethod.instructions.indexOf(inst.lambdaMetaFactoryCall.indy) + pos(x) - pos(y) + } + } - { - 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) - } - } + val sorted = mutable.TreeSet.empty[ClosureInstantiation] + sorted ++= closureInstantiations.values - case _ => - false - } + for (closureInst <- sorted) { + val warnings = rewriteClosureApplyInvocations(closureInst) + warnings.foreach(w => backendReporting.inlinerWarning(w.pos, w.toString)) } } @@ -131,9 +75,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { * Stores the values captured by a closure creation into fresh local variables. * 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 +89,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,22 +129,24 @@ 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 + def rewriteClosureApplyInvocations(closureInit: ClosureInstantiation): List[RewriteClosureApplyToClosureBodyFailed] = { + val lambdaBodyHandle = closureInit.lambdaMetaFactoryCall.implMethod + val ownerMethod = closureInit.ownerMethod + val ownerClass = closureInit.ownerClass // 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) + lazy val prodCons = new ProdConsAnalyzer(ownerMethod, 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 invocationsToRewrite: List[(MethodInsnNode, Either[RewriteClosureApplyToClosureBodyFailed, Int])] = ownerMethod.instructions.iterator.asScala.collect({ + case invocation: MethodInsnNode if isSamInvocation(invocation, closureInit.lambdaMetaFactoryCall.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) @@ -222,17 +169,17 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { // 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) + 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 = indy.bsmArgs(0).asInstanceOf[Type].getArgumentTypes // safe, checked in isClosureInstantiation - val firstArgLocal = methodNode.maxLocals + val argTypes = closureInit.lambdaMetaFactoryCall.samMethodType.getArgumentTypes + val firstArgLocal = ownerMethod.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] + val instantiatedMethodType = closureInit.lambdaMetaFactoryCall.instantiatedMethodType (argTypes, instantiatedMethodType.getArgumentTypes).zipped map { case (samArgType, instantiatedArgType) if samArgType != instantiatedArgType => // isClosureInstantiation ensures that the two types are reference types, so we don't @@ -243,7 +190,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { } } val argLocals = LocalsList.fromTypes(firstArgLocal, argTypes, castLoadTypes) - methodNode.maxLocals = firstArgLocal + argLocals.size + ownerMethod.maxLocals = firstArgLocal + argLocals.size (captureLocals, argLocals) } @@ -253,20 +200,20 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { case (invocation, Right(stackHeight)) => // store arguments - insertStoreOps(invocation, methodNode, argumentLocalsList) + insertStoreOps(invocation, ownerMethod, argumentLocalsList) // drop the closure from the stack - methodNode.instructions.insertBefore(invocation, new InsnNode(POP)) + ownerMethod.instructions.insertBefore(invocation, new InsnNode(POP)) // load captured values and arguments - insertLoadOps(invocation, methodNode, localsForCapturedValues) - insertLoadOps(invocation, methodNode, argumentLocalsList) + 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 > methodNode.maxStack) - methodNode.maxStack = invocationStackHeight + if (invocationStackHeight > ownerMethod.maxStack) + ownerMethod.maxStack = invocationStackHeight // replace the callsite with a new call to the body method val bodyOpcode = (lambdaBodyHandle.getTag: @switch) match { @@ -275,15 +222,19 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { case H_INVOKESPECIAL => INVOKESPECIAL case H_INVOKEINTERFACE => INVOKEINTERFACE case H_NEWINVOKESPECIAL => - val insns = methodNode.instructions + 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) - methodNode.instructions.insertBefore(invocation, bodyInvocation) - methodNode.instructions.remove(invocation) + 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) @@ -293,7 +244,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { def bodyMethodIsBeingCompiled = byteCodeRepository.classNodeAndSource(lambdaBodyHandle.getOwner).map(_._2 == CompilationUnit).getOrElse(false) val bodyMethodCallsite = Callsite( callsiteInstruction = bodyInvocation, - callsiteMethod = methodNode, + callsiteMethod = ownerMethod, callsiteClass = ownerClass, callee = bodyMethod.map({ case (bodyMethodNode, bodyMethodDeclClass) => Callee( 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 e8e848161c..8477f5461a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -9,6 +9,7 @@ package opt import scala.annotation.tailrec import scala.tools.asm +import asm.Handle import asm.Opcodes._ import asm.tree._ import scala.collection.convert.decorateAsScala._ @@ -455,9 +456,9 @@ class Inliner[BT <: BTypes](val btypes: BT) { case indy: InvokeDynamicInsnNode => callGraph.closureInstantiations.get(indy) match { - case Some((methodNode, ownerClass)) => + case Some(closureInit) => val newIndy = instructionMap(indy).asInstanceOf[InvokeDynamicInsnNode] - callGraph.closureInstantiations(newIndy) = (callsiteMethod, callsiteClass) + callGraph.closureInstantiations(newIndy) = ClosureInstantiation(closureInit.lambdaMetaFactoryCall.copy(indy = newIndy), callsiteMethod, callsiteClass) case None => } @@ -687,9 +688,67 @@ class Inliner[BT <: BTypes](val btypes: BT) { } } - case ivd: InvokeDynamicInsnNode => - // TODO @lry check necessary conditions to inline an indy, instead of giving up - Right(false) + case _: InvokeDynamicInsnNode if destinationClass == calleeDeclarationClass => + // within the same class, any indy instruction can be inlined + Right(true) + + // does the InvokeDynamicInsnNode call LambdaMetaFactory? + case LambdaMetaFactoryCall(_, _, implMethod, _) => + // an indy instr points to a "call site specifier" (CSP) [1] + // - a reference to a bootstrap method [2] + // - bootstrap method name + // - references to constant arguments, which can be: + // - constant (string, long, int, float, double) + // - class + // - method type (without name) + // - method handle + // - a method name+type + // + // execution [3] + // - resolve the CSP, yielding the boostrap method handle, the static args and the name+type + // - resolution entails accessibility checking [4] + // - execute the `invoke` method of the boostrap method handle (which is signature polymorphic, check its javadoc) + // - the descriptor for the call is made up from the actual arguments on the stack: + // - the first parameters are "MethodHandles.Lookup, String, MethodType", then the types of the constant arguments, + // - the return type is CallSite + // - the values for the call are + // - the bootstrap method handle of the CSP is the receiver + // - the Lookup object for the class in which the callsite occurs (obtained as through calling MethodHandles.lookup()) + // - the method name of the CSP + // - the method type of the CSP + // - the constants of the CSP (primitives are not boxed) + // - the resulting `CallSite` object + // - has as `type` the method type of the CSP + // - is popped from the operand stack + // - the `invokeExact` method (signature polymorphic!) of the `target` method handle of the CallSite is invoked + // - the method descriptor is that of the CSP + // - the receiver is the target of the CallSite + // - the other argument values are those that were on the operand stack at the indy instruction (indyLambda: the captured values) + // + // [1] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.10 + // [2] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.23 + // [3] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokedynamic + // [4] http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3 + + // We cannot generically check if an `invokedynamic` instruction can be safely inlined into + // a different class, that depends on the bootstrap method. The Lookup object passed to the + // bootstrap method is a capability to access private members of the callsite class. We can + // only move the invokedynamic to a new class if we know that the bootstrap method doesn't + // use this capability for otherwise non-accessible members. + // In the case of indyLambda, it depends on the visibility of the implMethod handle. If + // the implMethod is public, lambdaMetaFactory doesn't use the Lookup object's extended + // capability, and we can safely inline the instruction into a different class. + + val methodRefClass = classBTypeFromParsedClassfile(implMethod.getOwner) + for { + (methodNode, methodDeclClassNode) <- byteCodeRepository.methodNode(methodRefClass.internalName, implMethod.getName, implMethod.getDesc): Either[OptimizerWarning, (MethodNode, InternalName)] + methodDeclClass = classBTypeFromParsedClassfile(methodDeclClassNode) + res <- memberIsAccessible(methodNode.access, methodDeclClass, methodRefClass, destinationClass) + } yield { + res + } + + case _: InvokeDynamicInsnNode => Left(UnknownInvokeDynamicInstruction) case ci: LdcInsnNode => ci.cst match { case t: asm.Type => classIsAccessible(bTypeForDescriptorOrInternalNameFromClassfile(t.getInternalName), destinationClass) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index c8251a542c..dedf268b56 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -136,7 +136,7 @@ trait ScalaSettings extends AbsScalaSettings val sourceReader = StringSetting ("-Xsource-reader", "classname", "Specify a custom method for reading source files.", "") val reporter = StringSetting ("-Xreporter", "classname", "Specify a custom reporter for compiler messages.", "scala.tools.nsc.reporters.ConsoleReporter") val strictInference = BooleanSetting ("-Xstrict-inference", "Don't infer known-unsound types") - val source = ScalaVersionSetting ("-Xsource", "version", "Treat compiler input as Scala source for the specified version, see SI-8126.", initial = ScalaVersion("2.11")) + val source = ScalaVersionSetting ("-Xsource", "version", "Treat compiler input as Scala source for the specified version, see SI-8126.", initial = ScalaVersion("2.12")) val XnoPatmatAnalysis = BooleanSetting ("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.") val XfullLubs = BooleanSetting ("-Xfull-lubs", "Retains pre 2.10 behavior of less aggressive truncation of least upper bounds.") @@ -388,6 +388,23 @@ trait ScalaSettings extends AbsScalaSettings val Normal = "normal" val Discard = "discard" } + + def conflictWarning: Option[String] = { + def oldOptimiseFlagsInGenBCode: Option[String] = { + val optFlags: List[Setting] = if (optimise.value) List(optimise) else optimiseSettings.filter(_.value) + if (isBCodeActive && optFlags.nonEmpty) { + val msg = s"""Compiler settings for the 2.11 optimizer (${optFlags.map(_.name).mkString(", ")}) are incompatible with -Ybackend:GenBCode (which is the default in 2.12). + |The optimizer settings are ignored. See -Yopt:help for enabling the new optimizer in 2.12.""".stripMargin + Some(msg) + } else + None + } + + List(oldOptimiseFlagsInGenBCode /*, moreToCome */).flatten match { + case Nil => None + case warnings => Some("Conflicting compiler settings were detected. Some settings will be ignored.\n" + warnings.mkString("\n")) + } + } } object ClassPathRepresentationType { diff --git a/src/partest-extras/scala/tools/partest/ASMConverters.scala b/src/partest-extras/scala/tools/partest/ASMConverters.scala index 90f314428b..b4c686473b 100644 --- a/src/partest-extras/scala/tools/partest/ASMConverters.scala +++ b/src/partest-extras/scala/tools/partest/ASMConverters.scala @@ -58,21 +58,24 @@ object ASMConverters { case class Method(instructions: List[Instruction], handlers: List[ExceptionHandler], localVars: List[LocalVariable]) - case class Field (opcode: Int, owner: String, name: String, desc: String) extends Instruction - case class Incr (opcode: Int, `var`: Int, incr: Int) extends Instruction - case class Op (opcode: Int) extends Instruction - case class IntOp (opcode: Int, operand: Int) extends Instruction - case class Jump (opcode: Int, label: Label) extends Instruction - case class Ldc (opcode: Int, cst: Any) extends Instruction - case class LookupSwitch(opcode: Int, dflt: Label, keys: List[Int], labels: List[Label]) extends Instruction - case class TableSwitch (opcode: Int, min: Int, max: Int, dflt: Label, labels: List[Label]) extends Instruction - case class Invoke (opcode: Int, owner: String, name: String, desc: String, itf: Boolean) extends Instruction - case class NewArray (opcode: Int, desc: String, dims: Int) extends Instruction - case class TypeOp (opcode: Int, desc: String) extends Instruction - case class VarOp (opcode: Int, `var`: Int) extends Instruction - case class Label (offset: Int) extends Instruction { def opcode: Int = -1 } - case class FrameEntry (`type`: Int, local: List[Any], stack: List[Any]) extends Instruction { def opcode: Int = -1 } - case class LineNumber (line: Int, start: Label) extends Instruction { def opcode: Int = -1 } + case class Field (opcode: Int, owner: String, name: String, desc: String) extends Instruction + case class Incr (opcode: Int, `var`: Int, incr: Int) extends Instruction + case class Op (opcode: Int) extends Instruction + case class IntOp (opcode: Int, operand: Int) extends Instruction + case class Jump (opcode: Int, label: Label) extends Instruction + case class Ldc (opcode: Int, cst: Any) extends Instruction + case class LookupSwitch (opcode: Int, dflt: Label, keys: List[Int], labels: List[Label]) extends Instruction + case class TableSwitch (opcode: Int, min: Int, max: Int, dflt: Label, labels: List[Label]) extends Instruction + case class Invoke (opcode: Int, owner: String, name: String, desc: String, itf: Boolean) extends Instruction + case class InvokeDynamic(opcode: Int, name: String, desc: String, bsm: MethodHandle, bsmArgs: List[AnyRef]) extends Instruction + case class NewArray (opcode: Int, desc: String, dims: Int) extends Instruction + case class TypeOp (opcode: Int, desc: String) extends Instruction + case class VarOp (opcode: Int, `var`: Int) extends Instruction + case class Label (offset: Int) extends Instruction { def opcode: Int = -1 } + case class FrameEntry (`type`: Int, local: List[Any], stack: List[Any]) extends Instruction { def opcode: Int = -1 } + case class LineNumber (line: Int, start: Label) extends Instruction { def opcode: Int = -1 } + + case class MethodHandle(tag: Int, owner: String, name: String, desc: String) case class ExceptionHandler(start: Label, end: Label, handler: Label, desc: Option[String]) case class LocalVariable(name: String, desc: String, signature: Option[String], start: Label, end: Label, index: Int) @@ -111,6 +114,7 @@ object ASMConverters { case i: t.LookupSwitchInsnNode => LookupSwitch (op(i), applyLabel(i.dflt), lst(i.keys) map (x => x: Int), lst(i.labels) map applyLabel) case i: t.TableSwitchInsnNode => TableSwitch (op(i), i.min, i.max, applyLabel(i.dflt), lst(i.labels) map applyLabel) case i: t.MethodInsnNode => Invoke (op(i), i.owner, i.name, i.desc, i.itf) + case i: t.InvokeDynamicInsnNode => InvokeDynamic(op(i), i.name, i.desc, convertMethodHandle(i.bsm), convertBsmArgs(i.bsmArgs)) case i: t.MultiANewArrayInsnNode => NewArray (op(i), i.desc, i.dims) case i: t.TypeInsnNode => TypeOp (op(i), i.desc) case i: t.VarInsnNode => VarOp (op(i), i.`var`) @@ -119,6 +123,13 @@ object ASMConverters { case i: t.LineNumberNode => LineNumber (i.line, applyLabel(i.start)) } + private def convertBsmArgs(a: Array[Object]): List[Object] = a.map({ + case h: asm.Handle => convertMethodHandle(h) + case _ => a // can be: Class, method Type, primitive constant + })(collection.breakOut) + + private def convertMethodHandle(h: asm.Handle): MethodHandle = MethodHandle(h.getTag, h.getOwner, h.getName, h.getDesc) + private def convertHandlers(method: t.MethodNode): List[ExceptionHandler] = { method.tryCatchBlocks.asScala.map(h => ExceptionHandler(applyLabel(h.start), applyLabel(h.end), applyLabel(h.handler), Option(h.`type`)))(collection.breakOut) } @@ -197,21 +208,28 @@ object ASMConverters { case x => x.asInstanceOf[Object] } + def unconvertMethodHandle(h: MethodHandle): asm.Handle = new asm.Handle(h.tag, h.owner, h.name, h.desc) + def unconvertBsmArgs(a: List[Object]): Array[Object] = a.map({ + case h: MethodHandle => unconvertMethodHandle(h) + case o => o + })(collection.breakOut) + private def visitMethod(method: t.MethodNode, instruction: Instruction, asmLabel: Map[Label, asm.Label]): Unit = instruction match { - case Field(op, owner, name, desc) => method.visitFieldInsn(op, owner, name, desc) - case Incr(op, vr, incr) => method.visitIincInsn(vr, incr) - case Op(op) => method.visitInsn(op) - case IntOp(op, operand) => method.visitIntInsn(op, operand) - case Jump(op, label) => method.visitJumpInsn(op, asmLabel(label)) - case Ldc(op, cst) => method.visitLdcInsn(cst) - case LookupSwitch(op, dflt, keys, labels) => method.visitLookupSwitchInsn(asmLabel(dflt), keys.toArray, (labels map asmLabel).toArray) - case TableSwitch(op, min, max, dflt, labels) => method.visitTableSwitchInsn(min, max, asmLabel(dflt), (labels map asmLabel).toArray: _*) - case Invoke(op, owner, name, desc, itf) => method.visitMethodInsn(op, owner, name, desc, itf) - case NewArray(op, desc, dims) => method.visitMultiANewArrayInsn(desc, dims) - case TypeOp(op, desc) => method.visitTypeInsn(op, desc) - case VarOp(op, vr) => method.visitVarInsn(op, vr) - case l: Label => method.visitLabel(asmLabel(l)) - case FrameEntry(tp, local, stack) => method.visitFrame(tp, local.length, frameTypesToAsm(local, asmLabel).toArray, stack.length, frameTypesToAsm(stack, asmLabel).toArray) - case LineNumber(line, start) => method.visitLineNumber(line, asmLabel(start)) + case Field(op, owner, name, desc) => method.visitFieldInsn(op, owner, name, desc) + case Incr(op, vr, incr) => method.visitIincInsn(vr, incr) + case Op(op) => method.visitInsn(op) + case IntOp(op, operand) => method.visitIntInsn(op, operand) + case Jump(op, label) => method.visitJumpInsn(op, asmLabel(label)) + case Ldc(op, cst) => method.visitLdcInsn(cst) + case LookupSwitch(op, dflt, keys, labels) => method.visitLookupSwitchInsn(asmLabel(dflt), keys.toArray, (labels map asmLabel).toArray) + case TableSwitch(op, min, max, dflt, labels) => method.visitTableSwitchInsn(min, max, asmLabel(dflt), (labels map asmLabel).toArray: _*) + case Invoke(op, owner, name, desc, itf) => method.visitMethodInsn(op, owner, name, desc, itf) + case InvokeDynamic(op, name, desc, bsm, bsmArgs) => method.visitInvokeDynamicInsn(name, desc, unconvertMethodHandle(bsm), unconvertBsmArgs(bsmArgs)) + case NewArray(op, desc, dims) => method.visitMultiANewArrayInsn(desc, dims) + case TypeOp(op, desc) => method.visitTypeInsn(op, desc) + case VarOp(op, vr) => method.visitVarInsn(op, vr) + case l: Label => method.visitLabel(asmLabel(l)) + case FrameEntry(tp, local, stack) => method.visitFrame(tp, local.length, frameTypesToAsm(local, asmLabel).toArray, stack.length, frameTypesToAsm(stack, asmLabel).toArray) + case LineNumber(line, start) => method.visitLineNumber(line, asmLabel(start)) } } diff --git a/test/disabled/run/indylambda-specialization.scala b/test/disabled/run/indylambda-specialization.scala new file mode 100644 index 0000000000..2c66073e90 --- /dev/null +++ b/test/disabled/run/indylambda-specialization.scala @@ -0,0 +1,15 @@ +object Test { + def assertApply(expected: Boolean) = { + val frames = Thread.currentThread.getStackTrace.takeWhile(_.getMethodName != "main") + val usesObjectApply = frames.exists(_.getMethodName == "apply") + assert(expected == usesObjectApply, frames.mkString("\n")) + } + def assertSpecialized() = assertApply(false) + def assertUnspecialized() = assertApply(true) + def main(args: Array[String]): Unit = { + ((i: String) => {assertUnspecialized(); i}).apply("") + (() => {assertSpecialized(); 0}).apply() + ((i: Int) => {assertSpecialized(); i}).apply(0) + ((i: Int, j: Int) => {assertSpecialized(); i + j}).apply(0, 0) + } +} diff --git a/test/files/neg/incompatibleSettings.check b/test/files/neg/incompatibleSettings.check new file mode 100644 index 0000000000..0f28d41825 --- /dev/null +++ b/test/files/neg/incompatibleSettings.check @@ -0,0 +1,6 @@ +warning: Conflicting compiler settings were detected. Some settings will be ignored. +Compiler settings for the 2.11 optimizer (-optimise) are incompatible with -Ybackend:GenBCode (which is the default in 2.12). +The optimizer settings are ignored. See -Yopt:help for enabling the new optimizer in 2.12. +error: No warnings can be incurred under -Xfatal-warnings. +one warning found +one error found diff --git a/test/files/neg/incompatibleSettings.flags b/test/files/neg/incompatibleSettings.flags new file mode 100644 index 0000000000..00dd9c1c24 --- /dev/null +++ b/test/files/neg/incompatibleSettings.flags @@ -0,0 +1 @@ +-optimise -Ybackend:GenBCode -Xfatal-warnings diff --git a/test/files/neg/incompatibleSettings.scala b/test/files/neg/incompatibleSettings.scala new file mode 100644 index 0000000000..826a1a5bc2 --- /dev/null +++ b/test/files/neg/incompatibleSettings.scala @@ -0,0 +1 @@ +class C diff --git a/test/files/neg/inlineIndyLambdaPrivate.check b/test/files/neg/inlineIndyLambdaPrivate.check new file mode 100644 index 0000000000..dbd142f59e --- /dev/null +++ b/test/files/neg/inlineIndyLambdaPrivate.check @@ -0,0 +1,16 @@ +Test_2.scala:2: warning: A_1::test()Ljava/lang/String; could not be inlined: +The callee A_1::test()Ljava/lang/String; contains the instruction INVOKEDYNAMIC m()LA_1$Fun; [ + // handle kind 0x6 : INVOKESTATIC + java/lang/invoke/LambdaMetafactory.metafactory(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; + // arguments: + (Ljava/lang/String;)Ljava/lang/String;, + // handle kind 0x6 : INVOKESTATIC + A_1.lambda$test$0(Ljava/lang/String;)Ljava/lang/String;, + (Ljava/lang/String;)Ljava/lang/String; + ] +that would cause an IllegalAccessError when inlined into class Test. + def foo = A_1.test + ^ +error: No warnings can be incurred under -Xfatal-warnings. +one warning found +one error found diff --git a/test/files/neg/inlineIndyLambdaPrivate.flags b/test/files/neg/inlineIndyLambdaPrivate.flags new file mode 100644 index 0000000000..01b466bd8c --- /dev/null +++ b/test/files/neg/inlineIndyLambdaPrivate.flags @@ -0,0 +1 @@ +-Yopt:l:classpath -Yopt-inline-heuristics:everything -Yopt-warnings:_ -Xfatal-warnings
\ No newline at end of file diff --git a/test/files/neg/inlineIndyLambdaPrivate/A_1.java b/test/files/neg/inlineIndyLambdaPrivate/A_1.java new file mode 100644 index 0000000000..a9144a9fa6 --- /dev/null +++ b/test/files/neg/inlineIndyLambdaPrivate/A_1.java @@ -0,0 +1,9 @@ +public class A_1 { + interface Fun { + String m(String s); + } + public static final String test() { + Fun f = s -> s.trim(); + return f.m(" eh "); + } +} diff --git a/test/files/neg/inlineIndyLambdaPrivate/Test_2.scala b/test/files/neg/inlineIndyLambdaPrivate/Test_2.scala new file mode 100644 index 0000000000..dd59c05176 --- /dev/null +++ b/test/files/neg/inlineIndyLambdaPrivate/Test_2.scala @@ -0,0 +1,3 @@ +class Test { + def foo = A_1.test +} diff --git a/test/files/neg/sealed-final-neg.flags b/test/files/neg/sealed-final-neg.flags index cfabf7a5b4..3f0fcd2201 100644 --- a/test/files/neg/sealed-final-neg.flags +++ b/test/files/neg/sealed-final-neg.flags @@ -1 +1 @@ --Xfatal-warnings -Yinline-warnings -optimise
\ No newline at end of file +-Xfatal-warnings -Ybackend:GenASM -Yinline-warnings -optimise
\ No newline at end of file diff --git a/test/files/neg/t4425.flags b/test/files/neg/t4425.flags deleted file mode 100644 index 1182725e86..0000000000 --- a/test/files/neg/t4425.flags +++ /dev/null @@ -1 +0,0 @@ --optimize
\ No newline at end of file diff --git a/test/files/neg/t5639b.flags b/test/files/neg/t5639b.flags new file mode 100644 index 0000000000..90b87663af --- /dev/null +++ b/test/files/neg/t5639b.flags @@ -0,0 +1 @@ +-Xsource:2.11
\ No newline at end of file diff --git a/test/files/pos/inline-access-levels.flags b/test/files/pos/inline-access-levels.flags index 882f40f050..9bda07eb6c 100644 --- a/test/files/pos/inline-access-levels.flags +++ b/test/files/pos/inline-access-levels.flags @@ -1 +1 @@ --optimise -Xfatal-warnings -Yinline-warnings +-optimise -Ybackend:GenASM -Xfatal-warnings -Yinline-warnings diff --git a/test/files/pos/inliner2.flags b/test/files/pos/inliner2.flags index ea03113c66..bff4bb8afa 100644 --- a/test/files/pos/inliner2.flags +++ b/test/files/pos/inliner2.flags @@ -1 +1 @@ --optimise -Xfatal-warnings
\ No newline at end of file +-optimise -Ybackend:GenASM -Xfatal-warnings
\ No newline at end of file diff --git a/test/files/pos/sealed-final.flags b/test/files/pos/sealed-final.flags index cfabf7a5b4..63f5e65527 100644 --- a/test/files/pos/sealed-final.flags +++ b/test/files/pos/sealed-final.flags @@ -1 +1 @@ --Xfatal-warnings -Yinline-warnings -optimise
\ No newline at end of file +-Xfatal-warnings -Yinline-warnings -Ybackend:GenASM -optimise
\ No newline at end of file diff --git a/test/files/pos/t3420.flags b/test/files/pos/t3420.flags index ea03113c66..bff4bb8afa 100644 --- a/test/files/pos/t3420.flags +++ b/test/files/pos/t3420.flags @@ -1 +1 @@ --optimise -Xfatal-warnings
\ No newline at end of file +-optimise -Ybackend:GenASM -Xfatal-warnings
\ No newline at end of file diff --git a/test/files/pos/t8410.flags b/test/files/pos/t8410.flags index dcd5943c2f..2f32e3b26a 100644 --- a/test/files/pos/t8410.flags +++ b/test/files/pos/t8410.flags @@ -1 +1 @@ --optimise -Xfatal-warnings -deprecation:false -Yinline-warnings:false +-optimise -Ybackend:GenASM -Xfatal-warnings -deprecation:false -Yinline-warnings:false diff --git a/test/files/pos/t9393/Named.java b/test/files/pos/t9393/Named.java new file mode 100644 index 0000000000..144ddbf26e --- /dev/null +++ b/test/files/pos/t9393/Named.java @@ -0,0 +1,3 @@ +package bug; + +public @interface Named {} diff --git a/test/files/pos/t9393/NamedImpl.java b/test/files/pos/t9393/NamedImpl.java new file mode 100644 index 0000000000..7918739c2b --- /dev/null +++ b/test/files/pos/t9393/NamedImpl.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com> + */ +package bug; + +import bug.Named; +import java.io.Serializable; +import java.lang.annotation.Annotation; + +public class NamedImpl implements Named { + + public Class<? extends Annotation> annotationType() { + return null; + } +} diff --git a/test/files/pos/t9393/test.scala b/test/files/pos/t9393/test.scala new file mode 100644 index 0000000000..4df0476c98 --- /dev/null +++ b/test/files/pos/t9393/test.scala @@ -0,0 +1,3 @@ +class C { + new bug.NamedImpl +} diff --git a/test/files/run/blame_eye_triple_eee-double.flags b/test/files/run/blame_eye_triple_eee-double.flags deleted file mode 100644 index c9b68d70dc..0000000000 --- a/test/files/run/blame_eye_triple_eee-double.flags +++ /dev/null @@ -1 +0,0 @@ --optimise diff --git a/test/files/run/blame_eye_triple_eee-float.flags b/test/files/run/blame_eye_triple_eee-float.flags deleted file mode 100644 index c9b68d70dc..0000000000 --- a/test/files/run/blame_eye_triple_eee-float.flags +++ /dev/null @@ -1 +0,0 @@ --optimise diff --git a/test/files/run/classfile-format-51.scala b/test/files/run/classfile-format-51.scala index a62bcb147d..3ef0640b84 100644 --- a/test/files/run/classfile-format-51.scala +++ b/test/files/run/classfile-format-51.scala @@ -16,7 +16,7 @@ import Opcodes._ // verify. So the test includes a version check that short-circuits the whole test // on JDK 6 object Test extends DirectTest { - override def extraSettings: String = "-optimise -usejavacp -d " + testOutput.path + " -cp " + testOutput.path + override def extraSettings: String = "-Yopt:l:classpath -usejavacp -d " + testOutput.path + " -cp " + testOutput.path def generateClass() { val invokerClassName = "DynamicInvoker" diff --git a/test/files/run/classfile-format-52.scala b/test/files/run/classfile-format-52.scala index e278d40b8b..ebd0826303 100644 --- a/test/files/run/classfile-format-52.scala +++ b/test/files/run/classfile-format-52.scala @@ -13,7 +13,7 @@ import Opcodes._ // By its nature the test can only work on JDK 8+ because under JDK 7- the // interface won't verify. object Test extends DirectTest { - override def extraSettings: String = "-optimise -usejavacp -d " + testOutput.path + " -cp " + testOutput.path + override def extraSettings: String = "-Yopt:l:classpath -usejavacp -d " + testOutput.path + " -cp " + testOutput.path def generateInterface() { val interfaceName = "HasDefaultMethod" diff --git a/test/files/run/constant-optimization.flags b/test/files/run/constant-optimization.flags index c9b68d70dc..6c9965e749 100644 --- a/test/files/run/constant-optimization.flags +++ b/test/files/run/constant-optimization.flags @@ -1 +1 @@ --optimise +-optimise -Ybackend:GenASM diff --git a/test/files/run/dead-code-elimination.flags b/test/files/run/dead-code-elimination.flags index 49d036a887..b9bb09167e 100644 --- a/test/files/run/dead-code-elimination.flags +++ b/test/files/run/dead-code-elimination.flags @@ -1 +1 @@ --optimize +-optimize -Ybackend:GenASM diff --git a/test/files/run/elidable-opt.flags b/test/files/run/elidable-opt.flags index 62897ff218..6537d2f57a 100644 --- a/test/files/run/elidable-opt.flags +++ b/test/files/run/elidable-opt.flags @@ -1 +1 @@ --optimise -Xelide-below 900 +-optimise -Ybackend:GenASM -Xelide-below 900 diff --git a/test/files/run/elidable-opt.scala b/test/files/run/elidable-opt.scala index a2f29d2caf..ebada46de8 100644 --- a/test/files/run/elidable-opt.scala +++ b/test/files/run/elidable-opt.scala @@ -1,3 +1,6 @@ +/* + * filter: inliner warnings; re-run with + */ import annotation._ import elidable._ diff --git a/test/files/run/finalvar.flags b/test/files/run/finalvar.flags index aee3039bec..8d9be3d62e 100644 --- a/test/files/run/finalvar.flags +++ b/test/files/run/finalvar.flags @@ -1 +1 @@ --Yoverride-vars -Yinline
\ No newline at end of file +-Yoverride-vars -Yinline -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/icode-reader-dead-code.scala b/test/files/run/icode-reader-dead-code.scala index 00ba58829f..535ef2a2e2 100644 --- a/test/files/run/icode-reader-dead-code.scala +++ b/test/files/run/icode-reader-dead-code.scala @@ -36,7 +36,7 @@ object Test extends DirectTest { // If inlining fails, the compiler will issue an inliner warning that is not present in the // check file - compileString(newCompiler("-usejavacp", "-optimise"))(bCode) + compileString(newCompiler("-usejavacp", "-optimise", "-Ybackend:GenASM"))(bCode) } def readClass(file: String) = { diff --git a/test/files/run/macroPlugins-namerHooks.check b/test/files/run/macroPlugins-namerHooks.check index c2db5935d4..56b170cbd9 100644 --- a/test/files/run/macroPlugins-namerHooks.check +++ b/test/files/run/macroPlugins-namerHooks.check @@ -28,7 +28,7 @@ enterStat(<synthetic> val C$1: C = x$1.asInstanceOf[C]) enterSym(def <init>() = { super.<init>(); () }) enterSym(final override <synthetic> def toString() = "C") enterSym(case <synthetic> def apply(x: Int, y: Int): C = new C(x, y)) -enterSym(case <synthetic> def unapply(x$0: C) = if (x$0.==(null)) scala.this.None else Some(scala.Tuple2(x$0.x, x$0.y))) +enterSym(case <synthetic> def unapply(x$0: C): _root_.scala.Option[scala.Tuple2[Int, Int]] = if (x$0.==(null)) scala.this.None else Some(scala.Tuple2(x$0.x, x$0.y))) enterStat(def <init>() = { super.<init>(); () }) enterStat(final override <synthetic> def toString() = "C") enterSym(def <init>() = { super.<init>(); () }) diff --git a/test/files/run/noInlineUnknownIndy.check b/test/files/run/noInlineUnknownIndy.check new file mode 100644 index 0000000000..7cc6d1b675 --- /dev/null +++ b/test/files/run/noInlineUnknownIndy.check @@ -0,0 +1,13 @@ +newSource1.scala:1: warning: A_1::test()Ljava/lang/String; could not be inlined: +Failed to check if A_1::test()Ljava/lang/String; can be safely inlined to T without causing an IllegalAccessError. Checking instruction INVOKEDYNAMIC m()LA_1$Fun; [ + // handle kind 0x6 : INVOKESTATIC + not/java/lang/SomeLambdaMetafactory.notAMetaFactoryMethod(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; + // arguments: + (Ljava/lang/String;)Ljava/lang/String;, + // handle kind 0x6 : INVOKESTATIC + A_1.lambda$test$0(Ljava/lang/String;)Ljava/lang/String;, + (Ljava/lang/String;)Ljava/lang/String; + ] failed: +The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory). +class T { def foo = A_1.test } + ^ diff --git a/test/files/run/noInlineUnknownIndy/A_1.java b/test/files/run/noInlineUnknownIndy/A_1.java new file mode 100644 index 0000000000..a9144a9fa6 --- /dev/null +++ b/test/files/run/noInlineUnknownIndy/A_1.java @@ -0,0 +1,9 @@ +public class A_1 { + interface Fun { + String m(String s); + } + public static final String test() { + Fun f = s -> s.trim(); + return f.m(" eh "); + } +} diff --git a/test/files/run/noInlineUnknownIndy/Test.scala b/test/files/run/noInlineUnknownIndy/Test.scala new file mode 100644 index 0000000000..16d8126543 --- /dev/null +++ b/test/files/run/noInlineUnknownIndy/Test.scala @@ -0,0 +1,28 @@ +import java.io.File + +import scala.collection.convert.decorateAsScala._ +import scala.tools.asm.tree.{ClassNode, InvokeDynamicInsnNode} +import scala.tools.asm.{Handle, Opcodes} +import scala.tools.partest.BytecodeTest.modifyClassFile +import scala.tools.partest._ + +object Test extends DirectTest { + def code = ??? + + def compileCode(code: String) = { + val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator") + compileString(newCompiler("-cp", classpath, "-d", testOutput.path, "-Yopt:l:classpath", "-Yopt-inline-heuristics:everything", "-Yopt-warnings:_"))(code) + } + + def show(): Unit = { + val unknownBootstrapMethod = new Handle(Opcodes.H_INVOKESTATIC, "not/java/lang/SomeLambdaMetafactory", "notAMetaFactoryMethod", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;") + modifyClassFile(new File(testOutput.toFile, "A_1.class"))((cn: ClassNode) => { + val testMethod = cn.methods.iterator.asScala.find(_.name == "test").head + val indy = testMethod.instructions.iterator.asScala.collect({ case i: InvokeDynamicInsnNode => i }).next() + indy.bsm = unknownBootstrapMethod + cn + }) + + compileCode("class T { def foo = A_1.test }") + } +} diff --git a/test/files/run/optimizer-array-load.flags b/test/files/run/optimizer-array-load.flags index eb4d19bcb9..99bd6c895d 100644 --- a/test/files/run/optimizer-array-load.flags +++ b/test/files/run/optimizer-array-load.flags @@ -1 +1 @@ --optimise
\ No newline at end of file +-optimise -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/run-bug4840.flags b/test/files/run/run-bug4840.flags deleted file mode 100644 index eb4d19bcb9..0000000000 --- a/test/files/run/run-bug4840.flags +++ /dev/null @@ -1 +0,0 @@ --optimise
\ No newline at end of file diff --git a/test/files/run/synchronized.flags b/test/files/run/synchronized.flags index 49d036a887..b9bb09167e 100644 --- a/test/files/run/synchronized.flags +++ b/test/files/run/synchronized.flags @@ -1 +1 @@ --optimize +-optimize -Ybackend:GenASM diff --git a/test/files/run/t2106.check b/test/files/run/t2106.check index c6457216b5..b19165824b 100644 --- a/test/files/run/t2106.check +++ b/test/files/run/t2106.check @@ -1,13 +1,5 @@ -#partest !-Ybackend:GenASM t2106.scala:7: warning: A::foo()Ljava/lang/Object; is annotated @inline but could not be inlined: The callee A::foo()Ljava/lang/Object; contains the instruction INVOKEVIRTUAL java/lang/Object.clone ()Ljava/lang/Object; that would cause an IllegalAccessError when inlined into class Test$. def main(args: Array[String]): Unit = x.foo ^ -#partest -Ybackend:GenASM -t2106.scala:7: warning: Could not inline required method foo because access level required by callee not matched by caller. - def main(args: Array[String]): Unit = x.foo - ^ -t2106.scala:7: warning: At the end of the day, could not inline @inline-marked method foo - def main(args: Array[String]): Unit = x.foo - ^ diff --git a/test/files/run/t2106.flags b/test/files/run/t2106.flags index a2e413bb22..5d6ee831df 100644 --- a/test/files/run/t2106.flags +++ b/test/files/run/t2106.flags @@ -1 +1 @@ --optimise -Yinline-warnings -Yopt:l:classpath +-Yinline-warnings -Yopt:l:classpath diff --git a/test/files/run/t3368-c.check b/test/files/run/t3368-c.check index e0c10cc0dd..4cbe98c577 100644 --- a/test/files/run/t3368-c.check +++ b/test/files/run/t3368-c.check @@ -6,8 +6,8 @@ package <empty> { }; def x = { val $buf = new _root_.scala.xml.NodeBuffer(); - $buf.$amp$plus(new _root_.scala.xml.Text("hi & bye")); - $buf.$amp$plus(new _root_.scala.xml.Text("red & black")); + $buf.$amp$plus(new _root_.scala.xml.PCData("hi & bye")); + $buf.$amp$plus(new _root_.scala.xml.PCData("red & black")); $buf } }; @@ -24,7 +24,8 @@ package <empty> { new _root_.scala.xml.Elem(null, "b", _root_.scala.xml.Null, $scope, true) } }); - $buf.$amp$plus(new _root_.scala.xml.Text("starthi & bye")); + $buf.$amp$plus(new _root_.scala.xml.Text("start")); + $buf.$amp$plus(new _root_.scala.xml.PCData("hi & bye")); $buf.$amp$plus({ { new _root_.scala.xml.Elem(null, "c", _root_.scala.xml.Null, $scope, true) @@ -36,7 +37,8 @@ package <empty> { new _root_.scala.xml.Elem(null, "d", _root_.scala.xml.Null, $scope, true) } }); - $buf.$amp$plus(new _root_.scala.xml.Text("stuffred & black")); + $buf.$amp$plus(new _root_.scala.xml.Text("stuff")); + $buf.$amp$plus(new _root_.scala.xml.PCData("red & black")); $buf }: _*)) } @@ -46,18 +48,19 @@ package <empty> { def $init$() = { () }; - def d = new _root_.scala.xml.Text("hello, world"); + def d = new _root_.scala.xml.PCData("hello, world"); def e = { val $buf = new _root_.scala.xml.NodeBuffer(); - $buf.$amp$plus(new _root_.scala.xml.Text("hello, world")); - $buf.$amp$plus(new _root_.scala.xml.Text("hello, world")); + $buf.$amp$plus(new _root_.scala.xml.PCData("hello, world")); + $buf.$amp$plus(new _root_.scala.xml.PCData("hello, world")); $buf }; def f = { { new _root_.scala.xml.Elem(null, "foo", _root_.scala.xml.Null, $scope, false, ({ val $buf = new _root_.scala.xml.NodeBuffer(); - $buf.$amp$plus(new _root_.scala.xml.Text("xhello, world")); + $buf.$amp$plus(new _root_.scala.xml.Text("x")); + $buf.$amp$plus(new _root_.scala.xml.PCData("hello, world")); $buf }: _*)) } @@ -66,7 +69,7 @@ package <empty> { { new _root_.scala.xml.Elem(null, "foo", _root_.scala.xml.Null, $scope, false, ({ val $buf = new _root_.scala.xml.NodeBuffer(); - $buf.$amp$plus(new _root_.scala.xml.Text("hello, world")); + $buf.$amp$plus(new _root_.scala.xml.PCData("hello, world")); $buf }: _*)) } @@ -75,7 +78,8 @@ package <empty> { { new _root_.scala.xml.Elem(null, "foo", _root_.scala.xml.Null, $scope, false, ({ val $buf = new _root_.scala.xml.NodeBuffer(); - $buf.$amp$plus(new _root_.scala.xml.Text("hello, worldhello, world")); + $buf.$amp$plus(new _root_.scala.xml.PCData("hello, world")); + $buf.$amp$plus(new _root_.scala.xml.PCData("hello, world")); $buf }: _*)) } diff --git a/test/files/run/t3509.flags b/test/files/run/t3509.flags index 6933d924d3..9c59981aa9 100644 --- a/test/files/run/t3509.flags +++ b/test/files/run/t3509.flags @@ -1 +1 @@ --Yinline
\ No newline at end of file +-Yinline -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/t3569.flags b/test/files/run/t3569.flags index 6933d924d3..9c59981aa9 100644 --- a/test/files/run/t3569.flags +++ b/test/files/run/t3569.flags @@ -1 +1 @@ --Yinline
\ No newline at end of file +-Yinline -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/t4285.flags b/test/files/run/t4285.flags index eb4d19bcb9..99bd6c895d 100644 --- a/test/files/run/t4285.flags +++ b/test/files/run/t4285.flags @@ -1 +1 @@ --optimise
\ No newline at end of file +-optimise -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/t4935.flags b/test/files/run/t4935.flags index 49d036a887..b9bb09167e 100644 --- a/test/files/run/t4935.flags +++ b/test/files/run/t4935.flags @@ -1 +1 @@ --optimize +-optimize -Ybackend:GenASM diff --git a/test/files/run/t5789.scala b/test/files/run/t5789.scala index 2903dada48..c8d95f2153 100644 --- a/test/files/run/t5789.scala +++ b/test/files/run/t5789.scala @@ -5,7 +5,7 @@ import scala.tools.partest.ReplTest object Test extends ReplTest { - override def extraSettings = "-Yinline" + override def extraSettings = "-Yinline -Ybackend:GenASM" def code = """ val n = 2 () => n diff --git a/test/files/run/t6102.check b/test/files/run/t6102.check index 395864f57c..ce01362503 100644 --- a/test/files/run/t6102.check +++ b/test/files/run/t6102.check @@ -1,37 +1 @@ -[running phase parser on t6102.scala] -[running phase namer on t6102.scala] -[running phase packageobjects on t6102.scala] -[running phase typer on t6102.scala] -[running phase patmat on t6102.scala] -[running phase superaccessors on t6102.scala] -[running phase extmethods on t6102.scala] -[running phase pickler on t6102.scala] -[running phase refchecks on t6102.scala] -[running phase uncurry on t6102.scala] -[running phase tailcalls on t6102.scala] -[running phase specialize on t6102.scala] -[running phase explicitouter on t6102.scala] -[running phase erasure on t6102.scala] -[running phase posterasure on t6102.scala] -[running phase lazyvals on t6102.scala] -[running phase lambdalift on t6102.scala] -[running phase constructors on t6102.scala] -[running phase flatten on t6102.scala] -[running phase mixin on t6102.scala] -[running phase cleanup on t6102.scala] -[running phase delambdafy on t6102.scala] -[running phase icode on t6102.scala] -#partest -optimise -[running phase inliner on t6102.scala] -[running phase inlinehandlers on t6102.scala] -[running phase closelim on t6102.scala] -[running phase constopt on t6102.scala] -#partest -[running phase dce on t6102.scala] -#partest -Ybackend:GenASM -[running phase jvm on icode] -#partest !-Ybackend:GenASM -[running phase jvm on t6102.scala] -[running phase jvm on t6102.scala] -#partest hello diff --git a/test/files/run/t6102.flags b/test/files/run/t6102.flags index 726e2a997f..db58cf3b4c 100644 --- a/test/files/run/t6102.flags +++ b/test/files/run/t6102.flags @@ -1 +1 @@ --Ydead-code -Ydebug -Xfatal-warnings +-Yopt:l:classpath -Xfatal-warnings diff --git a/test/files/run/t6188.flags b/test/files/run/t6188.flags index 0ebca3e7af..b9bb09167e 100644 --- a/test/files/run/t6188.flags +++ b/test/files/run/t6188.flags @@ -1 +1 @@ - -optimize +-optimize -Ybackend:GenASM diff --git a/test/files/run/t7459b-optimize.flags b/test/files/run/t7459b-optimize.flags index 49d036a887..b9bb09167e 100644 --- a/test/files/run/t7459b-optimize.flags +++ b/test/files/run/t7459b-optimize.flags @@ -1 +1 @@ --optimize +-optimize -Ybackend:GenASM diff --git a/test/files/run/t7582.flags b/test/files/run/t7582.flags index 1182725e86..2cd4b38726 100644 --- a/test/files/run/t7582.flags +++ b/test/files/run/t7582.flags @@ -1 +1 @@ --optimize
\ No newline at end of file +-optimize -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/t7582b.flags b/test/files/run/t7582b.flags index 1182725e86..2cd4b38726 100644 --- a/test/files/run/t7582b.flags +++ b/test/files/run/t7582b.flags @@ -1 +1 @@ --optimize
\ No newline at end of file +-optimize -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/t8601.flags b/test/files/run/t8601.flags index 1182725e86..2cd4b38726 100644 --- a/test/files/run/t8601.flags +++ b/test/files/run/t8601.flags @@ -1 +1 @@ --optimize
\ No newline at end of file +-optimize -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/t8601b.flags b/test/files/run/t8601b.flags index 1182725e86..2cd4b38726 100644 --- a/test/files/run/t8601b.flags +++ b/test/files/run/t8601b.flags @@ -1 +1 @@ --optimize
\ No newline at end of file +-optimize -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/t8601c.flags b/test/files/run/t8601c.flags index 1182725e86..2cd4b38726 100644 --- a/test/files/run/t8601c.flags +++ b/test/files/run/t8601c.flags @@ -1 +1 @@ --optimize
\ No newline at end of file +-optimize -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/t8601d.flags b/test/files/run/t8601d.flags index 1182725e86..2cd4b38726 100644 --- a/test/files/run/t8601d.flags +++ b/test/files/run/t8601d.flags @@ -1 +1 @@ --optimize
\ No newline at end of file +-optimize -Ybackend:GenASM
\ No newline at end of file diff --git a/test/files/run/t8601e.flags b/test/files/run/t8601e.flags index 49d036a887..b9bb09167e 100644 --- a/test/files/run/t8601e.flags +++ b/test/files/run/t8601e.flags @@ -1 +1 @@ --optimize +-optimize -Ybackend:GenASM diff --git a/test/files/run/t9003.flags b/test/files/run/t9003.flags index 49d036a887..b9bb09167e 100644 --- a/test/files/run/t9003.flags +++ b/test/files/run/t9003.flags @@ -1 +1 @@ --optimize +-optimize -Ybackend:GenASM diff --git a/test/files/run/t9387.scala b/test/files/run/t9387.scala new file mode 100644 index 0000000000..3e33d19fd2 --- /dev/null +++ b/test/files/run/t9387.scala @@ -0,0 +1,20 @@ +class G[T] +object G { + def v[T](x: T): G[T] = null +} + +class A[T] +object A { + def apply[T](x: => G[T]): A[T] = null +} + +object T { + A[Unit](G.v(() => ())) // Was VerifyError +} + +object Test { + def main(args: Array[String]): Unit = { + T + } + +}
\ No newline at end of file diff --git a/test/files/run/t9387b.check b/test/files/run/t9387b.check new file mode 100644 index 0000000000..6a452c185a --- /dev/null +++ b/test/files/run/t9387b.check @@ -0,0 +1 @@ +() diff --git a/test/files/run/t9387b.scala b/test/files/run/t9387b.scala new file mode 100644 index 0000000000..6339f4caba --- /dev/null +++ b/test/files/run/t9387b.scala @@ -0,0 +1,16 @@ +object T { + val f: Unit = () => () + println(f) +} + +object U { + def f[T](t: T): T = t + f[Unit](() => ()) +} + +object Test { + def main(args: Array[String]): Unit = { + T + U + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala new file mode 100644 index 0000000000..f5f93fef5c --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala @@ -0,0 +1,55 @@ +package scala.tools.nsc.backend.jvm + +import org.junit.Assert._ +import org.junit.{Assert, Test} + +import scala.tools.asm.{Handle, Opcodes} +import scala.tools.asm.tree.InvokeDynamicInsnNode +import scala.tools.nsc.backend.jvm.AsmUtils._ +import scala.tools.nsc.backend.jvm.CodeGenTools._ +import scala.tools.testing.ClearAfterClass +import scala.collection.JavaConverters._ + +object IndyLambdaTest extends ClearAfterClass.Clearable { + var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode") + + def clear(): Unit = { + compiler = null + } +} + +class IndyLambdaTest extends ClearAfterClass { + ClearAfterClass.stateToClear = IndyLambdaTest + val compiler = IndyLambdaTest.compiler + + @Test def boxingBridgeMethodUsedSelectively(): Unit = { + def implMethodDescriptorFor(code: String): String = { + val method = compileMethods(compiler)(s"""def f = $code """).find(_.name == "f").get + val x = method.instructions.iterator.asScala.toList + x.flatMap { + case insn : InvokeDynamicInsnNode => insn.bsmArgs.collect { case h : Handle => h.getDesc } + case _ => Nil + }.head + } + // unspecialized functions that have a primitive in parameter or return position + // give rise to a "boxing bridge" method (which has the suffix `$adapted`). + // This is because Scala's unboxing of null values gives zero, whereas Java's throw a NPE. + + // 1. Here we show that we are calling the boxing bridge (the lambda bodies here are compiled into + // methods of `(I)Ljava/lang/Object;` / `(I)Ljava/lang/Object;` respectively.) + assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Int) => new Object")) + assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Object) => 0")) + + // 2a. We don't need such adaptations for parameters or return values with types that differ + // from Object due to other generic substitution, LambdaMetafactory will downcast the arguments. + assertEquals("(Ljava/lang/String;)Ljava/lang/String;", implMethodDescriptorFor("(x: String) => x")) + + // 2b. Testing 2a. in combination with 1. + assertEquals("(Ljava/lang/Object;)Ljava/lang/String;", implMethodDescriptorFor("(x: Int) => \"\"")) + assertEquals("(Ljava/lang/String;)Ljava/lang/Object;", implMethodDescriptorFor("(x: String) => 0")) + + // 3. Specialized functions, don't need any of this as they implement a method like `apply$mcII$sp`, + // and the (un)boxing is handled in the base class in code emitted by scalac. + assertEquals("(I)I", implMethodDescriptorFor("(x: Int) => x")) + } +} 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 9af9ef54fc..a5b3faced8 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala @@ -246,4 +246,46 @@ class ProdConsAnalyzerTest extends ClearAfterClass { testSingleInsn(a.consumersOfOutputsFrom(l2i), "IRETURN") testSingleInsn(a.producersForInputsOf(ret), "L2I") } + + @Test + def cyclicProdCons(): Unit = { + import Opcodes._ + val m = genMethod(descriptor = "(I)I")( + Label(1), + VarOp(ILOAD, 1), + IntOp(BIPUSH, 10), + Op(IADD), // consumer of the above ILOAD + + Op(ICONST_0), + Jump(IF_ICMPNE, Label(2)), + + VarOp(ILOAD, 1), + VarOp(ISTORE, 1), + Jump(GOTO, Label(1)), + + Label(2), + IntOp(BIPUSH, 9), + Op(IRETURN) + ) + m.maxLocals = 2 + m.maxStack = 2 + val a = new ProdConsAnalyzer(m, "C") + + val List(iadd) = findInstr(m, "IADD") + val firstLoad = iadd.getPrevious.getPrevious + assert(firstLoad.getOpcode == ILOAD) + val secondLoad = findInstr(m, "ISTORE").head.getPrevious + assert(secondLoad.getOpcode == ILOAD) + + testSingleInsn(a.producersForValueAt(iadd, 2), "ILOAD") + testSingleInsn(a.initialProducersForValueAt(iadd, 2), "ParameterProducer(1)") + testMultiInsns(a.producersForInputsOf(firstLoad), List("ParameterProducer", "ISTORE")) + testMultiInsns(a.producersForInputsOf(secondLoad), List("ParameterProducer", "ISTORE")) + + testSingleInsn(a.ultimateConsumersOfOutputsFrom(firstLoad), "IADD") + testSingleInsn(a.ultimateConsumersOfOutputsFrom(secondLoad), "IADD") + + testSingleInsn(a.consumersOfOutputsFrom(firstLoad), "IADD") + testSingleInsn(a.consumersOfOutputsFrom(secondLoad), "ISTORE") + } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala new file mode 100644 index 0000000000..69eed1f75d --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala @@ -0,0 +1,74 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.collection.generic.Clearable +import scala.collection.mutable.ListBuffer +import scala.reflect.internal.util.BatchSourceFile +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.asm.tree._ +import scala.tools.asm.tree.analysis._ +import scala.tools.nsc.backend.jvm.opt.BytecodeUtils.AsmAnalyzer +import scala.tools.nsc.io._ +import scala.tools.nsc.reporters.StoreReporter +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import AsmUtils._ + +import BackendReporting._ + +import scala.collection.convert.decorateAsScala._ +import scala.tools.testing.ClearAfterClass + +object ClosureOptimizerTest extends ClearAfterClass.Clearable { + var compiler = newCompiler(extraArgs = "-Yopt:l:classpath -Yopt-warnings") + def clear(): Unit = { compiler = null } +} + +@RunWith(classOf[JUnit4]) +class ClosureOptimizerTest extends ClearAfterClass { + ClearAfterClass.stateToClear = ClosureOptimizerTest + + val compiler = ClosureOptimizerTest.compiler + + @Test + def nothingTypedClosureBody(): Unit = { + val code = + """abstract class C { + | def isEmpty: Boolean + | @inline final def getOrElse[T >: C](f: => T) = if (isEmpty) f else this + | def t = getOrElse(throw new Error("")) + |} + """.stripMargin + + val List(c) = compileClasses(compiler)(code) + val t = c.methods.asScala.toList.find(_.name == "t").get + val List(bodyCall) = findInstr(t, "INVOKESTATIC C.C$$$anonfun$1 ()Lscala/runtime/Nothing$") + assert(bodyCall.getNext.getOpcode == ATHROW) + } + + @Test + def nullTypedClosureBody(): Unit = { + val code = + """abstract class C { + | def isEmpty: Boolean + | @inline final def getOrElse[T >: C](f: => T) = if (isEmpty) f else this + | def t = getOrElse(null) + |} + """.stripMargin + + val List(c) = compileClasses(compiler)(code) + val t = c.methods.asScala.toList.find(_.name == "t").get + val List(bodyCall) = findInstr(t, "INVOKESTATIC C.C$$$anonfun$1 ()Lscala/runtime/Null$") + assert(bodyCall.getNext.getOpcode == POP) + assert(bodyCall.getNext.getNext.getOpcode == ACONST_NULL) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala index 57088bdd2f..5ccb940415 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala @@ -27,7 +27,9 @@ object InlineInfoTest extends ClearAfterClass.Clearable { } @RunWith(classOf[JUnit4]) -class InlineInfoTest { +class InlineInfoTest extends ClearAfterClass { + ClearAfterClass.stateToClear = InlineInfoTest + val compiler = InlineInfoTest.compiler def compile(code: String) = { 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 0309bb97cc..617eced560 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -991,4 +991,41 @@ class InlinerTest extends ClearAfterClass { assert(2 == t.collect({case Ldc(_, "hai!") => }).size) // twice the body of f assert(1 == t.collect({case Jump(IFNONNULL, _) => }).size) // one single null check } + + @Test + def inlineIndyLambda(): Unit = { + val code = + """object M { + | @inline def m(s: String) = { + | val f = (x: String) => x.trim + | f(s) + | } + |} + |class C { + | @inline final def m(s: String) = { + | val f = (x: String) => x.trim + | f(s) + | } + | def t1 = m("foo") + | def t2 = M.m("bar") + |} + """.stripMargin + + val List(c, _, _) = compile(code) + + val t1 = getSingleMethod(c, "t1") + assert(t1.instructions exists { + case _: InvokeDynamic => true + case _ => false + }) + // the indy call is inlined into t, and the closure elimination rewrites the closure invocation to the body method + assertInvoke(t1, "C", "C$$$anonfun$2") + + val t2 = getSingleMethod(c, "t2") + assert(t2.instructions exists { + case _: InvokeDynamic => true + case _ => false + }) + assertInvoke(t2, "M$", "M$$$anonfun$1") + } } diff --git a/test/junit/scala/tools/nsc/settings/SettingsTest.scala b/test/junit/scala/tools/nsc/settings/SettingsTest.scala index 1a2d695d68..a0015f4465 100644 --- a/test/junit/scala/tools/nsc/settings/SettingsTest.scala +++ b/test/junit/scala/tools/nsc/settings/SettingsTest.scala @@ -172,7 +172,7 @@ class SettingsTest { assert(residual.isEmpty) assertTrue(s.source.value == ScalaVersion(expected)) } - check(expected = "2.11.0") // default + check(expected = "2.12.0") // default check(expected = "2.11.0", "-Xsource:2.11") check(expected = "2.10", "-Xsource:2.10.0") check(expected = "2.12", "-Xsource:2.12") |