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.scala40
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/BytecodeTest.scala198
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala178
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala35
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala64
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala63
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala146
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala362
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala123
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala174
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala91
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala50
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala28
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala226
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala86
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala22
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala37
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala12
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala63
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala168
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala60
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala44
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala1278
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala92
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala773
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala185
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala33
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala108
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala41
29 files changed, 3642 insertions, 1138 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
index 6ada0e20fb..58df4691e4 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
@@ -1,37 +1,29 @@
package scala.tools.nsc
package backend.jvm
+import org.junit.Assert._
+import org.junit.Test
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.nsc.backend.jvm.CodeGenTools._
-import scala.tools.testing.ClearAfterClass
-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 }
-}
+import scala.tools.asm.Opcodes
+import scala.tools.testing.BytecodeTesting
@RunWith(classOf[JUnit4])
-class BTypesTest extends ClearAfterClass {
- ClearAfterClass.stateToClear = BTypesTest
-
- val compiler = BTypesTest.compiler
- import compiler.genBCode.bTypes._
+class BTypesTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:none"
+ import compiler.global
+ locally {
+ new global.Run() // initializes some of the compiler
+ global.exitingDelambdafy(global.scalaPrimitives.init()) // needed: it's only done when running the backend, and we don't actually run the compiler
+ global.exitingDelambdafy(global.genBCode.bTypes.initializeCoreBTypes())
+ }
+ import global.genBCode.bTypes._
- def classBTFS(sym: compiler.Symbol) = compiler.exitingDelambdafy(classBTypeFromSymbol(sym))
+ def classBTFS(sym: global.Symbol) = global.exitingDelambdafy(classBTypeFromSymbol(sym))
- def jlo = compiler.definitions.ObjectClass
- def jls = compiler.definitions.StringClass
+ def jlo = global.definitions.ObjectClass
+ def jls = global.definitions.StringClass
def o = classBTFS(jlo)
def s = classBTFS(jls)
def oArr = ArrayBType(o)
diff --git a/test/junit/scala/tools/nsc/backend/jvm/BytecodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BytecodeTest.scala
new file mode 100644
index 0000000000..00b6d1cc42
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/BytecodeTest.scala
@@ -0,0 +1,198 @@
+package scala.tools.nsc.backend.jvm
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.tools.asm.Opcodes._
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
+
+@RunWith(classOf[JUnit4])
+class BytecodeTest extends BytecodeTesting {
+ import compiler._
+
+ @Test
+ def t6288bJumpPosition(): Unit = {
+ val code =
+ """object Case3 { // 01
+ | def unapply(z: Any): Option[Int] = Some(-1) // 02
+ | def main(args: Array[String]) { // 03
+ | ("": Any) match { // 04
+ | case x : String => // 05
+ | println("case 0") // 06 println and jump at 6
+ | case _ => // 07
+ | println("default") // 08 println and jump at 8
+ | } // 09
+ | println("done") // 10
+ | }
+ |}
+ """.stripMargin
+ val List(mirror, module) = compileClasses(code)
+
+ val unapplyLineNumbers = getInstructions(module, "unapply").filter(_.isInstanceOf[LineNumber])
+ assert(unapplyLineNumbers == List(LineNumber(2, Label(0))), unapplyLineNumbers)
+
+ val expected = List(
+ LineNumber(4, Label(0)),
+ LineNumber(5, Label(5)),
+ Jump(IFEQ, Label(20)),
+
+ LineNumber(6, Label(11)),
+ Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
+ Jump(GOTO, Label(33)),
+
+ LineNumber(5, Label(20)),
+ Jump(GOTO, Label(24)),
+
+ LineNumber(8, Label(24)),
+ Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
+ Jump(GOTO, Label(33)),
+
+ LineNumber(10, Label(33)),
+ Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false)
+ )
+
+ val mainIns = getInstructions(module, "main") filter {
+ case _: LineNumber | _: Invoke | _: Jump => true
+ case _ => false
+ }
+ assertSameCode(mainIns, expected)
+ }
+
+ @Test
+ def bytecodeForBranches(): Unit = {
+ val code =
+ """class C {
+ | def t1(b: Boolean) = if (b) 1 else 2
+ | def t2(x: Int) = if (x == 393) 1 else 2
+ | def t3(a: Array[String], b: AnyRef) = a != b && b == a
+ | def t4(a: AnyRef) = a == null || null != a
+ | def t5(a: AnyRef) = (a eq null) || (null ne a)
+ | def t6(a: Int, b: Boolean) = if ((a == 10) && b || a != 1) 1 else 2
+ | def t7(a: AnyRef, b: AnyRef) = a == b
+ | def t8(a: AnyRef) = Nil == a || "" != a
+ |}
+ """.stripMargin
+
+ val c = compileClass(code)
+
+ // t1: no unnecessary GOTOs
+ assertSameCode(getMethod(c, "t1"), List(
+ VarOp(ILOAD, 1), Jump(IFEQ, Label(6)),
+ Op(ICONST_1), Jump(GOTO, Label(9)),
+ Label(6), Op(ICONST_2),
+ Label(9), Op(IRETURN)))
+
+ // t2: no unnecessary GOTOs
+ assertSameCode(getMethod(c, "t2"), List(
+ VarOp(ILOAD, 1), IntOp(SIPUSH, 393), Jump(IF_ICMPNE, Label(7)),
+ Op(ICONST_1), Jump(GOTO, Label(10)),
+ Label(7), Op(ICONST_2),
+ Label(10), Op(IRETURN)))
+
+ // t3: Array == is translated to reference equality, AnyRef == to null checks and equals
+ assertSameCode(getMethod(c, "t3"), List(
+ // Array ==
+ VarOp(ALOAD, 1), VarOp(ALOAD, 2), Jump(IF_ACMPEQ, Label(23)),
+ // AnyRef ==
+ VarOp(ALOAD, 2), VarOp(ALOAD, 1), VarOp(ASTORE, 3), Op(DUP), Jump(IFNONNULL, Label(14)),
+ Op(POP), VarOp(ALOAD, 3), Jump(IFNULL, Label(19)), Jump(GOTO, Label(23)),
+ Label(14), VarOp(ALOAD, 3), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFEQ, Label(23)),
+ Label(19), Op(ICONST_1), Jump(GOTO, Label(26)),
+ Label(23), Op(ICONST_0),
+ Label(26), Op(IRETURN)))
+
+ val t4t5 = List(
+ VarOp(ALOAD, 1), Jump(IFNULL, Label(6)),
+ VarOp(ALOAD, 1), Jump(IFNULL, Label(10)),
+ Label(6), Op(ICONST_1), Jump(GOTO, Label(13)),
+ Label(10), Op(ICONST_0),
+ Label(13), Op(IRETURN))
+
+ // t4: one side is known null, so just a null check on the other
+ assertSameCode(getMethod(c, "t4"), t4t5)
+
+ // t5: one side known null, so just a null check on the other
+ assertSameCode(getMethod(c, "t5"), t4t5)
+
+ // t6: no unnecessary GOTOs
+ assertSameCode(getMethod(c, "t6"), List(
+ VarOp(ILOAD, 1), IntOp(BIPUSH, 10), Jump(IF_ICMPNE, Label(7)),
+ VarOp(ILOAD, 2), Jump(IFNE, Label(12)),
+ Label(7), VarOp(ILOAD, 1), Op(ICONST_1), Jump(IF_ICMPEQ, Label(16)),
+ Label(12), Op(ICONST_1), Jump(GOTO, Label(19)),
+ Label(16), Op(ICONST_2),
+ Label(19), Op(IRETURN)))
+
+ // t7: universal equality
+ assertInvoke(getMethod(c, "t7"), "scala/runtime/BoxesRunTime", "equals")
+
+ // t8: no null checks invoking equals on modules and constants
+ assertSameCode(getMethod(c, "t8"), List(
+ Field(GETSTATIC, "scala/collection/immutable/Nil$", "MODULE$", "Lscala/collection/immutable/Nil$;"), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFNE, Label(10)),
+ Ldc(LDC, ""), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false), Jump(IFNE, Label(14)),
+ Label(10), Op(ICONST_1), Jump(GOTO, Label(17)),
+ Label(14), Op(ICONST_0),
+ Label(17), Op(IRETURN)))
+ }
+
+ @Test // wrong local variable table for methods containing while loops
+ def t9179(): Unit = {
+ val code =
+ """class C {
+ | def t(): Unit = {
+ | var x = ""
+ | while (x != null) {
+ | foo()
+ | x = null
+ | }
+ | bar()
+ | }
+ | def foo(): Unit = ()
+ | def bar(): Unit = ()
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ val t = getMethod(c, "t")
+ val isFrameLine = (x: Instruction) => x.isInstanceOf[FrameEntry] || x.isInstanceOf[LineNumber]
+ assertSameCode(t.instructions.filterNot(isFrameLine), List(
+ Label(0), Ldc(LDC, ""), Label(3), VarOp(ASTORE, 1),
+ Label(5), VarOp(ALOAD, 1), Jump(IFNULL, Label(21)),
+ Label(10), VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "foo", "()V", false), Label(14), Op(ACONST_NULL), VarOp(ASTORE, 1), Label(18), Jump(GOTO, Label(5)),
+ Label(21), VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "bar", "()V", false), Label(26), Op(RETURN), Label(28)))
+ val labels = t.instructions collect { case l: Label => l }
+ val x = t.localVars.find(_.name == "x").get
+ assertEquals(x.start, labels(1))
+ assertEquals(x.end, labels(7))
+ }
+
+ @Test
+ def sd186_traitLineNumber(): Unit = {
+ val code =
+ """trait T {
+ | def t(): Unit = {
+ | toString
+ | toString
+ | }
+ |}
+ """.stripMargin
+ val t = compileClass(code)
+ val tMethod = getMethod(t, "t$")
+ val invoke = Invoke(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false)
+ // ths static accessor is positioned at the line number of the accessed method.
+ assertSameCode(tMethod.instructions,
+ List(Label(0), LineNumber(2, Label(0)), VarOp(ALOAD, 0), Invoke(INVOKESPECIAL, "T", "t", "()V", true), Op(RETURN), Label(4))
+ )
+ }
+
+ @Test
+ def sd233(): Unit = {
+ val code = "def f = { println(1); synchronized(println(2)) }"
+ val m = compileMethod(code)
+ val List(ExceptionHandler(_, _, _, desc)) = m.handlers
+ assert(desc == None, desc)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
deleted file mode 100644
index ee9580c1c3..0000000000
--- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
+++ /dev/null
@@ -1,178 +0,0 @@
-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, ClassNode, MethodNode}
-import scala.tools.cmd.CommandLineParser
-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
-import AsmUtils._
-
-object CodeGenTools {
- import ASMConverters._
-
- def genMethod( flags: Int = Opcodes.ACC_PUBLIC,
- name: String = "m",
- descriptor: String = "()V",
- genericSignature: String = null,
- throwsExceptions: Array[String] = null,
- handlers: List[ExceptionHandler] = Nil,
- localVars: List[LocalVariable] = Nil)(body: Instruction*): MethodNode = {
- val node = new MethodNode(flags, name, descriptor, genericSignature, throwsExceptions)
- applyToMethod(node, Method(body.toList, handlers, localVars))
- node
- }
-
- def wrapInClass(method: MethodNode): ClassNode = {
- val cls = new ClassNode()
- cls.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "C", null, "java/lang/Object", null)
- cls.methods.add(method)
- cls
- }
-
- private def resetOutput(compiler: Global): Unit = {
- compiler.settings.outputDirs.setSingleOutput(new VirtualDirectory("(memory)", None))
- }
-
- 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)
- new Global(settings, new StoreReporter)
- }
-
- def newRun(compiler: Global): compiler.Run = {
- compiler.reporter.reset()
- resetOutput(compiler)
- 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, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
- readAsmClasses(compile(compiler)(code, javaCode, allowMessage))
- }
-
- 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, allowMessage: StoreReporter#Info => Boolean = _ => false): List[Instruction] = {
- val List(m) = compileMethods(compiler)(code, allowMessage = allowMessage)
- instructionsFromMethod(m)
- }
-
- def singleMethod(compiler: Global)(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): Method = {
- val List(m) = compileMethods(compiler)(code, allowMessage = allowMessage)
- convertMethod(m)
- }
-
- def assertSameCode(actual: List[Instruction], expected: List[Instruction]): Unit = {
- assertTrue(s"\nExpected: $expected\nActual : $actual", actual === expected)
- }
-
- def getSingleMethod(classNode: ClassNode, name: String): Method =
- convertMethod(classNode.methods.asScala.toList.find(_.name == name).get)
-
- /**
- * Instructions that match `query` when textified.
- * If `query` starts with a `+`, the next instruction is returned.
- */
- def findInstr(method: MethodNode, query: String): List[AbstractInsnNode] = {
- val useNext = query(0) == '+'
- val instrPart = if (useNext) query.drop(1) else query
- val insns = method.instructions.iterator.asScala.find(i => textify(i) contains instrPart).toList
- if (useNext) insns.map(_.getNext) else insns
- }
-
- def assertHandlerLabelPostions(h: ExceptionHandler, instructions: List[Instruction], startIndex: Int, endIndex: Int, handlerIndex: Int): Unit = {
- val insVec = instructions.toVector
- assertTrue(h.start == insVec(startIndex) && h.end == insVec(endIndex) && h.handler == insVec(handlerIndex))
- }
-
- import scala.language.implicitConversions
-
- implicit def aliveInstruction(ins: Instruction): (Instruction, Boolean) = (ins, true)
-
- implicit class MortalInstruction(val ins: Instruction) extends AnyVal {
- def dead: (Instruction, Boolean) = (ins, false)
- }
-}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
new file mode 100644
index 0000000000..841e850b49
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
@@ -0,0 +1,35 @@
+package scala.tools.nsc.backend.jvm
+
+import org.junit.Assert._
+import org.junit.Test
+
+import scala.collection.JavaConverters
+import scala.collection.JavaConverters._
+import scala.reflect.internal.Flags
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.ClassNode
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
+
+class DefaultMethodTest extends BytecodeTesting {
+ import compiler._
+ @Test
+ def defaultMethodsViaGenBCode(): Unit = {
+ import global._
+ val code = "package pack { trait T { def foo: Int }}"
+ object makeFooDefaultMethod extends Transformer {
+ val Foo = TermName("foo")
+ /** Transforms a single tree. */
+ override def transform(tree: global.Tree): global.Tree = tree match {
+ case dd @ DefDef(_, Foo, _, _, _, _) =>
+ dd.symbol.setFlag(Flags.JAVA_DEFAULTMETHOD).resetFlag(Flags.DEFERRED)
+ copyDefDef(dd)(rhs = Literal(Constant(1)).setType(definitions.IntTpe))
+ case _ => super.transform(tree)
+ }
+ }
+ val asmClasses: List[ClassNode] = compiler.compileClassesTransformed(code, Nil, makeFooDefaultMethod.transform(_))
+ val foo = asmClasses.head.methods.iterator.asScala.toList.last
+ assertTrue("default method should not be abstract", (foo.access & Opcodes.ACC_ABSTRACT) == 0)
+ assertTrue("default method body emitted", foo.instructions.size() > 0)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
index 240d3523f1..a28599cd92 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
@@ -1,28 +1,23 @@
package scala.tools.nsc.backend.jvm
+import org.junit.Assert._
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-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 }
-}
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
@RunWith(classOf[JUnit4])
-class DirectCompileTest extends ClearAfterClass {
- ClearAfterClass.stateToClear = DirectCompileTest
-
- val compiler = DirectCompileTest.compiler
+class DirectCompileTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:method"
+ import compiler._
@Test
def testCompile(): Unit = {
- val List(("C.class", bytes)) = compile(compiler)(
+ val List(("C.class", bytes)) = compileToBytes(
"""class C {
| def f = 1
|}
@@ -33,12 +28,12 @@ class DirectCompileTest extends ClearAfterClass {
@Test
def testCompileClasses(): Unit = {
- val List(cClass, cModuleClass) = compileClasses(compiler)("class C; object C")
+ val List(cClass, cModuleClass) = compileClasses("class C; object C")
assertTrue(cClass.name == "C")
assertTrue(cModuleClass.name == "C$")
- val List(dMirror, dModuleClass) = compileClasses(compiler)("object D")
+ val List(dMirror, dModuleClass) = compileClasses("object D")
assertTrue(dMirror.name == "D")
assertTrue(dModuleClass.name == "D$")
@@ -46,25 +41,23 @@ class DirectCompileTest extends ClearAfterClass {
@Test
def testCompileMethods(): Unit = {
- val List(f, g) = compileMethods(compiler)(
+ val List(f, g) = compileMethods(
"""def f = 10
|def g = f
""".stripMargin)
- assertTrue(f.name == "f")
- assertTrue(g.name == "g")
- assertSameCode(instructionsFromMethod(f).dropNonOp,
+ assertSameCode(f.instructions.dropNonOp,
List(IntOp(BIPUSH, 10), Op(IRETURN)))
- assertSameCode(instructionsFromMethod(g).dropNonOp,
+ assertSameCode(g.instructions.dropNonOp,
List(VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "f", "()I", itf = false), Op(IRETURN)))
}
@Test
def testDropNonOpAliveLabels(): Unit = {
// makes sure that dropNoOp doesn't drop labels that are being used
- val List(f) = compileMethods(compiler)("""def f(x: Int) = if (x == 0) "a" else "b"""")
- assertSameCode(instructionsFromMethod(f).dropLinesFrames, List(
+ val is = compileInstructions("""def f(x: Int) = if (x == 0) "a" else "b"""")
+ assertSameCode(is.dropLinesFrames, List(
Label(0),
VarOp(ILOAD, 1),
Op(ICONST_0),
@@ -84,7 +77,7 @@ class DirectCompileTest extends ClearAfterClass {
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
+ val ins = getInstructions(b, "g")
assert(ins exists {
case Invoke(_, "B", "f", _, _) => true
case _ => false
@@ -93,6 +86,29 @@ class DirectCompileTest extends ClearAfterClass {
@Test
def compileErroneous(): Unit = {
- compileClasses(compiler)("class C { def f: String = 1 }", allowMessage = _.msg contains "type mismatch")
+ compileToBytes("class C { def f: String = 1 }", allowMessage = _.msg contains "type mismatch")
+ }
+
+ @Test
+ def residentRedefineFinalFlag(): Unit = {
+ val compiler = newCompiler()
+ val a = "final class C { def c1 = 0 }"
+ // for re-defined class symbols (C), the compiler did not clear the `final` flag.
+ // so compiling `D` would give an error `illegal inheritance from final class C`.
+ val b = "class C; class D extends C"
+ compiler.compileToBytes(a)
+ compiler.compileToBytes(b)
+ }
+
+ @Test
+ def residentMultipleRunsNotCompanions(): Unit = {
+ val compiler = newCompiler()
+ val a = List(("public class A { }", "A.java"))
+ // when checking that a class and its companion are defined in the same compilation unit, the
+ // compiler would also emit a warning if the two symbols are defined in separate runs. this
+ // would lead to an error message when compiling the scala class A.
+ val b = "class A"
+ compiler.compileToBytes("", a)
+ compiler.compileToBytes(b)
}
}
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..ac2aab01dc
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala
@@ -0,0 +1,63 @@
+package scala.tools.nsc.backend.jvm
+
+import org.junit.Assert._
+import org.junit.Test
+
+import scala.collection.JavaConverters._
+import scala.tools.asm.Handle
+import scala.tools.asm.tree.InvokeDynamicInsnNode
+import scala.tools.testing.BytecodeTesting
+
+class IndyLambdaTest extends BytecodeTesting {
+ import compiler._
+
+ @Test def boxingBridgeMethodUsedSelectively(): Unit = {
+ def implMethodDescriptorFor(code: String): String = {
+ val method = compileAsmMethods(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
+ }
+
+ val obj = "Ljava/lang/Object;"
+ val str = "Ljava/lang/String;"
+
+ // 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(s"($obj)$obj", implMethodDescriptorFor("(x: Int) => new Object"))
+ assertEquals(s"($obj)$obj", 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(s"($str)$str", implMethodDescriptorFor("(x: String) => x"))
+
+ // 2b. Testing 2a. in combination with 1.
+ assertEquals(s"($obj)$str", implMethodDescriptorFor("(x: Int) => \"\""))
+ assertEquals(s"($str)$obj", 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"))
+
+ // non-builtin sams are like specialized functions
+ compileToBytes("class VC(private val i: Int) extends AnyVal; trait FunVC { def apply(a: VC): VC }")
+ assertEquals("(I)I", implMethodDescriptorFor("((x: VC) => x): FunVC"))
+
+ compileToBytes("trait Fun1[T, U] { def apply(a: T): U }")
+ assertEquals(s"($obj)$str", implMethodDescriptorFor("(x => x.toString): Fun1[Int, String]"))
+ assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x => println(x)): Fun1[Int, Unit]"))
+ assertEquals(s"($obj)$str", implMethodDescriptorFor("((x: VC) => \"\") : Fun1[VC, String]"))
+ assertEquals(s"($str)$obj", implMethodDescriptorFor("((x: String) => new VC(0)) : Fun1[String, VC]"))
+
+ compileToBytes("trait Coll[A, Repr] extends Any")
+ compileToBytes("final class ofInt(val repr: Array[Int]) extends AnyVal with Coll[Int, Array[Int]]")
+
+ assertEquals(s"([I)$obj", implMethodDescriptorFor("((xs: Array[Int]) => new ofInt(xs)): Array[Int] => Coll[Int, Array[Int]]"))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
new file mode 100644
index 0000000000..1ad02c10cf
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala
@@ -0,0 +1,146 @@
+package scala.tools.nsc
+package backend.jvm
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.tools.asm.Opcodes._
+import scala.tools.nsc.reporters.StoreReporter
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
+
+
+@RunWith(classOf[JUnit4])
+class IndySammyTest extends BytecodeTesting {
+ import compiler._
+
+ def funClassName(from: String, to: String) = s"Fun$from$to"
+ def classPrologue(from: String, to: String) =
+ "class VC(private val i: Int) extends AnyVal\n" +
+ s"trait ${funClassName(from, to)} { def apply(a: $from): $to}"
+
+ def lamDef(from: String, to: String, body: String => String) =
+ s"""def lam = (x => ${body("x")}): ${funClassName(from, to)}"""
+
+ def appDef(arg: String) = s"""def app = lam($arg)"""
+
+ /* Create a lambda of type "$from => $to" (with body "$body(x)" if "x" is the argument name),
+ * and apply it to `arg`.
+ *
+ * Check:
+ * - the signature of the apply method
+ * - the instructions in the lambda's body (anonfun method)
+ * - the instructions used to create the argument for the application
+ * (and the return corresponding to the lambda's result type)
+ */
+ def test(from: String, to: String, arg: String, body: String => String = x => x)
+ (expectedSig: String, lamBody: List[Instruction], appArgs: List[Instruction], ret: Instruction)
+ (allowMessage: StoreReporter#Info => Boolean = _ => false) = {
+ val List(funClass, vcClass, vcCompanion) = compileClasses(s"${classPrologue(from, to)}")
+ val c = compileClass(s"class C { ${lamDef(from, to, body)}; ${appDef(arg)} }", allowMessage = allowMessage)
+
+ val applySig = getAsmMethod(funClass, "apply").desc
+ val anonfun = getMethod(c, "$anonfun$lam$1")
+ val lamInsn = getInstructions(c, "lam").dropNonOp
+ val applyInvoke = getMethod(c, "app")
+
+ assertEquals(expectedSig, applySig)
+ assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn)
+ assertSameCode(anonfun, lamBody)
+ assertSameCode(applyInvoke, List(
+ VarOp(ALOAD, 0),
+ Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List(
+ Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret)
+ )
+ }
+
+// def testSpecial(lam: String, lamTp: String, arg: String)(allowMessage: StoreReporter#Info => Boolean = _ => false) = {
+// val cls = compileClasses("trait Special[@specialized A] { def apply(a: A): A}" )
+// val methodNodes = compileMethods(compiler)(s"def lam : $lamTp = $lam" +";"+ appDef(arg), allowMessage)
+//
+// val anonfun = methodNodes.filter(_.name contains "$anonfun$").map(convertMethod)
+// val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp
+// val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get
+//
+// assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn)
+// assertSameCode(anonfun, lamBody)
+// assertSameCode(applyInvoke, List(
+// VarOp(ALOAD, 0),
+// Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List(
+// Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret)
+// )
+// }
+
+ // x => x : VC => VC applied to VC(1)
+ @Test
+ def testVC_VC_VC =
+ test("VC", "VC", "new VC(1)")("(I)I",
+ List(VarOp(ILOAD, 0), Op(IRETURN)),
+ List(Op(ICONST_1)),
+ Op(IRETURN))()
+
+ // x => new VC(x) : Int => VC applied to 1
+ @Test
+ def testInt_VC_1 =
+ test("Int", "VC", "1", x => s"new VC($x)")("(I)I",
+ List(VarOp(ILOAD, 0), Op(IRETURN)),
+ List(Op(ICONST_1)),
+ Op(IRETURN))()
+
+ // x => x : VC => Int applied to VC(1)
+ @Test
+ def testVC_Int_VC =
+ test("VC", "Int", "new VC(1)", x => "1")("(I)I",
+ List(Op(ICONST_1), Op(IRETURN)),
+ List(Op(ICONST_1)),
+ Op(IRETURN))()
+
+ // x => new VC(1) : VC => Any applied to VC(1)
+ @Test
+ def testVC_Any_VC =
+ test("VC", "Any", "new VC(1)", x => s"new VC(1)")("(I)Ljava/lang/Object;",
+ List(TypeOp(NEW, "VC"), Op(DUP), Op(ICONST_1), Invoke(INVOKESPECIAL, "VC", "<init>", "(I)V", false), Op(ARETURN)),
+ List(Op(ICONST_1)),
+ Op(ARETURN))()
+
+
+ // x => x : VC => Unit applied to VC(1)
+ @Test
+ def testVC_Unit_VC =
+ test("VC", "Unit", "new VC(1)")("(I)V",
+ List(VarOp(ILOAD, 0), Op(POP), Op(RETURN)),
+ List(Op(ICONST_1)),
+ Op(RETURN))(allowMessage = _.msg.contains("pure expression"))
+
+ // x => new VC(x.asInstanceOf[Int]) : Any => VC applied to 1
+ //
+ // Scala:
+ // def lam = (x => new VC(x.asInstanceOf[Int])): FunAny_VC
+ // def app = lam(1)
+ // Java:
+ // FunAny_VC lam() { return x -> BoxesRunTime.unboxToInt((Object)x); }
+ // int app() { lam().apply(BoxesRunTime.boxToInteger((int)1));
+ @Test
+ def testAny_VC_1 =
+ test("Any", "VC", "1", x => s"new VC($x.asInstanceOf[Int])")("(Ljava/lang/Object;)I",
+ List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), Op(IRETURN)),
+ List(Op(ICONST_1), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false)),
+ Op(IRETURN))()
+
+ // TODO
+ // x => x : Special[Int] applied to 1
+// @Test
+// def testSpecial_Int_1 =
+// testSpecial("x => x", "Special[Int]", "1")()
+
+
+ // Tests ThisReferringMethodsTraverser
+ @Test
+ def testStaticIfNoThisReference: Unit = {
+ val methodNodes = compileAsmMethods("def foo = () => () => () => 42")
+ methodNodes.forall(m => !m.name.contains("anonfun") || (m.access & ACC_STATIC) == ACC_STATIC)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala
new file mode 100644
index 0000000000..9a0899ffc5
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/OptimizedBytecodeTest.scala
@@ -0,0 +1,362 @@
+package scala.tools.nsc.backend.jvm
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.tools.asm.Opcodes._
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
+
+@RunWith(classOf[JUnit4])
+class OptimizedBytecodeTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:classpath -opt-warnings"
+ import compiler._
+
+ @Test
+ def t2171(): Unit = {
+ val code =
+ """class C {
+ | final def m(msg: => String) = try 0 catch { case ex: Throwable => println(msg) }
+ | def t(): Unit = while (true) m("...")
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameCode(getMethod(c, "t"), List(Label(0), Jump(GOTO, Label(0))))
+ }
+
+ @Test
+ def t3430(): Unit = {
+ val code =
+ """class C {
+ | final def m(f: String => Boolean) = f("a")
+ | def t(): Boolean =
+ | m { s1 =>
+ | m { s2 =>
+ | while (true) { }
+ | true
+ | }
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+
+ assertSameSummary(getMethod(c, "t"), List(
+ LDC, ASTORE, ALOAD /*0*/, ALOAD /*1*/, "$anonfun$t$1", IRETURN))
+ assertSameSummary(getMethod(c, "$anonfun$t$1"), List(LDC, "$anonfun$t$2", IRETURN))
+ assertSameSummary(getMethod(c, "$anonfun$t$2"), List(-1 /*A*/, GOTO /*A*/))
+ }
+
+ @Test
+ def t3252(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Boolean): Thread = {
+ | g {
+ | x match {
+ | case false => Tat.h { }
+ | }
+ | }
+ | }
+ |
+ | private def g[T](block: => T) = ???
+ |}
+ |object Tat {
+ | def h(block: => Unit): Nothing = ???
+ |}
+ """.stripMargin
+ val List(c, t, tMod) = compileClasses(code, allowMessage = _.msg.contains("not be exhaustive"))
+ assertSameSummary(getMethod(c, "t"), List(GETSTATIC, "$qmark$qmark$qmark", ATHROW))
+ }
+
+ @Test
+ def t6157(): Unit = {
+ val code =
+ """class C {
+ | def t = println(ErrorHandler.defaultIfIOException("String")("String"))
+ |}
+ |object ErrorHandler {
+ | import java.io.IOException
+ | @inline
+ | def defaultIfIOException[T](default: => T)(closure: => T): T = try closure catch {
+ | case e: IOException => default
+ | }
+ |}
+ """.stripMargin
+
+ val msg =
+ """ErrorHandler$::defaultIfIOException(Lscala/Function0;Lscala/Function0;)Ljava/lang/Object; is annotated @inline but could not be inlined:
+ |The operand stack at the callsite in C::t()V contains more values than the
+ |arguments expected by the callee ErrorHandler$::defaultIfIOException(Lscala/Function0;Lscala/Function0;)Ljava/lang/Object;. These values would be discarded
+ |when entering an exception handler declared in the inlined method.""".stripMargin
+
+ compileClasses(code, allowMessage = _.msg == msg)
+ }
+
+ @Test
+ def t6547(): Unit = { // "pos" test -- check that it compiles
+ val code =
+ """trait ConfigurableDefault[@specialized V] {
+ | def fillArray(arr: Array[V], v: V) = (arr: Any) match {
+ | case x: Array[Int] => null
+ | case x: Array[Long] => v.asInstanceOf[Long]
+ | }
+ |}
+ """.stripMargin
+ compileToBytes(code)
+ }
+
+ @Test
+ def t8062(): Unit = {
+ val c1 =
+ """package warmup
+ |object Warmup { def filter[A](p: Any => Boolean): Any = filter[Any](p) }
+ """.stripMargin
+ val c2 = "class C { def t = warmup.Warmup.filter[Any](x => false) }"
+ val List(c, _, _) = compileClassesSeparately(List(c1, c2), extraArgs = compilerArgs)
+ assertInvoke(getMethod(c, "t"), "warmup/Warmup$", "filter")
+ }
+
+ @Test
+ def t8306(): Unit = { // "pos" test
+ val code =
+ """class C {
+ | def foo: Int = 123
+ | lazy val extension: Int = foo match {
+ | case idx if idx != -1 => 15
+ | case _ => 17
+ | }
+ |}
+ """.stripMargin
+ compileToBytes(code)
+ }
+
+ @Test
+ def t8359(): Unit = { // "pos" test
+ // This is a minimization of code that crashed the compiler during bootstrapping
+ // in the first iteration of https://github.com/scala/scala/pull/4373, the PR
+ // that adjusted the order of free and declared params in LambdaLift.
+
+ // Was:
+ // java.lang.AssertionError: assertion failed:
+ // Record Record(<$anon: Function1>,Map(value a$1 -> Deref(LocalVar(value b)))) does not contain a field value b$1
+ // at scala.tools.nsc.Global.assert(Global.scala:262)
+ // at scala.tools.nsc.backend.icode.analysis.CopyPropagation$copyLattice$State.getFieldNonRecordValue(CopyPropagation.scala:113)
+ // at scala.tools.nsc.backend.icode.analysis.CopyPropagation$copyLattice$State.getFieldNonRecordValue(CopyPropagation.scala:122)
+ // at scala.tools.nsc.backend.opt.ClosureElimination$ClosureElim$$anonfun$analyzeMethod$1$$anonfun$apply$2.replaceFieldAccess$1(ClosureElimination.scala:124)
+ val code =
+ """package test
+ |class Typer {
+ | def bar(a: Boolean, b: Boolean): Unit = {
+ | @inline
+ | def baz(): Unit = {
+ | ((_: Any) => (Typer.this, a, b)).apply("")
+ | }
+ | ((_: Any) => baz()).apply("")
+ | }
+ |}
+ """.stripMargin
+ compileToBytes(code)
+ }
+
+ @Test
+ def t9123(): Unit = { // "pos" test
+ val code =
+ """trait Setting {
+ | type T
+ | def value: T
+ |}
+ |object Test {
+ | def test(x: Some[Setting]) = x match {
+ | case Some(dep) => Some(dep.value) map (_ => true)
+ | }
+ |}
+ """.stripMargin
+ compileToBytes(code)
+ }
+
+ @Test
+ def traitForceInfo(): Unit = {
+ // This did NOT crash unless it's in the interactive package.
+ // error: java.lang.AssertionError: assertion failed: trait Contexts.NoContext$ linkedModule: <none>List()
+ // at scala.Predef$.assert(Predef.scala:160)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser$innerClasses$.innerSymbol$1(ClassfileParser.scala:1211)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser$innerClasses$.classSymbol(ClassfileParser.scala:1223)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser.classNameToSymbol(ClassfileParser.scala:489)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser.sig2type$1(ClassfileParser.scala:757)
+ // at scala.tools.nsc.symtab.classfile.ClassfileParser.sig2type$1(ClassfileParser.scala:789)
+ val code =
+ """package scala.tools.nsc
+ |package interactive
+ |
+ |trait MyContextTrees {
+ | val self: Global
+ | val NoContext = self.analyzer.NoContext
+ |}
+ """.stripMargin
+ compileClasses(code)
+ }
+
+ @Test
+ def t9160(): Unit = {
+ val code =
+ """class C {
+ | def getInt: Int = 0
+ | def t(trees: Object): Int = {
+ | trees match {
+ | case Some(elems) =>
+ | case tree => getInt
+ | }
+ | 55
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameSummary(getMethod(c, "t"), List(
+ ALOAD /*1*/, INSTANCEOF /*Some*/, IFNE /*A*/,
+ ALOAD /*0*/, "getInt", POP,
+ -1 /*A*/, BIPUSH, IRETURN))
+ }
+
+ @Test
+ def t8796(): Unit = {
+ val code =
+ """final class C {
+ | def pr(): Unit = ()
+ | def t(index: Int): Unit = index match {
+ | case 0 => pr()
+ | case 1 => pr()
+ | case _ => t(index - 2)
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameSummary(getMethod(c, "t"), List(
+ -1 /*A*/, ILOAD /*1*/, TABLESWITCH,
+ -1, ALOAD, "pr", RETURN,
+ -1, ALOAD, "pr", RETURN,
+ -1, ILOAD, ICONST_2, ISUB, ISTORE, GOTO /*A*/))
+ }
+
+ @Test
+ def t8524(): Unit = {
+ val c1 =
+ """package library
+ |object Library {
+ | @inline def pleaseInlineMe() = 1
+ | object Nested { @inline def pleaseInlineMe() = 2 }
+ |}
+ """.stripMargin
+
+ val c2 =
+ """class C {
+ | def t = library.Library.pleaseInlineMe() + library.Library.Nested.pleaseInlineMe()
+ |}
+ """.stripMargin
+
+ val cls = compileClassesSeparately(List(c1, c2), extraArgs = compilerArgs)
+ val c = findClass(cls, "C")
+ assertSameSummary(getMethod(c, "t"), List(
+ GETSTATIC, IFNONNULL, ACONST_NULL, ATHROW, // module load and null checks not yet eliminated
+ -1, ICONST_1, GETSTATIC, IFNONNULL, ACONST_NULL, ATHROW,
+ -1, ICONST_2, IADD, IRETURN))
+ }
+
+ @Test
+ def privateInline(): Unit = {
+ val code =
+ """final class C {
+ | private var x1 = false
+ | var x2 = false
+ |
+ | @inline private def wrapper1[T](body: => T): T = {
+ | val saved = x1
+ | x1 = true
+ | try body
+ | finally x1 = saved
+ | }
+ |
+ | @inline private def wrapper2[T](body: => T): T = {
+ | val saved = x2
+ | x2 = true
+ | try body
+ | finally x2 = saved
+ | }
+ | // inlined
+ | def f1a() = wrapper1(5)
+ | // not inlined: even after inlining `identity`, the Predef module is already on the stack for the
+ | // subsequent null check (the receiver of an inlined method, in this case Predef, is checked for
+ | // nullness, to ensure an NPE is thrown)
+ | def f1b() = identity(wrapper1(5))
+ |
+ | def f2a() = wrapper2(5) // inlined
+ | def f2b() = identity(wrapper2(5)) // not inlined
+ |}
+ """.stripMargin
+ val c = compileClass(code, allowMessage = _.msg.contains("exception handler declared in the inlined method"))
+ assertInvoke(getMethod(c, "f1a"), "C", "$anonfun$f1a$1")
+ assertInvoke(getMethod(c, "f1b"), "C", "wrapper1")
+ assertInvoke(getMethod(c, "f2a"), "C", "$anonfun$f2a$1")
+ assertInvoke(getMethod(c, "f2b"), "C", "wrapper2")
+ }
+
+ @Test
+ def t7060(): Unit = {
+ val code =
+ """class C {
+ | @inline final def mbarray_apply_minibox(array: Any, tag: Byte): Long =
+ | if (tag == 0) array.asInstanceOf[Array[Long]](0)
+ | else array.asInstanceOf[Array[Byte]](0).toLong
+ |
+ | def t = mbarray_apply_minibox(null, 0)
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertNoInvoke(getMethod(c, "t"))
+ }
+
+ @Test
+ def t8315(): Unit = {
+ val code =
+ """class C {
+ | def t(as: Listt): Unit = {
+ | map(as, (_: Any) => return)
+ | }
+ | final def map(x: Listt, f: Any => Any): Any = {
+ | if (x eq Nill) "" else f("")
+ | }
+ |}
+ |object Nill extends Listt
+ |class Listt
+ """.stripMargin
+ val List(c, nil, nilMod, listt) = compileClasses(code)
+ assertInvoke(getMethod(c, "t"), "C", "$anonfun$t$1")
+ }
+
+ @Test
+ def t8315b(): Unit = {
+ val code =
+ """class C {
+ | def crash: Unit = {
+ | val key = ""
+ | try map(new F(key))
+ | catch { case _: Throwable => }
+ | }
+ | final def map(f: F): Any = f.apply("")
+ |}
+ |final class F(key: String) {
+ | final def apply(a: Any): Any = throw new RuntimeException(key)
+ |}
+ """.stripMargin
+ val List(c, f) = compileClasses(code)
+ assertInvoke(getMethod(c, "crash"), "C", "map")
+ }
+
+ @Test
+ def optimiseEnablesNewOpt(): Unit = {
+ val code = """class C { def t = (1 to 10) foreach println }"""
+ val List(c) = readAsmClasses(newCompiler(extraArgs = "-optimise -deprecation").compileToBytes(code, allowMessage = _.msg.contains("is deprecated")))
+ assertInvoke(getMethod(c, "t"), "C", "$anonfun$t$1") // range-foreach inlined from classpath
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala b/test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala
new file mode 100644
index 0000000000..af2c8f9ce0
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/StringConcatTest.scala
@@ -0,0 +1,123 @@
+package scala.tools.nsc
+package backend.jvm
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
+
+@RunWith(classOf[JUnit4])
+class StringConcatTest extends BytecodeTesting {
+ import compiler._
+
+ @Test
+ def appendOverloadNoBoxing(): Unit = {
+ val code =
+ """class C {
+ | def t1(
+ | v: Unit,
+ | z: Boolean,
+ | c: Char,
+ | b: Byte,
+ | s: Short,
+ | i: Int,
+ | l: Long,
+ | f: Float,
+ | d: Double,
+ | str: String,
+ | sbuf: java.lang.StringBuffer,
+ | chsq: java.lang.CharSequence,
+ | chrs: Array[Char]) = str + this + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs
+ |
+ | // similar, but starting off with any2stringadd
+ | def t2(
+ | v: Unit,
+ | z: Boolean,
+ | c: Char,
+ | b: Byte,
+ | s: Short,
+ | i: Int,
+ | l: Long,
+ | f: Float,
+ | d: Double,
+ | str: String,
+ | sbuf: java.lang.StringBuffer,
+ | chsq: java.lang.CharSequence,
+ | chrs: Array[Char]) = this + str + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+
+ def invokeNameDesc(m: String): List[String] = getInstructions(c, m) collect {
+ case Invoke(_, _, name, desc, _) => name + desc
+ }
+ assertEquals(invokeNameDesc("t1"), List(
+ "<init>()V",
+ "append(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
+ "append(Z)Ljava/lang/StringBuilder;",
+ "append(C)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(F)Ljava/lang/StringBuilder;",
+ "append(J)Ljava/lang/StringBuilder;",
+ "append(D)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/StringBuffer;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", // test that we're not using the [C overload
+ "toString()Ljava/lang/String;"))
+
+ assertEquals(invokeNameDesc("t2"), List(
+ "<init>()V",
+ "any2stringadd(Ljava/lang/Object;)Ljava/lang/Object;",
+ "$plus$extension(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String;",
+ "append(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
+ "append(Z)Ljava/lang/StringBuilder;",
+ "append(C)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(I)Ljava/lang/StringBuilder;",
+ "append(F)Ljava/lang/StringBuilder;",
+ "append(J)Ljava/lang/StringBuilder;",
+ "append(D)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/StringBuffer;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;",
+ "append(Ljava/lang/Object;)Ljava/lang/StringBuilder;", // test that we're not using the [C overload
+ "toString()Ljava/lang/String;"))
+ }
+
+ @Test
+ def concatPrimitiveCorrectness(): Unit = {
+ val obj: Object = new { override def toString = "TTT" }
+ def t(
+ v: Unit,
+ z: Boolean,
+ c: Char,
+ b: Byte,
+ s: Short,
+ i: Int,
+ l: Long,
+ f: Float,
+ d: Double,
+ str: String,
+ sbuf: java.lang.StringBuffer,
+ chsq: java.lang.CharSequence,
+ chrs: Array[Char]) = {
+ val s1 = str + obj + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs
+ val s2 = obj + str + v + z + c + b + s + i + f + l + d + sbuf + chsq + chrs
+ s1 + "//" + s2
+ }
+ def sbuf = { val r = new java.lang.StringBuffer(); r.append("sbuf"); r }
+ def chsq: java.lang.CharSequence = "chsq"
+ val s = t((), true, 'd', 3: Byte, 12: Short, 3, -32l, 12.3f, -4.2d, "me", sbuf, chsq, Array('a', 'b'))
+ val r = s.replaceAll("""\[C@\w+""", "<ARRAY>")
+ assertEquals(r, "meTTT()trued312312.3-32-4.2sbufchsq<ARRAY>//TTTme()trued312312.3-32-4.2sbufchsq<ARRAY>")
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala
index 94e776aadb..c173bacd46 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala
@@ -2,54 +2,39 @@ package scala.tools.nsc
package backend.jvm
package analysis
+import org.junit.Assert._
+import org.junit.Test
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.asm.tree.{AbstractInsnNode, MethodNode}
+import scala.collection.JavaConverters._
+import scala.tools.asm.tree.MethodNode
+import scala.tools.nsc.backend.jvm.AsmUtils._
import scala.tools.nsc.backend.jvm.BTypes._
-import scala.tools.partest.ASMConverters
-import ASMConverters._
-import scala.tools.testing.ClearAfterClass
import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
-import AsmUtils._
-
-import scala.collection.convert.decorateAsScala._
-
-object NullnessAnalyzerTest extends ClearAfterClass.Clearable {
- var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
-
- def clear(): Unit = {
- noOptCompiler = null
- }
-}
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
@RunWith(classOf[JUnit4])
-class NullnessAnalyzerTest extends ClearAfterClass {
- ClearAfterClass.stateToClear = NullnessAnalyzerTest
- val noOptCompiler = NullnessAnalyzerTest.noOptCompiler
-
- def newNullnessAnalyzer(methodNode: MethodNode, classInternalName: InternalName = "C"): NullnessAnalyzer = {
- val nullnessAnalyzer = new NullnessAnalyzer
- nullnessAnalyzer.analyze(classInternalName, methodNode)
- nullnessAnalyzer
- }
+class NullnessAnalyzerTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:none"
+ import compiler._
+ import global.genBCode.bTypes.backendUtils._
+
+ def newNullnessAnalyzer(methodNode: MethodNode, classInternalName: InternalName = "C") = new AsmAnalyzer(methodNode, classInternalName, new NullnessAnalyzer(global.genBCode.bTypes, methodNode))
- def testNullness(analyzer: NullnessAnalyzer, method: MethodNode, query: String, index: Int, nullness: Nullness): Unit = {
- for (i <- findInstr(method, query)) {
- val r = analyzer.frameAt(i, method).getValue(index).nullness
+ def testNullness(analyzer: AsmAnalyzer[NullnessValue], method: MethodNode, query: String, index: Int, nullness: NullnessValue): Unit = {
+ for (i <- findInstrs(method, query)) {
+ val r = analyzer.frameAt(i).getValue(index)
assertTrue(s"Expected: $nullness, found: $r. At instr ${textify(i)}", nullness == r)
}
}
// debug / helper for writing tests
- def showAllNullnessFrames(analyzer: NullnessAnalyzer, method: MethodNode): String = {
+ def showAllNullnessFrames(analyzer: AsmAnalyzer[NullnessValue], method: MethodNode): String = {
val instrLength = method.instructions.iterator.asScala.map(textify(_).length).max
val lines = for (i <- method.instructions.iterator.asScala) yield {
- val f = analyzer.frameAt(i, method)
+ val f = analyzer.frameAt(i)
val frameString = {
if (f == null) "null"
else (0 until (f.getLocals + f.getStackSize)).iterator
@@ -65,39 +50,40 @@ class NullnessAnalyzerTest extends ClearAfterClass {
@Test
def showNullnessFramesTest(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f = this.toString")
+ val m = compileAsmMethod("def f = this.toString")
// NOTE: the frame for an instruction represents the state *before* executing that instr.
// So in the frame for `ALOAD 0`, the stack is still empty.
val res =
- """ L0: 0: NotNull
- | LINENUMBER 1 L0: 0: NotNull
- | ALOAD 0: 0: NotNull
- |INVOKEVIRTUAL java/lang/Object.toString ()Ljava/lang/String;: 0: NotNull, 1: NotNull
- | ARETURN: 0: NotNull, 1: Unknown1
- | L0: null""".stripMargin
+ """ L0: 0: NotNull
+ | LINENUMBER 1 L0: 0: NotNull
+ | ALOAD 0: 0: NotNull
+ |INVOKEVIRTUAL C.toString ()Ljava/lang/String;: 0: NotNull, 1: NotNull
+ | ARETURN: 0: NotNull, 1: Unknown1
+ | L0: null""".stripMargin
+// println(showAllNullnessFrames(newNullnessAnalyzer(m), m))
assertEquals(showAllNullnessFrames(newNullnessAnalyzer(m), m), res)
}
@Test
def thisNonNull(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f = this.toString")
+ val m = compileAsmMethod("def f = this.toString")
val a = newNullnessAnalyzer(m)
- testNullness(a, m, "ALOAD 0", 0, NotNull)
+ testNullness(a, m, "ALOAD 0", 0, NotNullValue)
}
@Test
def instanceMethodCall(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f(a: String) = a.trim")
+ val m = compileAsmMethod("def f(a: String) = a.trim")
val a = newNullnessAnalyzer(m)
- testNullness(a, m, "INVOKEVIRTUAL java/lang/String.trim", 1, Unknown)
- testNullness(a, m, "ARETURN", 1, NotNull)
+ testNullness(a, m, "INVOKEVIRTUAL java/lang/String.trim", 1, UnknownValue1)
+ testNullness(a, m, "ARETURN", 1, NotNullValue)
}
@Test
def constructorCall(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f = { val a = new Object; a.toString }")
+ val m = compileAsmMethod("def f = { val a = new Object; a.toString }")
val a = newNullnessAnalyzer(m)
// for reference, the output of showAllNullnessFrames(a, m) - note that the frame represents the state *before* executing the instr.
@@ -110,40 +96,58 @@ class NullnessAnalyzerTest extends ClearAfterClass {
// ARETURN: 0: NotNull, 1: NotNull, 2: Unknown
for ((insn, index, nullness) <- List(
- ("+NEW", 2, Unknown), // new value at slot 2 on the stack
- ("+DUP", 3, Unknown),
- ("+INVOKESPECIAL java/lang/Object", 2, NotNull), // after calling the initializer on 3, the value at 2 becomes NotNull
- ("ASTORE 1", 1, Unknown), // before the ASTORE 1, nullness of the value in local 1 is Unknown
- ("+ASTORE 1", 1, NotNull), // after storing the value at 2 in local 1, the local 1 is NotNull
- ("+ALOAD 1", 2, NotNull), // loading the value 1 puts a NotNull value on the stack (at 2)
- ("+INVOKEVIRTUAL java/lang/Object.toString", 2, Unknown) // nullness of value returned by `toString` is Unknown
+ ("+NEW", 2, UnknownValue1), // new value at slot 2 on the stack
+ ("+DUP", 3, UnknownValue1),
+ ("+INVOKESPECIAL java/lang/Object", 2, NotNullValue), // after calling the initializer on 3, the value at 2 becomes NotNull
+ ("ASTORE 1", 1, UnknownValue1), // before the ASTORE 1, nullness of the value in local 1 is Unknown
+ ("+ASTORE 1", 1, NotNullValue), // after storing the value at 2 in local 1, the local 1 is NotNull
+ ("+ALOAD 1", 2, NotNullValue), // loading the value 1 puts a NotNull value on the stack (at 2)
+ ("+INVOKEVIRTUAL java/lang/Object.toString", 2, UnknownValue1) // nullness of value returned by `toString` is Unknown
)) testNullness(a, m, insn, index, nullness)
}
@Test
def explicitNull(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f = { var a: Object = null; a }")
+ val m = compileAsmMethod("def f = { var a: Object = null; a }")
val a = newNullnessAnalyzer(m)
for ((insn, index, nullness) <- List(
- ("+ACONST_NULL", 2, Null),
- ("+ASTORE 1", 1, Null),
- ("+ALOAD 1", 2, Null)
+ ("+ACONST_NULL", 2, NullValue),
+ ("+ASTORE 1", 1, NullValue),
+ ("+ALOAD 1", 2, NullValue)
)) testNullness(a, m, insn, index, nullness)
}
@Test
def stringLiteralsNotNull(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("""def f = { val a = "hi"; a.trim }""")
+ val m = compileAsmMethod("""def f = { val a = "hi"; a.trim }""")
val a = newNullnessAnalyzer(m)
- testNullness(a, m, "+ASTORE 1", 1, NotNull)
+ testNullness(a, m, "+ASTORE 1", 1, NotNullValue)
}
@Test
def newArraynotNull() {
- val List(m) = compileMethods(noOptCompiler)("def f = { val a = new Array[Int](2); a(0) }")
+ val m = compileAsmMethod("def f = { val a = new Array[Int](2); a(0) }")
+ val a = newNullnessAnalyzer(m)
+ testNullness(a, m, "+NEWARRAY T_INT", 2, NotNullValue) // new array on stack
+ testNullness(a, m, "+ASTORE 1", 1, NotNullValue) // local var (a)
+ }
+
+ @Test
+ def mergeNullNotNull(): Unit = {
+ val code =
+ """def f(o: Object) = {
+ | var a: Object = o
+ | var c: Object = null
+ | if ("".trim eq "-") {
+ | c = o
+ | }
+ | a.toString
+ |}
+ """.stripMargin
+ val m = compileAsmMethod(code)
val a = newNullnessAnalyzer(m)
- testNullness(a, m, "+NEWARRAY T_INT", 2, NotNull) // new array on stack
- testNullness(a, m, "+ASTORE 1", 1, NotNull) // local var (a)
+ val toSt = "+INVOKEVIRTUAL java/lang/Object.toString"
+ testNullness(a, m, toSt, 3, UnknownValue1)
}
@Test
@@ -166,29 +170,29 @@ class NullnessAnalyzerTest extends ClearAfterClass {
| // d is null here, assinged in both branches.
|}
""".stripMargin
- val List(m) = compileMethods(noOptCompiler)(code)
+ val m = compileAsmMethod(code)
val a = newNullnessAnalyzer(m)
val trim = "INVOKEVIRTUAL java/lang/String.trim"
val toSt = "INVOKEVIRTUAL java/lang/Object.toString"
val end = s"+$toSt"
for ((insn, index, nullness) <- List(
- (trim, 0, NotNull), // this
- (trim, 1, Unknown), // parameter o
- (trim, 2, Unknown), // a
- (trim, 3, Null), // b
- (trim, 4, Null), // c
- (trim, 5, Unknown), // d
-
- (toSt, 2, Unknown), // a, still the same
- (toSt, 3, Unknown), // b, was re-assinged in both branches to Unknown
- (toSt, 4, Unknown), // c, was re-assigned in one branch to Unknown
- (toSt, 5, Null), // d, was assigned to null in both branches
-
- (end, 2, NotNull), // a, NotNull (alias of b)
- (end, 3, NotNull), // b, receiver of toString
- (end, 4, Unknown), // c, no change (not an alias of b)
- (end, 5, Null) // d, no change
+ (trim, 0, NotNullValue), // this
+ (trim, 1, UnknownValue1), // parameter o
+ (trim, 2, UnknownValue1), // a
+ (trim, 3, NullValue), // b
+ (trim, 4, NullValue), // c
+ (trim, 5, UnknownValue1), // d
+
+ (toSt, 2, UnknownValue1), // a, still the same
+ (toSt, 3, UnknownValue1), // b, was re-assinged in both branches to Unknown
+ (toSt, 4, UnknownValue1), // c, was re-assigned in one branch to Unknown
+ (toSt, 5, NullValue), // d, was assigned to null in both branches
+
+ (end, 2, NotNullValue), // a, NotNull (alias of b)
+ (end, 3, NotNullValue), // b, receiver of toString
+ (end, 4, UnknownValue1), // c, no change (not an alias of b)
+ (end, 5, NullValue) // d, no change
)) testNullness(a, m, insn, index, nullness)
}
@@ -202,7 +206,7 @@ class NullnessAnalyzerTest extends ClearAfterClass {
| a.asInstanceOf[String].trim // the stack value (LOAD of local a) is still not-null after the CHECKCAST
|}
""".stripMargin
- val List(m) = compileMethods(noOptCompiler)(code)
+ val m = compileAsmMethod(code)
val a = newNullnessAnalyzer(m)
val instof = "+INSTANCEOF"
@@ -210,11 +214,11 @@ class NullnessAnalyzerTest extends ClearAfterClass {
val trim = "INVOKEVIRTUAL java/lang/String.trim"
for ((insn, index, nullness) <- List(
- (instof, 1, Unknown), // a after INSTANCEOF
- (instof, 2, Unknown), // x after INSTANCEOF
- (tost, 1, NotNull),
- (tost, 2, NotNull),
- (trim, 3, NotNull) // receiver at `trim`
+ (instof, 1, UnknownValue1), // a after INSTANCEOF
+ (instof, 2, UnknownValue1), // x after INSTANCEOF
+ (tost, 1, NotNullValue),
+ (tost, 2, NotNullValue),
+ (trim, 3, NotNullValue) // receiver at `trim`
)) testNullness(a, m, insn, index, nullness)
}
}
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 941a167114..8cb04822de 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala
@@ -2,30 +2,23 @@ package scala.tools.nsc
package backend.jvm
package analysis
+import org.junit.Assert._
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.junit.Assert._
import scala.tools.asm.Opcodes
import scala.tools.asm.tree.AbstractInsnNode
+import scala.tools.nsc.backend.jvm.AsmUtils._
import scala.tools.partest.ASMConverters._
-import scala.tools.testing.ClearAfterClass
-import CodeGenTools._
-import AsmUtils._
-
-object ProdConsAnalyzerTest extends ClearAfterClass.Clearable {
- var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
-
- def clear(): Unit = {
- noOptCompiler = null
- }
-}
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
@RunWith(classOf[JUnit4])
-class ProdConsAnalyzerTest extends ClearAfterClass {
- ClearAfterClass.stateToClear = ProdConsAnalyzerTest
- val noOptCompiler = ProdConsAnalyzerTest.noOptCompiler
+class ProdConsAnalyzerTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:none"
+ import compiler._
+ import global.genBCode.bTypes.backendUtils._
def prodToString(producer: AbstractInsnNode) = producer match {
case p: InitialProducer => p.toString
@@ -56,9 +49,9 @@ class ProdConsAnalyzerTest extends ClearAfterClass {
@Test
def parameters(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f = this.toString")
+ val m = compileAsmMethod("def f = this.toString")
val a = new ProdConsAnalyzer(m, "C")
- val call = findInstr(m, "INVOKEVIRTUAL").head
+ val call = findInstr(m, "INVOKEVIRTUAL")
testSingleInsn(a.producersForValueAt(call, 1), "ALOAD 0") // producer of stack value
testSingleInsn(a.producersForInputsOf(call), "ALOAD 0")
@@ -91,55 +84,55 @@ class ProdConsAnalyzerTest extends ClearAfterClass {
m.maxStack = 1
val a = new ProdConsAnalyzer(m, "C")
- val ifne = findInstr(m, "IFNE").head
+ val ifne = findInstr(m, "IFNE")
testSingleInsn(a.producersForValueAt(ifne, 1), "ParameterProducer")
- val ret = findInstr(m, "IRETURN").head
+ val ret = findInstr(m, "IRETURN")
testMultiInsns(a.producersForValueAt(ret, 1), List("ParameterProducer", "ISTORE 1"))
}
@Test
def branching(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f(x: Int) = { var a = x; if (a == 0) a = 12; a }")
+ val m = compileAsmMethod("def f(x: Int) = { var a = x; if (a == 0) a = 12; a }")
val a = new ProdConsAnalyzer(m, "C")
- val List(ret) = findInstr(m, "IRETURN")
+ val ret = findInstr(m, "IRETURN")
testMultiInsns(a.producersForValueAt(ret, 2), List("ISTORE 2", "ISTORE 2"))
testMultiInsns(a.initialProducersForValueAt(ret, 2), List("BIPUSH 12", "ParameterProducer"))
- val List(bipush) = findInstr(m, "BIPUSH 12")
+ val bipush = findInstr(m, "BIPUSH 12")
testSingleInsn(a.consumersOfOutputsFrom(bipush), "ISTORE 2")
testSingleInsn(a.ultimateConsumersOfValueAt(bipush.getNext, 3), "IRETURN")
}
@Test
def checkCast(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f(o: Object) = o.asInstanceOf[String]")
+ val m = compileAsmMethod("def f(o: Object) = o.asInstanceOf[String]")
val a = new ProdConsAnalyzer(m, "C")
- assert(findInstr(m, "CHECKCAST java/lang/String").length == 1)
+ assert(findInstrs(m, "CHECKCAST java/lang/String").length == 1)
- val List(ret) = findInstr(m, "ARETURN")
+ val ret = findInstr(m, "ARETURN")
testSingleInsn(a.initialProducersForInputsOf(ret), "ParameterProducer(1)")
}
@Test
def instanceOf(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f(o: Object) = o.isInstanceOf[String]")
+ val m = compileAsmMethod("def f(o: Object) = o.isInstanceOf[String]")
val a = new ProdConsAnalyzer(m, "C")
- assert(findInstr(m, "INSTANCEOF java/lang/String").length == 1)
+ assert(findInstrs(m, "INSTANCEOF java/lang/String").length == 1)
- val List(ret) = findInstr(m, "IRETURN")
+ val ret = findInstr(m, "IRETURN")
testSingleInsn(a.initialProducersForInputsOf(ret), "INSTANCEOF")
}
@Test
def unInitLocal(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f(b: Boolean) = { if (b) { var a = 0; println(a) }; 1 }")
+ val m = compileAsmMethod("def f(b: Boolean) = { if (b) { var a = 0; println(a) }; 1 }")
val a = new ProdConsAnalyzer(m, "C")
- val List(store) = findInstr(m, "ISTORE")
- val List(call) = findInstr(m, "INVOKEVIRTUAL")
- val List(ret) = findInstr(m, "IRETURN")
+ val store = findInstr(m, "ISTORE")
+ val call = findInstr(m, "INVOKEVIRTUAL")
+ val ret = findInstr(m, "IRETURN")
testSingleInsn(a.producersForValueAt(store, 2), "UninitializedLocalProducer(2)")
testSingleInsn(a.producersForValueAt(call, 2), "ISTORE")
@@ -148,11 +141,11 @@ class ProdConsAnalyzerTest extends ClearAfterClass {
@Test
def dupCopying(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f = new Object")
+ val m = compileAsmMethod("def f = new Object")
val a = new ProdConsAnalyzer(m, "C")
- val List(newO) = findInstr(m, "NEW")
- val List(constr) = findInstr(m, "INVOKESPECIAL")
+ val newO = findInstr(m, "NEW")
+ val constr = findInstr(m, "INVOKESPECIAL")
testSingleInsn(a.producersForInputsOf(constr), "DUP")
testSingleInsn(a.initialProducersForInputsOf(constr), "NEW")
@@ -177,11 +170,11 @@ class ProdConsAnalyzerTest extends ClearAfterClass {
m.maxStack = 4
val a = new ProdConsAnalyzer(m, "C")
- val List(dup2) = findInstr(m, "DUP2")
- val List(add) = findInstr(m, "IADD")
- val List(swap) = findInstr(m, "SWAP")
- val List(store) = findInstr(m, "ISTORE")
- val List(ret) = findInstr(m, "IRETURN")
+ val dup2 = findInstr(m, "DUP2")
+ val add = findInstr(m, "IADD")
+ val swap = findInstr(m, "SWAP")
+ val store = findInstr(m, "ISTORE")
+ val ret = findInstr(m, "IRETURN")
testMultiInsns(a.producersForInputsOf(dup2), List("ILOAD", "ILOAD"))
testSingleInsn(a.consumersOfValueAt(dup2.getNext, 4), "IADD")
@@ -212,9 +205,9 @@ class ProdConsAnalyzerTest extends ClearAfterClass {
m.maxStack = 1
val a = new ProdConsAnalyzer(m, "C")
- val List(inc) = findInstr(m, "IINC")
- val List(load) = findInstr(m, "ILOAD")
- val List(ret) = findInstr(m, "IRETURN")
+ val inc = findInstr(m, "IINC")
+ val load = findInstr(m, "ILOAD")
+ val ret = findInstr(m, "IRETURN")
testSingleInsn(a.producersForInputsOf(inc), "ParameterProducer(1)")
testSingleInsn(a.consumersOfOutputsFrom(inc), "ILOAD")
@@ -230,12 +223,12 @@ class ProdConsAnalyzerTest extends ClearAfterClass {
@Test
def copyingInsns(): Unit = {
- val List(m) = compileMethods(noOptCompiler)("def f = 0l.asInstanceOf[Int]")
+ val m = compileAsmMethod("def f = 0l.asInstanceOf[Int]")
val a = new ProdConsAnalyzer(m, "C")
- val List(cnst) = findInstr(m, "LCONST_0")
- val List(l2i) = findInstr(m, "L2I") // l2i is not a copying instruction
- val List(ret) = findInstr(m, "IRETURN")
+ val cnst = findInstr(m, "LCONST_0")
+ val l2i = findInstr(m, "L2I") // l2i is not a copying instruction
+ val ret = findInstr(m, "IRETURN")
testSingleInsn(a.consumersOfOutputsFrom(cnst), "L2I")
testSingleInsn(a.ultimateConsumersOfOutputsFrom(cnst), "L2I")
@@ -271,10 +264,10 @@ class ProdConsAnalyzerTest extends ClearAfterClass {
m.maxStack = 2
val a = new ProdConsAnalyzer(m, "C")
- val List(iadd) = findInstr(m, "IADD")
+ val iadd = findInstr(m, "IADD")
val firstLoad = iadd.getPrevious.getPrevious
assert(firstLoad.getOpcode == ILOAD)
- val secondLoad = findInstr(m, "ISTORE").head.getPrevious
+ val secondLoad = findInstr(m, "ISTORE").getPrevious
assert(secondLoad.getOpcode == ILOAD)
testSingleInsn(a.producersForValueAt(iadd, 2), "ILOAD")
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala
new file mode 100644
index 0000000000..33ca6a5fd2
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala
@@ -0,0 +1,50 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.tools.asm.tree.analysis._
+import scala.tools.nsc.backend.jvm.analysis.{AliasingAnalyzer, AliasingFrame}
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
+
+@RunWith(classOf[JUnit4])
+class AnalyzerTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:none"
+ import compiler._
+
+ @Test
+ def aliasingOfPrimitives(): Unit = {
+ val code =
+ """class C {
+ | def f(a: Int, b: Long) = {
+ | val c = a - b // a is converted with i2l
+ | val d = c
+ | val e = a
+ | // locals: 0 this -- 1 a -- 2-3 b -- 4-5 c -- 6-7 d -- 8 e
+ | e + d // e is converted with i2l
+ | }
+ |}
+ """.stripMargin
+
+ val c = compileClass(code)
+ val a = new AliasingAnalyzer(new BasicInterpreter)
+ val f = getAsmMethod(c, "f")
+ a.analyze("C", f)
+
+ val List(_, i2l) = findInstrs(f, "I2L")
+ val aliasesAtI2l = a.frameAt(i2l, f).asInstanceOf[AliasingFrame[_]].aliases
+ assertEquals(aliasesAtI2l(1).iterator.toList, List(1, 8, 9)) // a, e and stack top
+ assertEquals(aliasesAtI2l(4).iterator.toList, List(4, 6))
+
+ val add = findInstr(f, "LADD")
+ val aliasesAtAdd = a.frameAt(add, f).asInstanceOf[AliasingFrame[_]].aliases
+ assertEquals(aliasesAtAdd(1).iterator.toList, List(1, 8)) // after i2l the value on the stack is no longer an alias
+ assertEquals(aliasesAtAdd(4).iterator.toList, List(4, 6, 10)) // c, d and stack top
+ }
+}
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 1b6c080234..42a2c417a0 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
@@ -2,37 +2,29 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Test
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.Opcodes._
import scala.tools.nsc.backend.jvm.BTypes.InternalName
-import scala.tools.testing.AssertUtil._
-
-import CodeGenTools._
-import scala.tools.partest.ASMConverters
-import ASMConverters._
-
-import BackendReporting._
-
-import scala.collection.convert.decorateAsScala._
+import scala.tools.nsc.backend.jvm.BackendReporting._
+import scala.tools.testing.BytecodeTesting
@RunWith(classOf[JUnit4])
-class BTypesFromClassfileTest {
+class BTypesFromClassfileTest extends BytecodeTesting {
// inliner enabled -> inlineInfos are collected (and compared) in ClassBTypes
- val compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:inline-global")
+ override def compilerArgs = "-opt:inline-global"
- import compiler._
+ import compiler.global._
import definitions._
import genBCode.bTypes
import bTypes._
- def duringBackend[T](f: => T) = compiler.exitingDelambdafy(f)
+ def duringBackend[T](f: => T) = global.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
+ val run = new global.Run() // initializes some of the compiler
+ duringBackend(global.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()
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
index 9fda034a04..a74e73afc9 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
@@ -2,44 +2,62 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
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.collection.JavaConverters._
+import scala.collection.generic.Clearable
+import scala.collection.immutable.IntMap
import scala.tools.asm.tree._
-import scala.tools.asm.tree.analysis._
+import scala.tools.nsc.backend.jvm.BackendReporting._
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.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
@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] = {
+class CallGraphTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:inline-global -opt-warnings"
+ import compiler._
+ import global.genBCode.bTypes
+ val notPerRun: List[Clearable] = List(
+ bTypes.classBTypeFromInternalName,
+ bTypes.byteCodeRepository.compilingClasses,
+ bTypes.byteCodeRepository.parsedClasses,
+ bTypes.callGraph.callsites)
+ notPerRun foreach global.perRunCaches.unrecordCache
+
+ import global.genBCode.bTypes._
+ import callGraph._
+
+ def compile(code: String, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
notPerRun.foreach(_.clear())
- compileClasses(compiler)(code, allowMessage = allowMessage)
+ compileClasses(code, allowMessage = allowMessage).map(c => byteCodeRepository.classNode(c.name).get)
}
def callsInMethod(methodNode: MethodNode): List[MethodInsnNode] = methodNode.instructions.iterator.asScala.collect({
case call: MethodInsnNode => call
}).toList
+ def checkCallsite(call: MethodInsnNode, callsiteMethod: MethodNode, target: MethodNode, calleeDeclClass: ClassBType,
+ safeToInline: Boolean, atInline: Boolean, atNoInline: Boolean, argInfos: IntMap[ArgInfo] = IntMap.empty) = {
+ val callsite = callGraph.callsites(callsiteMethod)(call)
+ 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 == argInfos)
+ } catch {
+ case e: Throwable => println(callsite); throw e
+ }
+ }
+
@Test
def callGraphStructure(): Unit = {
val code =
@@ -54,7 +72,7 @@ class CallGraphTest {
| @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 }
+ | @inline @noinline def f7 = try { 0 } catch { case _: Throwable => 1 } // no warning, @noinline takes precedence
|}
|class D extends C {
| @inline override def f1 = try { 0 } catch { case _: Throwable => 1 }
@@ -73,80 +91,116 @@ class CallGraphTest {
// 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")
+ "D::f1()I is annotated @inline but could not be inlined:\nThe 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 could not be inlined:\nThe 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::f4()I is annotated @inline but could not be inlined:\nThe operand stack at the callsite in Test::t1(LC;)I contains more values",
+ "C::f4()I is annotated @inline but could not be inlined:\nThe 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(cCls, cMod, dCls, testCls) = compile(code, checkMsg)
+ assert(msgCount == 4, 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(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = getAsmMethods(cCls, _.startsWith("f"))
+ val List(df1, df3) = getAsmMethods(dCls, _.startsWith("f"))
+ val g1 = getAsmMethod(cMod, "g1")
+ val List(t1, t2) = getAsmMethods(testCls, _.startsWith("t"))
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)
+ checkCallsite(cf1Call, t1, cf1, cClassBType, false, false, false)
+ checkCallsite(cf2Call, t1, cf2, cClassBType, true, false, false)
+ checkCallsite(cf3Call, t1, cf3, cClassBType, false, true, false)
+ checkCallsite(cf4Call, t1, cf4, cClassBType, true, true, false)
+ checkCallsite(cf5Call, t1, cf5, cClassBType, false, false, true)
+ checkCallsite(cf6Call, t1, cf6, cClassBType, true, false, true)
+ checkCallsite(cf7Call, t1, cf7, cClassBType, false, true, true)
+ checkCallsite(cg1Call, t1, g1, cMClassBType, true, false, false)
+
+ checkCallsite(df1Call, t2, df1, dClassBType, false, true, false)
+ checkCallsite(df2Call, t2, cf2, cClassBType, true, false, false)
+ checkCallsite(df3Call, t2, df3, dClassBType, true, false, false)
+ checkCallsite(df4Call, t2, cf4, cClassBType, true, true, false)
+ checkCallsite(df5Call, t2, cf5, cClassBType, false, false, true)
+ checkCallsite(df6Call, t2, cf6, cClassBType, true, false, true)
+ checkCallsite(df7Call, t2, cf7, cClassBType, false, true, true)
+ checkCallsite(dg1Call, t2, g1, cMClassBType, true, false, false)
+ }
+
+ @Test
+ def callerSensitiveNotSafeToInline(): Unit = {
+ val code =
+ """class C {
+ | def m = java.lang.Class.forName("C")
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ val m = getAsmMethod(c, "m")
+ val List(fn) = callsInMethod(m)
+ val forNameMeth = byteCodeRepository.methodNode("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;").get._1
+ val classTp = classBTypeFromInternalName("java/lang/Class")
+ val r = callGraph.callsites(m)(fn)
+ checkCallsite(fn, m, forNameMeth, classTp, safeToInline = false, atInline = false, atNoInline = false)
+ }
+
+ @Test
+ def checkArgInfos(): Unit = {
+ val code =
+ """abstract class C {
+ | def h(f: Int => Int): Int = f(1)
+ | def t1 = h(x => x + 1)
+ | def t2(i: Int, f: Int => Int, z: Int) = h(f) + i - z
+ | def t3(f: Int => Int) = h(x => f(x + 1))
+ |}
+ |trait D {
+ | def iAmASam(x: Int): Int
+ | def selfSamCall = iAmASam(10)
+ |}
+ |""".stripMargin
+ val List(c, d) = compile(code)
+
+ def callIn(m: String) = callGraph.callsites.find(_._1.name == m).get._2.values.head
+ val t1h = callIn("t1")
+ assertEquals(t1h.argInfos.toList, List((1, FunctionLiteral)))
+
+ val t2h = callIn("t2")
+ assertEquals(t2h.argInfos.toList, List((1, ForwardedParam(2))))
+
+ val t3h = callIn("t3")
+ assertEquals(t3h.argInfos.toList, List((1, FunctionLiteral)))
+
+ val selfSamCall = callIn("selfSamCall")
+ assertEquals(selfSamCall.argInfos.toList, List((0,ForwardedParam(0))))
+ }
+
+ @Test
+ def argInfoAfterInlining(): Unit = {
+ val code =
+ """class C {
+ | def foo(f: Int => Int) = f(1) // not inlined
+ | @inline final def bar(g: Int => Int) = foo(g) // forwarded param 1
+ | @inline final def baz = foo(x => x + 1) // literal
+ |
+ | def t1 = bar(x => x + 1) // call to foo should have argInfo literal
+ | def t2(x: Int, f: Int => Int) = x + bar(f) // call to foo should have argInfo forwarded param 2
+ | def t3 = baz // call to foo should have argInfo literal
+ | def someFun: Int => Int = null
+ | def t4(x: Int) = x + bar(someFun) // call to foo has empty argInfo
+ |}
+ """.stripMargin
+
+ compile(code)
+ def callIn(m: String) = callGraph.callsites.find(_._1.name == m).get._2.values.head
+ assertEquals(callIn("t1").argInfos.toList, List((1, FunctionLiteral)))
+ assertEquals(callIn("t2").argInfos.toList, List((1, ForwardedParam(2))))
+ assertEquals(callIn("t3").argInfos.toList, List((1, FunctionLiteral)))
+ assertEquals(callIn("t4").argInfos.toList, Nil)
}
}
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..f672237f10
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ClosureOptimizerTest.scala
@@ -0,0 +1,86 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.tools.asm.Opcodes._
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
+
+@RunWith(classOf[JUnit4])
+class ClosureOptimizerTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:classpath -opt-warnings:_"
+ import 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 c = compileClass(code)
+ val t = getAsmMethod(c, "t")
+ val bodyCall = findInstr(t, "INVOKESTATIC C.$anonfun$t$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 c = compileClass(code)
+ val t = getAsmMethod(c, "t")
+ val bodyCall = findInstr(t, "INVOKESTATIC C.$anonfun$t$1 ()Lscala/runtime/Null$")
+ assert(bodyCall.getNext.getOpcode == POP)
+ assert(bodyCall.getNext.getNext.getOpcode == ACONST_NULL)
+ }
+
+ @Test
+ def makeLMFCastExplicit(): Unit = {
+ val code =
+ """class C {
+ | def t(l: List[String]) = {
+ | val fun: String => String = s => s
+ | fun(l.head)
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameCode(getMethod(c, "t"),
+ List(VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "scala/collection/immutable/List", "head", "()Ljava/lang/Object;", false),
+ TypeOp(CHECKCAST, "java/lang/String"), Invoke(INVOKESTATIC, "C", "$anonfun$t$1", "(Ljava/lang/String;)Ljava/lang/String;", false),
+ Op(ARETURN)))
+ }
+
+ @Test
+ def closureOptWithUnreachableCode(): Unit = {
+ // this example used to crash the ProdCons analysis in the closure optimizer - ProdCons
+ // expects no unreachable code.
+ val code =
+ """class C {
+ | @inline final def m = throw new Error("")
+ | def t = {
+ | val f = (x: Int) => x + 1
+ | m
+ | f(10) // unreachable after inlining m
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameSummary(getMethod(c, "t"), List(NEW, DUP, LDC, "<init>", ATHROW))
+ }
+}
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 76492cfa23..6f54f170b5 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
@@ -2,23 +2,21 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
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 scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting._
+import scala.tools.testing.ClearAfterClass
@RunWith(classOf[JUnit4])
-class CompactLocalVariablesTest {
-
+class CompactLocalVariablesTest extends ClearAfterClass {
// 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,compact-locals")
- val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
+ val methodOptCompiler = cached("methodOptCompiler", () => newCompiler(extraArgs = "-opt:unreachable-code,compact-locals"))
+ val noCompactVarsCompiler = cached("noCompactVarsCompiler", () => newCompiler(extraArgs = "-opt:unreachable-code"))
@Test
def compactUnused(): Unit = {
@@ -58,8 +56,8 @@ class CompactLocalVariablesTest {
|}
|""".stripMargin
- val List(noCompact) = compileMethods(noCompactVarsCompiler)(code)
- val List(withCompact) = compileMethods(methodOptCompiler)(code)
+ val noCompact = noCompactVarsCompiler.compileAsmMethod(code)
+ val withCompact = methodOptCompiler.compileAsmMethod(code)
// code is the same, except for local var indices
assertTrue(noCompact.instructions.size == withCompact.instructions.size)
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 cb01f3d164..77215304fd 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyExceptionHandlersTest.scala
@@ -2,32 +2,23 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
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.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
-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 extends ClearAfterClass {
- ClearAfterClass.stateToClear = EmptyExceptionHandlersTest
+class EmptyExceptionHandlersTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:unreachable-code"
+ def dceCompiler = compiler
- val noOptCompiler = EmptyExceptionHandlersTest.noOptCompiler
- val dceCompiler = EmptyExceptionHandlersTest.dceCompiler
+ val noOptCompiler = cached("noOptCompiler", () => newCompiler(extraArgs = "-opt:l:none"))
val exceptionDescriptor = "java/lang/Exception"
@@ -69,8 +60,8 @@ class EmptyExceptionHandlersTest extends ClearAfterClass {
def eliminateUnreachableHandler(): Unit = {
val code = "def f: Unit = try { } catch { case _: Exception => println(0) }; println(1)"
- assertTrue(singleMethod(noOptCompiler)(code).handlers.length == 1)
- val optMethod = singleMethod(dceCompiler)(code)
+ assertTrue(noOptCompiler.compileMethod(code).handlers.length == 1)
+ val optMethod = dceCompiler.compileMethod(code)
assertTrue(optMethod.handlers.isEmpty)
val code2 =
@@ -82,7 +73,7 @@ class EmptyExceptionHandlersTest extends ClearAfterClass {
| println(2)
|}""".stripMargin
- assertTrue(singleMethod(dceCompiler)(code2).handlers.isEmpty)
+ assertTrue(dceCompiler.compileMethod(code2).handlers.isEmpty)
}
@Test
@@ -94,6 +85,6 @@ class EmptyExceptionHandlersTest extends ClearAfterClass {
| catch { case _: Exception => 2 }
|}""".stripMargin
- assertTrue(singleMethod(dceCompiler)(code).handlers.length == 1)
+ assertTrue(dceCompiler.compileMethod(code).handlers.length == 1)
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala
index 7283e20745..d57d44f2a3 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/EmptyLabelsAndLineNumbersTest.scala
@@ -2,16 +2,16 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
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.testing.AssertUtil._
-import CodeGenTools._
+import scala.tools.asm.Opcodes._
import scala.tools.partest.ASMConverters
-import ASMConverters._
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.AssertUtil._
+import scala.tools.testing.BytecodeTesting._
@RunWith(classOf[JUnit4])
class EmptyLabelsAndLineNumbersTest {
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..6f098e1432 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala
@@ -2,37 +2,33 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
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._
+import scala.collection.JavaConverters._
+import scala.collection.generic.Clearable
+import scala.tools.nsc.backend.jvm.BTypes.MethodInlineInfo
+import scala.tools.nsc.backend.jvm.BackendReporting._
+import scala.tools.testing.BytecodeTesting
-object InlineInfoTest extends ClearAfterClass.Clearable {
- var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath")
- def clear(): Unit = { compiler = null }
+@RunWith(classOf[JUnit4])
+class InlineInfoTest extends BytecodeTesting {
+ import compiler._
+ import global.genBCode.bTypes
- def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes)
- notPerRun foreach compiler.perRunCaches.unrecordCache
-}
+ override def compilerArgs = "-opt:l:classpath"
-@RunWith(classOf[JUnit4])
-class InlineInfoTest {
- val compiler = InlineInfoTest.compiler
+ def notPerRun: List[Clearable] = List(
+ bTypes.classBTypeFromInternalName,
+ bTypes.byteCodeRepository.compilingClasses,
+ bTypes.byteCodeRepository.parsedClasses)
+ notPerRun foreach global.perRunCaches.unrecordCache
def compile(code: String) = {
- InlineInfoTest.notPerRun.foreach(_.clear())
- compileClasses(compiler)(code)
+ notPerRun.foreach(_.clear())
+ compiler.compileClasses(code)
}
@Test
@@ -55,13 +51,30 @@ class InlineInfoTest {
|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 fromSyms = classes.map(c => global.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)
+ global.genBCode.bTypes.inlineInfoFromClassfile(c)
})
assert(fromSyms == fromAttrs)
}
+
+ @Test // scala-dev#20
+ def javaStaticMethodsInlineInfoInMixedCompilation(): Unit = {
+ val jCode =
+ """public class A {
+ | public static final int bar() { return 100; }
+ | public final int baz() { return 100; }
+ |}
+ """.stripMargin
+ compileClasses("class C { new A }", javaCode = List((jCode, "A.java")))
+ val info = global.genBCode.bTypes.classBTypeFromInternalName("A").info.get.inlineInfo
+ assertEquals(info.methodInfos, Map(
+ "bar()I" -> MethodInlineInfo(true,false,false),
+ "<init>()V" -> MethodInlineInfo(false,false,false),
+ "baz()I" -> MethodInlineInfo(true,false,false)))
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala
index 029caa995c..b1aa27fd27 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala
@@ -2,48 +2,21 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Test
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 }
-}
+
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
@RunWith(classOf[JUnit4])
-class InlineWarningTest extends ClearAfterClass {
- ClearAfterClass.stateToClear = InlineWarningTest
+class InlineWarningTest extends BytecodeTesting {
+ def optCp = "-opt:l:classpath"
+ override def compilerArgs = s"$optCp -opt-warnings"
- val compiler = InlineWarningTest.compiler
+ import compiler._
- def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
- compileClasses(compiler)(scalaCode, javaCode, allowMessage)
- }
+ val compilerWarnAll = cached("compilerWarnAll", () => newCompiler(extraArgs = s"$optCp -opt-warnings:_"))
@Test
def nonFinal(): Unit = {
@@ -62,37 +35,14 @@ class InlineWarningTest extends ClearAfterClass {
""".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 _)})
+ "C::m1()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.",
+ "T::m2()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.",
+ "D::m2()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.")
+ compileToBytes(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 {
@@ -103,11 +53,11 @@ class InlineWarningTest extends ClearAfterClass {
""".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"})
+ compileToBytes(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
+// @Test -- TODO
def mixedWarnings(): Unit = {
val javaCode =
"""public class A {
@@ -125,29 +75,29 @@ class InlineWarningTest extends ClearAfterClass {
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,
+ |Note that class A is defined in a Java source (mixed compilation), no bytecode is available.""".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)
+ |Note that class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin)
var c = 0
- val List(b) = compile(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.tail.exists(i.msg contains _)})
+ val List(b) = compileToBytes(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")))
+ newCompiler(extraArgs = s"$optCp -opt-warnings:none").compileToBytes(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 _)})
+ newCompiler(extraArgs = s"$optCp -opt-warnings:no-inline-mixed").compileToBytes(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.exists(i.msg contains _)})
assert(c == 2, c)
}
@Test
def cannotInlinePrivateCallIntoDifferentClass(): Unit = {
val code =
- """class M {
+ """class A {
| @inline final def f = {
| @noinline def nested = 0
| nested
@@ -156,18 +106,45 @@ class InlineWarningTest extends ClearAfterClass {
| def t = f // ok
|}
|
- |class N {
- | def t(a: M) = a.f // not possible
+ |class B {
+ | def t(a: A) = a.f // not possible
+ |}
+ """.stripMargin
+
+ val warn =
+ """A::f()I is annotated @inline but could not be inlined:
+ |The callee A::f()I contains the instruction INVOKESTATIC A.nested$1 ()I
+ |that would cause an IllegalAccessError when inlined into class B""".stripMargin
+
+ var c = 0
+ compileToBytes(code, allowMessage = i => { c += 1; i.msg contains warn })
+ assert(c == 1, c)
+ }
+
+ @Test
+ def dontWarnWhenNotIlnineAnnotated(): Unit = {
+ val code =
+ """class A {
+ | final def f(t: Int => Int) = {
+ | @noinline def nested = 0
+ | nested + t(1)
+ | }
+ | def t = f(x => x + 1)
+ |}
+ |
+ |class B {
+ | def t(a: A) = a.f(x => x + 1)
|}
""".stripMargin
+ compileToBytes(code, allowMessage = _ => false) // no warnings allowed
val warn =
- """M::f()I is annotated @inline but could not be inlined:
- |The callee M::f()I contains the instruction INVOKESPECIAL M.nested$1 ()I
- |that would cause an IllegalAccessError when inlined into class N""".stripMargin
+ """A::f(Lscala/Function1;)I could not be inlined:
+ |The callee A::f(Lscala/Function1;)I contains the instruction INVOKESTATIC A.nested$1 ()I
+ |that would cause an IllegalAccessError when inlined into class B""".stripMargin
var c = 0
- compile(code, allowMessage = i => { c += 1; i.msg contains warn })
+ compilerWarnAll.compileToBytes(code, allowMessage = i => { c += 1; i.msg contains warn })
assert(c == 1, c)
}
@@ -188,7 +165,42 @@ class InlineWarningTest extends ClearAfterClass {
|does not have the same strictfp mode as the callee C::f()I.""".stripMargin
var c = 0
- compile(code, allowMessage = i => { c += 1; i.msg contains warn })
+ compileToBytes(code, allowMessage = i => { c += 1; i.msg contains warn })
assert(c == 1, c)
}
+
+ @Test // scala-dev#20
+ def mixedCompilationSpuriousWarning(): Unit = {
+ val jCode =
+ """public class A {
+ | public static final int bar() { return 100; }
+ | public final int baz() { return 100; }
+ |}
+ """.stripMargin
+
+ val sCode =
+ """class C {
+ | @inline final def foo = A.bar()
+ | @inline final def fii(a: A) = a.baz()
+ | def t = foo + fii(new A)
+ |}
+ """.stripMargin
+
+ val warns = List(
+ """C::foo()I is annotated @inline but could not be inlined:
+ |Failed to check if C::foo()I can be safely inlined to C 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 class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin,
+
+ """C::fii(LA;)I is annotated @inline but could not be inlined:
+ |Failed to check if C::fii(LA;)I can be safely inlined to C without causing an IllegalAccessError. Checking instruction INVOKEVIRTUAL A.baz ()I failed:
+ |The method baz()I could not be found in the class A or any of its parents.
+ |Note that class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin
+ )
+ var c = 0
+ compileClasses(sCode, javaCode = List((jCode, "A.java")), allowMessage = i => { c += 1;
+ warns.exists(i.msg.contains)
+ })
+ assert(c == 2)
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
index 7ed0e13226..bf9da0f48f 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerIllegalAccessTest.scala
@@ -2,37 +2,25 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Test
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.collection.JavaConverters._
+import scala.tools.asm.Opcodes._
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 }
-}
+import scala.tools.nsc.backend.jvm.AsmUtils._
+import scala.tools.testing.BytecodeTesting
@RunWith(classOf[JUnit4])
-class InlinerIllegalAccessTest extends ClearAfterClass {
- ClearAfterClass.stateToClear = InlinerIllegalAccessTest
+class InlinerIllegalAccessTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:none"
- val compiler = InlinerIllegalAccessTest.compiler
- import compiler.genBCode.bTypes._
+ import compiler._
+ import global.genBCode.bTypes._
- def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.add(c, ByteCodeRepository.Classfile)
- def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins)
+ def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.add(c, None)
+ def assertEmpty(ins: List[AbstractInsnNode]) = for (i <- ins)
throw new AssertionError(textify(i))
@Test
@@ -40,7 +28,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass {
val code =
"""package a {
| private class C { // the Scala compiler makes all classes public
- | def f1 = new C // NEW a/C
+ | def f1 = new C // NEW a/C, INVOKESPECIAL a/C.<init> ()V
| def f2 = new Array[C](0) // ANEWARRAY a/C
| def f3 = new Array[Array[C]](0) // ANEWARRAY [La/C;
| }
@@ -51,23 +39,23 @@ class InlinerIllegalAccessTest extends ClearAfterClass {
|}
""".stripMargin
- val allClasses = compileClasses(compiler)(code)
+ val allClasses = compileClasses(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) = {
+ def check(classNode: ClassNode, test: List[AbstractInsnNode] => Unit) = {
for (m <- methods)
- test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(cClass.name), classBTypeFromParsedClassfile(classNode.name)).map(_._1))
+ test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(cClass.name), classBTypeFromParsedClassfile(classNode.name)).right.get)
}
check(cClass, assertEmpty)
check(dClass, assertEmpty)
check(eClass, assertEmpty) // C is public, so accessible in E
- byteCodeRepository.classes.clear()
+ byteCodeRepository.parsedClasses.clear()
classBTypeFromInternalName.clear()
cClass.access &= ~ACC_PUBLIC // ftw
@@ -77,7 +65,11 @@ class InlinerIllegalAccessTest extends ClearAfterClass {
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) => ()
+ case (ti: TypeInsnNode) :: is if Set("a/C", "[La/C;")(ti.desc) =>
+ is match {
+ case List(mi: MethodInsnNode) => assert(mi.owner == "a/C" && mi.name == "<init>")
+ case Nil =>
+ }
// MatchError otherwise
})
}
@@ -127,7 +119,7 @@ class InlinerIllegalAccessTest extends ClearAfterClass {
|}
""".stripMargin
- val allClasses = compileClasses(compiler)(code)
+ val allClasses = compileClasses(code)
val List(cCl, dCl, eCl, fCl, gCl, hCl, iCl) = allClasses
addToRepo(allClasses)
@@ -153,12 +145,12 @@ class InlinerIllegalAccessTest extends ClearAfterClass {
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))
+ def check(method: MethodNode, decl: ClassNode, dest: ClassNode, test: List[AbstractInsnNode] => Unit): Unit = {
+ test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(decl.name), classBTypeFromParsedClassfile(dest.name)).right.get)
}
- val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match {
- case Some(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => ()
+ val cOrDOwner = (_: List[AbstractInsnNode] @unchecked) match {
+ case List(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => ()
// MatchError otherwise
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
index 5c9bd1c188..9b1609a130 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
@@ -2,30 +2,18 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Test
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"
-}
+import scala.tools.testing.BytecodeTesting._
@RunWith(classOf[JUnit4])
class InlinerSeparateCompilationTest {
- import InlinerSeparateCompilationTest._
- import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke}
+ val args = "-opt:l:classpath"
@Test
- def inlnieMixedinMember(): Unit = {
+ def inlineMixedinMember(): Unit = {
val codeA =
"""trait T {
| @inline def f = 0
@@ -43,11 +31,11 @@ class InlinerSeparateCompilationTest {
|}
""".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"))
+ val warn = "T::f()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden."
+ val List(c, o, oMod, t) = compileClassesSeparately(List(codeA, codeB), args + " -opt-warnings", _.msg contains warn)
+ assertInvoke(getMethod(c, "t1"), "T", "f")
+ assertNoInvoke(getMethod(c, "t2"))
+ assertNoInvoke(getMethod(c, "t3"))
}
@Test
@@ -64,8 +52,8 @@ class InlinerSeparateCompilationTest {
|}
""".stripMargin
- val List(c, t, tCls) = compileClassesSeparately(List(codeA, codeB), args)
- assertNoInvoke(getSingleMethod(c, "t1"))
+ val List(c, t) = compileClassesSeparately(List(codeA, codeB), args)
+ assertNoInvoke(getMethod(c, "t1"))
}
@Test
@@ -87,8 +75,8 @@ class InlinerSeparateCompilationTest {
|}
""".stripMargin
- val List(c, t, tCls, u, uCls) = compileClassesSeparately(List(codeA, codeB), args)
- for (m <- List("t1", "t2", "t3")) assertNoInvoke(getSingleMethod(c, m))
+ val List(c, t, u) = compileClassesSeparately(List(codeA, codeB), args)
+ for (m <- List("t1", "t2", "t3")) assertNoInvoke(getMethod(c, m))
}
@Test
@@ -108,8 +96,8 @@ class InlinerSeparateCompilationTest {
|$assembly
""".stripMargin
- val List(a, aCls, t, tCls) = compileClassesSeparately(List(codeA, assembly), args)
- assertNoInvoke(getSingleMethod(tCls, "f"))
- assertNoInvoke(getSingleMethod(aCls, "n"))
+ val List(a, t) = compileClassesSeparately(List(codeA, assembly), args)
+ assertNoInvoke(getMethod(t, "f"))
+ assertNoInvoke(getMethod(a, "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
index 0309bb97cc..7be88816d5 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
@@ -2,72 +2,48 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.junit.Test
+
+import scala.collection.JavaConverters._
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.backend.jvm.BackendReporting._
import scala.tools.nsc.reporters.StoreReporter
-import scala.tools.testing.AssertUtil._
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
-import CodeGenTools._
-import scala.tools.partest.ASMConverters
-import ASMConverters._
-import AsmUtils._
-
-import BackendReporting._
-
-import scala.collection.convert.decorateAsScala._
-import scala.tools.testing.ClearAfterClass
+@RunWith(classOf[JUnit4])
+class InlinerTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:classpath -opt-warnings"
-object InlinerTest extends ClearAfterClass.Clearable {
- val args = "-Ybackend:GenBCode -Yopt:l:classpath -Yopt-warnings"
- var compiler = newCompiler(extraArgs = args)
+ val inlineOnlyCompiler = cached("inlineOnlyCompiler", () => newCompiler(extraArgs = "-opt:inline-project"))
+ import compiler._
+ import global.genBCode.bTypes
// 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 notPerRun: List[Clearable] = List(
+ bTypes.classBTypeFromInternalName,
+ bTypes.byteCodeRepository.compilingClasses,
+ bTypes.byteCodeRepository.parsedClasses,
+ bTypes.callGraph.callsites)
+ notPerRun foreach global.perRunCaches.unrecordCache
- def assertNoInvoke(m: Method): Unit = assertNoInvoke(m.instructions)
- def assertNoInvoke(ins: List[Instruction]): Unit = {
- assert(!ins.exists(_.isInstanceOf[Invoke]), ins.stringLines)
- }
+ import global.genBCode.bTypes.{byteCodeRepository, callGraph, inliner, inlinerHeuristics}
+ import inlinerHeuristics._
- 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)
+ notPerRun.foreach(_.clear())
+ compileToBytes(scalaCode, javaCode, allowMessage)
+ // Use the class nodes stored in the byteCodeRepository. The ones returned by compileClasses are not the same,
+ // these are created new from the classfile byte array. They are completely separate instances which cannot
+ // be used to look up methods / callsites in the callGraph hash maps for example.
+ byteCodeRepository.compilingClasses.valuesIterator.map(_._1).toList.sortBy(_.name)
}
def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = {
@@ -79,27 +55,25 @@ class InlinerTest extends ClearAfterClass {
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)
+ def getCallsite(method: MethodNode, calleeName: String) = callGraph.callsites(method).valuesIterator.find(_.callee.get.callee.name == calleeName).get
- 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()
+ def gMethAndFCallsite(code: String, mod: ClassNode => Unit = _ => ()) = {
+ val List(c) = compile(code)
+ mod(c)
+ val gMethod = getAsmMethod(c, "g")
+ val fCall = getCallsite(gMethod, "f")
+ (gMethod, fCall)
+ }
- val analyzer = new AsmAnalyzer(g, clsBType.internalName)
+ def canInlineTest(code: String, mod: ClassNode => Unit = _ => ()): Option[OptimizerWarning] = {
+ val cs = gMethAndFCallsite(code, mod)._2
+ inliner.earlyCanInlineCheck(cs) orElse inliner.canInlineCallsite(cs).map(_._1)
+ }
- val r = inliner.inline(
- fCall,
- analyzer.frameAt(fCall).getStackSize,
- g,
- clsBType,
- f,
- clsBType,
- receiverKnownNotNull = true,
- keepLineNumbers = true)
- (g, r)
+ def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): MethodNode = {
+ val (gMethod, fCall) = gMethAndFCallsite(code, mod)
+ inliner.inline(InlineRequest(fCall, Nil, null))
+ gMethod
}
@Test
@@ -111,10 +85,10 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val (g, _) = inlineTest(code)
+ val g = inlineTest(code)
val gConv = convertMethod(g)
- assertSameCode(gConv.instructions.dropNonOp,
+ assertSameCode(gConv,
List(
VarOp(ALOAD, 0), VarOp(ASTORE, 1), // store this
Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(10)), // store return value
@@ -145,16 +119,23 @@ class InlinerTest extends ClearAfterClass {
// 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 ???
+ val g = inlineTest(code)
+
+ val invokeQQQ = List(
+ Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"),
+ Invoke(INVOKEVIRTUAL, "scala/Predef$", "$qmark$qmark$qmark", "()Lscala/runtime/Nothing$;", false))
+
+ val gBeforeLocalOpt = VarOp(ALOAD, 0) :: VarOp(ASTORE, 1) :: invokeQQQ ::: List(
+ VarOp(ASTORE, 2),
+ Jump(GOTO, Label(11)),
+ Label(11),
+ VarOp(ALOAD, 2),
+ Op(ATHROW))
- assertSameCode(convertMethod(g).instructions.dropNonOp.take(4), expectedInlined)
+ assertSameCode(convertMethod(g), gBeforeLocalOpt)
- compiler.genBCode.bTypes.localOpt.methodOptimizations(g, "C")
- assertSameCode(convertMethod(g).instructions.dropNonOp,
- expectedInlined ++ List(VarOp(ASTORE, 2), VarOp(ALOAD, 2), Op(ATHROW)))
+ global.genBCode.bTypes.localOpt.methodOptimizations(g, "C")
+ assertSameCode(convertMethod(g), invokeQQQ :+ Op(ATHROW))
}
@Test
@@ -166,11 +147,11 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val (_, can) = inlineTest(code, cls => {
- val f = cls.methods.asScala.find(_.name == "f").get
+ val can = canInlineTest(code, cls => {
+ val f = getAsmMethod(cls, "f")
f.access |= ACC_SYNCHRONIZED
})
- assert(can.get.isInstanceOf[SynchronizedMethod], can)
+ assert(can.nonEmpty && can.get.isInstanceOf[SynchronizedMethod], can)
}
@Test
@@ -181,7 +162,7 @@ class InlinerTest extends ClearAfterClass {
| def g = f + 1
|}
""".stripMargin
- val (_, r) = inlineTest(code)
+ val r = canInlineTest(code)
assert(r.isEmpty, r)
}
@@ -195,8 +176,8 @@ class InlinerTest extends ClearAfterClass {
| def g = println(f)
|}
""".stripMargin
- val (_, r) = inlineTest(code)
- assert(r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r)
+ val r = canInlineTest(code)
+ assert(r.nonEmpty && r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r)
}
@Test
@@ -216,29 +197,10 @@ class InlinerTest extends ClearAfterClass {
""".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)
+ val hMeth = getAsmMethod(d, "h")
+ val gCall = getCallsite(hMeth, "g")
+ val r = inliner.canInlineCallsite(gCall)
+ assert(r.nonEmpty && r.get._1.isInstanceOf[IllegalAccessInstruction], r)
}
@Test
@@ -252,7 +214,7 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
val List(cCls) = compile(code)
- val instructions = getSingleMethod(cCls, "test").instructions
+ val instructions = getInstructions(cCls, "test")
assert(instructions.contains(Op(ICONST_0)), instructions.stringLines)
assert(!instructions.contains(Op(ICONST_1)), instructions)
}
@@ -273,7 +235,7 @@ class InlinerTest extends ClearAfterClass {
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)) {
+ for (callsite <- callGraph.callsites.valuesIterator.flatMap(_.valuesIterator) if methods.contains(callsite.callsiteMethod)) {
checkCallsite(callsite, g)
}
}
@@ -295,8 +257,8 @@ class InlinerTest extends ClearAfterClass {
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)) {
+ assert(callGraph.callsites.valuesIterator.flatMap(_.valuesIterator).size == 7, callGraph.callsites)
+ for (callsite <- callGraph.callsites.valuesIterator.flatMap(_.valuesIterator) if methods.contains(callsite.callsiteMethod)) {
checkCallsite(callsite, g)
}
}
@@ -318,7 +280,7 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
val List(c, _, _) = compile(code)
- val ins = getSingleMethod(c, "f").instructions
+ val ins = getInstructions(c, "f")
val invokeSysArraycopy = Invoke(INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false)
assert(ins contains invokeSysArraycopy, ins.stringLines)
}
@@ -336,7 +298,7 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
val List(c) = compile(code)
- assert(callGraph.callsites.values exists (_.callsiteInstruction.name == "clone"))
+ assert(callGraph.callsites.valuesIterator.flatMap(_.valuesIterator) exists (_.callsiteInstruction.name == "clone"))
}
@Test
@@ -349,8 +311,8 @@ class InlinerTest extends ClearAfterClass {
| def g(t: T) = t.f
|}
""".stripMargin
- val List(c, t, tClass) = compile(code)
- assertNoInvoke(getSingleMethod(c, "g"))
+ val List(c, t) = compile(code)
+ assertNoInvoke(getMethod(c, "g"))
}
@Test
@@ -363,7 +325,7 @@ class InlinerTest extends ClearAfterClass {
""".stripMargin
val List(c) = compile(code)
// no more invoke, f is inlined
- assertNoInvoke(getSingleMethod(c, "g"))
+ assertNoInvoke(getMethod(c, "g"))
}
@Test
@@ -375,26 +337,14 @@ class InlinerTest extends ClearAfterClass {
""".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)
+ val fMeth = getAsmMethod(c, "f")
+ val call = getCallsite(fMeth, "lowestOneBit")
- assert(r.isEmpty, r)
- val ins = instructionsFromMethod(f)
+ val warning = inliner.canInlineCallsite(call)
+ assert(warning.isEmpty, warning)
+
+ inliner.inline(InlineRequest(call, Nil, null))
+ val ins = instructionsFromMethod(fMeth)
// no invocations, lowestOneBit is inlined
assertNoInvoke(ins)
@@ -425,7 +375,8 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val List(c) = compile(code)
+ // use a compiler without local optimizations (cleanups)
+ val c = inlineOnlyCompiler.compileClass(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
@@ -465,12 +416,12 @@ class InlinerTest extends ClearAfterClass {
"""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
+ |Note that class A is defined in a Java source (mixed compilation), no bytecode is available.""".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 ins = getInstructions(b, "g")
val invokeFlop = Invoke(INVOKEVIRTUAL, "B", "flop", "()I", false)
assert(ins contains invokeFlop, ins.stringLines)
}
@@ -488,23 +439,10 @@ class InlinerTest extends ClearAfterClass {
| def t2(c: C) = c.f
|}
""".stripMargin
- val List(c, t, tClass) = compile(code)
+ val List(c, t) = 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 implementation method is inlined into the mixin, so there's no invocation in the mixin
- assertNoInvoke(getSingleMethod(c, "f"))
+ assertNoInvoke(getMethod(c, "t1"))
+ assertNoInvoke(getMethod(c, "t2"))
}
@Test
@@ -521,9 +459,9 @@ class InlinerTest extends ClearAfterClass {
| def t2 = g
|}
""".stripMargin
- val List(c, t, tClass, u, uClass) = compile(code)
- assertNoInvoke(getSingleMethod(c, "t1"))
- assertNoInvoke(getSingleMethod(c, "t2"))
+ val List(c, t, u) = compile(code)
+ assertNoInvoke(getMethod(c, "t1"))
+ assertNoInvoke(getMethod(c, "t2"))
}
@Test
@@ -537,14 +475,12 @@ class InlinerTest extends ClearAfterClass {
| 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")
+ val warn = "::f()I is annotated @inline but could not be inlined:\nThe 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 _)})
+ val List(c, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
assert(count == 2, count)
- assertInvoke(getSingleMethod(c, "t1"), "T", "f")
- assertInvoke(getSingleMethod(c, "t2"), "C", "f")
+ assertInvoke(getMethod(c, "t1"), "T", "f")
+ assertInvoke(getMethod(c, "t2"), "C", "f")
}
@Test
@@ -557,8 +493,8 @@ class InlinerTest extends ClearAfterClass {
| def t1(t: T) = t.f
|}
""".stripMargin
- val List(c, t, tClass) = compile(code)
- assertNoInvoke(getSingleMethod(c, "t1"))
+ val List(c, t) = compile(code)
+ assertNoInvoke(getMethod(c, "t1"))
}
@Test
@@ -569,7 +505,7 @@ class InlinerTest extends ClearAfterClass {
|}
|object O extends T {
| @inline def g = 1
- | // mixin generates `def f = T$class.f(this)`, which is inlined here (we get ICONST_0)
+ | // mixin generates `def f = super[T].f`, 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
@@ -577,16 +513,16 @@ class InlinerTest extends ClearAfterClass {
| 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"
+ val warn = "T::f()I is annotated @inline but could not be inlined:\nThe 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})
+ val List(c, oMirror, oModule, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
assert(count == 1, count)
- assertNoInvoke(getSingleMethod(oModule, "f"))
+ assertNoInvoke(getMethod(t, "f"))
- assertNoInvoke(getSingleMethod(c, "t1"))
- assertNoInvoke(getSingleMethod(c, "t2"))
- assertInvoke(getSingleMethod(c, "t3"), "T", "f")
+ assertNoInvoke(getMethod(c, "t1"))
+ assertNoInvoke(getMethod(c, "t2"))
+ assertInvoke(getMethod(c, "t3"), "T", "f")
}
@Test
@@ -598,24 +534,22 @@ class InlinerTest extends ClearAfterClass {
|}
|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.
+ | @inline final def n = m // inlined (m is final)
|}
|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 t1(a: Assembly) = a.f // inlined (f is final)
| def t2(a: Assembly) = a.n
|}
""".stripMargin
- val List(assembly, assemblyClass, c, t, tClass) = compile(code)
+ val List(assembly, c, t) = compile(code)
- assertNoInvoke(getSingleMethod(tClass, "f"))
+ assertNoInvoke(getMethod(t, "f"))
- assertNoInvoke(getSingleMethod(assemblyClass, "n"))
+ assertNoInvoke(getMethod(assembly, "n"))
- assertNoInvoke(getSingleMethod(c, "t1"))
- assertNoInvoke(getSingleMethod(c, "t2"))
+ assertNoInvoke(getMethod(c, "t1"))
+ assertNoInvoke(getMethod(c, "t2"))
}
@Test
@@ -647,30 +581,30 @@ class InlinerTest extends ClearAfterClass {
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
+ | @inline def g1 = f // not inlined: f not final
|}
|
- |// erased self-type (used in impl class for `self` parameter): T1
+ |// erased self-type: 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
+ | @inline def g2a = f // inlined: resolved as T2a.f
|}
|
|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.
+ | // mixin generates accessors like `def g1 = super[T1].g1`, the impl super 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
+ | def m4a(t: T1) = t.f // T1.f is not final, so not inlined, we get an interface call T1.f
+ | def m5a(t: T2a) = t.f // inlined, we get 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
+ | @inline def g2b = f // not inlined: resolved as T1.f, we get an interface call T1.f
|}
|
|final class Cb extends T1 with T2b {
@@ -679,35 +613,29 @@ class InlinerTest extends ClearAfterClass {
| 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
+ | def m5b(t: T2b) = t.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"
+ val warning = "T1::f()I is annotated @inline but could not be inlined:\nThe 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})
+ val List(ca, cb, t1, t2a, t2b) = 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")
+ assertNoInvoke(getMethod(t2a, "g2a"))
+ assertInvoke(getMethod(t2b, "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(getMethod(ca, "m1a"), "T1", "f")
+ assertNoInvoke(getMethod(ca, "m2a")) // no invoke, see comment on def g2a
+ assertNoInvoke(getMethod(ca, "m3a"))
+ assertInvoke(getMethod(ca, "m4a"), "T1", "f")
+ assertNoInvoke(getMethod(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"))
+ assertInvoke(getMethod(cb, "m1b"), "T1", "f")
+ assertInvoke(getMethod(cb, "m2b"), "T1", "f") // invoke, see comment on def g2b
+ assertNoInvoke(getMethod(cb, "m3b"))
+ assertInvoke(getMethod(cb, "m4b"), "T1", "f")
+ assertNoInvoke(getMethod(cb, "m5b"))
}
@Test
@@ -724,7 +652,7 @@ class InlinerTest extends ClearAfterClass {
|} // 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"))
+ assertNoInvoke(getMethod(t, "t1"))
}
@Test
@@ -732,16 +660,15 @@ class InlinerTest extends ClearAfterClass {
val code =
"""class C {
| trait T { @inline final def f = 1 }
- | class D extends T{
+ | 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"))
+ val List(c, d, t) = compile(code)
+ assertNoInvoke(getMethod(d, "m"))
+ assertNoInvoke(getMethod(c, "m"))
}
@Test
@@ -754,9 +681,9 @@ class InlinerTest extends ClearAfterClass {
| 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 List(c, t) = compile(code)
+ val t1 = getMethod(t, "t1")
+ val t2 = getMethod(t, "t2")
val cast = TypeOp(CHECKCAST, "C")
Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions))
}
@@ -771,7 +698,7 @@ class InlinerTest extends ClearAfterClass {
| 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"
+ val warn = "C::foo()I is annotated @inline but could not be inlined:\nThe 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)
@@ -799,12 +726,12 @@ class InlinerTest extends ClearAfterClass {
"""sealed trait T {
| lazy val a = 0
| val b = 1
- | final lazy val c = 2
+ | final lazy val c: Int = 2 // make sure it doesn't get a constant type
| 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 f = 5
+ | @noinline final def g = 6
|
| @noinline def h: Int
| @inline def i: Int
@@ -813,12 +740,12 @@ class InlinerTest extends ClearAfterClass {
|trait U { // not sealed
| lazy val a = 0
| val b = 1
- | final lazy val c = 2
+ | final lazy val c: Int = 2 // make sure it doesn't get a constant type
| 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 f = 5
+ | @noinline final def g = 6
|
| @noinline def h: Int
| @inline def i: Int
@@ -835,30 +762,30 @@ class InlinerTest extends ClearAfterClass {
|}
""".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")
+ val List(c, t, u) = compile(code, allowMessage = _.msg contains "::i()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.")
+ val m1 = getMethod(c, "m1")
assertInvoke(m1, "T", "a")
assertInvoke(m1, "T", "b")
- assertInvoke(m1, "T", "c")
+// assertInvoke(m1, "T", "c") -- this lazy val is implemented purely in the trait, as it's constant, so it *can* be inlined
- assertNoInvoke(getSingleMethod(c, "m2"))
+ assertNoInvoke(getMethod(c, "m2"))
- val m3 = getSingleMethod(c, "m3")
- assertInvoke(m3, "T$class", "f")
- assertInvoke(m3, "T$class", "g")
+ val m3 = getMethod(c, "m3")
+ assertInvoke(m3, "T", "f")
+ assertInvoke(m3, "T", "g")
assertInvoke(m3, "T", "h")
assertInvoke(m3, "T", "i")
- val m4 = getSingleMethod(c, "m4")
+ val m4 = getMethod(c, "m4")
assertInvoke(m4, "U", "a")
assertInvoke(m4, "U", "b")
- assertInvoke(m4, "U", "c")
+// assertInvoke(m4, "U", "c") -- this lazy val is implemented purely in the trait, as it's constant, so it *can* be inlined
- assertNoInvoke(getSingleMethod(c, "m5"))
+ assertNoInvoke(getMethod(c, "m5"))
- val m6 = getSingleMethod(c, "m6")
+ val m6 = getMethod(c, "m6")
assertInvoke(m6, "U", "f")
- assertInvoke(m6, "U$class", "g")
+ assertInvoke(m6, "U", "g")
assertInvoke(m6, "U", "h")
assertInvoke(m6, "U", "i")
}
@@ -892,11 +819,11 @@ class InlinerTest extends ClearAfterClass {
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
+ |Note that class A$Inner could not be found on the classpath.""".stripMargin
var c = 0
- compileClasses(newCompiler(extraArgs = InlinerTest.args + " -Yopt-warnings:_"))(
+ newCompiler(extraArgs = compilerArgs + " -opt-warnings:_").compileClasses(
scalaCode,
List((javaCode, "A.java")),
allowMessage = i => {c += 1; i.msg contains warn})
@@ -906,11 +833,11 @@ class InlinerTest extends ClearAfterClass {
@Test
def inlineInvokeSpecial(): Unit = {
val code =
- """class Aa {
+ """class A {
| def f1 = 0
|}
- |class B extends Aa {
- | @inline final override def f1 = 1 + super.f1 // invokespecial Aa.f1
+ |class B extends A {
+ | @inline final override def f1 = 1 + super.f1 // invokespecial A.f1
|
| private def f2m = 0 // public B$$f2m in bytecode
| @inline final def f2 = f2m // invokevirtual B.B$$f2m
@@ -934,21 +861,21 @@ class InlinerTest extends ClearAfterClass {
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
+ |The callee B::f1()I contains the instruction INVOKESPECIAL A.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(getMethod(b, "t1"), "A", "f1")
+ assertInvoke(getMethod(b, "t2"), "B", "B$$f2m")
+ assertInvoke(getMethod(b, "t3"), "B", "<init>")
+ assertInvoke(getMethod(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>")
+ assertInvoke(getMethod(t, "t1"), "B", "f1")
+ assertInvoke(getMethod(t, "t2"), "B", "B$$f2m")
+ assertInvoke(getMethod(t, "t3"), "B", "<init>")
+ assertInvoke(getMethod(t, "t4"), "B", "<init>")
}
@Test
@@ -958,8 +885,8 @@ class InlinerTest extends ClearAfterClass {
| def t = System.arraycopy(null, 0, null, 0, 0)
|}
""".stripMargin
- val List(c) = compileClasses(newCompiler(extraArgs = InlinerTest.args + " -Yopt-inline-heuristics:everything"))(code)
- assertInvoke(getSingleMethod(c, "t"), "java/lang/System", "arraycopy")
+ val c = newCompiler(extraArgs = compilerArgs + " -Yopt-inline-heuristics:everything").compileClass(code)
+ assertInvoke(getMethod(c, "t"), "java/lang/System", "arraycopy")
}
@Test
@@ -973,7 +900,7 @@ class InlinerTest extends ClearAfterClass {
""".stripMargin
val List(c) = compile(code)
- assertInvoke(getSingleMethod(c, "t"), "java/lang/Error", "<init>")
+ assertInvoke(getMethod(c, "t"), "java/lang/Error", "<init>")
}
@Test
@@ -986,9 +913,844 @@ class InlinerTest extends ClearAfterClass {
""".stripMargin
val List(c) = compile(code)
- val t = getSingleMethod(c, "t").instructions
+ val t = getInstructions(c, "t")
assertNoInvoke(t)
- assert(2 == t.collect({case Ldc(_, "hai!") => }).size) // twice the body of f
+ assert(1 == t.collect({case Ldc(_, "hai!") => }).size) // push-pop eliminates the first LDC("hai!")
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 = getMethod(c, "t1")
+ assertNoIndy(t1)
+ // the indy call is inlined into t, and the closure elimination rewrites the closure invocation to the body method
+ assertInvoke(t1, "C", "$anonfun$m$2")
+
+ val t2 = getMethod(c, "t2")
+ assertNoIndy(t2)
+ assertInvoke(t2, "M$", "$anonfun$m$1")
+ }
+
+ @Test
+ def inlinePostRequests(): Unit = {
+ val code =
+ """class C {
+ | final def f = 10
+ | final def g = f + 19
+ | final def h = g + 29
+ | final def i = h + 39
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ val hMeth = getAsmMethod(c, "h")
+ val gMeth = getAsmMethod(c, "g")
+ val iMeth = getAsmMethod(c, "i")
+ val fCall = getCallsite(gMeth, "f")
+ val gCall = getCallsite(hMeth, "g")
+ val hCall = getCallsite(iMeth, "h")
+
+ val warning = inliner.canInlineCallsite(gCall)
+ assert(warning.isEmpty, warning)
+
+ inliner.inline(InlineRequest(hCall,
+ post = List(InlineRequest(gCall,
+ post = List(InlineRequest(fCall, Nil, null)), null)), null))
+ assertNoInvoke(convertMethod(iMeth)) // no invoke in i: first h is inlined, then the inlined call to g is also inlined, etc for f
+ assertInvoke(convertMethod(gMeth), "C", "f") // g itself still has the call to f
+ }
+
+ @Test
+ def postRequestSkipAlreadyInlined(): Unit = {
+ val code =
+ """class C {
+ | final def a = 10
+ | final def b = a + 20
+ | final def c = b + 30
+ | final def d = c + 40
+ |}
+ """.stripMargin
+
+ val List(cl) = compile(code)
+ val List(b, c, d) = List("b", "c", "d").map(getAsmMethod(cl, _))
+ val aCall = getCallsite(b, "a")
+ val bCall = getCallsite(c, "b")
+ val cCall = getCallsite(d, "c")
+
+ inliner.inline(InlineRequest(bCall, Nil, null))
+
+ val req = InlineRequest(cCall,
+ List(InlineRequest(bCall,
+ List(InlineRequest(aCall, Nil, null)), null)), null)
+ inliner.inline(req)
+
+ assertNoInvoke(convertMethod(d))
+ }
+
+ @Test
+ def inlineAnnotatedCallsite(): Unit = {
+ val code =
+ """class C {
+ | final def a(x: Int, f: Int => Int): Int = f(x)
+ | final def b(x: Int) = x
+ | final def c = 1
+ | final def d[T] = 2
+ | final def e[T](x: T) = c
+ | final def f[T](x: T) = println(x)
+ | final def g(x: Int)(y: Int) = x
+ |
+ | def t1 = a(10, _ + 1)
+ | def t2 = a(10, _ + 1): @noinline
+ | def t3 = b(3)
+ | def t4 = b(3): @inline
+ | def t5 = c: @inline
+ | def t6 = d[Int]: @inline
+ | def t7 = e[Int](2): @inline
+ | def t8 = f[Int](2): @inline
+ | def t9 = g(1)(2): @inline
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ assertInvoke(getMethod(c, "t1"), "C", "$anonfun$t1$1")
+ assertInvoke(getMethod(c, "t2"), "C", "a")
+ assertInvoke(getMethod(c, "t3"), "C", "b")
+ assertNoInvoke(getMethod(c, "t4"))
+ assertNoInvoke(getMethod(c, "t5"))
+ assertNoInvoke(getMethod(c, "t6"))
+ assertInvoke(getMethod(c, "t7"), "C", "c")
+ assertInvoke(getMethod(c, "t8"), "scala/Predef$", "println")
+ assertNoInvoke(getMethod(c, "t9"))
+ }
+
+ @Test
+ def inlineNoInlineOverride(): Unit = {
+ val code =
+ """class C {
+ | @inline final def f1(x: Int) = x
+ | @noinline final def f2(x: Int) = x
+ | final def f3(x: Int) = x
+ |
+ | def t1 = f1(1) // inlined
+ | def t2 = f2(1) // not inlined
+ | def t3 = f1(1): @noinline // not inlined
+ | def t4 = f2(1): @inline // inlined
+ | def t5 = f3(1): @inline // inlined
+ | def t6 = f3(1): @noinline // not inlined
+ |
+ | def t7 = f1(1) + (f3(1): @inline) // without parenthesis, the ascription encloses the entire expression..
+ | def t8 = f1(1) + (f1(1): @noinline)
+ | def t9 = f1(1) + f1(1) : @noinline // the ascription goes on the entire expression, so on the + invocation.. both f1 are inlined
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ assertNoInvoke(getMethod(c, "t1"))
+ assertInvoke(getMethod(c, "t2"), "C", "f2")
+ assertInvoke(getMethod(c, "t3"), "C", "f1")
+ assertNoInvoke(getMethod(c, "t4"))
+ assertNoInvoke(getMethod(c, "t5"))
+ assertInvoke(getMethod(c, "t6"), "C", "f3")
+ assertNoInvoke(getMethod(c, "t7"))
+ assertInvoke(getMethod(c, "t8"), "C", "f1")
+ assertNoInvoke(getMethod(c, "t9"))
+ }
+
+ @Test
+ def inlineHigherOrder(): Unit = {
+ val code =
+ """class C {
+ | final def h(f: Int => Int): Int = f(0)
+ | def t1 = h(x => x + 1)
+ | def t2 = {
+ | val fun = (x: Int) => x + 1
+ | h(fun)
+ | }
+ | def t3(f: Int => Int) = h(f)
+ | def t4(f: Int => Int) = {
+ | val fun = f
+ | h(fun)
+ | }
+ | def t5 = h(Map(0 -> 10)) // not currently inlined
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ assertInvoke(getMethod(c, "t1"), "C", "$anonfun$t1$1")
+ assertInvoke(getMethod(c, "t2"), "C", "$anonfun$t2$1")
+ assertInvoke(getMethod(c, "t3"), "scala/Function1", "apply$mcII$sp")
+ assertInvoke(getMethod(c, "t4"), "scala/Function1", "apply$mcII$sp")
+ assertInvoke(getMethod(c, "t5"), "C", "h")
+ }
+
+ @Test
+ def twoStepNoInlineHandler(): Unit = {
+ val code =
+ """class C {
+ | @inline final def f = try 1 catch { case _: Throwable => 2 }
+ | @inline final def g = f
+ | def t = println(g) // cannot inline g onto non-empty stack once that f was inlined into g
+ |}
+ """.stripMargin
+
+ val warn =
+ """C::g()I is annotated @inline but could not be inlined:
+ |The operand stack at the callsite in C::t()V contains more values than the
+ |arguments expected by the callee C::g()I. These values would be discarded
+ |when entering an exception handler declared in the inlined method.""".stripMargin
+
+ val List(c) = compile(code, allowMessage = _.msg contains warn)
+ assertInvoke(getMethod(c, "t"), "C", "g")
+ }
+
+ @Test
+ def twoStepNoInlinePrivate(): Unit = {
+ val code =
+ """class C {
+ | @inline final def g = {
+ | @noinline def f = 0
+ | f
+ | }
+ | @inline final def h = g // after inlining g, h has an invocate of private method f$1
+ |}
+ |class D {
+ | def t(c: C) = c.h // cannot inline
+ |}
+ """.stripMargin
+
+ val warn =
+ """C::h()I is annotated @inline but could not be inlined:
+ |The callee C::h()I contains the instruction INVOKESTATIC C.f$1 ()I
+ |that would cause an IllegalAccessError when inlined into class D.""".stripMargin
+
+ val List(c, d) = compile(code, allowMessage = _.msg contains warn)
+ assertInvoke(getMethod(c, "h"), "C", "f$1")
+ assertInvoke(getMethod(d, "t"), "C", "h")
+ }
+
+ @Test
+ def twoStepInlinePrivate(): Unit = {
+ val code =
+ """class C {
+ | @inline final def g = { // initially, g invokes the private method f$1, but then f$1 is inlined
+ | @inline def f = 0
+ | f
+ | }
+ |}
+ |class D {
+ | def t(c: C) = c.g // can inline
+ |}
+ """.stripMargin
+
+ val List(c, d) = compile(code)
+ assertNoInvoke(getMethod(c, "g"))
+ assertNoInvoke(getMethod(d, "t"))
+ }
+
+ @Test
+ def optimizeSpecializedClosures(): Unit = {
+ val code =
+ """class ValKl(val x: Int) extends AnyVal
+ |
+ |class C {
+ | def t1 = {
+ | // IndyLambda: SAM type is JFunction1$mcII$sp, SAM is apply$mcII$sp(I)I, body method is $anonfun(I)I
+ | val f = (x: Int) => x + 1
+ | // invocation of apply$mcII$sp(I)I, matches the SAM in IndyLambda. no boxing / unboxing needed.
+ | f(10)
+ | // opt: re-write the invocation to the body method
+ | }
+ |
+ | @inline final def m1a(f: Long => Int) = f(1l)
+ | def t1a = m1a(l => l.toInt) // after inlining m1a, we have the same situation as in t1
+ |
+ | def t2 = {
+ | // there is no specialized variant of Function2 for this combination of types, so the IndyLambda has to create a generic Function2.
+ | // IndyLambda: SAM type is JFunction2, SAM is apply(ObjectObject)Object, body method is $anonfun$adapted(ObjectObject)Object
+ | val f = (b: Byte, i: Int) => i + b
+ | // invocation of apply(ObjectOjbect)Object, matches SAM in IndyLambda. arguments are boxed, result unboxed.
+ | f(1, 2)
+ | // opt: re-wrtie to $anonfun$adapted
+ | // inline that call, then we get box-unbox pairs (can be eliminated) and a call to $anonfun(BI)I
+ | }
+ |
+ | def t3 = {
+ | // similar to t2: for functions with value class parameters, IndyLambda always uses the generic Function version.
+ | // IndyLambda: SAM type is JFunction1, SAM is apply(Object)Object, body method is $anonfun$adapted(Object)Object
+ | val f = (a: ValKl) => a
+ | // invocation of apply(Object)Object, ValKl instance is created, result extracted
+ | f(new ValKl(1))
+ | // opt: re-write to $anonfun$adapted.
+ | // inline that call, then we get value class instantiation-extraction pairs and a call to $anonfun(I)I
+ | }
+ |
+ | def t4 = {
+ | // IndyLambda: SAM type is JFunction1$mcII$sp, SAM is apply$mcII$sp(I)I, body method is $anonfun(I)I
+ | val f: Int => Any = (x: Int) => 1
+ | // invocation of apply(Object)Object, argument is boxed. method name and type doesn't match IndyLambda.
+ | f(10)
+ | // opt: rewriting to the body method requires inserting an unbox operation for the argument, and a box operation for the result
+ | // that produces a box-unbox pair and a call to $anonfun(I)I
+ | }
+ |
+ |
+ | @inline final def m4a[T, U, V](f: (T, U) => V, x: T, y: U) = f(x, y) // invocation to generic apply(ObjectObject)Object
+ | def t4a = m4a((x: Int, y: Double) => 1l + x + y.toLong, 1, 2d) // IndyLambda uses specilized JFunction2$mcJID$sp. after inlining m4a, similar to t4.
+ |
+ | def t5 = {
+ | // no specialization for the comibnation of primitives
+ | // IndyLambda: SAM type is JFunction2, SAM is generic apply, body method is $anonfun$adapted
+ | val f: (Int, Byte) => Any = (x: Int, b: Byte) => 1
+ | // invocation of generic apply.
+ | f(10, 3)
+ | // opt: re-write to $anonfun$adapted, inline that method. generates box-unbox pairs and a call to $anonfun(IB)I
+ | }
+ |
+ | def t5a = m4a((x: Int, y: Byte) => 1, 12, 31.toByte) // similar to t5 after inlining m4a
+ |
+ | // m6$mIVc$sp invokes apply$mcVI$sp
+ | @inline final def m6[@specialized(Int) T, @specialized(Unit) U](f: T => U, x: T): Unit = f(x)
+ | // IndyLambda: JFunction1$mcVI$sp, SAM is apply$mcVI$sp, body method $anonfun(I)V
+ | // invokes m6$mIVc$sp (Lscala/Function1;I)V
+ | def t6 = m6((x: Int) => (), 10)
+ | // opt: after inlining m6, the closure method invocation (apply$mcVI$sp) matches the IndyLambda, the call can be rewritten, no boxing
+ |
+ | // m7 invokes apply
+ | @inline final def m7[@specialized(Boolean) T, @specialized(Int) U](f: T => U, x: T): Unit = f(x)
+ | // IndyLambda: JFunction1, SAM is apply(Object)Object, body method is $anonfun$adapted(Obj)Obj
+ | // `true` is boxed before passing to m7
+ | def t7 = m7((x: Boolean) => (), true)
+ | // opt: after inlining m7, the apply call is re-written to $anonfun$adapted, which is then inlined.
+ | // we get a box-unbox pair and a call to $anonfun(Z)V
+ |
+ |
+ | // invokes the generic apply(ObjObj)Obj
+ | @inline final def m8[T, U, V](f: (T, U) => V, x: T, y: U) = f(x, y)
+ | // IndyLambda: JFunction2$mcJID$sp, SAM is apply$mcJID$sp, body method $anonfun(ID)J
+ | // boxes the int and double arguments and calls m8, unboxToLong the result
+ | def t8 = m8((x: Int, y: Double) => 1l + x + y.toLong, 1, 2d)
+ | // opt: after inlining m8, rewrite to the body method $anonfun(ID)J, which requires inserting unbox operations for the params, box for the result
+ | // the box-unbox pairs can then be optimized away
+ |
+ | // m9$mVc$sp invokes apply$mcVI$sp
+ | @inline final def m9[@specialized(Unit) U](f: Int => U): Unit = f(1)
+ | // IndyLambda: JFunction1, SAM is apply(Obj)Obj, body method $anonfun$adapted(Ojb)Obj
+ | // invocation of m9$mVc$sp
+ | def t9 = m9(println)
+ | // opt: after inlining m9, rewrite to $anonfun$adapted(Ojb)Obj, which requires inserting a box operation for the parameter.
+ | // then we inline $adapted, which has signature (Obj)V. the `BoxedUnit.UNIT` from the body of $anonfun$adapted is eliminated by push-pop
+ |
+ | def t9a = (1 to 10) foreach println // similar to t9
+ |
+ | def intCons(i: Int): Unit = ()
+ | // IndyLambda: JFunction1$mcVI$sp, SAM is apply$mcVI$sp, body method $anonfun(I)V
+ | def t10 = m9(intCons)
+ | // after inlining m9, rewrite the apply$mcVI$sp call to the body method, no adaptations required
+ |
+ | def t10a = (1 to 10) foreach intCons // similar to t10
+ |}
+ """.stripMargin
+ val List(c, _, _) = compile(code)
+
+ assertSameSummary(getMethod(c, "t1"), List(BIPUSH, "$anonfun$t1$1", IRETURN))
+ assertSameSummary(getMethod(c, "t1a"), List(LCONST_1, "$anonfun$t1a$1", IRETURN))
+ assertSameSummary(getMethod(c, "t2"), List(ICONST_1, ICONST_2, "$anonfun$t2$1",IRETURN))
+
+ // val a = new ValKl(n); new ValKl(anonfun(a.x)).x
+ // value class instantiation-extraction should be optimized by boxing elim
+ assertSameSummary(getMethod(c, "t3"), List(
+ NEW, DUP, ICONST_1, "<init>", ASTORE,
+ NEW, DUP, ALOAD, "x",
+ "$anonfun$t3$1",
+ "<init>",
+ "x", IRETURN))
+
+ assertSameSummary(getMethod(c, "t4"), List(BIPUSH, "$anonfun$t4$1", "boxToInteger", ARETURN))
+ assertSameSummary(getMethod(c, "t4a"), List(ICONST_1, LDC, "$anonfun$t4a$1", LRETURN))
+ assertSameSummary(getMethod(c, "t5"), List(BIPUSH, ICONST_3, "$anonfun$t5$1", "boxToInteger", ARETURN))
+ assertSameSummary(getMethod(c, "t5a"), List(BIPUSH, BIPUSH, I2B, "$anonfun$t5a$1", IRETURN))
+ assertSameSummary(getMethod(c, "t6"), List(BIPUSH, "$anonfun$t6$1", RETURN))
+ assertSameSummary(getMethod(c, "t7"), List(ICONST_1, "$anonfun$t7$1", RETURN))
+ assertSameSummary(getMethod(c, "t8"), List(ICONST_1, LDC, "$anonfun$t8$1", LRETURN))
+ assertSameSummary(getMethod(c, "t9"), List(ICONST_1, "boxToInteger", "$anonfun$t9$1", RETURN))
+
+ // t9a inlines Range.foreach, which is quite a bit of code, so just testing the core
+ assertInvoke(getMethod(c, "t9a"), "C", "$anonfun$t9a$1")
+ assertInvoke(getMethod(c, "t9a"), "scala/runtime/BoxesRunTime", "boxToInteger")
+
+ assertSameSummary(getMethod(c, "t10"), List(
+ ICONST_1, ISTORE,
+ ALOAD, ILOAD,
+ "$anonfun$t10$1", RETURN))
+
+ // t10a inlines Range.foreach
+ assertInvoke(getMethod(c, "t10a"), "C", "$anonfun$t10a$1")
+ assertDoesNotInvoke(getMethod(c, "t10a"), "boxToInteger")
+ }
+
+ @Test
+ def refElimination(): Unit = {
+ val code =
+ """class C {
+ | def t1 = {
+ | var i = 0
+ | @inline def inner() = i += 1
+ | inner()
+ | i
+ | }
+ |
+ | final def m(f: Int => Unit) = f(10)
+ | def t2 = {
+ | var x = -1 // IntRef not yet eliminated: closure elimination does not
+ | m(i => if (i == 10) x = 1) // yet inline the anonfun method, need to improve the heuristsics
+ | x
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ assertSameCode(getMethod(c, "t1"), List(Op(ICONST_0), Op(ICONST_1), Op(IADD), Op(IRETURN)))
+ assertEquals(getMethod(c, "t2").instructions collect { case i: Invoke => i.owner +"."+ i.name }, List(
+ "scala/runtime/IntRef.create", "C.$anonfun$t2$1"))
+ }
+
+ @Test
+ def tupleElimination(): Unit = {
+ val code =
+ """class C {
+ | @inline final def tpl[A, B](a: A, b: B) = (a, b)
+ | @inline final def t_1[A, B](t: (A, B)) = t._1
+ | @inline final def t_2[A, B](t: (A, B)) = t._2
+ |
+ | def t1 = {
+ | val t = (3, 4) // specialized tuple
+ | t_1(t) + t_2(t) // invocations to generic _1 / _2, box operation inserted when eliminated
+ | }
+ |
+ | def t2 = {
+ | val t = tpl(1, 2) // generic Tuple2[Integer, Integer] created
+ | t._1 + t._2 // invokes the specialized _1$mcI$sp, eliminating requires adding an unbox operation
+ | }
+ |
+ | @inline final def m = (1, 3)
+ | def t3 = {
+ | val (a, b) = m
+ | a - b
+ | }
+ |
+ | def t4 = {
+ | val ((a, b), (c, d)) = (m, m)
+ | a + b + c + d
+ | }
+ |
+ | def t5 = m match {
+ | case (1, y) => y
+ | case (x, y) => x * y
+ | }
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ assertSameCode(getMethod(c, "t1"), List(Op(ICONST_3), Op(ICONST_4), Op(IADD), Op(IRETURN)))
+ assertSameCode(getMethod(c, "t2"), List(Op(ICONST_1), Op(ICONST_2), Op(IADD), Op(IRETURN)))
+ assertSameCode(getMethod(c, "t3"), List(Op(ICONST_1), Op(ICONST_3), Op(ISUB), Op(IRETURN)))
+ assertNoInvoke(getMethod(c, "t4"))
+ assertNoInvoke(getMethod(c, "t5"))
+ }
+
+ @Test
+ def redundantCasts(): Unit = {
+
+ // we go through the hoop of inlining the casts because erasure eliminates `asInstanceOf` calls
+ // that are statically known to succeed. For example the following cast is removed by erasure:
+ // `(if (b) c else d).asInstanceOf[C]`
+
+ val code =
+ """class C {
+ | @inline final def asO(a: Any) = a.asInstanceOf[Object]
+ | @inline final def asC(a: Any) = a.asInstanceOf[C]
+ | @inline final def asD(a: Any) = a.asInstanceOf[D]
+ |
+ | def t1(c: C) = asC(c) // eliminated
+ | def t2(c: C) = asO(c) // eliminated
+ | def t3(c: Object) = asC(c) // not elimianted
+ | def t4(c: C, d: D, b: Boolean) = asC(if (b) c else d) // not eliminated: lub of two non-equal reference types approximated with Object
+ | def t5(c: C, d: D, b: Boolean) = asO(if (b) c else d)
+ | def t6(c: C, cs: Array[C], b: Boolean) = asO(if (b) c else cs)
+ |}
+ |class D extends C
+ """.stripMargin
+ val List(c, _) = compile(code)
+ def casts(m: String) = getInstructions(c, m) collect { case TypeOp(CHECKCAST, tp) => tp }
+ assertSameCode(getMethod(c, "t1"), List(VarOp(ALOAD, 1), Op(ARETURN)))
+ assertSameCode(getMethod(c, "t2"), List(VarOp(ALOAD, 1), Op(ARETURN)))
+ assertSameCode(getMethod(c, "t3"), List(VarOp(ALOAD, 1), TypeOp(CHECKCAST, "C"), Op(ARETURN)))
+ assertEquals(casts("t4"), List("C"))
+ assertEquals(casts("t5"), Nil)
+ assertEquals(casts("t6"), Nil)
+ }
+
+ @Test
+ def inlineFromSealed(): Unit = {
+ val code =
+ """sealed abstract class Foo {
+ | @inline def bar(x: Int) = x + 1
+ |}
+ |object Foo {
+ | def mkFoo(): Foo = new Baz2
+ |}
+ |
+ |object Baz1 extends Foo
+ |final class Baz2 extends Foo
+ |
+ |object Test {
+ | def f = Foo.mkFoo() bar 10
+ |}
+ """.stripMargin
+
+ val cls = compile(code)
+ val test = findClass(cls, "Test$")
+ assertSameSummary(getMethod(test, "f"), List(
+ GETSTATIC, "mkFoo",
+ BIPUSH, ISTORE,
+ IFNONNULL, ACONST_NULL, ATHROW, -1 /*label*/,
+ ILOAD, ICONST_1, IADD, IRETURN))
+ }
+
+ @Test // a test taken from the test suite for the 2.11 inliner
+ def oldInlineHigherOrderTest(): Unit = {
+ val code =
+ """class C {
+ | private var debug = false
+ | @inline private def ifelse[T](cond: => Boolean, ifPart: => T, elsePart: => T): T = if (cond) ifPart else elsePart
+ | final def t = ifelse(debug, 1, 2)
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+
+ // box-unbox will clean it up
+ assertSameSummary(getMethod(c, "t"), List(
+ ALOAD, "$anonfun$t$1", IFEQ /*A*/,
+ "$anonfun$t$2", IRETURN,
+ -1 /*A*/, "$anonfun$t$3", IRETURN))
+ }
+
+ @Test
+ def inlineProject(): Unit = {
+ val codeA = "final class A { @inline def f = 1 }"
+ val codeB = "class B { def t(a: A) = a.f }"
+ // tests that no warning is emitted
+ val List(a, b) = compileClassesSeparately(List(codeA, codeB), extraArgs = "-opt:l:project -opt-warnings")
+ assertInvoke(getMethod(b, "t"), "A", "f")
+ }
+
+ @Test
+ def sd86(): Unit = {
+ val code =
+ """trait T1 { @inline def f = 999 }
+ |trait T2 { self: T1 => @inline override def f = 1 } // note that f is not final
+ |class C extends T1 with T2
+ """.stripMargin
+ val List(c, t1, t2) = compile(code, allowMessage = _ => true)
+ // we never inline into mixin forwarders, see scala-dev#259
+ assertInvoke(getMethod(c, "f"), "T2", "f$")
+ }
+
+ @Test
+ def sd140(): Unit = {
+ val code =
+ """trait T { @inline def f = 0 }
+ |trait U extends T { @inline override def f = 1 }
+ |trait V extends T { def m = 0 }
+ |final class K extends V with U { override def m = super[V].m }
+ |class C { def t = (new K).f }
+ """.stripMargin
+ val c :: _ = compile(code)
+ assertSameSummary(getMethod(c, "t"), List(NEW, "<init>", ICONST_1, IRETURN)) // ICONST_1, U.f is inlined (not T.f)
+ }
+
+ @Test
+ def inlineArrayForeach(): Unit = {
+ val code =
+ """class C {
+ | def consume(x: Int) = ()
+ | def t(a: Array[Int]): Unit = a foreach consume
+ |}
+ """.stripMargin
+ val List(c) = compile(code)
+ val t = getMethod(c, "t")
+ assertNoIndy(t)
+ assertInvoke(t, "C", "$anonfun$t$1")
+ }
+
+ @Test
+ def t9121(): Unit = {
+ val codes = List(
+ """package p1
+ |object Implicits {
+ | class ScalaObservable(val underlying: Any) extends AnyVal {
+ | @inline def scMap[R](f: String): Any = f.toRx
+ | }
+ | implicit class RichFunction1[T1, R](val f: String) extends AnyVal {
+ | def toRx: Any = ""
+ | }
+ |}
+ """.stripMargin,
+ """
+ |import p1.Implicits._
+ |class C {
+ | def t(): Unit = new ScalaObservable("").scMap("")
+ |}
+ """.stripMargin)
+ val c :: _ = compileClassesSeparately(codes, extraArgs = compilerArgs)
+ assertInvoke(getMethod(c, "t"), "p1/Implicits$RichFunction1$", "toRx$extension")
+ }
+
+ @Test
+ def keepLineNumbersPerCompilationUnit(): Unit = {
+ val code1 =
+ """class A {
+ | def fx(): Unit = ()
+ | @inline final def ma = {
+ | fx()
+ | 1
+ | }
+ |}
+ """.stripMargin
+ val code2 =
+ """class B extends A {
+ | @inline final def mb = {
+ | fx()
+ | 1
+ | }
+ |}
+ |class C extends B {
+ | @inline final def mc = {
+ | fx()
+ | 1
+ | }
+ | def t1 = ma // no lines, not the same source file
+ | def t2 = mb // lines
+ | def t3 = mc // lines
+ |}
+ """.stripMargin
+ notPerRun.foreach(_.clear())
+ val run = compiler.newRun
+ run.compileSources(List(makeSourceFile(code1, "A.scala"), makeSourceFile(code2, "B.scala")))
+ val List(_, _, c) = readAsmClasses(getGeneratedClassfiles(global.settings.outputDirs.getSingleOutput.get))
+ def is(name: String) = getMethod(c, name).instructions.filterNot(_.isInstanceOf[FrameEntry])
+
+ assertSameCode(is("t1"), List(
+ Label(0), LineNumber(12, Label(0)),
+ VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "A", "fx", "()V", false),
+ Op(ICONST_1), Op(IRETURN), Label(6)))
+
+ assertSameCode(is("t2"), List(
+ Label(0), LineNumber(3, Label(0)), VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "B", "fx", "()V", false),
+ Label(4), LineNumber(4, Label(4)), Op(ICONST_1), Op(IRETURN), Label(8)))
+
+ assertSameCode(is("t3"), List(
+ Label(0), LineNumber(9, Label(0)), VarOp(ALOAD, 0), Invoke(INVOKEVIRTUAL, "C", "fx", "()V", false),
+ Label(4), LineNumber(10, Label(4)), Op(ICONST_1), Op(IRETURN), Label(8)))
+ }
+
+ @Test
+ def traitHO(): Unit = {
+ val code =
+ """trait T {
+ | def foreach(f: Int => Unit): Unit = f(1)
+ |}
+ |final class C extends T {
+ | def cons(x: Int): Unit = ()
+ | def t1 = foreach(cons)
+ |}
+ """.stripMargin
+ val List(c, t) = compile(code)
+ assertNoIndy(getMethod(c, "t1"))
+ }
+
+ @Test
+ def limitInlinedLocalVariableNames(): Unit = {
+ val code =
+ """class C {
+ | def f(x: Int): Int = x
+ | @inline final def methodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(param: Int) =
+ | f(param)
+ | @inline final def anotherMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(param: Int) =
+ | methodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(f(param))
+ | @inline final def oneMoreMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(param: Int) =
+ | anotherMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(f(param))
+ | @inline final def yetAnotherMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(param: Int) =
+ | oneMoreMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(f(param))
+ | @inline final def oneLastMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(param: Int) =
+ | yetAnotherMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(f(param))
+ | def t(p: Int) =
+ | oneLastMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(f(p)) +
+ | oneLastMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence(f(p))
+ |}
+ """.stripMargin
+
+ val List(c) = compile(code)
+ assertEquals(getAsmMethod(c, "t").localVariables.asScala.toList.map(l => (l.name, l.index)).sortBy(_._2),List(
+ ("this",0),
+ ("p",1),
+ ("oneLastMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence_param",2),
+ ("oneLastMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchS_yetAnotherMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFren_param",3),
+ ("oneLastMethodWithVeryVeryLongNameAlmostLik_yetAnotherMethodWithVeryVeryLongNameAlmost_oneMoreMethodWithVeryVeryLongNameAlmostLik_param",4),
+ ("oneLastMethodWithVeryVeryLongNam_yetAnotherMethodWithVeryVeryLong_oneMoreMethodWithVeryVeryLongNam_anotherMethodWithVeryVeryLongNam_param",5),
+ ("oneLastMethodWithVeryVery_yetAnotherMethodWithVeryV_oneMoreMethodWithVeryVery_anotherMethodWithVeryVery_methodWithVeryVeryLongNam_param",6),
+ ("oneLastMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchSentence_param",7),
+ ("oneLastMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFrenchS_yetAnotherMethodWithVeryVeryLongNameAlmostLikeAGermanWordOrAFren_param",8),
+ ("oneLastMethodWithVeryVeryLongNameAlmostLik_yetAnotherMethodWithVeryVeryLongNameAlmost_oneMoreMethodWithVeryVeryLongNameAlmostLik_param",9),
+ ("oneLastMethodWithVeryVeryLongNam_yetAnotherMethodWithVeryVeryLong_oneMoreMethodWithVeryVeryLongNam_anotherMethodWithVeryVeryLongNam_param",10),
+ ("oneLastMethodWithVeryVery_yetAnotherMethodWithVeryV_oneMoreMethodWithVeryVery_anotherMethodWithVeryVery_methodWithVeryVeryLongNam_param",11)))
+ }
+
+ @Test
+ def sd259(): Unit = {
+ // - trait methods are not inlined into their static super accessors, and also not into mixin forwarders.
+ // - inlining an invocation of a mixin forwarder also inlines the static accessor and the trait method body.
+ val code =
+ """trait T {
+ | def m1a = 1
+ | final def m1b = 1
+ |
+ | @inline def m2a = 2
+ | @inline final def m2b = 2
+ |
+ | def m3a(f: Int => Int) = f(1)
+ | final def m3b(f: Int => Int) = f(1)
+ |}
+ |final class A extends T
+ |class C {
+ | def t1(t: T) = t.m1a
+ | def t2(t: T) = t.m1b
+ | def t3(t: T) = t.m2a
+ | def t4(t: T) = t.m2b
+ | def t5(t: T) = t.m3a(x => x)
+ | def t6(t: T) = t.m3b(x => x)
+ |
+ | def t7(a: A) = a.m1a
+ | def t8(a: A) = a.m1b
+ | def t9(a: A) = a.m2a
+ | def t10(a: A) = a.m2b
+ | def t11(a: A) = a.m3a(x => x)
+ | def t12(a: A) = a.m3b(x => x)
+ |}
+ """.stripMargin
+ val warn = "T::m2a()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden."
+ var count = 0
+ val List(a, c, t) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
+ assert(count == 1)
+
+ assertInvoke(getMethod(t, "m1a$"), "T", "m1a")
+ assertInvoke(getMethod(t, "m1b$"), "T", "m1b")
+ assertInvoke(getMethod(t, "m2a$"), "T", "m2a")
+ assertInvoke(getMethod(t, "m2b$"), "T", "m2b")
+ assertInvoke(getMethod(t, "m3a$"), "T", "m3a")
+ assertInvoke(getMethod(t, "m3b$"), "T", "m3b")
+
+ assertInvoke(getMethod(a, "m1a"), "T", "m1a$")
+ assertInvoke(getMethod(a, "m1b"), "T", "m1b$")
+ assertInvoke(getMethod(a, "m2a"), "T", "m2a$")
+ assertInvoke(getMethod(a, "m2b"), "T", "m2b$")
+ assertInvoke(getMethod(a, "m3a"), "T", "m3a$")
+ assertInvoke(getMethod(a, "m3b"), "T", "m3b$")
+
+ assertInvoke(getMethod(c, "t1"), "T", "m1a")
+ assertInvoke(getMethod(c, "t2"), "T", "m1b")
+
+ assertInvoke(getMethod(c, "t3"), "T", "m2a") // could not inline
+ assertNoInvoke(getMethod(c, "t4"))
+
+ assertInvoke(getMethod(c, "t5"), "T", "m3a") // could not inline
+ assertInvoke(getMethod(c, "t6"), "C", "$anonfun$t6$1") // both forwarders inlined, closure eliminated
+
+ assertInvoke(getMethod(c, "t7"), "A", "m1a")
+ assertInvoke(getMethod(c, "t8"), "A", "m1b")
+
+ assertNoInvoke(getMethod(c, "t9"))
+ assertNoInvoke(getMethod(c, "t10"))
+
+ assertInvoke(getMethod(c, "t11"), "C", "$anonfun$t11$1") // both forwarders inlined, closure eliminated
+ assertInvoke(getMethod(c, "t12"), "C", "$anonfun$t12$1") // both forwarders inlined, closure eliminated
+ }
+
+ @Test
+ def sd259b(): Unit = {
+ val code =
+ """trait T {
+ | def get = 1
+ | @inline final def m = try { get } catch { case _: Throwable => 1 }
+ |}
+ |class A extends T
+ |class C {
+ | def t(a: A) = 1 + a.m // cannot inline a try block onto a non-empty stack
+ |}
+ """.stripMargin
+ val warn =
+ """T::m()I is annotated @inline but could not be inlined:
+ |The operand stack at the callsite in C::t(LA;)I contains more values than the
+ |arguments expected by the callee T::m()I. These values would be discarded
+ |when entering an exception handler declared in the inlined method.""".stripMargin
+ val List(a, c, t) = compile(code, allowMessage = _.msg contains warn)
+
+ // inlinig of m$ is rolled back, because <invokespecial T.m> is not legal in class C.
+ assertInvoke(getMethod(c, "t"), "T", "m$")
+ }
+
+ @Test
+ def sd259c(): Unit = {
+ val code =
+ """trait T {
+ | def bar = 1
+ | @inline final def m = {
+ | def impl = bar // private, non-static method
+ | impl
+ | }
+ |}
+ |class A extends T
+ |class C {
+ | def t(a: A) = a.m
+ |}
+ """.stripMargin
+ val warn =
+ """T::m()I is annotated @inline but could not be inlined:
+ |The callee T::m()I contains the instruction INVOKESPECIAL T.impl$1 ()I
+ |that would cause an IllegalAccessError when inlined into class C.""".stripMargin
+ val List(a, c, t) = compile(code, allowMessage = _.msg contains warn)
+ assertInvoke(getMethod(c, "t"), "T", "m$")
+ }
+
+ @Test
+ def sd259d(): Unit = {
+ val code =
+ """trait T {
+ | @inline final def m = 1
+ |}
+ |class C extends T {
+ | def t = super.m // inline call to T.m$ here, we're not in the mixin forwarder C.m
+ |}
+ """.stripMargin
+ val List(c, t) = compileClasses(code)
+ assertNoInvoke(getMethod(c, "t"))
+ assertInvoke(getMethod(c, "m"), "T", "m$")
+ }
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
deleted file mode 100644
index 5ef2458c0a..0000000000
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOpts.scala
+++ /dev/null
@@ -1,92 +0,0 @@
-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.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 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 }"
- 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
- def cannotEliminateLoadBoxedUnit(): Unit = {
- // the compiler inserts a boxed into the try block. it's therefore non-empty (and live) and not eliminated.
- val code = "def f = { try {} catch { case _: Throwable => 0 }; 1 }"
- val m = singleMethod(methodOptCompiler)(code)
- assertTrue(m.handlers.length == 1)
- assertSameCode(m.instructions.take(3), List(Label(0), LineNumber(1, Label(0)), Field(GETSTATIC, "scala/runtime/BoxedUnit", "UNIT", "Lscala/runtime/BoxedUnit;")))
- }
-
- @Test
- def inlineThrowInCatchNotTry(): Unit = {
- // the try block does not contain the `ATHROW` instruction, but in the catch block, `ATHROW` is inlined
- val code = "def f(e: Exception) = throw { try e catch { case _: Throwable => e } }"
- val m = singleMethod(methodOptCompiler)(code)
- assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
- assertSameCode(m.instructions,
- wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW))
- )
- }
-
- @Test
- def inlineReturnInCatchNotTry(): Unit = {
- val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }"
- // cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState)
- val m = singleMethod(methodOptCompiler)(code)
- assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
- assertSameCode(m.instructions,
- wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN)))
- }
-
- @Test
- def simplifyJumpsInTryCatchFinally(): Unit = {
- val code =
- """def f: Int =
- | try {
- | return 1
- | } catch {
- | case _: Throwable =>
- | return 2
- | } finally {
- | return 2
- | // dead
- | val x = try 10 catch { case _: Throwable => 11 }
- | println(x)
- | }
- """.stripMargin
- val m = singleMethod(methodOptCompiler)(code)
- assertTrue(m.handlers.length == 2)
- assertSameCode(m.instructions.dropNonOp, // drop line numbers and labels that are only used by line numbers
-
- // one single label left :-)
- List(Op(ICONST_1), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), Op(POP), Op(ICONST_2), VarOp(ISTORE, 2), Jump(GOTO, Label(20)), VarOp(ASTORE, 3), Op(ICONST_2), Op(IRETURN), Label(20), Op(ICONST_2), Op(IRETURN))
- )
- }
-}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala
new file mode 100644
index 0000000000..2c697bfe50
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala
@@ -0,0 +1,773 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.Assert._
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+import scala.collection.JavaConverters._
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree.ClassNode
+import scala.tools.nsc.backend.jvm.AsmUtils._
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
+
+@RunWith(classOf[JUnit4])
+class MethodLevelOptsTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:method"
+ import compiler._
+
+ def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
+
+ def locals(c: ClassNode, m: String) = getAsmMethod(c, m).localVariables.asScala.toList.map(l => (l.name, l.index)).sortBy(_._2)
+
+ @Test
+ def eliminateEmptyTry(): Unit = {
+ val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }"
+ val warn = "a pure expression does nothing in statement position"
+ assertSameCode(compileInstructions(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN)))
+ }
+
+ @Test
+ def eliminateLoadBoxedUnit(): Unit = {
+ // the compiler inserts a boxed into the try block. it's therefore non-empty (and live) and not eliminated.
+ val code = "def f = { try {} catch { case _: Throwable => 0 }; 1 }"
+ val m = compileMethod(code)
+ assertTrue(m.handlers.length == 0)
+ assertSameCode(m, List(Op(ICONST_1), Op(IRETURN)))
+ }
+
+ @Test
+ def inlineThrowInCatchNotTry(): Unit = {
+ // the try block does not contain the `ATHROW` instruction, but in the catch block, `ATHROW` is inlined
+ val code = "def f(e: Exception) = throw { try e catch { case _: Throwable => e } }"
+ val m = compileMethod(code)
+ assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
+ assertSameCode(m.instructions,
+ wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW))
+ )
+ }
+
+ @Test
+ def inlineReturnInCatchNotTry(): Unit = {
+ val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }"
+ // cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState)
+ val m = compileMethod(code)
+ assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
+ assertSameCode(m.instructions,
+ wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN)))
+ }
+
+ @Test
+ def simplifyJumpsInTryCatchFinally(): Unit = {
+ val code =
+ """def f: Int =
+ | try {
+ | return 1
+ | } catch {
+ | case _: Throwable =>
+ | return 2
+ | } finally {
+ | return 3
+ | // dead
+ | val x = try 10 catch { case _: Throwable => 11 }
+ | println(x)
+ | }
+ """.stripMargin
+ val m = compileMethod(code)
+ assertTrue(m.handlers.isEmpty)
+ assertSameCode(m, List(Op(ICONST_3), Op(IRETURN)))
+ }
+
+ @Test
+ def nullStoreLoadElim(): Unit = {
+ // point of this test: we have two cleanups
+ // - remove `ACONST_NULL; ASTORE x` if x is otherwise not live
+ // - remove `ASTORE x; ALOAD x` if x is otherwise not live
+ // in the example below, we have `ACONST_NULL; ASTORE x; ALOAD x`. in this case the store-load
+ // should be removed (even though it looks like a null-store at first).
+ val code =
+ """class C {
+ | def t = {
+ | val x = null
+ | x.toString
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameCode(getMethod(c, "t"), List(
+ Op(ACONST_NULL), Invoke(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false), Op(ARETURN)))
+ }
+
+ @Test
+ def deadStoreReferenceElim(): Unit = {
+ val code =
+ """class C {
+ | def t = {
+ | var a = "a" // assign to non-initialized, directly removed by dead store
+ | a = "b" // assign to initialized, replaced by null-store, which is then removed: the var is not live, the uses are null-store or store-load
+ | a = "c"
+ | a // store-load pair will be eliminated
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameCode(
+ getMethod(c, "t"), List(Ldc(LDC, "c"), Op(ARETURN)))
+ }
+
+ @Test
+ def deadStoreReferenceKeepNull(): Unit = {
+ val code =
+ """class C {
+ | def t = {
+ | var a = "el" // this store is live, used in the println.
+ | println(a)
+ | a = "met" // since it's an ASTORE to a live variable, cannot elim the store (SI-5313), but store null instead.
+ | // so we get `LDC met; POP; ACONST_NULL; ASTORE 1`. the `LDC met; POP` is eliminated by push-pop.
+ | a = "zit" // this store is live, so we get `LDC zit; ASOTRE 1; ALOAD 1; ARETURN`.
+ | // we cannot eliminated the store-load sequence, because the local is live (again SI-5313).
+ | a
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+
+ assertSameCode(getMethod(c, "t"), List(
+ Ldc(LDC, "el"), VarOp(ASTORE, 1),
+ Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
+ Op(ACONST_NULL), VarOp(ASTORE, 1),
+ Ldc(LDC, "zit"), VarOp(ASTORE, 1), VarOp(ALOAD, 1), Op(ARETURN)))
+ }
+
+ @Test
+ def elimUnusedTupleObjectStringBox(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Int, y: Int): Int = {
+ | val a = (x, y) // Tuple2$mcII$sp
+ | val b = (a, y) // Tuple2
+ | val c = (new Object, "krik", new String) // unused java/lang/Object, java/lang/String allocation and string constant is also eliminated
+ | val d = new java.lang.Integer(x)
+ | val e = new String(new Array[Char](23)) // array allocation not eliminated, as it may throw (negative size, SI-8601)
+ | val f = new scala.runtime.IntRef(11)
+ | x + y
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameCode(getMethod(c, "t"), List(
+ IntOp(BIPUSH, 23), IntOp(NEWARRAY, 5), Op(POP), VarOp(ILOAD, 1), VarOp(ILOAD, 2), Op(IADD), Op(IRETURN)))
+ }
+
+ @Test
+ def noElimImpureConstructor(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Int, y: Int): Int = {
+ | val a = new java.lang.Integer("nono")
+ | x + y
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameCode(getMethod(c, "t"), List(
+ TypeOp(NEW, "java/lang/Integer"), Ldc(LDC, "nono"), Invoke(INVOKESPECIAL, "java/lang/Integer", "<init>", "(Ljava/lang/String;)V", false),
+ VarOp(ILOAD, 1), VarOp(ILOAD, 2), Op(IADD), Op(IRETURN)))
+ }
+
+ @Test
+ def elimUnusedBoxUnbox(): Unit = {
+ val code =
+ """class C {
+ | def t(a: Long): Int = {
+ | val t = 3 + a
+ | val u = a + t
+ | val v: Any = u // scala/runtime/BoxesRunTime.boxToLong
+ |
+ | val w = (v, a) // a Tuple2 (not specialized because first value is Any)
+ | // so calls scala/runtime/BoxesRunTime.boxToLong on the second value
+ |
+ | val x = v.asInstanceOf[Long] // scala/runtime/BoxesRunTime.unboxToLong
+ |
+ | val z = (java.lang.Long.valueOf(a), t) // java box call on the left, scala/runtime/BoxesRunTime.boxToLong on the right
+ |
+ | 0
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameCode(getMethod(c, "t"), List(Op(ICONST_0), Op(IRETURN)))
+ }
+
+ @Test
+ def elimUnusedClosure(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Int, y: Int): Int = {
+ | val f = (a: Int) => a + x + y
+ | val g = (b: Int) => b - x
+ | val h = (s: String) => println(s)
+ | f(30)
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameCode(getMethod(c, "t"), List(
+ IntOp(BIPUSH, 30), VarOp(ISTORE, 3), // no constant propagation, so we keep the store (and load below) of a const
+ VarOp(ILOAD, 1),
+ VarOp(ILOAD, 2),
+ VarOp(ILOAD, 3),
+ Invoke(INVOKESTATIC, "C", "$anonfun$t$1", "(III)I", false), Op(IRETURN)))
+ }
+
+ @Test
+ def rewriteSpecializedClosureCall(): Unit = {
+ val code =
+ """class C {
+ | def t = {
+ | val f1 = (x: Int) => println(x) // int-unit specialization
+ | val f2 = (x: Int, y: Long) => x == y // int-long-boolean
+ | f1(1)
+ | f2(3, 4)
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ val t = getMethod(c, "t")
+ assert(!t.instructions.exists(_.opcode == INVOKEDYNAMIC), t)
+ }
+
+ @Test
+ def boxUnboxPrimitive(): Unit = {
+ val code =
+ """class C {
+ | def t1 = {
+ | val a: Any = runtime.BoxesRunTime.boxToInteger(1)
+ | runtime.BoxesRunTime.unboxToInt(a) + 1
+ | }
+ |
+ | // two box and two unbox operations
+ | def t2(b: Boolean) = {
+ | val a = if (b) (3l: Any) else 2l
+ | a.asInstanceOf[Long] + 1 + a.asInstanceOf[Long]
+ | }
+ |
+ | def t3(i: Integer): Int = i.asInstanceOf[Int]
+ |
+ | def t4(l: Long): Any = l
+ |
+ | def t5(i: Int): Int = {
+ | val b = Integer.valueOf(i)
+ | val c: Integer = i
+ | b.asInstanceOf[Int] + c.intValue
+ | }
+ |
+ | def t6: Long = {
+ | val y = new java.lang.Boolean(true)
+ | val i: Integer = if (y) new Integer(10) else 13
+ | val j: java.lang.Long = 3l
+ | j + i
+ | }
+ |
+ | def t7: Int = {
+ | val a: Any = 3
+ | a.asInstanceOf[Int] + a.asInstanceOf[Int]
+ | }
+ |
+ | def t8 = null.asInstanceOf[Int]
+ |
+ | def t9: Int = {
+ | val a = Integer.valueOf(10)
+ | val b = runtime.BoxesRunTime.unboxToInt(a)
+ | a + b
+ | }
+ |
+ | @noinline def escape(a: Any) = ()
+ |
+ | // example E4 in BoxUnbox doc comment
+ | def t10: Int = {
+ | val a = Integer.valueOf(10) // int 10 is stored into local
+ | escape(a)
+ | a // no unbox, 10 is read from local
+ | }
+ |
+ | // the boxes here cannot be eliminated. see doc comment in BoxUnbox, example E1.
+ | def t11(b: Boolean): Int = {
+ | val i = Integer.valueOf(10)
+ | val j = Integer.valueOf(41)
+ | escape(i) // force rewrite method M1 (see doc in BoxUnbox)
+ | val res: Integer = if (b) i else j
+ | res.toInt // cannot be re-written to a local variable read - we don't know which local to read
+ | }
+ |
+ | // both boxes have a single unboxing consumer, and the escape. note that the escape does
+ | // NOT put the two boxes into the same set of rewrite operations: we can rewrite both
+ | // boxes with their unbox individually. in both cases the box also escapes, so method
+ | // M1 will keep the box around.
+ | def t12(b: Boolean): Int = {
+ | val i = Integer.valueOf(10)
+ | val j = Integer.valueOf(32)
+ | escape(if (b) i else j) // force method M1. the escape here is a consumer for both boxes
+ | if (b) i.toInt else j.toInt // both boxes (i, j) have their own unboxing consumer
+ | }
+ |}
+ """.stripMargin
+
+ val c = compileClass(code)
+
+ assertNoInvoke(getMethod(c, "t1"))
+ assertNoInvoke(getMethod(c, "t2"))
+ assertInvoke(getMethod(c, "t3"), "scala/runtime/BoxesRunTime", "unboxToInt")
+ assertInvoke(getMethod(c, "t4"), "scala/runtime/BoxesRunTime", "boxToLong")
+ assertNoInvoke(getMethod(c, "t5"))
+ assertNoInvoke(getMethod(c, "t6"))
+ assertNoInvoke(getMethod(c, "t7"))
+ assertSameSummary(getMethod(c, "t8"), List(ICONST_0, IRETURN))
+ assertNoInvoke(getMethod(c, "t9"))
+ // t10: no invocation of unbox
+ assertEquals(getInstructions(c, "t10") collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List(
+ ("java/lang/Integer", "valueOf"),
+ ("C", "escape")))
+
+ assertSameSummary(getMethod(c, "t11"), List(
+ BIPUSH, "valueOf", ASTORE /*2*/,
+ BIPUSH, "valueOf", ASTORE /*3*/,
+ ALOAD /*0*/, ALOAD /*2*/, "escape",
+ ILOAD /*1*/, IFEQ /*L1*/, ALOAD /*2*/, GOTO /*L2*/, /*Label L1*/ -1, ALOAD /*3*/, /*Label L2*/ -1,
+ ASTORE /*4*/, GETSTATIC /*Predef*/, ALOAD /*4*/, "Integer2int", IRETURN))
+
+ // no unbox invocations
+ assertEquals(getInstructions(c, "t12") collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List(
+ ("java/lang/Integer", "valueOf"),
+ ("java/lang/Integer", "valueOf"),
+ ("C", "escape")))
+ }
+
+ @Test
+ def refEliminiation(): Unit = {
+ val code =
+ """class C {
+ | import runtime._
+ | @noinline def escape(a: Any) = ()
+ |
+ | def t1 = { // box eliminated
+ | val r = new IntRef(0)
+ | r.elem
+ | }
+ |
+ | def t2(b: Boolean) = {
+ | val r1 = IntRef.zero() // both eliminated
+ | val r2 = IntRef.create(1)
+ | val res: IntRef = if (b) r1 else r2
+ | res.elem
+ | }
+ |
+ | def t3 = {
+ | val r = LongRef.create(10l) // eliminated
+ | r.elem += 3
+ | r.elem
+ | }
+ |
+ | def t4(b: Boolean) = {
+ | val x = BooleanRef.create(false) // eliminated
+ | if (b) x.elem = true
+ | if (x.elem) "a" else "b"
+ | }
+ |
+ | def t5 = {
+ | val r = IntRef.create(10) // not eliminated: the box might be modified in the escape
+ | escape(r)
+ | r.elem
+ | }
+ |
+ | def t6(b: Boolean) = {
+ | val r1 = IntRef.zero()
+ | val r2 = IntRef.create(1)
+ | r1.elem = 39
+ | val res: IntRef = if (b) r1 else r2
+ | res.elem // boxes remain: can't rewrite this read, don't know which local
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameSummary(getMethod(c, "t1"), List(ICONST_0, IRETURN))
+ assertNoInvoke(getMethod(c, "t2"))
+ assertSameSummary(getMethod(c, "t3"), List(LDC, LDC, LADD, LRETURN))
+ assertNoInvoke(getMethod(c, "t4"))
+ assertEquals(getInstructions(c, "t5") collect { case Field(_, owner, name, _) => s"$owner.$name" },
+ List("scala/runtime/IntRef.elem"))
+ assertEquals(getInstructions(c, "t6") collect { case Field(op, owner, name, _) => s"$op $owner.$name" },
+ List(s"$PUTFIELD scala/runtime/IntRef.elem", s"$GETFIELD scala/runtime/IntRef.elem"))
+ }
+
+ @Test
+ def tupleElimination(): Unit = {
+ val code =
+ """class C {
+ | def t1(b: Boolean) = {
+ | val t = ("hi", "fish")
+ | if (b) t._1 else t._2
+ | }
+ |
+ | def t2 = {
+ | val t = (1, 3) // specialized tuple
+ | t._1 + t._2 // specialized accessors (_1$mcII$sp)
+ | }
+ |
+ | def t3 = {
+ | // boxed before tuple creation, a non-specialized tuple is created
+ | val t = (new Integer(3), Integer.valueOf(4))
+ | t._1 + t._2 // invokes the generic `_1` / `_2` getters, both values unboxed by Integer2int
+ | }
+ |
+ | def t4: Any = {
+ | val t = (3, 3) // specialized tuple is created, ints are not boxed
+ | (t: Tuple2[Any, Any])._1 // when eliminating the _1 call, need to insert a boxing operation
+ | }
+ |
+ | // the inverse of t4 also happens: an Tuple[Integer] where _1$mcI$sp is invoked. In this
+ | // case, an unbox operation needs to be added when eliminating the extraction. The only
+ | // way I found to test this is with an inlined generic method, see InlinerTest.tupleElimination.
+ | def tpl[A, B](a: A, b: B) = (a, b)
+ | def t5: Int = tpl(1, 2)._1 // invokes _1$mcI$sp
+ |
+ | def t6 = {
+ | val (a, b) = (1, 2)
+ | a - b
+ | }
+ |
+ | def t7 = {
+ | // this example is more tricky to handle than it looks, see doc comment in BoxUnbox.
+ | val ((a, b), c) = ((1, 2), 3)
+ | a + b + c
+ | }
+ |
+ | def t8 = {
+ | val ((a, b), (c, d)) = ((1, 2), (3, Integer.valueOf(10)))
+ | a + b + c + d
+ | }
+ |
+ | def t9(a: Int, b: Int) = (a, b) match { // tuple is optimized away
+ | case (x, y) if x == y => 0
+ | case (x, y) => x + y
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertNoInvoke(getMethod(c, "t1"))
+ assertSameSummary(getMethod(c, "t2"), List(ICONST_1, ICONST_3, IADD, IRETURN))
+ assertSameSummary(getMethod(c, "t3"), List(ICONST_3, ICONST_4, IADD, IRETURN))
+ assertSameSummary(getMethod(c, "t4"), List(ICONST_3, "boxToInteger", ARETURN))
+ assertEquals(getInstructions(c, "t5") collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List(
+ ("scala/runtime/BoxesRunTime", "boxToInteger"),
+ ("scala/runtime/BoxesRunTime", "boxToInteger"),
+ ("C", "tpl"),
+ ("scala/Tuple2", "_1$mcI$sp")))
+ assertSameSummary(getMethod(c, "t6"), List(ICONST_1, ICONST_2, ISUB, IRETURN))
+ assertSameSummary(getMethod(c, "t7"), List(
+ ICONST_1, ICONST_2, ISTORE, ISTORE,
+ ICONST_3, ISTORE,
+ ILOAD, ILOAD, IADD, ILOAD, IADD, IRETURN))
+ assertNoInvoke(getMethod(c, "t8"))
+ assertNoInvoke(getMethod(c, "t9"))
+ }
+
+ @Test
+ def nullnessOpts(): Unit = {
+ val code =
+ """class C {
+ | def t1 = {
+ | val a = new C
+ | if (a == null)
+ | println() // eliminated
+ | a
+ | }
+ |
+ | def t2 = null.asInstanceOf[Long] // replaced by zero value
+ |
+ | def t3 = {
+ | val t = (1, 3)
+ | val a = null
+ | if (t ne a) t._1
+ | else throw new Error()
+ | }
+ |
+ | def t4 = {
+ | val i = Integer.valueOf(1)
+ | val a = null
+ | if (i eq a) throw new Error()
+ | else i.toInt
+ | }
+ |
+ | def t5 = {
+ | val i = runtime.DoubleRef.zero()
+ | if (i == null) throw new Error()
+ | else i.elem
+ | }
+ |
+ | def t6 = {
+ | var a = null
+ | var i = null
+ | a = i // eliminated (store of null to variable that is already null)
+ | a // replaced by ACONST_NULL (load of variable that is known null)
+ | }
+ |
+ | def t7 = {
+ | val a = null
+ | a.isInstanceOf[String] // eliminated, replaced by 0 (null.isInstanceOf is always false)
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameSummary(getMethod(c, "t1"), List(NEW, DUP, "<init>", ARETURN))
+ assertSameCode(getMethod(c, "t2"), List(Op(LCONST_0), Op(LRETURN)))
+ assertSameCode(getMethod(c, "t3"), List(Op(ICONST_1), Op(IRETURN)))
+ assertSameCode(getMethod(c, "t4"), List(Op(ICONST_1), Op(IRETURN)))
+ assertSameCode(getMethod(c, "t5"), List(Op(DCONST_0), Op(DRETURN)))
+ assertSameCode(getMethod(c, "t6"), List(Op(ACONST_NULL), Op(ARETURN)))
+ assertSameCode(getMethod(c, "t7"), List(Op(ICONST_0), Op(IRETURN)))
+ }
+
+ @Test
+ def elimRedundantNullCheck(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Object) = {
+ | val bool = x == null
+ | if (x != null) 1 else 0
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertSameCode(
+ getMethod(c, "t"), List(
+ VarOp(ALOAD, 1), Jump(IFNULL, Label(6)), Op(ICONST_1), Op(IRETURN), Label(6), Op(ICONST_0), Op(IRETURN)))
+ }
+
+ @Test
+ def t5313(): Unit = {
+ val code =
+ """class C {
+ | def randomBoolean = scala.util.Random.nextInt % 2 == 0
+ |
+ | // 3 stores to kept1 (slot 1), 1 store to result (slot 2)
+ | def t1 = {
+ | var kept1 = new Object
+ | val result = new java.lang.ref.WeakReference(kept1)
+ | kept1 = null // we can't eliminate this assignment because result can observe
+ | // when the object has no more references. See SI-5313
+ | kept1 = new Object // could eliminate this one with a more elaborate analysis (we know it contains null)
+ | // however, such is not implemented: if a var is live, then stores are kept.
+ | result
+ | }
+ |
+ | // only two variables are live: kept2 and kept3. they end up on slots 1 and 2.
+ | // kept2 has 2 stores, kept3 has 1 store.
+ | def t2 = {
+ | var erased2 = null // we can eliminate this store because it's never used
+ | val erased3 = erased2 // and this
+ | var erased4 = erased2 // and this
+ | val erased5 = erased4 // and this
+ | var kept2: Object = new Object // ultimately can't be eliminated
+ | while(randomBoolean) {
+ | val kept3 = kept2
+ | kept2 = null // this can't, because it clobbers kept2, which is used
+ | erased4 = null // safe to eliminate
+ | println(kept3)
+ | }
+ | 0
+ | }
+ |
+ | def t3 = {
+ | var kept4 = new Object // have to keep, it's used
+ | try
+ | println(kept4)
+ | catch {
+ | case _ : Throwable => kept4 = null // have to keep, it clobbers kept4 which is used
+ | }
+ | 0
+ | }
+ |
+ | def t4 = {
+ | var kept5 = new Object
+ | print(kept5)
+ | kept5 = null // can't eliminate it's a clobber and it's used
+ | print(kept5)
+ | kept5 = null // eliminated by nullness analysis (store null to a local that is known to be null)
+ | 0
+ | }
+ |
+ | def t5 = {
+ | while(randomBoolean) {
+ | var kept6: AnyRef = null // not used, but have to keep because it clobbers the next used store
+ | // on the back edge of the loop
+ | kept6 = new Object // used
+ | println(kept6)
+ | }
+ | 0
+ | }
+ |}
+ """.stripMargin
+
+ val c = compileClass(code)
+ def stores(m: String) = getInstructions(c, m).filter(_.opcode == ASTORE)
+
+ assertEquals(locals(c, "t1"), List(("this",0), ("kept1",1), ("result",2)))
+ assert(stores("t1") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 2), VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
+ textify(getAsmMethod(c, "t1")))
+
+ assertEquals(locals(c, "t2"), List(("this",0), ("kept2",1), ("kept3",2)))
+ assert(stores("t2") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 2), VarOp(ASTORE, 1)),
+ textify(getAsmMethod(c, "t2")))
+
+ assertEquals(locals(c, "t3"), List(("this",0), ("kept4",1)))
+ assert(stores("t3") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
+ textify(getAsmMethod(c, "t3")))
+
+ assertEquals(locals(c, "t4"), List(("this",0), ("kept5",1)))
+ assert(stores("t4") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
+ textify(getAsmMethod(c, "t4")))
+
+ assertEquals(locals(c, "t5"), List(("this",0), ("kept6",1)))
+ assert(stores("t5") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
+ textify(getAsmMethod(c, "t5")))
+ }
+
+ @Test
+ def testCpp(): Unit = {
+ // copied from an old test (run/test-cpp.scala)
+ val code =
+ """class C {
+ | import scala.util.Random._
+ |
+ | def t1(x: Int) = {
+ | val y = x
+ | println(y)
+ | }
+ |
+ | def t2 = {
+ | val x = 2
+ | val y = x
+ | println(y)
+ | }
+ |
+ | def t3 = {
+ | val x = this
+ | val y = x
+ | println(y)
+ | }
+ |
+ | def f = nextInt
+ |
+ | def t4 = {
+ | val x = f
+ | val y = x
+ | println(y)
+ | }
+ |
+ | def t5 = {
+ | var x = nextInt
+ | var y = x
+ | println(y)
+ |
+ | y = nextInt
+ | x = y
+ | println(x)
+ | }
+ |}
+ """.stripMargin
+
+ val c = compileClass(code)
+ assertEquals(locals(c, "t1"), List(("this", 0), ("x", 1)))
+
+ assertEquals(locals(c, "t2"), List(("this", 0), ("x", 1)))
+ // we don't have constant propagation (yet).
+ // the local var can't be optimized as a store;laod sequence, there's a GETSTATIC between the two
+ assertSameSummary(getMethod(c, "t2"), List(
+ ICONST_2, ISTORE, GETSTATIC, ILOAD, "boxToInteger", "println", RETURN))
+
+ assertEquals(locals(c, "t3"), List(("this", 0)))
+ assertEquals(locals(c, "t4"), List(("this", 0), ("x", 1)))
+ assertEquals(locals(c, "t5"), List(("this", 0), ("x", 1)))
+ }
+
+ @Test
+ def t7006(): Unit = {
+ val code =
+ """class C {
+ | def t: Unit = {
+ | try {
+ | val x = 3
+ | } finally {
+ | print("hello")
+ | }
+ | while(true) { }
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ val t = getMethod(c, "t")
+ assertEquals(t.handlers, Nil)
+ assertEquals(locals(c, "t"), List(("this", 0)))
+ assertSameSummary(t, List(GETSTATIC, LDC, "print", -1, GOTO))
+ }
+
+ @Test
+ def booleanOrderingCompare(): Unit = {
+ val code =
+ """class C {
+ | def compare(x: Boolean, y: Boolean) = (x, y) match {
+ | case (false, true) => -1
+ | case (true, false) => 1
+ | case _ => 0
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+ assertNoInvoke(getMethod(c, "compare"))
+ }
+
+ @Test
+ def t8790(): Unit = {
+ val code =
+ """class C {
+ | def t(x: Int, y: Int): String = (x, y) match {
+ | case (7, 8) => "a"
+ | case _ => "b"
+ | }
+ |}
+ """.stripMargin
+ val c = compileClass(code)
+
+ assertSameSummary(getMethod(c, "t"), List(
+ BIPUSH, ILOAD, IF_ICMPNE,
+ BIPUSH, ILOAD, IF_ICMPNE,
+ LDC, ASTORE, GOTO,
+ -1, LDC, ASTORE,
+ -1, ALOAD, ARETURN))
+ }
+
+ @Test
+ def elimSamLambda(): Unit = {
+ val code =
+ """class C {
+ | def t1(x: Int) = {
+ | val fun: java.util.function.IntFunction[Int] = y => y + 1
+ | fun(x)
+ | }
+ | def t2(x: Int) = {
+ | val fun: T = i => i + 1
+ | fun.f(x)
+ | }
+ |}
+ |trait T { def f(x: Int): Int }
+ """.stripMargin
+ val List(c, t) = compileClasses(code)
+ assertSameSummary(getMethod(c, "t1"), List(ILOAD, "$anonfun$t1$1", IRETURN))
+ assertSameSummary(getMethod(c, "t2"), List(ILOAD, "$anonfun$t2$1", IRETURN))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
index f8e887426b..073eba7aa6 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala
@@ -2,33 +2,49 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
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 }
-}
+import scala.collection.JavaConverters._
+import scala.tools.asm.tree.ClassNode
+import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo}
+import scala.tools.testing.BytecodeTesting
@RunWith(classOf[JUnit4])
-class ScalaInlineInfoTest {
- val compiler = newCompiler()
+class ScalaInlineInfoTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:l:none"
+ import compiler._
+
+ def inlineInfo(c: ClassNode): InlineInfo = c.attrs.asScala.collect({ case a: InlineInfoAttribute => a.inlineInfo }).head
+
+ def mapDiff[A, B](a: Map[A, B], b: Map[A, B]) = {
+ val r = new StringBuilder
+ for ((a, av) <- a) {
+ if (!b.contains(a)) r.append(s"missing in b: $a\n")
+ else if (av != b(a)) r.append(s"different for $a: $av != ${b(a)}\n")
+ }
+ for (b <- b.keys.toList diff a.keys.toList) {
+ r.append(s"missing in a: $b\n")
+ }
+ r.toString
+ }
+
+ def assertSameMethods(c: ClassNode, nameAndSigs: Set[String]): Unit = {
+ val r = new StringBuilder
+ val inClass = c.methods.iterator.asScala.map(m => m.name + m.desc).toSet
+ for (m <- inClass.diff(nameAndSigs)) r.append(s"method in classfile found, but no inline info: $m")
+ for (m <- nameAndSigs.diff(inClass)) r.append(s"inline info found, but no method in classfile: $m")
+ assert(r.isEmpty, r.toString)
+ }
@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)
+ | private def f2 = 1 // default method only (not in subclass)
| def f3 = {
| def nest = 0 // nested method (does not end up in the interface)
| nest
@@ -38,13 +54,13 @@ class ScalaInlineInfoTest {
| def f4 = super.toString // super accessor
|
| object O // module accessor (method is generated)
- | def f5 = {
+ | final def f5 = {
| object L { val x = 0 } // nested module (just flattened out)
| L.x
| }
|
| @noinline
- | def f6: Int // abstract method (not in impl class)
+ | def f6: Int // abstract method
|
| // fields
|
@@ -55,31 +71,124 @@ class ScalaInlineInfoTest {
|
| final val x5 = 0
|}
+ |class C extends T {
+ | def f6 = 0
+ | var x3 = 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
+ val cs @ List(c, t, tl, to) = compileClasses(code)
+ val infoT = inlineInfo(t)
+ val expectT = InlineInfo (
false, // final class
+ None, // not a sam
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))),
+ ("O()LT$O$;", MethodInlineInfo(false,false,false)),
+ ("T$$super$toString()Ljava/lang/String;", MethodInlineInfo(true ,false,false)),
+ ("T$_setter_$x1_$eq(I)V", MethodInlineInfo(false,false,false)),
+ ("f1()I", MethodInlineInfo(false,false,false)),
+ ("f1$(LT;)I", MethodInlineInfo(true ,false,false)),
+ ("f2()I", MethodInlineInfo(true ,false,false)), // no static impl method for private method f2
+ ("f3()I", MethodInlineInfo(false,false,false)),
+ ("f3$(LT;)I", MethodInlineInfo(true ,false,false)),
+ ("f4()Ljava/lang/String;", MethodInlineInfo(false,true, false)),
+ ("f4$(LT;)Ljava/lang/String;", MethodInlineInfo(true ,true, false)),
+ ("f5()I", MethodInlineInfo(true ,false,false)),
+ ("f5$(LT;)I", MethodInlineInfo(true ,false,false)),
+ ("f6()I", MethodInlineInfo(false,false,true )), // no static impl method for abstract method f6
+ ("x1()I", MethodInlineInfo(false,false,false)),
+ ("y2()I", MethodInlineInfo(false,false,false)),
+ ("y2_$eq(I)V", MethodInlineInfo(false,false,false)),
+ ("x3()I", MethodInlineInfo(false,false,false)),
+ ("x3_$eq(I)V", MethodInlineInfo(false,false,false)),
+ ("x4()I", MethodInlineInfo(false,false,false)),
+ ("x4$(LT;)I", MethodInlineInfo(true ,false,false)),
+ ("x5()I", MethodInlineInfo(true, false,false)),
+ ("x5$(LT;)I", MethodInlineInfo(true ,false,false)),
+ ("L$1(Lscala/runtime/LazyRef;)LT$L$2$;", MethodInlineInfo(true, false,false)),
+ ("nest$1()I", MethodInlineInfo(true, false,false)),
+ ("$init$(LT;)V", MethodInlineInfo(true,false,false)),
+ ("L$lzycompute$1(Lscala/runtime/LazyRef;)LT$L$2$;", MethodInlineInfo(true,false,false))
+ ),
None // warning
)
- assert(info == expect, info)
+
+ assert(infoT == expectT, mapDiff(expectT.methodInfos, infoT.methodInfos) + infoT)
+ assertSameMethods(t, expectT.methodInfos.keySet)
+
+ val infoC = inlineInfo(c)
+ val expectC = InlineInfo(false, None, Map(
+ "O()LT$O$;" -> MethodInlineInfo(true ,false,false),
+ "f1()I" -> MethodInlineInfo(false,false,false),
+ "f3()I" -> MethodInlineInfo(false,false,false),
+ "f4()Ljava/lang/String;" -> MethodInlineInfo(false,true,false),
+ "f5()I" -> MethodInlineInfo(true,false,false),
+ "f6()I" -> MethodInlineInfo(false,false,false),
+ "x1()I" -> MethodInlineInfo(false,false,false),
+ "T$_setter_$x1_$eq(I)V" -> MethodInlineInfo(false,false,false),
+ "y2()I" -> MethodInlineInfo(false,false,false),
+ "y2_$eq(I)V" -> MethodInlineInfo(false,false,false),
+ "x3()I" -> MethodInlineInfo(false,false,false),
+ "x3_$eq(I)V" -> MethodInlineInfo(false,false,false),
+ "x4$lzycompute()I" -> MethodInlineInfo(true ,false,false),
+ "x4()I" -> MethodInlineInfo(false,false,false),
+ "T$$super$toString()Ljava/lang/String;" -> MethodInlineInfo(true ,false,false),
+ "<init>()V" -> MethodInlineInfo(false,false,false),
+ "O$lzycompute$1()V" -> MethodInlineInfo(true,false,false)
+ ),
+ None)
+
+ assert(infoC == expectC, mapDiff(expectC.methodInfos, infoC.methodInfos) + infoC)
+ assertSameMethods(c, expectC.methodInfos.keySet)
+ }
+
+ @Test
+ def inlineInfoSam(): Unit = {
+ val code =
+ """trait C { // expected to be seen as sam: g(I)I
+ | def f = 0
+ | def g(x: Int): Int
+ | val foo = "hi"
+ |}
+ |abstract class D {
+ | val biz: Int
+ |}
+ |trait T { // expected to be seen as sam: h(Ljava/lang/String;)I
+ | def h(a: String): Int
+ |}
+ |trait E extends T { // expected to be seen as sam: h(Ljava/lang/String;)I
+ | def hihi(x: Int) = x
+ |}
+ |class F extends T {
+ | def h(a: String) = 0
+ |}
+ |trait U {
+ | def conc() = 10
+ | def nullary: Int
+ |}
+ """.stripMargin
+ val cs = compileClasses(code)
+ val sams = cs.map(c => (c.name, inlineInfo(c).sam))
+ assertEquals(sams,
+ List(
+ ("C",Some("g(I)I")),
+ ("D",None),
+ ("E",Some("h(Ljava/lang/String;)I")),
+ ("F",None),
+ ("T",Some("h(Ljava/lang/String;)I")),
+ ("U",None)))
+ }
+
+ @Test
+ def lzyComputeInlineInfo(): Unit = {
+ val code = "class C { object O }"
+ val List(c, om) = compileClasses(code)
+ val infoC = inlineInfo(c)
+ val expected = Map(
+ "<init>()V" -> MethodInlineInfo(false,false,false),
+ "O$lzycompute$1()V" -> MethodInlineInfo(true,false,false),
+ "O()LC$O$;" -> MethodInlineInfo(true,false,false))
+ assert(infoC.methodInfos == expected, mapDiff(infoC.methodInfos, expected))
+ assertSameMethods(c, expected.keySet)
}
}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
index a685ae7dd5..992a0e541b 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala
@@ -2,15 +2,15 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
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.asm.Opcodes._
import scala.tools.partest.ASMConverters
-import ASMConverters._
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting._
@RunWith(classOf[JUnit4])
class SimplifyJumpsTest {
@@ -96,10 +96,22 @@ class SimplifyJumpsTest {
instructionsFromMethod(method),
List(VarOp(ILOAD, 1), Jump(IFLT, Label(3))) ::: rest.tail )
- // no label allowed between begin and rest. if there's another label, then there could be a
- // branch that label. eliminating the GOTO would change the behavior.
- val nonOptMethod = genMethod()(begin ::: Label(22) :: rest: _*)
- assertFalse(LocalOptImpls.simplifyJumps(nonOptMethod))
+ // branch over goto is OK even if there's a label in between, if that label is not a jump target
+ val withNonJumpTargetLabel = genMethod()(begin ::: Label(22) :: rest: _*)
+ assertTrue(LocalOptImpls.simplifyJumps(withNonJumpTargetLabel))
+ assertSameCode(
+ instructionsFromMethod(withNonJumpTargetLabel),
+ List(VarOp(ILOAD, 1), Jump(IFLT, Label(3)), Label(22)) ::: rest.tail )
+
+ // if the Label(22) between IFGE and GOTO is the target of some jump, we cannot rewrite the IFGE
+ // and remove the GOTO: removing the GOTO would change semantics. However, the jump that targets
+ // Label(22) will be re-written (jump-chain collapsing), so in a second round, the IFGE is still
+ // rewritten to IFLT
+ val twoRounds = genMethod()(List(VarOp(ILOAD, 1), Jump(IFLE, Label(22))) ::: begin ::: Label(22) :: rest: _*)
+ assertTrue(LocalOptImpls.simplifyJumps(twoRounds))
+ assertSameCode(
+ instructionsFromMethod(twoRounds),
+ List(VarOp(ILOAD, 1), Jump(IFLE, Label(3)), VarOp(ILOAD, 1), Jump(IFLT, Label(3)), Label(22)) ::: rest.tail )
}
@Test
@@ -167,6 +179,9 @@ class SimplifyJumpsTest {
VarOp(ILOAD, 1),
Jump(IFGE, Label(target)),
+ VarOp(ILOAD, 1), // some code to prevent rewriting the conditional jump
+ Op(IRETURN),
+
Label(4),
Jump(GOTO, Label(3)),
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 902af7b7fa..68ce61b48a 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
@@ -2,49 +2,28 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
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.Opcodes._
+import scala.tools.partest.ASMConverters._
import scala.tools.testing.AssertUtil._
-
-import CodeGenTools._
-import scala.tools.partest.ASMConverters
-import ASMConverters._
+import scala.tools.testing.BytecodeTesting._
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 extends ClearAfterClass {
- ClearAfterClass.stateToClear = UnreachableCodeTest
-
- val methodOptCompiler = UnreachableCodeTest.methodOptCompiler
- val dceCompiler = UnreachableCodeTest.dceCompiler
- val noOptCompiler = UnreachableCodeTest.noOptCompiler
- val noOptNoFramesCompiler = UnreachableCodeTest.noOptNoFramesCompiler
+ // jvm-1.6 enables emitting stack map frames, which impacts the code generation wrt dead basic blocks,
+ // see comment in BCodeBodyBuilder
+ val methodOptCompiler = cached("methodOptCompiler", () => newCompiler(extraArgs = "-opt:l:method"))
+ val dceCompiler = cached("dceCompiler", () => newCompiler(extraArgs = "-opt:unreachable-code"))
+ val noOptCompiler = cached("noOptCompiler", () => newCompiler(extraArgs = "-opt:l:none"))
def assertEliminateDead(code: (Instruction, Boolean)*): Unit = {
val method = genMethod()(code.map(_._1): _*)
- LocalOptImpls.removeUnreachableCodeImpl(method, "C")
+ dceCompiler.global.genBCode.bTypes.localOpt.removeUnreachableCodeImpl(method, "C")
val nonEliminated = instructionsFromMethod(method)
val expectedLive = code.filter(_._2).map(_._1).toList
assertSameCode(nonEliminated, expectedLive)
@@ -131,10 +110,10 @@ class UnreachableCodeTest extends ClearAfterClass {
@Test
def basicEliminationCompiler(): Unit = {
val code = "def f: Int = { return 1; 2 }"
- val withDce = singleMethodInstructions(dceCompiler)(code)
+ val withDce = dceCompiler.compileInstructions(code)
assertSameCode(withDce.dropNonOp, List(Op(ICONST_1), Op(IRETURN)))
- val noDce = singleMethodInstructions(noOptCompiler)(code)
+ val noDce = noOptCompiler.compileInstructions(code)
// The emitted code is ICONST_1, IRETURN, ICONST_2, IRETURN. The latter two are dead.
//
@@ -152,11 +131,6 @@ class UnreachableCodeTest extends ClearAfterClass {
// Finally, instructions in the dead basic blocks are replaced by ATHROW, as explained in
// a comment in BCodeBodyBuilder.
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 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)))
}
@Test
@@ -165,23 +139,23 @@ class UnreachableCodeTest extends ClearAfterClass {
def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
val code = "def f: Int = { return 0; try { 1 } catch { case _: Exception => 2 } }"
- val m = singleMethod(dceCompiler)(code)
+ val m = dceCompiler.compileMethod(code)
assertTrue(m.handlers.isEmpty) // redundant (if code is gone, handler is gone), but done once here for extra safety
assertSameCode(m.instructions,
wrapInDefault(Op(ICONST_0), Op(IRETURN)))
val code2 = "def f: Unit = { try { } catch { case _: Exception => () }; () }"
// requires fixpoint optimization of methodOptCompiler (dce alone is not enough): first the handler is eliminated, then it's dead catch block.
- assertSameCode(singleMethodInstructions(methodOptCompiler)(code2), wrapInDefault(Op(RETURN)))
+ assertSameCode(methodOptCompiler.compileInstructions(code2), wrapInDefault(Op(RETURN)))
val code3 = "def f: Unit = { try { } catch { case _: Exception => try { } catch { case _: Exception => () } }; () }"
- assertSameCode(singleMethodInstructions(methodOptCompiler)(code3), wrapInDefault(Op(RETURN)))
+ assertSameCode(methodOptCompiler.compileInstructions(code3), wrapInDefault(Op(RETURN)))
// this example requires two iterations to get rid of the outer handler.
// the first iteration of DCE cannot remove the inner handler. then the inner (empty) handler is removed.
// then the second iteration of DCE removes the inner catch block, and then the outer handler is removed.
val code4 = "def f: Unit = { try { try { } catch { case _: Exception => () } } catch { case _: Exception => () }; () }"
- assertSameCode(singleMethodInstructions(methodOptCompiler)(code4), wrapInDefault(Op(RETURN)))
+ assertSameCode(methodOptCompiler.compileInstructions(code4), wrapInDefault(Op(RETURN)))
}
@Test // test the dce-testing tools
@@ -198,7 +172,7 @@ class UnreachableCodeTest extends ClearAfterClass {
}
@Test
- def bytecodeEquivalence: Unit = {
+ def bytecodeEquivalence(): Unit = {
assertTrue(List(VarOp(ILOAD, 1)) ===
List(VarOp(ILOAD, 2)))
assertTrue(List(VarOp(ILOAD, 1), VarOp(ISTORE, 1)) ===
@@ -225,4 +199,50 @@ class UnreachableCodeTest extends ClearAfterClass {
assertTrue(List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(3)), List("java/lang/Object", Label(4))), Label(3), Label(4)) ===
List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(1)), List("java/lang/Object", Label(3))), Label(1), Label(3)))
}
+
+ @Test
+ def loadNullNothingBytecode(): Unit = {
+ val code =
+ """class C {
+ | def nl: Null = null
+ | def nt: Nothing = throw new Error("")
+ | def cons(a: Any) = ()
+ |
+ | def t1 = cons(null)
+ | def t2 = cons(nl)
+ | def t3 = cons(throw new Error(""))
+ | def t4 = cons(nt)
+ |}
+ """.stripMargin
+ val c = noOptCompiler.compileClass(code)
+
+ assertSameSummary(getMethod(c, "nl"), List(ACONST_NULL, ARETURN))
+
+ assertSameSummary(getMethod(c, "nt"), List(
+ NEW, DUP, LDC, "<init>", ATHROW))
+
+ assertSameSummary(getMethod(c, "t1"), List(
+ ALOAD, ACONST_NULL, "cons", RETURN))
+
+ // GenBCode introduces POP; ACONST_NULL after loading an expression of type scala.runtime.Null$,
+ // see comment in BCodeBodyBuilder.adapt
+ assertSameSummary(getMethod(c, "t2"), List(
+ ALOAD, ALOAD, "nl", POP, ACONST_NULL, "cons", RETURN))
+
+ // the bytecode generated by GenBCode is ... ATHROW; INVOKEVIRTUAL C.cons; RETURN
+ // the ASM classfile writer creates a new basic block (creates a label) right after the ATHROW
+ // and replaces all instructions by NOP*; ATHROW, see comment in BCodeBodyBuilder.adapt
+ // NOTE: DCE is enabled by default and gets rid of the redundant code (tested below)
+ assertSameSummary(getMethod(c, "t3"), List(
+ ALOAD, NEW, DUP, LDC, "<init>", ATHROW, NOP, NOP, NOP, ATHROW))
+
+ // GenBCode introduces an ATHROW after the invocation of C.nt, see BCodeBodyBuilder.adapt
+ // NOTE: DCE is enabled by default and gets rid of the redundant code (tested below)
+ assertSameSummary(getMethod(c, "t4"), List(
+ ALOAD, ALOAD, "nt", ATHROW, NOP, NOP, NOP, ATHROW))
+
+ val cDCE = dceCompiler.compileClass(code)
+ assertSameSummary(getMethod(cDCE, "t3"), List(ALOAD, NEW, DUP, LDC, "<init>", ATHROW))
+ assertSameSummary(getMethod(cDCE, "t4"), List(ALOAD, ALOAD, "nt", ATHROW))
+ }
}
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 769736669b..7ca09ff41d 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala
@@ -2,28 +2,20 @@ package scala.tools.nsc
package backend.jvm
package opt
+import org.junit.Assert._
+import org.junit.Test
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.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 }
-}
+import scala.collection.JavaConverters._
+import scala.tools.partest.ASMConverters._
+import scala.tools.testing.BytecodeTesting
+import scala.tools.testing.BytecodeTesting._
@RunWith(classOf[JUnit4])
-class UnusedLocalVariablesTest extends ClearAfterClass {
- ClearAfterClass.stateToClear = UnusedLocalVariablesTest
-
- val dceCompiler = UnusedLocalVariablesTest.dceCompiler
+class UnusedLocalVariablesTest extends BytecodeTesting {
+ override def compilerArgs = "-opt:unreachable-code"
+ import compiler._
@Test
def removeUnusedVar(): Unit = {
@@ -56,7 +48,7 @@ class UnusedLocalVariablesTest extends ClearAfterClass {
| }
|}
|""".stripMargin
- val cls = compileClasses(dceCompiler)(code).head
+ val cls = compileClass(code)
val m = convertMethod(cls.methods.asScala.toList.find(_.desc == "(I)V").get)
assertTrue(m.localVars.length == 2) // this, a, but not y
@@ -77,19 +69,14 @@ class UnusedLocalVariablesTest extends ClearAfterClass {
|}
""".stripMargin
- val clss2 = compileClasses(dceCompiler)(code2)
- val cls2 = clss2.find(_.name == "C").get
- val companion2 = clss2.find(_.name == "C$").get
-
- val clsConstr = convertMethod(cls2.methods.asScala.toList.find(_.name == "<init>").get)
- val companionConstr = convertMethod(companion2.methods.asScala.toList.find(_.name == "<init>").get)
+ val List(cls2, companion2) = compileClasses(code2)
- assertTrue(clsConstr.localVars.length == 1) // this
- assertTrue(companionConstr.localVars.length == 1) // this
+ assertTrue(getMethod(cls2, "<init>").localVars.length == 1) // this
+ assertTrue(getMethod(companion2, "<init>").localVars.length == 1) // this
}
def assertLocalVarCount(code: String, numVars: Int): Unit = {
- assertTrue(singleMethod(dceCompiler)(code).localVars.length == numVars)
+ assertTrue(compileMethod(code).localVars.length == numVars)
}
}