summaryrefslogtreecommitdiff
path: root/test/junit/scala/tools/nsc/backend
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2015-03-11 12:09:21 -0700
committerLukas Rytz <lukas.rytz@gmail.com>2015-03-11 13:47:07 -0700
commitf8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc (patch)
treed7745fc7de76b699304e1ddac255e531134400ff /test/junit/scala/tools/nsc/backend
parent027e97981d9b6a3783e9ab247cc898017b3de821 (diff)
downloadscala-f8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc.tar.gz
scala-f8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc.tar.bz2
scala-f8bb3d5289e5eb84ccd94386e5c3df1bdf8b91bc.zip
Inline final methods defined in traits
In order to inline a final trait method, callsites of such methods are first re-written from interface calls to static calls of the trait's implementation class. Then inlining proceeds as ususal. One problem that came up during development was that mixin methods are added to class symbols only for classes being compiled, but not for others. In order to inline a mixin method, we need the InlineInfo, which so far was built using the class (and method) symbols. So we had a problem with separate compilation. Looking up the symbol from a given classfile name was already known to be brittle (it's also one of the weak points of the current inliner), so we changed the strategy. Now the InlineInfo for every class is encoded in a new classfile attribute. This classfile attribute is relatively small, because all strings it references (class internal names, method names, method descriptors) would exist anyway in the constant pool, so it just adds a few references. When building the InlineInfo for a class symbol, we only look at the symbol properties for symbols being compiled in the current run. For unpickled symbols, we build the InlineInfo by reading the classfile attribute. This change also adds delambdafy:method classes to currentRun.symSource. Otherwise, currentRun.compiles(lambdaClass) is false.
Diffstat (limited to 'test/junit/scala/tools/nsc/backend')
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala64
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala13
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala13
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala65
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala114
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala325
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala4
9 files changed, 555 insertions, 51 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
index e94f33db3d..c64f6e7f10 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
@@ -14,6 +14,7 @@ import scala.tools.nsc.settings.{MutableSettings, ScalaSettings}
import scala.tools.nsc.{Settings, Global}
import scala.tools.partest.ASMConverters
import scala.collection.JavaConverters._
+import scala.tools.testing.TempDir
object CodeGenTools {
import ASMConverters._
@@ -42,20 +43,27 @@ object CodeGenTools {
}
def newCompiler(defaultArgs: String = "-usejavacp", extraArgs: String = ""): Global = {
+ val compiler = newCompilerWithoutVirtualOutdir(defaultArgs, extraArgs)
+ resetOutput(compiler)
+ compiler
+ }
+
+ def newCompilerWithoutVirtualOutdir(defaultArgs: String = "-usejavacp", extraArgs: String = ""): Global = {
val settings = new Settings()
val args = (CommandLineParser tokenize defaultArgs) ++ (CommandLineParser tokenize extraArgs)
settings.processArguments(args, processAll = true)
- val compiler = new Global(settings)
- resetOutput(compiler)
- compiler
+ new Global(settings)
}
- def compile(compiler: Global)(code: String): List[(String, Array[Byte])] = {
+ def newRun(compiler: Global): compiler.Run = {
compiler.reporter.reset()
resetOutput(compiler)
- val run = new compiler.Run()
- run.compileSources(List(new BatchSourceFile("unitTestSource.scala", code)))
- val outDir = compiler.settings.outputDirs.getSingleOutput.get
+ new compiler.Run()
+ }
+
+ 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) {
@@ -67,8 +75,46 @@ object CodeGenTools {
files(outDir)
}
- def compileClasses(compiler: Global)(code: String): List[ClassNode] = {
- compile(compiler)(code).map(p => AsmUtils.readClass(p._2)).sortBy(_.name)
+ def compile(compiler: Global)(scalaCode: String, javaCode: List[(String, String)] = Nil): List[(String, Array[Byte])] = {
+ val run = newRun(compiler)
+ run.compileSources(makeSourceFile(scalaCode, "unitTestSource.scala") :: javaCode.map(p => makeSourceFile(p._1, p._2)))
+ 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 = ""): 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")))
+ }
+
+ val classfiles = getGeneratedClassfiles(outDir)
+ outDir.delete()
+ classfiles
+ }
+
+ def compileClassesSeparately(codes: List[String], extraArgs: String = "") = {
+ readAsmClasses(compileSeparately(codes, extraArgs))
+ }
+
+ 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): List[ClassNode] = {
+ readAsmClasses(compile(compiler)(code, javaCode))
}
def compileMethods(compiler: Global)(code: String): List[MethodNode] = {
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
index 3b1b009037..94877fb037 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DirectCompileTest.scala
@@ -78,4 +78,17 @@ class DirectCompileTest extends ClearAfterClass {
Label(11)
))
}
+
+ @Test
+ def testSeparateCompilation(): Unit = {
+ val codeA = "class A { def f = 1 }"
+ val codeB = "class B extends A { def g = f }"
+ val List(a, b) = compileClassesSeparately(List(codeA, codeB))
+ val ins = getSingleMethod(b, "g").instructions
+ assert(ins exists {
+ case Invoke(_, "B", "f", _, _) => true
+ case _ => false
+ }, ins)
+
+ }
}
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 f7c9cab284..761f214f82 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala
@@ -59,17 +59,12 @@ class BTypesFromClassfileTest {
else (fromSym.flags | ACC_PRIVATE | ACC_PUBLIC) == (fromClassfile.flags | ACC_PRIVATE | ACC_PUBLIC)
}, s"class flags differ\n$fromSym\n$fromClassfile")
- // when parsing from classfile, the inline infos are obtained through the classSymbol, which
- // is searched based on the classfile name. this lookup can fail.
- assert(fromSym.inlineInfos.size == fromClassfile.inlineInfos.size || fromClassfile.inlineInfos.isEmpty,
- s"wrong # of inline infos:\n${fromSym.inlineInfos.keys.toList.sorted}\n${fromClassfile.inlineInfos.keys.toList.sorted}")
- fromClassfile.inlineInfos foreach {
- case (signature, inlineInfo) =>
- assert(fromSym.inlineInfos(signature) == inlineInfo, s"inline infos differ for $signature:\n$inlineInfo\n${fromClassfile.inlineInfos(signature)}")
- }
+ // we don't compare InlineInfos in this test: in both cases (from symbol and from classfile) they
+ // are actually created by looking at the classfile members, not the symbol's. InlineInfos are only
+ // built from symbols for classes that are being compiled, which is not the case here. Instead
+ // there's a separate InlineInfoTest.
val chk1 = sameBTypes(fromSym.superClass, fromClassfile.superClass, checked)
-
val chk2 = sameBTypes(fromSym.interfaces, fromClassfile.interfaces, chk1)
// The fromSym info has only member classes, no local or anonymous. The symbol is read from the
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 69bd92b4ba..16f09db189 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
@@ -89,8 +89,8 @@ class CallGraphTest {
assert(callee.callee == target)
assert(callee.calleeDeclarationClass == calleeDeclClass)
assert(callee.safeToInline == safeToInline)
- assert(callee.annotatedInline.get == atInline)
- assert(callee.annotatedNoInline.get == atNoInline)
+ assert(callee.annotatedInline == atInline)
+ assert(callee.annotatedNoInline == atNoInline)
assert(callsite.argInfos == List()) // not defined yet
} catch {
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
index fc748196d0..76492cfa23 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CompactLocalVariablesTest.scala
@@ -17,8 +17,8 @@ class CompactLocalVariablesTest {
// recurse-unreachable-jumps is required for eliminating catch blocks, in the first dce round they
// are still live.only after eliminating the empty handler the catch blocks become unreachable.
- val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps,compact-locals")
- val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,recurse-unreachable-jumps")
+ val methodOptCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code,compact-locals")
+ val noCompactVarsCompiler = newCompiler(extraArgs = "-target:jvm-1.6 -Ybackend:GenBCode -Yopt:unreachable-code")
@Test
def compactUnused(): Unit = {
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala
new file mode 100644
index 0000000000..4e12ed757e
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineInfoTest.scala
@@ -0,0 +1,65 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.collection.generic.Clearable
+import org.junit.Assert._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import AsmUtils._
+import scala.tools.testing.ClearAfterClass
+
+import scala.collection.convert.decorateAsScala._
+
+object InlineInfoTest extends ClearAfterClass.Clearable {
+ var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:classpath")
+ def clear(): Unit = { compiler = null }
+
+ def notPerRun: List[Clearable] = List(compiler.genBCode.bTypes.classBTypeFromInternalName, compiler.genBCode.bTypes.byteCodeRepository.classes)
+ notPerRun foreach compiler.perRunCaches.unrecordCache
+}
+
+@RunWith(classOf[JUnit4])
+class InlineInfoTest {
+ val compiler = InlineInfoTest.compiler
+
+ def compile(code: String) = {
+ InlineInfoTest.notPerRun.foreach(_.clear())
+ compileClasses(compiler)(code)
+ }
+
+ @Test
+ def inlineInfosFromSymbolAndAttribute(): Unit = {
+ val code =
+ """trait T {
+ | @inline def f: Int
+ | @noinline final def g = 0
+ |}
+ |trait U { self: T =>
+ | @inline def f = 0
+ | final def h = 0
+ | final class K {
+ | @inline def i = 0
+ | }
+ |}
+ |sealed trait V {
+ | @inline def j = 0
+ |}
+ |class C extends T with U
+ """.stripMargin
+ val classes = compile(code)
+ val fromSyms = classes.map(c => compiler.genBCode.bTypes.classBTypeFromInternalName(c.name).info.inlineInfo)
+
+ val fromAttrs = classes.map(c => {
+ assert(c.attrs.asScala.exists(_.isInstanceOf[InlineInfoAttribute]), c.attrs)
+ compiler.genBCode.bTypes.inlineInfoFromClassfile(c)
+ })
+
+ assert(fromSyms == fromAttrs)
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
new file mode 100644
index 0000000000..58a262c401
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerSeparateCompilationTest.scala
@@ -0,0 +1,114 @@
+package scala.tools.nsc
+package backend.jvm
+package opt
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Test
+import scala.tools.asm.Opcodes._
+import org.junit.Assert._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+import AsmUtils._
+
+import scala.collection.convert.decorateAsScala._
+
+object InlinerSeparateCompilationTest {
+ val args = "-Ybackend:GenBCode -Yopt:l:classpath"
+}
+
+@RunWith(classOf[JUnit4])
+class InlinerSeparateCompilationTest {
+ import InlinerSeparateCompilationTest._
+ import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke}
+
+ @Test
+ def inlnieMixedinMember(): Unit = {
+ val codeA =
+ """trait T {
+ | @inline def f = 0
+ |}
+ |object O extends T {
+ | @inline def g = 1
+ |}
+ """.stripMargin
+
+ val codeB =
+ """class C {
+ | def t1(t: T) = t.f
+ | def t2 = O.f
+ | def t3 = O.g
+ |}
+ """.stripMargin
+
+ val List(c, o, oMod, t, tCls) = compileClassesSeparately(List(codeA, codeB), args)
+ assertInvoke(getSingleMethod(c, "t1"), "T", "f")
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ assertNoInvoke(getSingleMethod(c, "t3"))
+ }
+
+ @Test
+ def inlineSealedMember(): Unit = {
+ val codeA =
+ """sealed trait T {
+ | @inline def f = 1
+ |}
+ """.stripMargin
+
+ val codeB =
+ """class C {
+ | def t1(t: T) = t.f
+ |}
+ """.stripMargin
+
+ val List(c, t, tCls) = compileClassesSeparately(List(codeA, codeB), args)
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ }
+
+ @Test
+ def inlineInheritedMember(): Unit = {
+ val codeA =
+ """trait T {
+ | @inline final def f = 1
+ |}
+ |trait U extends T {
+ | @inline final def g = f
+ |}
+ """.stripMargin
+
+ val codeB =
+ """class C extends U {
+ | def t1 = this.f
+ | def t2 = this.g
+ | def t3(t: T) = t.f
+ |}
+ """.stripMargin
+
+ val List(c, t, tCls, u, uCls) = compileClassesSeparately(List(codeA, codeB), args)
+ for (m <- List("t1", "t2", "t3")) assertNoInvoke(getSingleMethod(c, m))
+ }
+
+ @Test
+ def inlineWithSelfType(): Unit = {
+ val assembly =
+ """trait Assembly extends T {
+ | @inline final def g = 1
+ | @inline final def n = m
+ |}
+ """.stripMargin
+
+ val codeA =
+ s"""trait T { self: Assembly =>
+ | @inline final def f = g
+ | @inline final def m = 1
+ |}
+ |$assembly
+ """.stripMargin
+
+ val List(a, aCls, t, tCls) = compileClassesSeparately(List(codeA, assembly), args)
+ assertNoInvoke(getSingleMethod(tCls, "f"))
+ assertNoInvoke(getSingleMethod(aCls, "n"))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
index 4e7a2399a2..694dff8dee 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
@@ -33,18 +33,37 @@ object InlinerTest extends ClearAfterClass.Clearable {
notPerRun foreach compiler.perRunCaches.unrecordCache
def clear(): Unit = { compiler = null }
+
+ implicit class listStringLines[T](val l: List[T]) extends AnyVal {
+ def stringLines = l.mkString("\n")
+ }
+
+ def assertNoInvoke(m: Method): Unit = assertNoInvoke(m.instructions)
+ def assertNoInvoke(ins: List[Instruction]): Unit = {
+ assert(!ins.exists(_.isInstanceOf[Invoke]), ins.stringLines)
+ }
+
+ def assertInvoke(m: Method, receiver: String, method: String): Unit = assertInvoke(m.instructions, receiver, method)
+ def assertInvoke(l: List[Instruction], receiver: String, method: String): Unit = {
+ assert(l.exists {
+ case Invoke(_, `receiver`, `method`, _, _) => true
+ case _ => false
+ }, l.stringLines)
+ }
}
@RunWith(classOf[JUnit4])
class InlinerTest extends ClearAfterClass {
ClearAfterClass.stateToClear = InlinerTest
+ import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke}
+
val compiler = InlinerTest.compiler
import compiler.genBCode.bTypes._
- def compile(code: String): List[ClassNode] = {
+ def compile(scalaCode: String, javaCode: List[(String, String)] = Nil): List[ClassNode] = {
InlinerTest.notPerRun.foreach(_.clear())
- compileClasses(compiler)(code)
+ compileClasses(compiler)(scalaCode, javaCode)
}
def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = {
@@ -229,8 +248,8 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
val List(cCls) = compile(code)
- val instructions = instructionsFromMethod(cCls.methods.asScala.find(_.name == "test").get)
- assert(instructions.contains(Op(ICONST_0)), instructions mkString "\n")
+ val instructions = getSingleMethod(cCls, "test").instructions
+ assert(instructions.contains(Op(ICONST_0)), instructions.stringLines)
assert(!instructions.contains(Op(ICONST_1)), instructions)
}
@@ -282,16 +301,22 @@ class InlinerTest extends ClearAfterClass {
def arraycopy(): Unit = {
// also tests inlining of a void-returning method (no return value on the stack)
val code =
- """class C {
+ """// can't use the `compat.Platform.arraycopy` from the std lib for now, because the classfile doesn't have a ScalaInlineInfo attribute
+ |object Platform {
+ | @inline def arraycopy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int) {
+ | System.arraycopy(src, srcPos, dest, destPos, length)
+ | }
+ |}
+ |class C {
| def f(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = {
- | compat.Platform.arraycopy(src, srcPos, dest, destPos, length)
+ | Platform.arraycopy(src, srcPos, dest, destPos, length)
| }
|}
""".stripMargin
- val List(c) = compile(code)
- val ins = instructionsFromMethod(c.methods.asScala.find(_.name == "f").get)
+ val List(c, _, _) = compile(code)
+ val ins = getSingleMethod(c, "f").instructions
val invokeSysArraycopy = Invoke(INVOKESTATIC, "java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", false)
- assert(ins contains invokeSysArraycopy, ins mkString "\n")
+ assert(ins contains invokeSysArraycopy, ins.stringLines)
}
@Test
@@ -311,7 +336,7 @@ class InlinerTest extends ClearAfterClass {
}
@Test
- def atInlineInTraitDoesNotCrash(): Unit = {
+ def atInlineInTrait(): Unit = {
val code =
"""trait T {
| @inline final def f = 0
@@ -321,10 +346,7 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
val List(c, t, tClass) = compile(code)
- val ins = instructionsFromMethod(c.methods.asScala.find(_.name == "g").get)
- val invokeF = Invoke(INVOKEINTERFACE, "T", "f", "()I", true)
- // no inlining yet
- assert(ins contains invokeF, ins mkString "\n")
+ assertNoInvoke(getSingleMethod(c, "g"))
}
@Test
@@ -336,10 +358,8 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
val List(c) = compile(code)
- val ins = instructionsFromMethod(c.methods.asScala.find(_.name == "g").get)
- println(ins)
// no more invoke, f is inlined
- assert(ins.count(_.isInstanceOf[Invoke]) == 0, ins mkString "\n")
+ assertNoInvoke(getSingleMethod(c, "g"))
}
@Test
@@ -373,11 +393,11 @@ class InlinerTest extends ClearAfterClass {
val ins = instructionsFromMethod(f)
// no invocations, lowestOneBit is inlined
- assert(ins.count(_.isInstanceOf[Invoke]) == 0, ins mkString "\n")
+ assertNoInvoke(ins)
// no null check when inlining a static method
ins foreach {
- case Jump(IFNONNULL, _) => assert(false, ins mkString "\n")
+ case Jump(IFNONNULL, _) => assert(false, ins.stringLines)
case _ =>
}
}
@@ -437,16 +457,267 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- InlinerTest.notPerRun.foreach(_.clear())
- compiler.reporter.reset()
- compiler.settings.outputDirs.setSingleOutput(new VirtualDirectory("(memory)", None))
- val run = new compiler.Run()
- run.compileSources(List(new BatchSourceFile("A.java", javaCode), new BatchSourceFile("B.scala", scalaCode)))
- val outDir = compiler.settings.outputDirs.getSingleOutput.get
- val List(b) = outDir.iterator.map(f => AsmUtils.readClass(f.toByteArray)).toList.sortBy(_.name)
+ val List(b) = compile(scalaCode, List((javaCode, "A.java")))
val ins = getSingleMethod(b, "g").instructions
val invokeFlop = Invoke(INVOKEVIRTUAL, "B", "flop", "()I", false)
- assert(ins contains invokeFlop, ins mkString "\n")
+ assert(ins contains invokeFlop, ins.stringLines)
+ }
+
+ @Test
+ def inlineFromTraits(): Unit = {
+ val code =
+ """trait T {
+ | @inline final def f = g
+ | @inline final def g = 1
+ |}
+ |
+ |class C extends T {
+ | def t1(t: T) = t.f
+ | def t2(c: C) = c.f
+ |}
+ """.stripMargin
+ val List(c, t, tClass) = compile(code)
+ // both are just `return 1`, no more calls
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ }
+
+ @Test
+ def inlineMixinMethods(): Unit = {
+ val code =
+ """trait T {
+ | @inline final def f = 1
+ |}
+ |class C extends T
+ """.stripMargin
+ val List(c, t, tClass) = compile(code)
+ // the static implementaiton method is inlined into the mixin, so there's no invocation in the mixin
+ assertNoInvoke(getSingleMethod(c, "f"))
+ }
+
+ @Test
+ def inlineTraitInherited(): Unit = {
+ val code =
+ """trait T {
+ | @inline final def f = 1
+ |}
+ |trait U extends T {
+ | @inline final def g = f
+ |}
+ |class C extends U {
+ | def t1 = f
+ | def t2 = g
+ |}
+ """.stripMargin
+ val List(c, t, tClass, u, uClass) = compile(code)
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ }
+
+ @Test
+ def virtualTraitNoInline(): Unit = {
+ val code =
+ """trait T {
+ | @inline def f = 1
+ |}
+ |class C extends T {
+ | def t1(t: T) = t.f
+ | def t2 = this.f
+ |}
+ """.stripMargin
+ val List(c, t, tClass) = compile(code)
+ assertInvoke(getSingleMethod(c, "t1"), "T", "f")
+ assertInvoke(getSingleMethod(c, "t2"), "C", "f")
+ }
+
+ @Test
+ def sealedTraitInline(): Unit = {
+ val code =
+ """sealed trait T {
+ | @inline def f = 1
+ |}
+ |class C {
+ | def t1(t: T) = t.f
+ |}
+ """.stripMargin
+ val List(c, t, tClass) = compile(code)
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ }
+
+ @Test
+ def inlineFromObject(): Unit = {
+ val code =
+ """trait T {
+ | @inline def f = 0
+ |}
+ |object O extends T {
+ | @inline def g = 1
+ | // mixin generates `def f = T$class.f(this)`, which is inlined here (we get ICONST_0)
+ |}
+ |class C {
+ | def t1 = O.f // the mixin method of O is inlined, so we directly get the ICONST_0
+ | def t2 = O.g // object members are inlined
+ | def t3(t: T) = t.f // no inlining here
+ |}
+ """.stripMargin
+ val List(c, oMirror, oModule, t, tClass) = compile(code)
+
+ assertNoInvoke(getSingleMethod(oModule, "f"))
+
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ assertInvoke(getSingleMethod(c, "t3"), "T", "f")
+ }
+
+ @Test
+ def selfTypeInline(): Unit = {
+ val code =
+ """trait T { self: Assembly =>
+ | @inline final def f = g
+ | @inline final def m = 1
+ |}
+ |trait Assembly extends T {
+ | @inline final def g = 1
+ | @inline final def n = m // inlined. (*)
+ | // (*) the declaration class of m is T. the signature of T$class.m is m(LAssembly;)I. so we need the self type to build the
+ | // signature. then we can look up the MethodNode of T$class.m and then rewrite the INVOKEINTERFACE to INVOKESTATIC.
+ |}
+ |class C {
+ | def t1(a: Assembly) = a.f // like above, decl class is T, need self-type of T to rewrite the interface call to static.
+ | def t2(a: Assembly) = a.n
+ |}
+ """.stripMargin
+
+ val List(assembly, assemblyClass, c, t, tClass) = compile(code)
+
+ assertNoInvoke(getSingleMethod(tClass, "f"))
+
+ assertNoInvoke(getSingleMethod(assemblyClass, "n"))
+
+ assertNoInvoke(getSingleMethod(c, "t1"))
+ assertNoInvoke(getSingleMethod(c, "t2"))
+ }
+
+ @Test
+ def selfTypeInline2(): Unit = {
+ // There are some interesting things going on here with the self types. Here's a short version:
+ //
+ // trait T1 { def f = 1 }
+ // trait T2a { self: T1 with T2a => // self type in the backend: T1
+ // def f = 2
+ // def g = f // resolved to T2a.f
+ // }
+ // trait T2b { self: T2b with T1 => // self type in the backend: T2b
+ // def f = 2
+ // def g = f // resolved to T1.f
+ // }
+ //
+ // scala> val t = typeOf[T2a]; exitingMixin(t.typeOfThis.typeSymbol) // self type of T2a is T1
+ // res28: $r.intp.global.Symbol = trait T1
+ //
+ // scala> typeOf[T2a].typeOfThis.member(newTermName("f")).owner // f in T2a is resolved as T2a.f
+ // res29: $r.intp.global.Symbol = trait T2a
+ //
+ // scala> val t = typeOf[T2b]; exitingMixin(t.typeOfThis.typeSymbol) // self type of T2b is T1
+ // res30: $r.intp.global.Symbol = trait T2b
+ //
+ // scala> typeOf[T2b].typeOfThis.member(newTermName("f")).owner // f in T2b is resolved as T1.f
+ // res31: $r.intp.global.Symbol = trait T1
+
+ val code =
+ """trait T1 {
+ | @inline def f: Int = 0
+ | @inline def g1 = f // not inlined: f not final, so T1$class.g1 has an interface call T1.f
+ |}
+ |
+ |// erased self-type (used in impl class for `self` parameter): T1
+ |trait T2a { self: T1 with T2a =>
+ | @inline override final def f = 1
+ | @inline def g2a = f // inlined: resolved as T2a.f, which is re-written to T2a$class.f, so T2a$class.g2a has ICONST_1
+ |}
+ |
+ |final class Ca extends T1 with T2a {
+ | // mixin generates accessors like `def g1 = T1$class.g1`, the impl class method call is inlined into the accessor.
+ |
+ | def m1a = g1 // call to accessor, inlined, we get the interface call T1.f
+ | def m2a = g2a // call to accessor, inlined, we get ICONST_1
+ | def m3a = f // call to accessor, inlined, we get ICONST_1
+ |
+ | def m4a(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
+ | def m5a(t: T2a) = t.f // re-written to T2a$class.f, inlined, ICONST_1
+ |}
+ |
+ |// erased self-type: T2b
+ |trait T2b { self: T2b with T1 =>
+ | @inline override final def f = 1
+ | @inline def g2b = f // not inlined: resolved as T1.f, so T2b$class.g2b has an interface call T1.f
+ |}
+ |
+ |final class Cb extends T1 with T2b {
+ | def m1b = g1 // inlined, we get the interface call to T1.f
+ | def m2b = g2b // inlined, we get the interface call to T1.f
+ | def m3b = f // inlined, we get ICONST_1
+ |
+ | def m4b(t: T1) = t.f // T1.f is not final, so not inlined, interface call to T1.f
+ | def m5b(t: T2b) = t.f // re-written to T2b$class.f, inlined, ICONST_1
+ |}
+ """.stripMargin
+ val List(ca, cb, t1, t1C, t2a, t2aC, t2b, t2bC) = compile(code)
+
+ val t2aCfDesc = t2aC.methods.asScala.find(_.name == "f").get.desc
+ assert(t2aCfDesc == "(LT1;)I", t2aCfDesc) // self-type of T2a is T1
+
+ val t2bCfDesc = t2bC.methods.asScala.find(_.name == "f").get.desc
+ assert(t2bCfDesc == "(LT2b;)I", t2bCfDesc) // self-type of T2b is T2b
+
+ assertNoInvoke(getSingleMethod(t2aC, "g2a"))
+ assertInvoke(getSingleMethod(t2bC, "g2b"), "T1", "f")
+
+ assertInvoke(getSingleMethod(ca, "m1a"), "T1", "f")
+ assertNoInvoke(getSingleMethod(ca, "m2a")) // no invoke, see comment on def g2a
+ assertNoInvoke(getSingleMethod(ca, "m3a"))
+ assertInvoke(getSingleMethod(ca, "m4a"), "T1", "f")
+ assertNoInvoke(getSingleMethod(ca, "m5a"))
+
+ assertInvoke(getSingleMethod(cb, "m1b"), "T1", "f")
+ assertInvoke(getSingleMethod(cb, "m2b"), "T1", "f") // invoke, see comment on def g2b
+ assertNoInvoke(getSingleMethod(cb, "m3b"))
+ assertInvoke(getSingleMethod(cb, "m4b"), "T1", "f")
+ assertNoInvoke(getSingleMethod(cb, "m5b"))
+ }
+
+ @Test
+ def finalSubclassInline(): Unit = {
+ val code =
+ """class C {
+ | @inline def f = 0
+ | @inline final def g = 1
+ |}
+ |final class D extends C
+ |object E extends C
+ |class T {
+ | def t1(d: D) = d.f + d.g + E.f + E.g // d.f can be inlined because the reciever type is D, which is final.
+ |} // so d.f can be resolved statically. same for E.f
+ """.stripMargin
+ val List(c, d, e, eModule, t) = compile(code)
+ assertNoInvoke(getSingleMethod(t, "t1"))
+ }
+
+ @Test
+ def inlineFromNestedClasses(): Unit = {
+ val code =
+ """class C {
+ | trait T { @inline final def f = 1 }
+ | class D extends T{
+ | def m(t: T) = t.f
+ | }
+ |
+ | def m(d: D) = d.f
+ |}
+ """.stripMargin
+ val List(c, d, t, tC) = compile(code)
+ assertNoInvoke(getSingleMethod(d, "m"))
+ assertNoInvoke(getSingleMethod(c, "m"))
}
}
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 7c9636b8b7..c2e2a1b883 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
@@ -22,8 +22,8 @@ object UnreachableCodeTest extends ClearAfterClass.Clearable {
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.
- var noOptNoFramesCompiler = newCompiler(extraArgs = "-target:jvm-1.5 -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