summaryrefslogtreecommitdiff
path: root/test/junit/scala/tools/nsc/backend/jvm
diff options
context:
space:
mode:
Diffstat (limited to 'test/junit/scala/tools/nsc/backend/jvm')
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala52
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala106
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala29
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala16
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala152
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala19
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala67
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala146
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala198
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala115
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala953
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala15
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala85
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala39
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala12
16 files changed, 1939 insertions, 69 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
index 2347e8288e..6ada0e20fb 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
@@ -7,35 +7,41 @@ import org.junit.Test
import scala.tools.asm.Opcodes
import org.junit.Assert._
-@RunWith(classOf[JUnit4])
-class BTypesTest {
- val settings = new Settings()
- settings.processArgumentString("-usejavacp")
- val g: Global = new Global(settings)
- val run = new g.Run() // initializes some compiler internals
- import g.{definitions => d, Symbol}
+import scala.tools.nsc.backend.jvm.CodeGenTools._
+import scala.tools.testing.ClearAfterClass
- def duringBackend[T](f: => T) = g.exitingDelambdafy(f)
+object BTypesTest extends ClearAfterClass.Clearable {
+ var compiler = {
+ val comp = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+ new comp.Run() // initializes some of the compiler
+ comp.exitingDelambdafy(comp.scalaPrimitives.init()) // needed: it's only done when running the backend, and we don't actually run the compiler
+ comp.exitingDelambdafy(comp.genBCode.bTypes.initializeCoreBTypes())
+ comp
+ }
+ def clear(): Unit = { compiler = null }
+}
- val btypes = new BTypesFromSymbols[g.type](g)
- import btypes._
- duringBackend(btypes.initializeCoreBTypes())
+@RunWith(classOf[JUnit4])
+class BTypesTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = BTypesTest
- def classBTypeFromSymbol(sym: Symbol) = duringBackend(btypes.classBTypeFromSymbol(sym))
+ val compiler = BTypesTest.compiler
+ import compiler.genBCode.bTypes._
- val jlo = d.ObjectClass
- val jls = d.StringClass
+ def classBTFS(sym: compiler.Symbol) = compiler.exitingDelambdafy(classBTypeFromSymbol(sym))
- val o = classBTypeFromSymbol(jlo)
- val s = classBTypeFromSymbol(jls)
- val oArr = ArrayBType(o)
- val method = MethodBType(List(oArr, INT, DOUBLE, s), UNIT)
+ def jlo = compiler.definitions.ObjectClass
+ def jls = compiler.definitions.StringClass
+ def o = classBTFS(jlo)
+ def s = classBTFS(jls)
+ def oArr = ArrayBType(o)
+ def method = MethodBType(List(oArr, INT, DOUBLE, s), UNIT)
@Test
def classBTypesEquality() {
- val s1 = classBTypeFromSymbol(jls)
- val s2 = classBTypeFromSymbol(jls)
- val o = classBTypeFromSymbol(jlo)
+ val s1 = classBTFS(jls)
+ val s2 = classBTFS(jls)
+ val o = classBTFS(jlo)
assertEquals(s1, s2)
assertEquals(s1.hashCode, s2.hashCode)
assert(s1 != o)
@@ -53,7 +59,7 @@ class BTypesTest {
assert(FLOAT.typedOpcode(Opcodes.IALOAD) == Opcodes.FALOAD)
assert(LONG.typedOpcode(Opcodes.IALOAD) == Opcodes.LALOAD)
assert(DOUBLE.typedOpcode(Opcodes.IALOAD) == Opcodes.DALOAD)
- assert(classBTypeFromSymbol(jls).typedOpcode(Opcodes.IALOAD) == Opcodes.AALOAD)
+ assert(classBTFS(jls).typedOpcode(Opcodes.IALOAD) == Opcodes.AALOAD)
assert(UNIT.typedOpcode(Opcodes.IRETURN) == Opcodes.RETURN)
assert(BOOL.typedOpcode(Opcodes.IRETURN) == Opcodes.IRETURN)
@@ -64,7 +70,7 @@ class BTypesTest {
assert(FLOAT.typedOpcode(Opcodes.IRETURN) == Opcodes.FRETURN)
assert(LONG.typedOpcode(Opcodes.IRETURN) == Opcodes.LRETURN)
assert(DOUBLE.typedOpcode(Opcodes.IRETURN) == Opcodes.DRETURN)
- assert(classBTypeFromSymbol(jls).typedOpcode(Opcodes.IRETURN) == Opcodes.ARETURN)
+ assert(classBTFS(jls).typedOpcode(Opcodes.IRETURN) == Opcodes.ARETURN)
}
@Test
diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
index c1c5a71b83..5d5215d887 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
@@ -2,16 +2,20 @@ package scala.tools.nsc.backend.jvm
import org.junit.Assert._
+import scala.collection.mutable.ListBuffer
import scala.reflect.internal.util.BatchSourceFile
import scala.reflect.io.VirtualDirectory
import scala.tools.asm.Opcodes
-import scala.tools.asm.tree.{AbstractInsnNode, LabelNode, ClassNode, MethodNode}
+import scala.tools.asm.tree.{ClassNode, MethodNode}
import scala.tools.cmd.CommandLineParser
import scala.tools.nsc.backend.jvm.opt.LocalOpt
-import scala.tools.nsc.settings.{MutableSettings, ScalaSettings}
+import scala.tools.nsc.io.AbstractFile
+import scala.tools.nsc.reporters.StoreReporter
+import scala.tools.nsc.settings.MutableSettings
import scala.tools.nsc.{Settings, Global}
import scala.tools.partest.ASMConverters
import scala.collection.JavaConverters._
+import scala.tools.testing.TempDir
object CodeGenTools {
import ASMConverters._
@@ -40,38 +44,104 @@ object CodeGenTools {
}
def newCompiler(defaultArgs: String = "-usejavacp", extraArgs: String = ""): Global = {
+ val compiler = newCompilerWithoutVirtualOutdir(defaultArgs, extraArgs)
+ resetOutput(compiler)
+ compiler
+ }
+
+ def newCompilerWithoutVirtualOutdir(defaultArgs: String = "-usejavacp", extraArgs: String = ""): Global = {
val settings = new Settings()
val args = (CommandLineParser tokenize defaultArgs) ++ (CommandLineParser tokenize extraArgs)
settings.processArguments(args, processAll = true)
- val compiler = new Global(settings)
- resetOutput(compiler)
- compiler
+ new Global(settings, new StoreReporter)
}
- def compile(compiler: Global)(code: String): List[(String, Array[Byte])] = {
+ def newRun(compiler: Global): compiler.Run = {
compiler.reporter.reset()
resetOutput(compiler)
- val run = new compiler.Run()
- run.compileSources(List(new BatchSourceFile("unitTestSource.scala", code)))
- val outDir = compiler.settings.outputDirs.getSingleOutput.get
- (for (f <- outDir.iterator if !f.isDirectory) yield (f.name, f.toByteArray)).toList
+ new compiler.Run()
+ }
+
+ def reporter(compiler: Global) = compiler.reporter.asInstanceOf[StoreReporter]
+
+ def makeSourceFile(code: String, filename: String): BatchSourceFile = new BatchSourceFile(filename, code)
+
+ def getGeneratedClassfiles(outDir: AbstractFile): List[(String, Array[Byte])] = {
+ def files(dir: AbstractFile): List[(String, Array[Byte])] = {
+ val res = ListBuffer.empty[(String, Array[Byte])]
+ for (f <- dir.iterator) {
+ if (!f.isDirectory) res += ((f.name, f.toByteArray))
+ else if (f.name != "." && f.name != "..") res ++= files(f)
+ }
+ res.toList
+ }
+ files(outDir)
+ }
+
+ def checkReport(compiler: Global, allowMessage: StoreReporter#Info => Boolean = _ => false): Unit = {
+ val disallowed = reporter(compiler).infos.toList.filter(!allowMessage(_)) // toList prevents an infer-non-wildcard-existential warning.
+ if (disallowed.nonEmpty) {
+ val msg = disallowed.mkString("\n")
+ assert(false, "The compiler issued non-allowed warnings or errors:\n" + msg)
+ }
+ }
+
+ def compile(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[(String, Array[Byte])] = {
+ val run = newRun(compiler)
+ run.compileSources(makeSourceFile(scalaCode, "unitTestSource.scala") :: javaCode.map(p => makeSourceFile(p._1, p._2)))
+ checkReport(compiler, allowMessage)
+ getGeneratedClassfiles(compiler.settings.outputDirs.getSingleOutput.get)
+ }
+
+ /**
+ * Compile multiple Scala files separately into a single output directory.
+ *
+ * Note that a new compiler instance is created for compiling each file because symbols survive
+ * across runs. This makes separate compilation slower.
+ *
+ * The output directory is a physical directory, I have not figured out if / how it's possible to
+ * add a VirtualDirectory to the classpath of a compiler.
+ */
+ def compileSeparately(codes: List[String], extraArgs: String = "", allowMessage: StoreReporter#Info => Boolean = _ => false, afterEach: AbstractFile => Unit = _ => ()): List[(String, Array[Byte])] = {
+ val outDir = AbstractFile.getDirectory(TempDir.createTempDir())
+ val outDirPath = outDir.canonicalPath
+ val argsWithOutDir = extraArgs + s" -d $outDirPath -cp $outDirPath"
+
+ for (code <- codes) {
+ val compiler = newCompilerWithoutVirtualOutdir(extraArgs = argsWithOutDir)
+ new compiler.Run().compileSources(List(makeSourceFile(code, "unitTestSource.scala")))
+ checkReport(compiler, allowMessage)
+ afterEach(outDir)
+ }
+
+ val classfiles = getGeneratedClassfiles(outDir)
+ outDir.delete()
+ classfiles
+ }
+
+ def compileClassesSeparately(codes: List[String], extraArgs: String = "", allowMessage: StoreReporter#Info => Boolean = _ => false, afterEach: AbstractFile => Unit = _ => ()) = {
+ readAsmClasses(compileSeparately(codes, extraArgs, allowMessage, afterEach))
+ }
+
+ def readAsmClasses(classfiles: List[(String, Array[Byte])]) = {
+ classfiles.map(p => AsmUtils.readClass(p._2)).sortBy(_.name)
}
- def compileClasses(compiler: Global)(code: String): List[ClassNode] = {
- compile(compiler)(code).map(p => AsmUtils.readClass(p._2)).sortBy(_.name)
+ def compileClasses(compiler: Global)(code: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
+ readAsmClasses(compile(compiler)(code, javaCode, allowMessage))
}
- def compileMethods(compiler: Global)(code: String): List[MethodNode] = {
- compileClasses(compiler)(s"class C { $code }").head.methods.asScala.toList.filterNot(_.name == "<init>")
+ def compileMethods(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): List[MethodNode] = {
+ compileClasses(compiler)(s"class C { $code }", allowMessage = allowMessage).head.methods.asScala.toList.filterNot(_.name == "<init>")
}
- def singleMethodInstructions(compiler: Global)(code: String): List[Instruction] = {
- val List(m) = compileMethods(compiler)(code)
+ def singleMethodInstructions(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): List[Instruction] = {
+ val List(m) = compileMethods(compiler)(code, allowMessage = allowMessage)
instructionsFromMethod(m)
}
- def singleMethod(compiler: Global)(code: String): Method = {
- val List(m) = compileMethods(compiler)(code)
+ def singleMethod(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): Method = {
+ val List(m) = compileMethods(compiler)(code, allowMessage = allowMessage)
convertMethod(m)
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
index 89900291ca..4086f7dd7b 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
@@ -7,10 +7,18 @@ import org.junit.Assert._
import CodeGenTools._
import scala.tools.asm.Opcodes._
import scala.tools.partest.ASMConverters._
+import scala.tools.testing.ClearAfterClass
+
+object DirectCompileTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:method")
+ def clear(): Unit = { compiler = null }
+}
@RunWith(classOf[JUnit4])
-class DirectCompileTest {
- val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:method")
+class DirectCompileTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = DirectCompileTest
+
+ val compiler = DirectCompileTest.compiler
@Test
def testCompile(): Unit = {
@@ -70,4 +78,21 @@ class DirectCompileTest {
Label(11)
))
}
+
+ @Test
+ def testSeparateCompilation(): Unit = {
+ val codeA = "class A { def f = 1 }"
+ val codeB = "class B extends A { def g = f }"
+ val List(a, b) = compileClassesSeparately(List(codeA, codeB))
+ val ins = getSingleMethod(b, "g").instructions
+ assert(ins exists {
+ case Invoke(_, "B", "f", _, _) => true
+ case _ => false
+ }, ins)
+ }
+
+ @Test
+ def compileErroneous(): Unit = {
+ compileClasses(compiler)("class C { def f: String = 1 }", allowMessage = _.msg contains "type mismatch")
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
index 2975bd060d..1b6c080234 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
@@ -15,11 +15,14 @@ import CodeGenTools._
import scala.tools.partest.ASMConverters
import ASMConverters._
+import BackendReporting._
+
import scala.collection.convert.decorateAsScala._
@RunWith(classOf[JUnit4])
class BTypesFromClassfileTest {
- val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode")
+ // inliner enabled -> inlineInfos are collected (and compared) in ClassBTypes
+ val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global")
import compiler._
import definitions._
@@ -29,6 +32,7 @@ class BTypesFromClassfileTest {
def duringBackend[T](f: => T) = compiler.exitingDelambdafy(f)
val run = new compiler.Run() // initializes some of the compiler
+ duringBackend(compiler.scalaPrimitives.init()) // needed: it's only done when running the backend, and we don't actually run the compiler
duringBackend(bTypes.initializeCoreBTypes())
def clearCache() = bTypes.classBTypeFromInternalName.clear()
@@ -37,7 +41,7 @@ class BTypesFromClassfileTest {
if (checked(fromSym.internalName)) checked
else {
assert(fromSym == fromClassfile, s"$fromSym != $fromClassfile")
- sameInfo(fromSym.info, fromClassfile.info, checked + fromSym.internalName)
+ sameInfo(fromSym.info.get, fromClassfile.info.get, checked + fromSym.internalName)
}
}
@@ -57,8 +61,12 @@ class BTypesFromClassfileTest {
else (fromSym.flags | ACC_PRIVATE | ACC_PUBLIC) == (fromClassfile.flags | ACC_PRIVATE | ACC_PUBLIC)
}, s"class flags differ\n$fromSym\n$fromClassfile")
- val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked)
+ // we don't compare InlineInfos in this test: in both cases (from symbol and from classfile) they
+ // are actually created by looking at the classfile members, not the symbol's. InlineInfos are only
+ // built from symbols for classes that are being compiled, which is not the case here. Instead
+ // there's a separate InlineInfoTest.
+ val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked)
val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1)
// The fromSym info has only member classes, no local or anonymous. The symbol is read from the
@@ -67,7 +75,7 @@ class BTypesFromClassfileTest {
// and anonymous classes as members of the outer class. But not for unpickled symbols).
// The fromClassfile info has all nested classes, including anonymous and local. So we filter
// them out: member classes are identified by having the `outerName` defined.
- val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.nestedInfo.get.outerName.isDefined)
+ val memberClassesFromClassfile = fromClassfile.nestedClasses.filter(_.info.get.nestedInfo.get.outerName.isDefined)
// Sorting is required: the backend sorts all InnerClass entries by internalName before writing
// them to the classfile (to make it deterministic: the entries are collected in a Set during
// code generation).
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
new file mode 100644
index 0000000000..9fda034a04
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
@@ -0,0 +1,152 @@
+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.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.asm.tree._
+import scala.tools.asm.tree.analysis._
+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._
+
+@RunWith(classOf[JUnit4])
+class CallGraphTest {
+ val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global -Yopt-warnings")
+ import compiler.genBCode.bTypes._
+
+ // allows inspecting the caches after a compilation run
+ val notPerRun: List[Clearable] = List(classBTypeFromInternalName, byteCodeRepository.classes, callGraph.callsites)
+ notPerRun foreach compiler.perRunCaches.unrecordCache
+
+ def compile(code: String, allowMessage: StoreReporter#Info => Boolean): List[ClassNode] = {
+ notPerRun.foreach(_.clear())
+ compileClasses(compiler)(code, allowMessage = allowMessage)
+ }
+
+ def callsInMethod(methodNode: MethodNode): List[MethodInsnNode] = methodNode.instructions.iterator.asScala.collect({
+ case call: MethodInsnNode => call
+ }).toList
+
+ @Test
+ def callGraphStructure(): Unit = {
+ val code =
+ """class C {
+ | // try-catch prevents inlining - we want to analyze the callsite
+ | def f1 = try { 0 } catch { case _: Throwable => 1 }
+ | final def f2 = try { 0 } catch { case _: Throwable => 1 }
+ |
+ | @inline def f3 = try { 0 } catch { case _: Throwable => 1 }
+ | @inline final def f4 = try { 0 } catch { case _: Throwable => 1 }
+ |
+ | @noinline def f5 = try { 0 } catch { case _: Throwable => 1 }
+ | @noinline final def f6 = try { 0 } catch { case _: Throwable => 1 }
+ |
+ | @inline @noinline def f7 = try { 0 } catch { case _: Throwable => 1 }
+ |}
+ |class D extends C {
+ | @inline override def f1 = try { 0 } catch { case _: Throwable => 1 }
+ | override final def f3 = try { 0 } catch { case _: Throwable => 1 }
+ |}
+ |object C {
+ | def g1 = try { 0 } catch { case _: Throwable => 1 }
+ |}
+ |class Test {
+ | def t1(c: C) = c.f1 + c.f2 + c.f3 + c.f4 + c.f5 + c.f6 + c.f7 + C.g1
+ | def t2(d: D) = d.f1 + d.f2 + d.f3 + d.f4 + d.f5 + d.f6 + d.f7 + C.g1
+ |}
+ """.stripMargin
+
+ // Get the ClassNodes from the code repo (don't use the unparsed ClassNodes returned by compile).
+ // The callGraph.callsites map is indexed by instructions of those ClassNodes.
+
+ val ok = Set(
+ "D::f1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for D.f1: C.f1 is not annotated @inline
+ "C::f3()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // only one warning for C.f3: D.f3 does not have @inline (and it would also be safe to inline)
+ "C::f7()I is annotated @inline but cannot be inlined: the method is not final and may be overridden", // two warnings (the error message mentions C.f7 even if the receiver type is D, because f7 is inherited from C)
+ "operand stack at the callsite in Test::t1(LC;)I contains more values",
+ "operand stack at the callsite in Test::t2(LD;)I contains more values")
+ var msgCount = 0
+ val checkMsg = (m: StoreReporter#Info) => {
+ msgCount += 1
+ ok exists (m.msg contains _)
+ }
+ val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg).map(c => byteCodeRepository.classNode(c.name).get)
+ assert(msgCount == 6, msgCount)
+
+ val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = cCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name)
+ val List(df1, df3) = dCls.methods.iterator.asScala.filter(_.name.startsWith("f")).toList.sortBy(_.name)
+ val g1 = cMod.methods.iterator.asScala.find(_.name == "g1").get
+ val List(t1, t2) = testCls.methods.iterator.asScala.filter(_.name.startsWith("t")).toList.sortBy(_.name)
+
+ val List(cf1Call, cf2Call, cf3Call, cf4Call, cf5Call, cf6Call, cf7Call, cg1Call) = callsInMethod(t1)
+ val List(df1Call, df2Call, df3Call, df4Call, df5Call, df6Call, df7Call, dg1Call) = callsInMethod(t2)
+
+ def checkCallsite(callsite: callGraph.Callsite,
+ call: MethodInsnNode, callsiteMethod: MethodNode, target: MethodNode, calleeDeclClass: ClassBType,
+ safeToInline: Boolean, atInline: Boolean, atNoInline: Boolean) = try {
+ assert(callsite.callsiteInstruction == call)
+ assert(callsite.callsiteMethod == callsiteMethod)
+ val callee = callsite.callee.get
+ assert(callee.callee == target)
+ assert(callee.calleeDeclarationClass == calleeDeclClass)
+ assert(callee.safeToInline == safeToInline)
+ assert(callee.annotatedInline == atInline)
+ assert(callee.annotatedNoInline == atNoInline)
+
+ assert(callsite.argInfos == List()) // not defined yet
+ } catch {
+ case e: Throwable => println(callsite); throw e
+ }
+
+ val cClassBType = classBTypeFromClassNode(cCls)
+ val cMClassBType = classBTypeFromClassNode(cMod)
+ val dClassBType = classBTypeFromClassNode(dCls)
+
+ checkCallsite(callGraph.callsites(cf1Call),
+ cf1Call, t1, cf1, cClassBType, false, false, false)
+ checkCallsite(callGraph.callsites(cf2Call),
+ cf2Call, t1, cf2, cClassBType, true, false, false)
+ checkCallsite(callGraph.callsites(cf3Call),
+ cf3Call, t1, cf3, cClassBType, false, true, false)
+ checkCallsite(callGraph.callsites(cf4Call),
+ cf4Call, t1, cf4, cClassBType, true, true, false)
+ checkCallsite(callGraph.callsites(cf5Call),
+ cf5Call, t1, cf5, cClassBType, false, false, true)
+ checkCallsite(callGraph.callsites(cf6Call),
+ cf6Call, t1, cf6, cClassBType, true, false, true)
+ checkCallsite(callGraph.callsites(cf7Call),
+ cf7Call, t1, cf7, cClassBType, false, true, true)
+ checkCallsite(callGraph.callsites(cg1Call),
+ cg1Call, t1, g1, cMClassBType, true, false, false)
+
+ checkCallsite(callGraph.callsites(df1Call),
+ df1Call, t2, df1, dClassBType, false, true, false)
+ checkCallsite(callGraph.callsites(df2Call),
+ df2Call, t2, cf2, cClassBType, true, false, false)
+ checkCallsite(callGraph.callsites(df3Call),
+ df3Call, t2, df3, dClassBType, true, false, false)
+ checkCallsite(callGraph.callsites(df4Call),
+ df4Call, t2, cf4, cClassBType, true, true, false)
+ checkCallsite(callGraph.callsites(df5Call),
+ df5Call, t2, cf5, cClassBType, false, false, true)
+ checkCallsite(callGraph.callsites(df6Call),
+ df6Call, t2, cf6, cClassBType, true, false, true)
+ checkCallsite(callGraph.callsites(df7Call),
+ df7Call, t2, cf7, cClassBType, false, true, true)
+ checkCallsite(callGraph.callsites(dg1Call),
+ dg1Call, t2, g1, cMClassBType, true, false, false)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
index fc748196d0..76492cfa23 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
@@ -17,8 +17,8 @@ class CompactLocalVariablesTest {
// recurse-unreachable-jumps is required for eliminating catch blocks, in the first dce round they
// are still live.only after eliminating the empty handler the catch blocks become unreachable.
- val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps,compact-locals")
- val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps")
+ val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,compact-locals")
+ val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
@Test
def compactUnused(): Unit = {
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
index 7d83c54b5b..7b0504fec0 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
@@ -11,9 +11,23 @@ import org.junit.Assert._
import CodeGenTools._
import scala.tools.partest.ASMConverters
import ASMConverters._
+import scala.tools.testing.ClearAfterClass
+
+object EmptyExceptionHandlersTest extends ClearAfterClass.Clearable {
+ var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+ var dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code")
+ def clear(): Unit = {
+ noOptCompiler = null
+ dceCompiler = null
+ }
+}
@RunWith(classOf[JUnit4])
-class EmptyExceptionHandlersTest {
+class EmptyExceptionHandlersTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = EmptyExceptionHandlersTest
+
+ val noOptCompiler = EmptyExceptionHandlersTest.noOptCompiler
+ val dceCompiler = EmptyExceptionHandlersTest.dceCompiler
val exceptionDescriptor = "java/lang/Exception"
@@ -51,9 +65,6 @@ class EmptyExceptionHandlersTest {
assertTrue(convertMethod(asmMethod).handlers.isEmpty)
}
- val noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
- val dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code")
-
@Test
def eliminateUnreachableHandler(): Unit = {
val code = "def f: Unit = try { } catch { case _: Exception => println(0) }; println(1)"
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala
new file mode 100644
index 0000000000..57088bdd2f
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala
@@ -0,0 +1,67 @@
+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 org.junit.Assert._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import AsmUtils._
+import scala.tools.testing.ClearAfterClass
+
+import BackendReporting._
+
+import scala.collection.convert.decorateAsScala._
+
+object InlineInfoTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath")
+ def clear(): Unit = { compiler = null }
+
+ def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes)
+ notPerRun foreach compiler.perRunCaches.unrecordCache
+}
+
+@RunWith(classOf[JUnit4])
+class InlineInfoTest {
+ val compiler = InlineInfoTest.compiler
+
+ def compile(code: String) = {
+ InlineInfoTest.notPerRun.foreach(_.clear())
+ compileClasses(compiler)(code)
+ }
+
+ @Test
+ def inlineInfosFromSymbolAndAttribute(): Unit = {
+ val code =
+ """trait T {
+ | @inline def f: Int
+ | @noinline final def g = 0
+ |}
+ |trait U { self: T =>
+ | @inline def f = 0
+ | final def h = 0
+ | final class K {
+ | @inline def i = 0
+ | }
+ |}
+ |sealed trait V {
+ | @inline def j = 0
+ |}
+ |class C extends T with U
+ """.stripMargin
+ val classes = compile(code)
+ val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.get.inlineInfo)
+
+ val fromAttrs = classes.map(c => {
+ assert(c.attrs.asScala.exists(_.isInstanceOf[InlineInfoAttribute]), c.attrs)
+ compiler.genBCode.bTypes.inlineInfoFromClassfile(c)
+ })
+
+ assert(fromSyms == fromAttrs)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala
new file mode 100644
index 0000000000..fedc074a15
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala
@@ -0,0 +1,146 @@
+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 InlineWarningTest extends ClearAfterClass.Clearable {
+ val argsNoWarn = "-Ybackend:GenBCode -Yopt:l:classpath"
+ val args = argsNoWarn + " -Yopt-warnings"
+ var compiler = newCompiler(extraArgs = args)
+ def clear(): Unit = { compiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class InlineWarningTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = InlineWarningTest
+
+ val compiler = InlineWarningTest.compiler
+
+ def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
+ compileClasses(compiler)(scalaCode, javaCode, allowMessage)
+ }
+
+ @Test
+ def nonFinal(): Unit = {
+ val code =
+ """class C {
+ | @inline def m1 = 1
+ |}
+ |trait T {
+ | @inline def m2 = 1
+ |}
+ |class D extends C with T
+ |
+ |class Test {
+ | def t1(c: C, t: T, d: D) = c.m1 + t.m2 + d.m1 + d.m2
+ |}
+ """.stripMargin
+ var count = 0
+ val warns = Set(
+ "C::m1()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
+ "T::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
+ "D::m2()I is annotated @inline but cannot be inlined: the method is not final and may be overridden")
+ compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
+ assert(count == 4, count)
+ }
+
+ @Test
+ def traitMissingImplClass(): Unit = {
+ val codeA = "trait T { @inline final def f = 1 }"
+ val codeB = "class C { def t1(t: T) = t.f }"
+
+ val removeImpl = (outDir: AbstractFile) => {
+ val f = outDir.lookupName("T$class.class", directory = false)
+ if (f != null) f.delete()
+ }
+
+ val warn =
+ """T::f()I is annotated @inline but cannot be inlined: the trait method call could not be rewritten to the static implementation method. Possible reason:
+ |The method f(LT;)I could not be found in the class T$class or any of its parents.
+ |Note that the following parent classes could not be found on the classpath: T$class""".stripMargin
+
+ var c = 0
+ compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.args, afterEach = removeImpl, allowMessage = i => {c += 1; i.msg contains warn})
+ assert(c == 1, c)
+
+ // only summary here
+ compileSeparately(List(codeA, codeB), extraArgs = InlineWarningTest.argsNoWarn, afterEach = removeImpl, allowMessage = _.msg contains "there was one inliner warning")
+ }
+
+ @Test
+ def handlerNonEmptyStack(): Unit = {
+ val code =
+ """class C {
+ | @noinline def q = 0
+ | @inline final def foo = try { q } catch { case e: Exception => 2 }
+ | def t1 = println(foo) // inline warning here: foo cannot be inlined on top of a non-empty stack
+ |}
+ """.stripMargin
+
+ var c = 0
+ compile(code, allowMessage = i => {c += 1; i.msg contains "operand stack at the callsite in C::t1()V contains more values"})
+ assert(c == 1, c)
+ }
+
+ @Test
+ def mixedWarnings(): Unit = {
+ val javaCode =
+ """public class A {
+ | public static final int bar() { return 100; }
+ |}
+ """.stripMargin
+
+ val scalaCode =
+ """class B {
+ | @inline final def flop = A.bar
+ | def g = flop
+ |}
+ """.stripMargin
+
+ val warns = List(
+ """failed to determine if bar should be inlined:
+ |The method bar()I could not be found in the class A or any of its parents.
+ |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin,
+
+ """B::flop()I is annotated @inline but could not be inlined:
+ |Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed:
+ |The method bar()I could not be found in the class A or any of its parents.
+ |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin)
+
+ var c = 0
+ val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.tail.exists(i.msg contains _)})
+ assert(c == 1, c)
+
+ // no warnings here
+ compileClasses(newCompiler(extraArgs = InlineWarningTest.argsNoWarn + " -Yopt-warnings:none"))(scalaCode, List((javaCode, "A.java")))
+
+ c = 0
+ compileClasses(newCompiler(extraArgs = InlineWarningTest.argsNoWarn + " -Yopt-warnings:no-inline-mixed"))(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.exists(i.msg contains _)})
+ assert(c == 2, c)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
new file mode 100644
index 0000000000..b4839dcec8
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
@@ -0,0 +1,198 @@
+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.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import scala.tools.asm.tree._
+import scala.tools.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import AsmUtils._
+
+import scala.collection.convert.decorateAsScala._
+import scala.tools.testing.ClearAfterClass
+
+object InlinerIllegalAccessTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+ def clear(): Unit = { compiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class InlinerIllegalAccessTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = InlinerIllegalAccessTest
+
+ val compiler = InlinerIllegalAccessTest.compiler
+ import compiler.genBCode.bTypes._
+
+ def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.add(c, ByteCodeRepository.Classfile)
+ def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) throw new AssertionError(textify(i))
+
+ @Test
+ def typeAccessible(): Unit = {
+ val code =
+ """package a {
+ | private class C { // the Scala compiler makes all classes public
+ | def f1 = new C // NEW a/C
+ | def f2 = new Array[C](0) // ANEWARRAY a/C
+ | def f3 = new Array[Array[C]](0) // ANEWARRAY [La/C;
+ | }
+ | class D
+ |}
+ |package b {
+ | class E
+ |}
+ """.stripMargin
+
+ val allClasses = compileClasses(compiler)(code)
+ val List(cClass, dClass, eClass) = allClasses
+ assert(cClass.name == "a/C" && dClass.name == "a/D" && eClass.name == "b/E", s"${cClass.name}, ${dClass.name}, ${eClass.name}")
+ addToRepo(allClasses) // they are not on the compiler's classpath, so we add them manually to the code repo
+
+ val methods = cClass.methods.asScala.filter(_.name(0) == 'f').toList
+
+ def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = {
+ for (m <- methods)
+ test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(cClass.name), classBTypeFromParsedClassfile(classNode.name)).map(_._1))
+ }
+
+ check(cClass, assertEmpty)
+ check(dClass, assertEmpty)
+ check(eClass, assertEmpty) // C is public, so accessible in E
+
+ byteCodeRepository.classes.clear()
+ classBTypeFromInternalName.clear()
+
+ cClass.access &= ~ACC_PUBLIC // ftw
+ addToRepo(allClasses)
+
+ // private classes can be accessed from the same package
+ check(cClass, assertEmpty)
+ check(dClass, assertEmpty) // accessing a private class in the same package is OK
+ check(eClass, {
+ case Some(ti: TypeInsnNode) if Set("a/C", "[La/C;")(ti.desc) => ()
+ // MatchError otherwise
+ })
+ }
+
+ @Test
+ def memberAccessible(): Unit = {
+ val code =
+ """package a {
+ | class C {
+ | /*public*/ def a = 0
+ | /*default*/ def b = 0
+ | protected def c = 0
+ | private def d = 0
+ |
+ | /*public static*/ def e = 0
+ | /*default static*/ def f = 0
+ | protected /*static*/ def g = 0
+ | private /*static*/ def h = 0
+ |
+ | def raC = a
+ | def rbC = b
+ | def rcC = c
+ | def rdC = d
+ | def reC = e
+ | def rfC = f
+ | def rgC = g
+ | def rhC = h
+ | }
+ |
+ | class D extends C {
+ | def rbD = b // 1: default access b, accessed in D, declared in C. can be inlined into any class in the same package as C.
+ | def rcD = c // 2: protected c, accessed in D. can be inlined into C, D or E, but not into F (F and D are unrelated).
+ |
+ | def rfD = f // 1
+ | def rgD = g // 2
+ | }
+ | class E extends D
+ |
+ | class F extends C
+ |
+ | class G
+ |}
+ |
+ |package b {
+ | class H extends a.C
+ | class I
+ |}
+ """.stripMargin
+
+ val allClasses = compileClasses(compiler)(code)
+ val List(cCl, dCl, eCl, fCl, gCl, hCl, iCl) = allClasses
+ addToRepo(allClasses)
+
+ // set flags that Scala scala doesn't (default access, static) - a hacky way to test all access modes.
+ val names = ('a' to 'h').map(_.toString).toSet
+ val List(a, b, c, d, e, f, g, h) = cCl.methods.asScala.toList.filter(m => names(m.name))
+
+ def checkAccess(a: MethodNode, expected: Int): Unit = {
+ assert((a.access & (ACC_STATIC | ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE)) == expected, s"${a.name}, ${a.access}")
+ }
+
+ checkAccess(a, ACC_PUBLIC)
+ b.access &= ~ACC_PUBLIC; checkAccess(b, 0) // make it default access
+ c.access &= ~ACC_PUBLIC; c.access |= ACC_PROTECTED; checkAccess(c, ACC_PROTECTED) // make it protected - scalac actually never emits PROTECTED in bytecode, see javaFlags in BTypesFromSymbols
+ checkAccess(d, ACC_PRIVATE)
+
+ e.access |= ACC_STATIC; checkAccess(e, ACC_STATIC | ACC_PUBLIC)
+ f.access &= ~ACC_PUBLIC; f.access |= ACC_STATIC; checkAccess(f, ACC_STATIC)
+ g.access &= ~ACC_PUBLIC; g.access |= (ACC_STATIC | ACC_PROTECTED); checkAccess(g, ACC_STATIC | ACC_PROTECTED)
+ h.access |= ACC_STATIC; checkAccess(h, ACC_STATIC | ACC_PRIVATE)
+
+ val List(raC, rbC, rcC, rdC, reC, rfC, rgC, rhC) = cCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name)
+
+ val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name)
+
+ def check(method: MethodNode, decl: ClassNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = {
+ test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(decl.name), classBTypeFromParsedClassfile(dest.name)).map(_._1))
+ }
+
+ val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match {
+ case Some(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => ()
+ // MatchError otherwise
+ }
+
+ // PUBLIC
+
+ // public methods allowed everywhere
+ for (m <- Set(raC, reC); c <- allClasses) check(m, cCl, c, assertEmpty)
+
+ // DEFAULT ACCESS
+
+ // default access OK in same package
+ for ((m, declCls) <- Set((rbC, cCl), (rfC, cCl), (rbD, dCl), (rfD, dCl)); c <- allClasses) {
+ if (c.name startsWith "a/") check(m, declCls, c, assertEmpty)
+ else check(m, declCls, c, cOrDOwner)
+ }
+
+ // PROTECTED
+
+ // protected accessed in same class, or protected static accessed in subclass(rgD).
+ // can be inlined to subclasses, and classes in the same package (gCl)
+ for ((m, declCls) <- Set((rcC, cCl), (rgC, cCl), (rgD, dCl)); c <- Set(cCl, dCl, eCl, fCl, gCl, hCl)) check(m, declCls, c, assertEmpty)
+
+ // protected in non-subclass and different package
+ for (m <- Set(rcC, rgC)) check(m, cCl, iCl, cOrDOwner)
+
+ // non-static protected accessed in subclass (rcD). can be inlined to related class, or classes in the same package
+ for (c <- Set(cCl, dCl, eCl, fCl, gCl)) check(rcD, dCl, c, assertEmpty)
+
+ // rcD cannot be inlined into non-related classes, if the declaration and destination are not in the same package
+ for (c <- Set(hCl, iCl)) check(rcD, dCl, c, cOrDOwner)
+
+ // PRIVATE
+
+ // privated method accesses can only be inlined in the same class
+ for (m <- Set(rdC, rhC)) check(m, cCl, cCl, assertEmpty)
+ for (m <- Set(rdC, rhC); c <- allClasses.tail) check(m, cCl, c, cOrDOwner)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
new file mode 100644
index 0000000000..5c9bd1c188
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
@@ -0,0 +1,115 @@
+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.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import AsmUtils._
+
+import scala.collection.convert.decorateAsScala._
+
+object InlinerSeparateCompilationTest {
+ val args = "-Ybackend:GenBCode -Yopt:l:classpath"
+}
+
+@RunWith(classOf[JUnit4])
+class InlinerSeparateCompilationTest {
+ import InlinerSeparateCompilationTest._
+ import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke}
+
+ @Test
+ def inlnieMixedinMember(): Unit = {
+ val codeA =
+ """trait T {
+ | @inline def f = 0
+ |}
+ |object O extends T {
+ | @inline def g = 1
+ |}
+ """.stripMargin
+
+ val codeB =
+ """class C {
+ | def t1(t: T) = t.f
+ | def t2 = O.f
+ | def t3 = O.g
+ |}
+ """.stripMargin
+
+ val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
+ val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args + " -Yopt-warnings", _.msg contains warn)
+ assertInvoke(getSingleMethod(c, "t1"), "T", "f")
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ assertNoInvoke(getSingleMethod(c, "t3"))
+ }
+
+ @Test
+ def inlineSealedMember(): Unit = {
+ val codeA =
+ """sealed trait T {
+ | @inline def f = 1
+ |}
+ """.stripMargin
+
+ val codeB =
+ """class C {
+ | def t1(t: T) = t.f
+ |}
+ """.stripMargin
+
+ val List(c, t, tCls) = compileClassesSeparately(List(codeA, codeB), args)
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ }
+
+ @Test
+ def inlineInheritedMember(): Unit = {
+ val codeA =
+ """trait T {
+ | @inline final def f = 1
+ |}
+ |trait U extends T {
+ | @inline final def g = f
+ |}
+ """.stripMargin
+
+ val codeB =
+ """class C extends U {
+ | def t1 = this.f
+ | def t2 = this.g
+ | def t3(t: T) = t.f
+ |}
+ """.stripMargin
+
+ val List(c, t, tCls, u, uCls) = compileClassesSeparately(List(codeA, codeB), args)
+ for (m <- List("t1", "t2", "t3")) assertNoInvoke(getSingleMethod(c, m))
+ }
+
+ @Test
+ def inlineWithSelfType(): Unit = {
+ val assembly =
+ """trait Assembly extends T {
+ | @inline final def g = 1
+ | @inline final def n = m
+ |}
+ """.stripMargin
+
+ val codeA =
+ s"""trait T { self: Assembly =>
+ | @inline final def f = g
+ | @inline final def m = 1
+ |}
+ |$assembly
+ """.stripMargin
+
+ val List(a, aCls, t, tCls) = compileClassesSeparately(List(codeA, assembly), args)
+ assertNoInvoke(getSingleMethod(tCls, "f"))
+ assertNoInvoke(getSingleMethod(aCls, "n"))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
new file mode 100644
index 0000000000..39fb28570e
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
@@ -0,0 +1,953 @@
+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 InlinerTest extends ClearAfterClass.Clearable {
+ val args = "-Ybackend:GenBCode -Yopt:l:classpath -Yopt-warnings"
+ var compiler = newCompiler(extraArgs = args)
+
+ // allows inspecting the caches after a compilation run
+ def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes, compiler.genBCode.bTypes.callGraph.callsites)
+ notPerRun foreach compiler.perRunCaches.unrecordCache
+
+ def clear(): Unit = { compiler = null }
+
+ implicit class listStringLines[T](val l: List[T]) extends AnyVal {
+ def stringLines = l.mkString("\n")
+ }
+
+ def assertNoInvoke(m: Method): Unit = assertNoInvoke(m.instructions)
+ def assertNoInvoke(ins: List[Instruction]): Unit = {
+ assert(!ins.exists(_.isInstanceOf[Invoke]), ins.stringLines)
+ }
+
+ def assertInvoke(m: Method, receiver: String, method: String): Unit = assertInvoke(m.instructions, receiver, method)
+ def assertInvoke(l: List[Instruction], receiver: String, method: String): Unit = {
+ assert(l.exists {
+ case Invoke(_, `receiver`, `method`, _, _) => true
+ case _ => false
+ }, l.stringLines)
+ }
+}
+
+@RunWith(classOf[JUnit4])
+class InlinerTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = InlinerTest
+
+ import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke}
+
+ val compiler = InlinerTest.compiler
+ import compiler.genBCode.bTypes._
+
+ def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
+ InlinerTest.notPerRun.foreach(_.clear())
+ compileClasses(compiler)(scalaCode, javaCode, allowMessage)
+ }
+
+ def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = {
+ assert(callsite.callsiteMethod.instructions.contains(callsite.callsiteInstruction), instructionsFromMethod(callsite.callsiteMethod))
+
+ val callsiteClassNode = byteCodeRepository.classNode(callsite.callsiteClass.internalName).get
+ assert(callsiteClassNode.methods.contains(callsite.callsiteMethod), callsiteClassNode.methods.asScala.map(_.name).toList)
+
+ assert(callsite.callee.get.callee == callee, callsite.callee.get.callee.name)
+ }
+
+ // inline first invocation of f into g in class C
+ def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, Option[CannotInlineWarning]) = {
+ val List(cls) = compile(code)
+ mod(cls)
+ val clsBType = classBTypeFromParsedClassfile(cls.name)
+
+ val List(f, g) = cls.methods.asScala.filter(m => Set("f", "g")(m.name)).toList.sortBy(_.name)
+ val fCall = g.instructions.iterator.asScala.collect({ case i: MethodInsnNode if i.name == "f" => i }).next()
+
+ val analyzer = new AsmAnalyzer(g, clsBType.internalName)
+
+ val r = inliner.inline(
+ fCall,
+ analyzer.frameAt(fCall).getStackSize,
+ g,
+ clsBType,
+ f,
+ clsBType,
+ receiverKnownNotNull = true,
+ keepLineNumbers = true)
+ (g, r)
+ }
+
+ @Test
+ def simpleInlineOK(): Unit = {
+ val code =
+ """class C {
+ | def f = 1
+ | def g = f + f
+ |}
+ """.stripMargin
+
+ val (g, _) = inlineTest(code)
+
+ val gConv = convertMethod(g)
+ assertSameCode(gConv.instructions.dropNonOp,
+ List(
+ VarOp(ALOAD, 0), VarOp(ASTORE, 1), // store this
+ Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(10)), // store return value
+ Label(10), VarOp(ILOAD, 2), // load return value
+ VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "f", "()I", false), Op(IADD), Op(IRETURN)))
+
+ // line numbers are kept, so there's a line 2 (from the inlined f)
+ assert(gConv.instructions exists {
+ case LineNumber(2, _) => true
+ case _ => false
+ }, gConv.instructions.filter(_.isInstanceOf[LineNumber]))
+
+ assert(gConv.localVars.map(_.name).sorted == List("f_this", "this"), gConv.localVars)
+ assert(g.maxStack == 2 && g.maxLocals == 3, s"${g.maxLocals} - ${g.maxStack}")
+ }
+
+ @Test
+ def nothingTypedOK(): Unit = {
+ val code =
+ """class C {
+ | def f: Nothing = ???
+ | def g: Int = { f; 1 }
+ |}
+ """.stripMargin
+
+ // On the bytecode level, methods of type Nothing have return type Nothing$.
+ // This can be treated like any other result object.
+
+ // See also discussion around ATHROW in BCodeBodyBuilder
+
+ val (g, _) = inlineTest(code)
+ val expectedInlined = List(
+ VarOp(ALOAD, 0), VarOp(ASTORE, 1), // store this
+ Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), Invoke(INVOKEVIRTUAL, "scala/Predef$", "$qmark$qmark$qmark", "()Lscala/runtime/Nothing$;", false)) // inlined call to ???
+
+ assertSameCode(convertMethod(g).instructions.dropNonOp.take(4), expectedInlined)
+
+ localOpt.methodOptimizations(g, "C")
+ assertSameCode(convertMethod(g).instructions.dropNonOp,
+ expectedInlined ++ List(VarOp(ASTORE, 2), VarOp(ALOAD, 2), Op(ATHROW)))
+ }
+
+ @Test
+ def synchronizedNoInline(): Unit = {
+ val code =
+ """class C {
+ | def f: Int = 0
+ | def g: Int = f
+ |}
+ """.stripMargin
+
+ val (_, can) = inlineTest(code, cls => {
+ val f = cls.methods.asScala.find(_.name == "f").get
+ f.access |= ACC_SYNCHRONIZED
+ })
+ assert(can.get.isInstanceOf[SynchronizedMethod], can)
+ }
+
+ @Test
+ def tryCatchOK(): Unit = {
+ val code =
+ """class C {
+ | def f: Int = try { 1 } catch { case _: Exception => 2 }
+ | def g = f + 1
+ |}
+ """.stripMargin
+ val (_, r) = inlineTest(code)
+ assert(r.isEmpty, r)
+ }
+
+ @Test
+ def tryCatchNoInline(): Unit = {
+ // cannot inline f: there's a value on g's stack. if f throws and enters the handler, all values
+ // on the stack are removed, including the one of g's stack that we still need.
+ val code =
+ """class C {
+ | def f: Int = try { 1 } catch { case _: Exception => 2 }
+ | def g = println(f)
+ |}
+ """.stripMargin
+ val (_, r) = inlineTest(code)
+ assert(r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r)
+ }
+
+ @Test
+ def illegalAccessNoInline(): Unit = {
+ val code =
+ """package a {
+ | class C {
+ | private def f: Int = 0
+ | def g: Int = f
+ | }
+ |}
+ |package b {
+ | class D {
+ | def h(c: a.C): Int = c.g + 1
+ | }
+ |}
+ """.stripMargin
+
+ val List(c, d) = compile(code)
+
+ val cTp = classBTypeFromParsedClassfile(c.name)
+ val dTp = classBTypeFromParsedClassfile(d.name)
+
+ val g = c.methods.asScala.find(_.name == "g").get
+ val h = d.methods.asScala.find(_.name == "h").get
+ val gCall = h.instructions.iterator.asScala.collect({
+ case m: MethodInsnNode if m.name == "g" => m
+ }).next()
+
+ val analyzer = new AsmAnalyzer(h, dTp.internalName)
+
+ val r = inliner.inline(
+ gCall,
+ analyzer.frameAt(gCall).getStackSize,
+ h,
+ dTp,
+ g,
+ cTp,
+ receiverKnownNotNull = true,
+ keepLineNumbers = true)
+
+ assert(r.get.isInstanceOf[IllegalAccessInstruction], r)
+ }
+
+ @Test
+ def inlineSimpleAtInline(): Unit = {
+ val code =
+ """class C {
+ | @inline final def f = 0
+ | final def g = 1
+ |
+ | def test = f + g
+ |}
+ """.stripMargin
+ val List(cCls) = compile(code)
+ val instructions = getSingleMethod(cCls, "test").instructions
+ assert(instructions.contains(Op(ICONST_0)), instructions.stringLines)
+ assert(!instructions.contains(Op(ICONST_1)), instructions)
+ }
+
+ @Test
+ def cyclicInline(): Unit = {
+ val code =
+ """class C {
+ | @inline final def f: Int = g
+ | @inline final def g: Int = f
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ val methods @ List(_, g) = c.methods.asScala.filter(_.name.length == 1).toList
+ val List(fIns, gIns) = methods.map(instructionsFromMethod(_).dropNonOp)
+ val invokeG = Invoke(INVOKEVIRTUAL, "C", "g", "()I", false)
+ assert(fIns contains invokeG, fIns) // no inlining into f, that request is elided
+ assert(gIns contains invokeG, gIns) // f is inlined into g, g invokes itself recursively
+
+ assert(callGraph.callsites.size == 3, callGraph.callsites)
+ for (callsite <- callGraph.callsites.values if methods.contains(callsite.callsiteMethod)) {
+ checkCallsite(callsite, g)
+ }
+ }
+
+ @Test
+ def cyclicInline2(): Unit = {
+ val code =
+ """class C {
+ | @inline final def h: Int = f
+ | @inline final def f: Int = g + g
+ | @inline final def g: Int = h
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ val methods @ List(f, g, h) = c.methods.asScala.filter(_.name.length == 1).sortBy(_.name).toList
+ val List(fIns, gIns, hIns) = methods.map(instructionsFromMethod(_).dropNonOp)
+ val invokeG = Invoke(INVOKEVIRTUAL, "C", "g", "()I", false)
+ assert(fIns.count(_ == invokeG) == 2, fIns) // no inlining into f, these requests are elided
+ assert(gIns.count(_ == invokeG) == 2, gIns)
+ assert(hIns.count(_ == invokeG) == 2, hIns)
+
+ assert(callGraph.callsites.size == 7, callGraph.callsites)
+ for (callsite <- callGraph.callsites.values if methods.contains(callsite.callsiteMethod)) {
+ checkCallsite(callsite, g)
+ }
+ }
+
+ @Test
+ def arraycopy(): Unit = {
+ // also tests inlining of a void-returning method (no return value on the stack)
+ val code =
+ """// can't use the `compat.Platform.arraycopy` from the std lib for now, because the classfile doesn't have a ScalaInlineInfo attribute
+ |object Platform {
+ | @inline def arraycopy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int) {
+ | System.arraycopy(src, srcPos, dest, destPos, length)
+ | }
+ |}
+ |class C {
+ | def f(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = {
+ | Platform.arraycopy(src, srcPos, dest, destPos, length)
+ | }
+ |}
+ """.stripMargin
+ val List(c, _, _) = compile(code)
+ val ins = getSingleMethod(c, "f").instructions
+ val invokeSysArraycopy = Invoke(INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false)
+ assert(ins contains invokeSysArraycopy, ins.stringLines)
+ }
+
+ @Test
+ def arrayMemberMethod(): Unit = {
+ // This used to crash when building the call graph. The `owner` field of the MethodInsnNode
+ // for the invocation of `clone` is not an internal name, but a full array descriptor
+ // [Ljava.lang.Object; - the documentation in the ASM library didn't mention that possibility.
+ val code =
+ """class C {
+ | def f(a: Array[Object]) = {
+ | a.clone()
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ assert(callGraph.callsites.values exists (_.callsiteInstruction.name == "clone"))
+ }
+
+ @Test
+ def atInlineInTrait(): Unit = {
+ val code =
+ """trait T {
+ | @inline final def f = 0
+ |}
+ |class C {
+ | def g(t: T) = t.f
+ |}
+ """.stripMargin
+ val List(c, t, tClass) = compile(code)
+ assertNoInvoke(getSingleMethod(c, "g"))
+ }
+
+ @Test
+ def inlinePrivateMethodWithHandler(): Unit = {
+ val code =
+ """class C {
+ | @inline private def f = try { 0 } catch { case _: Throwable => 1 }
+ | def g = f
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ // no more invoke, f is inlined
+ assertNoInvoke(getSingleMethod(c, "g"))
+ }
+
+ @Test
+ def inlineStaticCall(): Unit = {
+ val code =
+ """class C {
+ | def f = Integer.lowestOneBit(103)
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ val f = c.methods.asScala.find(_.name == "f").get
+ val callsiteIns = f.instructions.iterator().asScala.collect({ case c: MethodInsnNode => c }).next()
+ val clsBType = classBTypeFromParsedClassfile(c.name)
+ val analyzer = new AsmAnalyzer(f, clsBType.internalName)
+
+ val integerClassBType = classBTypeFromInternalName("java/lang/Integer")
+ val lowestOneBitMethod = byteCodeRepository.methodNode(integerClassBType.internalName, "lowestOneBit", "(I)I").get._1
+
+ val r = inliner.inline(
+ callsiteIns,
+ analyzer.frameAt(callsiteIns).getStackSize,
+ f,
+ clsBType,
+ lowestOneBitMethod,
+ integerClassBType,
+ receiverKnownNotNull = false,
+ keepLineNumbers = false)
+
+ assert(r.isEmpty, r)
+ val ins = instructionsFromMethod(f)
+
+ // no invocations, lowestOneBit is inlined
+ assertNoInvoke(ins)
+
+ // no null check when inlining a static method
+ ins foreach {
+ case Jump(IFNONNULL, _) => assert(false, ins.stringLines)
+ case _ =>
+ }
+ }
+
+ @Test
+ def maxLocalsMaxStackAfterInline(): Unit = {
+ val code =
+ """class C {
+ | @inline final def f1(x: Int): Int = {
+ | val a = x + 1
+ | math.max(a, math.min(10, a - 1))
+ | }
+ |
+ | @inline final def f2(x: Int): Unit = {
+ | val a = x + 1
+ | println(math.max(a, 10))
+ | }
+ |
+ | def g1 = println(f1(32))
+ | def g2 = println(f2(32))
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ val ms @ List(f1, f2, g1, g2) = c.methods.asScala.filter(_.name.length == 2).toList
+
+ // stack height at callsite of f1 is 1, so max of g1 after inlining is max of f1 + 1
+ assert(g1.maxStack == 7 && f1.maxStack == 6, s"${g1.maxStack} - ${f1.maxStack}")
+
+ // locals in f1: this, x, a
+ // locals in g1 after inlining: this, this-of-f1, x, a, return value
+ assert(g1.maxLocals == 5 && f1.maxLocals == 3, s"${g1.maxLocals} - ${f1.maxLocals}")
+
+ // like maxStack in g1 / f1
+ assert(g2.maxStack == 5 && f2.maxStack == 4, s"${g2.maxStack} - ${f2.maxStack}")
+
+ // like maxLocals for g1 / f1, but no return value
+ assert(g2.maxLocals == 4 && f2.maxLocals == 3, s"${g2.maxLocals} - ${f2.maxLocals}")
+ }
+
+ @Test
+ def mixedCompilationNoInline(): Unit = {
+ // The inliner checks if the invocation `A.bar` can be safely inlined. For that it needs to have
+ // the bytecode of the invoked method. In mixed compilation, there's no classfile available for
+ // A, so `flop` cannot be inlined, we cannot check if it's safe.
+
+ val javaCode =
+ """public class A {
+ | public static final int bar() { return 100; }
+ |}
+ """.stripMargin
+
+ val scalaCode =
+ """class B {
+ | @inline final def flop = A.bar
+ | def g = flop
+ |}
+ """.stripMargin
+
+ val warn =
+ """B::flop()I is annotated @inline but could not be inlined:
+ |Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed:
+ |The method bar()I could not be found in the class A or any of its parents.
+ |Note that the following parent classes are defined in Java sources (mixed compilation), no bytecode is available: A""".stripMargin
+
+ var c = 0
+ val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; i.msg contains warn})
+ assert(c == 1, c)
+ val ins = getSingleMethod(b, "g").instructions
+ val invokeFlop = Invoke(INVOKEVIRTUAL, "B", "flop", "()I", false)
+ assert(ins contains invokeFlop, ins.stringLines)
+ }
+
+ @Test
+ def inlineFromTraits(): Unit = {
+ val code =
+ """trait T {
+ | @inline final def f = g
+ | @inline final def g = 1
+ |}
+ |
+ |class C extends T {
+ | def t1(t: T) = t.f
+ | def t2(c: C) = c.f
+ |}
+ """.stripMargin
+ val List(c, t, tClass) = compile(code)
+ // both are just `return 1`, no more calls
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ }
+
+ @Test
+ def inlineMixinMethods(): Unit = {
+ val code =
+ """trait T {
+ | @inline final def f = 1
+ |}
+ |class C extends T
+ """.stripMargin
+ val List(c, t, tClass) = compile(code)
+ // the static implementaiton method is inlined into the mixin, so there's no invocation in the mixin
+ assertNoInvoke(getSingleMethod(c, "f"))
+ }
+
+ @Test
+ def inlineTraitInherited(): Unit = {
+ val code =
+ """trait T {
+ | @inline final def f = 1
+ |}
+ |trait U extends T {
+ | @inline final def g = f
+ |}
+ |class C extends U {
+ | def t1 = f
+ | def t2 = g
+ |}
+ """.stripMargin
+ val List(c, t, tClass, u, uClass) = compile(code)
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ }
+
+ @Test
+ def virtualTraitNoInline(): Unit = {
+ val code =
+ """trait T {
+ | @inline def f = 1
+ |}
+ |class C extends T {
+ | def t1(t: T) = t.f
+ | def t2 = this.f
+ |}
+ """.stripMargin
+ val warns = Set(
+ "C::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
+ "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden")
+ var count = 0
+ val List(c, t, tClass) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
+ assert(count == 2, count)
+ assertInvoke(getSingleMethod(c, "t1"), "T", "f")
+ assertInvoke(getSingleMethod(c, "t2"), "C", "f")
+ }
+
+ @Test
+ def sealedTraitInline(): Unit = {
+ val code =
+ """sealed trait T {
+ | @inline def f = 1
+ |}
+ |class C {
+ | def t1(t: T) = t.f
+ |}
+ """.stripMargin
+ val List(c, t, tClass) = compile(code)
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ }
+
+ @Test
+ def inlineFromObject(): Unit = {
+ val code =
+ """trait T {
+ | @inline def f = 0
+ |}
+ |object O extends T {
+ | @inline def g = 1
+ | // mixin generates `def f = T$class.f(this)`, which is inlined here (we get ICONST_0)
+ |}
+ |class C {
+ | def t1 = O.f // the mixin method of O is inlined, so we directly get the ICONST_0
+ | def t2 = O.g // object members are inlined
+ | def t3(t: T) = t.f // no inlining here
+ |}
+ """.stripMargin
+ val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
+ var count = 0
+ val List(c, oMirror, oModule, t, tClass) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
+ assert(count == 1, count)
+
+ assertNoInvoke(getSingleMethod(oModule, "f"))
+
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ assertInvoke(getSingleMethod(c, "t3"), "T", "f")
+ }
+
+ @Test
+ def selfTypeInline(): Unit = {
+ val code =
+ """trait T { self: Assembly =>
+ | @inline final def f = g
+ | @inline final def m = 1
+ |}
+ |trait Assembly extends T {
+ | @inline final def g = 1
+ | @inline final def n = m // inlined. (*)
+ | // (*) the declaration class of m is T. the signature of T$class.m is m(LAssembly;)I. so we need the self type to build the
+ | // signature. then we can look up the MethodNode of T$class.m and then rewrite the INVOKEINTERFACE to INVOKESTATIC.
+ |}
+ |class C {
+ | def t1(a: Assembly) = a.f // like above, decl class is T, need self-type of T to rewrite the interface call to static.
+ | def t2(a: Assembly) = a.n
+ |}
+ """.stripMargin
+
+ val List(assembly, assemblyClass, c, t, tClass) = compile(code)
+
+ assertNoInvoke(getSingleMethod(tClass, "f"))
+
+ assertNoInvoke(getSingleMethod(assemblyClass, "n"))
+
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ }
+
+ @Test
+ def selfTypeInline2(): Unit = {
+ // There are some interesting things going on here with the self types. Here's a short version:
+ //
+ // trait T1 { def f = 1 }
+ // trait T2a { self: T1 with T2a => // self type in the backend: T1
+ // def f = 2
+ // def g = f // resolved to T2a.f
+ // }
+ // trait T2b { self: T2b with T1 => // self type in the backend: T2b
+ // def f = 2
+ // def g = f // resolved to T1.f
+ // }
+ //
+ // scala> val t = typeOf[T2a]; exitingMixin(t.typeOfThis.typeSymbol) // self type of T2a is T1
+ // res28: $r.intp.global.Symbol = trait T1
+ //
+ // scala> typeOf[T2a].typeOfThis.member(newTermName("f")).owner // f in T2a is resolved as T2a.f
+ // res29: $r.intp.global.Symbol = trait T2a
+ //
+ // scala> val t = typeOf[T2b]; exitingMixin(t.typeOfThis.typeSymbol) // self type of T2b is T1
+ // res30: $r.intp.global.Symbol = trait T2b
+ //
+ // scala> typeOf[T2b].typeOfThis.member(newTermName("f")).owner // f in T2b is resolved as T1.f
+ // res31: $r.intp.global.Symbol = trait T1
+
+ val code =
+ """trait T1 {
+ | @inline def f: Int = 0
+ | @inline def g1 = f // not inlined: f not final, so T1$class.g1 has an interface call T1.f
+ |}
+ |
+ |// erased self-type (used in impl class for `self` parameter): T1
+ |trait T2a { self: T1 with T2a =>
+ | @inline override final def f = 1
+ | @inline def g2a = f // inlined: resolved as T2a.f, which is re-written to T2a$class.f, so T2a$class.g2a has ICONST_1
+ |}
+ |
+ |final class Ca extends T1 with T2a {
+ | // mixin generates accessors like `def g1 = T1$class.g1`, the impl class method call is inlined into the accessor.
+ |
+ | def m1a = g1 // call to accessor, inlined, we get the interface call T1.f
+ | def m2a = g2a // call to accessor, inlined, we get ICONST_1
+ | def m3a = f // call to accessor, inlined, we get ICONST_1
+ |
+ | def m4a(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
+ | def m5a(t: T2a) = t.f // re-written to T2a$class.f, inlined, ICONST_1
+ |}
+ |
+ |// erased self-type: T2b
+ |trait T2b { self: T2b with T1 =>
+ | @inline override final def f = 1
+ | @inline def g2b = f // not inlined: resolved as T1.f, so T2b$class.g2b has an interface call T1.f
+ |}
+ |
+ |final class Cb extends T1 with T2b {
+ | def m1b = g1 // inlined, we get the interface call to T1.f
+ | def m2b = g2b // inlined, we get the interface call to T1.f
+ | def m3b = f // inlined, we get ICONST_1
+ |
+ | def m4b(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
+ | def m5b(t: T2b) = t.f // re-written to T2b$class.f, inlined, ICONST_1
+ |}
+ """.stripMargin
+
+ val warning = "T1::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
+ var count = 0
+ val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code, allowMessage = i => {count += 1; i.msg contains warning})
+ assert(count == 4, count) // see comments, f is not inlined 4 times
+
+ val t2aCfDesc = t2aC.methods.asScala.find(_.name == "f").get.desc
+ assert(t2aCfDesc == "(LT1;)I", t2aCfDesc) // self-type of T2a is T1
+
+ val t2bCfDesc = t2bC.methods.asScala.find(_.name == "f").get.desc
+ assert(t2bCfDesc == "(LT2b;)I", t2bCfDesc) // self-type of T2b is T2b
+
+ assertNoInvoke(getSingleMethod(t2aC, "g2a"))
+ assertInvoke(getSingleMethod(t2bC, "g2b"), "T1", "f")
+
+ assertInvoke(getSingleMethod(ca, "m1a"), "T1", "f")
+ assertNoInvoke(getSingleMethod(ca, "m2a")) // no invoke, see comment on def g2a
+ assertNoInvoke(getSingleMethod(ca, "m3a"))
+ assertInvoke(getSingleMethod(ca, "m4a"), "T1", "f")
+ assertNoInvoke(getSingleMethod(ca, "m5a"))
+
+ assertInvoke(getSingleMethod(cb, "m1b"), "T1", "f")
+ assertInvoke(getSingleMethod(cb, "m2b"), "T1", "f") // invoke, see comment on def g2b
+ assertNoInvoke(getSingleMethod(cb, "m3b"))
+ assertInvoke(getSingleMethod(cb, "m4b"), "T1", "f")
+ assertNoInvoke(getSingleMethod(cb, "m5b"))
+ }
+
+ @Test
+ def finalSubclassInline(): Unit = {
+ val code =
+ """class C {
+ | @inline def f = 0
+ | @inline final def g = 1
+ |}
+ |final class D extends C
+ |object E extends C
+ |class T {
+ | def t1(d: D) = d.f + d.g + E.f + E.g // d.f can be inlined because the reciever type is D, which is final.
+ |} // so d.f can be resolved statically. same for E.f
+ """.stripMargin
+ val List(c, d, e, eModule, t) = compile(code)
+ assertNoInvoke(getSingleMethod(t, "t1"))
+ }
+
+ @Test
+ def inlineFromNestedClasses(): Unit = {
+ val code =
+ """class C {
+ | trait T { @inline final def f = 1 }
+ | class D extends T{
+ | def m(t: T) = t.f
+ | }
+ |
+ | def m(d: D) = d.f
+ |}
+ """.stripMargin
+ val List(c, d, t, tC) = compile(code)
+ assertNoInvoke(getSingleMethod(d, "m"))
+ assertNoInvoke(getSingleMethod(c, "m"))
+ }
+
+ @Test
+ def inlineTraitCastReceiverToSelf(): Unit = {
+ val code =
+ """class C { def foo(x: Int) = x }
+ |trait T { self: C =>
+ | @inline final def f(x: Int) = foo(x)
+ | def t1 = f(1)
+ | def t2(t: T) = t.f(2)
+ |}
+ """.stripMargin
+ val List(c, t, tc) = compile(code)
+ val t1 = getSingleMethod(tc, "t1")
+ val t2 = getSingleMethod(tc, "t2")
+ val cast = TypeOp(CHECKCAST, "C")
+ Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions))
+ }
+
+ @Test
+ def abstractMethodWarning(): Unit = {
+ val code =
+ """abstract class C {
+ | @inline def foo: Int
+ |}
+ |class T {
+ | def t1(c: C) = c.foo
+ |}
+ """.stripMargin
+ val warn = "C::foo()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
+ var c = 0
+ compile(code, allowMessage = i => {c += 1; i.msg contains warn})
+ assert(c == 1, c)
+ }
+
+ @Test
+ def abstractFinalMethodError(): Unit = {
+ val code =
+ """abstract class C {
+ | @inline final def foo: Int
+ |}
+ |trait T {
+ | @inline final def bar: Int
+ |}
+ """.stripMargin
+ val err = "abstract member may not have final modifier"
+ var i = 0
+ compile(code, allowMessage = info => {i += 1; info.msg contains err})
+ assert(i == 2, i)
+ }
+
+ @Test
+ def noInlineTraitFieldAccessors(): Unit = {
+ val code =
+ """sealed trait T {
+ | lazy val a = 0
+ | val b = 1
+ | final lazy val c = 2
+ | final val d = 3
+ | final val d1: Int = 3
+ |
+ | @noinline def f = 5 // re-written to T$class
+ | @noinline final def g = 6 // re-written
+ |
+ | @noinline def h: Int
+ | @inline def i: Int
+ |}
+ |
+ |trait U { // not sealed
+ | lazy val a = 0
+ | val b = 1
+ | final lazy val c = 2
+ | final val d = 3
+ | final val d1: Int = 3
+ |
+ | @noinline def f = 5 // not re-written (not final)
+ | @noinline final def g = 6 // re-written
+ |
+ | @noinline def h: Int
+ | @inline def i: Int
+ |}
+ |
+ |class C {
+ | def m1(t: T) = t.a + t.b + t.c + t.d1
+ | def m2(t: T) = t.d // inlined by the type-checker's constant folding
+ | def m3(t: T) = t.f + t.g + t.h + t.i
+ |
+ | def m4(u: U) = u.a + u.b + u.c + u.d1
+ | def m5(u: U) = u.d
+ | def m6(u: U) = u.f + u.g + u.h + u.i
+ |}
+ """.stripMargin
+
+ val List(c, t, tClass, u, uClass) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined")
+ val m1 = getSingleMethod(c, "m1")
+ assertInvoke(m1, "T", "a")
+ assertInvoke(m1, "T", "b")
+ assertInvoke(m1, "T", "c")
+
+ assertNoInvoke(getSingleMethod(c, "m2"))
+
+ val m3 = getSingleMethod(c, "m3")
+ assertInvoke(m3, "T$class", "f")
+ assertInvoke(m3, "T$class", "g")
+ assertInvoke(m3, "T", "h")
+ assertInvoke(m3, "T", "i")
+
+ val m4 = getSingleMethod(c, "m4")
+ assertInvoke(m4, "U", "a")
+ assertInvoke(m4, "U", "b")
+ assertInvoke(m4, "U", "c")
+
+ assertNoInvoke(getSingleMethod(c, "m5"))
+
+ val m6 = getSingleMethod(c, "m6")
+ assertInvoke(m6, "U", "f")
+ assertInvoke(m6, "U$class", "g")
+ assertInvoke(m6, "U", "h")
+ assertInvoke(m6, "U", "i")
+ }
+
+ @Test
+ def mixedNoCrashSI9111(): Unit = {
+ val javaCode =
+ """public final class A {
+ | public static final class T { }
+ | public static final class Inner {
+ | public static final class T { }
+ | public T newT() { return null; }
+ | }
+ |}
+ """.stripMargin
+
+ val scalaCode =
+ """class C {
+ | val i = new A.Inner()
+ |}
+ """.stripMargin
+
+ // We don't get to see the warning about SI-9111, because it is associated with the MethodInlineInfo
+ // of method newT, which is not actually used.
+ // The problem is: if we reference `newT` in the scalaCode, the scala code does not compile,
+ // because then SI-9111 triggers during type-checking class C, in the compiler frontend, and
+ // we don't even get to the backend.
+ // Nevertheless, the workaround for SI-9111 in BcodeAsmCommon.buildInlineInfoFromClassSymbol
+ // is still necessary, otherwise this test crashes.
+ // The warning below is the typical warning we get in mixed compilation.
+ val warn =
+ """failed to determine if <init> should be inlined:
+ |The method <init>()V could not be found in the class A$Inner or any of its parents.
+ |Note that the following parent classes could not be found on the classpath: A$Inner""".stripMargin
+
+ var c = 0
+
+ compileClasses(newCompiler(extraArgs = InlinerTest.args + " -Yopt-warnings:_"))(
+ scalaCode,
+ List((javaCode, "A.java")),
+ allowMessage = i => {c += 1; i.msg contains warn})
+ assert(c == 1, c)
+ }
+
+ @Test
+ def inlineInvokeSpecial(): Unit = {
+ val code =
+ """class Aa {
+ | def f1 = 0
+ |}
+ |class B extends Aa {
+ | @inline final override def f1 = 1 + super.f1 // invokespecial Aa.f1
+ |
+ | private def f2m = 0 // public B$$f2m in bytecode
+ | @inline final def f2 = f2m // invokevirtual B.B$$f2m
+ |
+ | private def this(x: Int) = this() // public in bytecode
+ | @inline final def f3 = new B() // invokespecial B.<init>()
+ | @inline final def f4 = new B(1) // invokespecial B.<init>(I)
+ |
+ | def t1 = f1 // inlined
+ | def t2 = f2 // inlined
+ | def t3 = f3 // inlined
+ | def t4 = f4 // inlined
+ |}
+ |class T {
+ | def t1(b: B) = b.f1 // cannot inline: contains a super call
+ | def t2(b: B) = b.f2 // inlined
+ | def t3(b: B) = b.f3 // inlined
+ | def t4(b: B) = b.f4 // inlined
+ |}
+ """.stripMargin
+
+ val warn =
+ """B::f1()I is annotated @inline but could not be inlined:
+ |The callee B::f1()I contains the instruction INVOKESPECIAL Aa.f1 ()I
+ |that would cause an IllegalAccessError when inlined into class T.""".stripMargin
+ var c = 0
+ val List(a, b, t) = compile(code, allowMessage = i => {c += 1; i.msg contains warn})
+ assert(c == 1, c)
+
+ assertInvoke(getSingleMethod(b, "t1"), "Aa", "f1")
+ assertInvoke(getSingleMethod(b, "t2"), "B", "B$$f2m")
+ assertInvoke(getSingleMethod(b, "t3"), "B", "<init>")
+ assertInvoke(getSingleMethod(b, "t4"), "B", "<init>")
+
+ assertInvoke(getSingleMethod(t, "t1"), "B", "f1")
+ assertInvoke(getSingleMethod(t, "t2"), "B", "B$$f2m")
+ assertInvoke(getSingleMethod(t, "t3"), "B", "<init>")
+ assertInvoke(getSingleMethod(t, "t4"), "B", "<init>")
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
index 5430e33d6c..1ce1b88ff2 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
@@ -13,17 +13,26 @@ import scala.tools.testing.AssertUtil._
import CodeGenTools._
import scala.tools.partest.ASMConverters
import ASMConverters._
+import scala.tools.testing.ClearAfterClass
+
+object MethodLevelOpts extends ClearAfterClass.Clearable {
+ var methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
+ def clear(): Unit = { methodOptCompiler = null }
+}
@RunWith(classOf[JUnit4])
-class MethodLevelOpts {
- val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
+class MethodLevelOpts extends ClearAfterClass {
+ ClearAfterClass.stateToClear = MethodLevelOpts
+
+ val methodOptCompiler = MethodLevelOpts.methodOptCompiler
def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
@Test
def eliminateEmptyTry(): Unit = {
val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }"
- assertSameCode(singleMethodInstructions(methodOptCompiler)(code), wrapInDefault(Op(ICONST_1), Op(IRETURN)))
+ val warn = "a pure expression does nothing in statement position"
+ assertSameCode(singleMethodInstructions(methodOptCompiler)(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN)))
}
@Test
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
new file mode 100644
index 0000000000..f8e887426b
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
@@ -0,0 +1,85 @@
+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.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import CodeGenTools._
+import scala.tools.nsc.backend.jvm.BTypes.{MethodInlineInfo, InlineInfo}
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import scala.collection.convert.decorateAsScala._
+
+object ScalaInlineInfoTest {
+ var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
+ def clear(): Unit = { compiler = null }
+}
+
+@RunWith(classOf[JUnit4])
+class ScalaInlineInfoTest {
+ val compiler = newCompiler()
+
+ @Test
+ def traitMembersInlineInfo(): Unit = {
+ val code =
+ """trait T {
+ | def f1 = 1 // concrete method
+ | private def f2 = 1 // implOnly method (does not end up in the interface)
+ | def f3 = {
+ | def nest = 0 // nested method (does not end up in the interface)
+ | nest
+ | }
+ |
+ | @inline
+ | def f4 = super.toString // super accessor
+ |
+ | object O // module accessor (method is generated)
+ | def f5 = {
+ | object L { val x = 0 } // nested module (just flattened out)
+ | L.x
+ | }
+ |
+ | @noinline
+ | def f6: Int // abstract method (not in impl class)
+ |
+ | // fields
+ |
+ | val x1 = 0
+ | var y2 = 0
+ | var x3: Int
+ | lazy val x4 = 0
+ |
+ | final val x5 = 0
+ |}
+ """.stripMargin
+
+ val cs @ List(t, tl, to, tCls) = compileClasses(compiler)(code)
+ val List(info) = t.attrs.asScala.collect({ case a: InlineInfoAttribute => a.inlineInfo }).toList
+ val expect = InlineInfo(
+ None, // self type
+ false, // final class
+ Map(
+ ("O()LT$O$;", MethodInlineInfo(true, false,false,false)),
+ ("T$$super$toString()Ljava/lang/String;",MethodInlineInfo(false,false,false,false)),
+ ("T$_setter_$x1_$eq(I)V", MethodInlineInfo(false,false,false,false)),
+ ("f1()I", MethodInlineInfo(false,true, false,false)),
+ ("f3()I", MethodInlineInfo(false,true, false,false)),
+ ("f4()Ljava/lang/String;", MethodInlineInfo(false,true, true, false)),
+ ("f5()I", MethodInlineInfo(false,true, false,false)),
+ ("f6()I", MethodInlineInfo(false,false,false,true )),
+ ("x1()I", MethodInlineInfo(false,false,false,false)),
+ ("x3()I", MethodInlineInfo(false,false,false,false)),
+ ("x3_$eq(I)V", MethodInlineInfo(false,false,false,false)),
+ ("x4()I", MethodInlineInfo(false,false,false,false)),
+ ("x5()I", MethodInlineInfo(true, false,false,false)),
+ ("y2()I", MethodInlineInfo(false,false,false,false)),
+ ("y2_$eq(I)V", MethodInlineInfo(false,false,false,false))),
+ None // warning
+ )
+ assert(info == expect, info)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
index 4a45dd9138..da9853148b 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
@@ -13,9 +13,34 @@ import scala.tools.testing.AssertUtil._
import CodeGenTools._
import scala.tools.partest.ASMConverters
import ASMConverters._
+import scala.tools.testing.ClearAfterClass
+
+object UnreachableCodeTest extends ClearAfterClass.Clearable {
+ // jvm-1.6 enables emitting stack map frames, which impacts the code generation wrt dead basic blocks,
+ // see comment in BCodeBodyBuilder
+ var methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
+ var dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
+ var noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none")
+
+ // jvm-1.5 disables computing stack map frames, and it emits dead code as-is. note that this flag triggers a deprecation warning
+ var noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none -deprecation")
+
+ def clear(): Unit = {
+ methodOptCompiler = null
+ dceCompiler = null
+ noOptCompiler = null
+ noOptNoFramesCompiler = null
+ }
+}
@RunWith(classOf[JUnit4])
-class UnreachableCodeTest {
+class UnreachableCodeTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = UnreachableCodeTest
+
+ val methodOptCompiler = UnreachableCodeTest.methodOptCompiler
+ val dceCompiler = UnreachableCodeTest.dceCompiler
+ val noOptCompiler = UnreachableCodeTest.noOptCompiler
+ val noOptNoFramesCompiler = UnreachableCodeTest.noOptNoFramesCompiler
def assertEliminateDead(code: (Instruction, Boolean)*): Unit = {
val method = genMethod()(code.map(_._1): _*)
@@ -25,15 +50,6 @@ class UnreachableCodeTest {
assertSameCode(nonEliminated, expectedLive)
}
- // jvm-1.6 enables emitting stack map frames, which impacts the code generation wrt dead basic blocks,
- // see comment in BCodeBodyBuilder
- val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:method")
- val dceCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
- val noOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:l:none")
-
- // jvm-1.5 disables computing stack map frames, and it emits dead code as-is.
- val noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -Ybackend:GenBCode -Yopt:l:none")
-
@Test
def basicElimination(): Unit = {
assertEliminateDead(
@@ -138,7 +154,8 @@ class UnreachableCodeTest {
assertSameCode(noDce.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ATHROW), Op(ATHROW)))
// when NOT computing stack map frames, ASM's ClassWriter does not replace dead code by NOP/ATHROW
- val noDceNoFrames = singleMethodInstructions(noOptNoFramesCompiler)(code)
+ val warn = "target:jvm-1.5 is deprecated"
+ val noDceNoFrames = singleMethodInstructions(noOptNoFramesCompiler)(code, allowMessage = _.msg contains warn)
assertSameCode(noDceNoFrames.dropNonOp, List(Op(ICONST_1), Op(IRETURN), Op(ICONST_2), Op(IRETURN)))
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala
index 24a1f9d1c1..769736669b 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala
@@ -12,10 +12,18 @@ import scala.collection.JavaConverters._
import CodeGenTools._
import scala.tools.partest.ASMConverters
import ASMConverters._
+import scala.tools.testing.ClearAfterClass
+
+object UnusedLocalVariablesTest extends ClearAfterClass.Clearable {
+ var dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code")
+ def clear(): Unit = { dceCompiler = null }
+}
@RunWith(classOf[JUnit4])
-class UnusedLocalVariablesTest {
- val dceCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:unreachable-code")
+class UnusedLocalVariablesTest extends ClearAfterClass {
+ ClearAfterClass.stateToClear = UnusedLocalVariablesTest
+
+ val dceCompiler = UnusedLocalVariablesTest.dceCompiler
@Test
def removeUnusedVar(): Unit = {