summaryrefslogtreecommitdiff
path: root/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
diff options
context:
space:
mode:
Diffstat (limited to 'test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala')
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala862
1 files changed, 692 insertions, 170 deletions
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..e2a495fb2b 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala
@@ -6,17 +6,11 @@ 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
@@ -25,49 +19,43 @@ import AsmUtils._
import BackendReporting._
-import scala.collection.convert.decorateAsScala._
+import scala.collection.JavaConverters._
import scala.tools.testing.ClearAfterClass
object InlinerTest extends ClearAfterClass.Clearable {
- val args = "-Ybackend:GenBCode -Yopt:l:classpath -Yopt-warnings"
+ val args = "-Yopt:l:classpath -Yopt-warnings"
var compiler = newCompiler(extraArgs = args)
+ var inlineOnlyCompiler = newCompiler(extraArgs = "-Yopt:inline-project")
// 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)
+ def notPerRun: List[Clearable] = List(
+ compiler.genBCode.bTypes.classBTypeFromInternalName,
+ compiler.genBCode.bTypes.byteCodeRepository.compilingClasses,
+ compiler.genBCode.bTypes.byteCodeRepository.parsedClasses,
+ compiler.genBCode.bTypes.callGraph.callsites)
notPerRun foreach compiler.perRunCaches.unrecordCache
- def clear(): Unit = { compiler = null }
-
- implicit class listStringLines[T](val l: List[T]) extends AnyVal {
- def stringLines = l.mkString("\n")
- }
-
- def assertNoInvoke(m: Method): Unit = assertNoInvoke(m.instructions)
- def assertNoInvoke(ins: List[Instruction]): Unit = {
- assert(!ins.exists(_.isInstanceOf[Invoke]), ins.stringLines)
- }
-
- def assertInvoke(m: Method, receiver: String, method: String): Unit = assertInvoke(m.instructions, receiver, method)
- def assertInvoke(l: List[Instruction], receiver: String, method: String): Unit = {
- assert(l.exists {
- case Invoke(_, `receiver`, `method`, _, _) => true
- case _ => false
- }, l.stringLines)
- }
+ def clear(): Unit = { compiler = null; inlineOnlyCompiler = null }
}
@RunWith(classOf[JUnit4])
class InlinerTest extends ClearAfterClass {
ClearAfterClass.stateToClear = InlinerTest
- import InlinerTest.{listStringLines, assertInvoke, assertNoInvoke}
-
val compiler = InlinerTest.compiler
import compiler.genBCode.bTypes._
+ import compiler.genBCode.bTypes.backendUtils._
+ import inlinerHeuristics._
+
+ val inlineOnlyCompiler = InlinerTest.inlineOnlyCompiler
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)
+ // 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.toList.sortBy(_.name)
}
def checkCallsite(callsite: callGraph.Callsite, callee: MethodNode) = {
@@ -79,27 +67,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 = findAsmMethod(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.canInlineBody(cs)
+ }
- 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))
+ gMethod
}
@Test
@@ -111,10 +97,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 +131,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)
- assertSameCode(convertMethod(g).instructions.dropNonOp.take(4), expectedInlined)
+ 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), gBeforeLocalOpt)
compiler.genBCode.bTypes.localOpt.methodOptimizations(g, "C")
- assertSameCode(convertMethod(g).instructions.dropNonOp,
- expectedInlined ++ List(VarOp(ASTORE, 2), VarOp(ALOAD, 2), Op(ATHROW)))
+ assertSameCode(convertMethod(g), invokeQQQ :+ Op(ATHROW))
}
@Test
@@ -166,11 +159,11 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val (_, can) = inlineTest(code, cls => {
+ val can = canInlineTest(code, cls => {
val f = cls.methods.asScala.find(_.name == "f").get
f.access |= ACC_SYNCHRONIZED
})
- assert(can.get.isInstanceOf[SynchronizedMethod], can)
+ assert(can.nonEmpty && can.get.isInstanceOf[SynchronizedMethod], can)
}
@Test
@@ -181,7 +174,7 @@ class InlinerTest extends ClearAfterClass {
| def g = f + 1
|}
""".stripMargin
- val (_, r) = inlineTest(code)
+ val r = canInlineTest(code)
assert(r.isEmpty, r)
}
@@ -195,8 +188,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 +209,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 = findAsmMethod(d, "h")
+ val gCall = getCallsite(hMeth, "g")
+ val r = inliner.canInlineBody(gCall)
+ assert(r.nonEmpty && r.get.isInstanceOf[IllegalAccessInstruction], r)
}
@Test
@@ -273,7 +247,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 +269,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)
}
}
@@ -336,7 +310,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,7 +323,7 @@ class InlinerTest extends ClearAfterClass {
| def g(t: T) = t.f
|}
""".stripMargin
- val List(c, t, tClass) = compile(code)
+ val List(c, t) = compile(code)
assertNoInvoke(getSingleMethod(c, "g"))
}
@@ -375,26 +349,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 = findAsmMethod(c, "f")
+ val call = getCallsite(fMeth, "lowestOneBit")
- assert(r.isEmpty, r)
- val ins = instructionsFromMethod(f)
+ val warning = inliner.canInlineBody(call)
+ assert(warning.isEmpty, warning)
+
+ inliner.inline(InlineRequest(call, Nil))
+ val ins = instructionsFromMethod(fMeth)
// no invocations, lowestOneBit is inlined
assertNoInvoke(ins)
@@ -425,7 +387,8 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val List(c) = compile(code)
+ // use a compiler without local optimizations (cleanups)
+ val List(c) = compileClasses(inlineOnlyCompiler)(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,7 +428,7 @@ 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 the parent 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})
@@ -488,26 +451,13 @@ 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"))
- }
-
- @Test
def inlineTraitInherited(): Unit = {
val code =
"""trait T {
@@ -521,7 +471,7 @@ class InlinerTest extends ClearAfterClass {
| def t2 = g
|}
""".stripMargin
- val List(c, t, tClass, u, uClass) = compile(code)
+ val List(c, t, u) = compile(code)
assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
}
@@ -541,7 +491,7 @@ class InlinerTest extends ClearAfterClass {
"C::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden",
"T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden")
var count = 0
- val List(c, t, tClass) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
+ val List(c, t) = compile(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
assert(count == 2, count)
assertInvoke(getSingleMethod(c, "t1"), "T", "f")
assertInvoke(getSingleMethod(c, "t2"), "C", "f")
@@ -557,7 +507,7 @@ class InlinerTest extends ClearAfterClass {
| def t1(t: T) = t.f
|}
""".stripMargin
- val List(c, t, tClass) = compile(code)
+ val List(c, t) = compile(code)
assertNoInvoke(getSingleMethod(c, "t1"))
}
@@ -569,7 +519,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
@@ -579,10 +529,10 @@ class InlinerTest extends ClearAfterClass {
""".stripMargin
val warn = "T::f()I is annotated @inline but cannot be inlined: the method is not final and may be overridden"
var count = 0
- val List(c, oMirror, oModule, t, tClass) = compile(code, allowMessage = i => {count += 1; i.msg contains warn})
+ 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(getSingleMethod(t, "f"))
assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
@@ -598,21 +548,19 @@ 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(getSingleMethod(t, "f"))
- assertNoInvoke(getSingleMethod(assemblyClass, "n"))
+ assertNoInvoke(getSingleMethod(assembly, "n"))
assertNoInvoke(getSingleMethod(c, "t1"))
assertNoInvoke(getSingleMethod(c, "t2"))
@@ -647,30 +595,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,23 +627,17 @@ 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"
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(getSingleMethod(t2a, "g2a"))
+ assertInvoke(getSingleMethod(t2b, "g2b"), "T1", "f")
assertInvoke(getSingleMethod(ca, "m1a"), "T1", "f")
assertNoInvoke(getSingleMethod(ca, "m2a")) // no invoke, see comment on def g2a
@@ -732,14 +674,13 @@ 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)
+ val List(c, d, t) = compile(code)
assertNoInvoke(getSingleMethod(d, "m"))
assertNoInvoke(getSingleMethod(c, "m"))
}
@@ -754,9 +695,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 = getSingleMethod(t, "t1")
+ val t2 = getSingleMethod(t, "t2")
val cast = TypeOp(CHECKCAST, "C")
Set(t1, t2).foreach(m => assert(m.instructions.contains(cast), m.instructions))
}
@@ -803,8 +744,8 @@ class InlinerTest extends ClearAfterClass {
| 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
@@ -817,8 +758,8 @@ class InlinerTest extends ClearAfterClass {
| 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,7 +776,7 @@ 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 List(c, t, u) = compile(code, allowMessage = _.msg contains "i()I is annotated @inline but cannot be inlined")
val m1 = getSingleMethod(c, "m1")
assertInvoke(m1, "T", "a")
assertInvoke(m1, "T", "b")
@@ -844,8 +785,8 @@ class InlinerTest extends ClearAfterClass {
assertNoInvoke(getSingleMethod(c, "m2"))
val m3 = getSingleMethod(c, "m3")
- assertInvoke(m3, "T$class", "f")
- assertInvoke(m3, "T$class", "g")
+ assertInvoke(m3, "T", "f")
+ assertInvoke(m3, "T", "g")
assertInvoke(m3, "T", "h")
assertInvoke(m3, "T", "i")
@@ -858,7 +799,7 @@ class InlinerTest extends ClearAfterClass {
val m6 = getSingleMethod(c, "m6")
assertInvoke(m6, "U", "f")
- assertInvoke(m6, "U$class", "g")
+ assertInvoke(m6, "U", "g")
assertInvoke(m6, "U", "h")
assertInvoke(m6, "U", "i")
}
@@ -892,7 +833,7 @@ 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 the parent class A$Inner could not be found on the classpath.""".stripMargin
var c = 0
@@ -988,7 +929,588 @@ class InlinerTest extends ClearAfterClass {
val List(c) = compile(code)
val t = getSingleMethod(c, "t").instructions
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 = getSingleMethod(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", "C$$$anonfun$2")
+
+ val t2 = getSingleMethod(c, "t2")
+ assertNoIndy(t2)
+ assertInvoke(t2, "M$", "M$$$anonfun$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 = findAsmMethod(c, "h")
+ val gMeth = findAsmMethod(c, "g")
+ val iMeth = findAsmMethod(c, "i")
+ val fCall = getCallsite(gMeth, "f")
+ val gCall = getCallsite(hMeth, "g")
+ val hCall = getCallsite(iMeth, "h")
+
+ val warning = inliner.canInlineBody(gCall)
+ assert(warning.isEmpty, warning)
+
+ inliner.inline(InlineRequest(hCall,
+ post = List(InlineRequest(gCall,
+ post = List(InlineRequest(fCall, Nil))))))
+ 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(findAsmMethod(cl, _))
+ val aCall = getCallsite(b, "a")
+ val bCall = getCallsite(c, "b")
+ val cCall = getCallsite(d, "c")
+
+ inliner.inline(InlineRequest(bCall, Nil))
+
+ val req = InlineRequest(cCall,
+ List(InlineRequest(bCall,
+ List(InlineRequest(aCall, Nil)))))
+ 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(getSingleMethod(c, "t1"), "C", "C$$$anonfun$1")
+ assertInvoke(getSingleMethod(c, "t2"), "C", "a")
+ assertInvoke(getSingleMethod(c, "t3"), "C", "b")
+ assertNoInvoke(getSingleMethod(c, "t4"))
+ assertNoInvoke(getSingleMethod(c, "t5"))
+ assertNoInvoke(getSingleMethod(c, "t6"))
+ assertInvoke(getSingleMethod(c, "t7"), "C", "c")
+ assertInvoke(getSingleMethod(c, "t8"), "scala/Predef$", "println")
+ assertNoInvoke(getSingleMethod(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 // not inlined (cannot override the def-site @noinline)
+ | 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(getSingleMethod(c, "t1"))
+ assertInvoke(getSingleMethod(c, "t2"), "C", "f2")
+ assertInvoke(getSingleMethod(c, "t3"), "C", "f1")
+ assertInvoke(getSingleMethod(c, "t4"), "C", "f2")
+ assertNoInvoke(getSingleMethod(c, "t5"))
+ assertInvoke(getSingleMethod(c, "t6"), "C", "f3")
+ assertNoInvoke(getSingleMethod(c, "t7"))
+ assertInvoke(getSingleMethod(c, "t8"), "C", "f1")
+ assertNoInvoke(getSingleMethod(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(getSingleMethod(c, "t1"), "C", "C$$$anonfun$1")
+ assertInvoke(getSingleMethod(c, "t2"), "C", "C$$$anonfun$2")
+ assertInvoke(getSingleMethod(c, "t3"), "scala/Function1", "apply$mcII$sp")
+ assertInvoke(getSingleMethod(c, "t4"), "scala/Function1", "apply$mcII$sp")
+ assertInvoke(getSingleMethod(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(getSingleMethod(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 INVOKESPECIAL 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(getSingleMethod(c, "h"), "C", "f$1")
+ assertInvoke(getSingleMethod(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(getSingleMethod(c, "g"))
+ assertNoInvoke(getSingleMethod(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(getSingleMethod(c, "t1"), List(BIPUSH, "C$$$anonfun$1", IRETURN))
+ assertSameSummary(getSingleMethod(c, "t1a"), List(LCONST_1, "C$$$anonfun$2", IRETURN))
+ assertSameSummary(getSingleMethod(c, "t2"), List(ICONST_1, ICONST_2, "C$$$anonfun$3",IRETURN))
+
+ // val a = new ValKl(n); new ValKl(anonfun(a.x)).x
+ // value class instantiation-extraction should be optimized by boxing elim
+ assertSameSummary(getSingleMethod(c, "t3"), List(
+ NEW, DUP, ICONST_1, "<init>", ASTORE,
+ NEW, DUP, ALOAD, "x",
+ "C$$$anonfun$4",
+ "<init>",
+ "x", IRETURN))
+
+ assertSameSummary(getSingleMethod(c, "t4"), List(BIPUSH, "C$$$anonfun$5", "boxToInteger", ARETURN))
+ assertSameSummary(getSingleMethod(c, "t4a"), List(ICONST_1, LDC, "C$$$anonfun$6", LRETURN))
+ assertSameSummary(getSingleMethod(c, "t5"), List(BIPUSH, ICONST_3, "C$$$anonfun$7", "boxToInteger", ARETURN))
+ assertSameSummary(getSingleMethod(c, "t5a"), List(BIPUSH, BIPUSH, I2B, "C$$$anonfun$8", IRETURN))
+ assertSameSummary(getSingleMethod(c, "t6"), List(BIPUSH, "C$$$anonfun$9", RETURN))
+ assertSameSummary(getSingleMethod(c, "t7"), List(ICONST_1, "C$$$anonfun$10", RETURN))
+ assertSameSummary(getSingleMethod(c, "t8"), List(ICONST_1, LDC, "C$$$anonfun$11", LRETURN))
+ assertSameSummary(getSingleMethod(c, "t9"), List(ICONST_1, "boxToInteger", "C$$$anonfun$12", RETURN))
+
+ // t9a inlines Range.foreach, which is quite a bit of code, so just testing the core
+ assertInvoke(getSingleMethod(c, "t9a"), "C", "C$$$anonfun$13")
+ assertInvoke(getSingleMethod(c, "t9a"), "scala/runtime/BoxesRunTime", "boxToInteger")
+
+ assertSameSummary(getSingleMethod(c, "t10"), List(
+ ICONST_1, ISTORE,
+ ALOAD, ILOAD,
+ "C$$$anonfun$14", RETURN))
+
+ // t10a inlines Range.foreach
+ assertInvoke(getSingleMethod(c, "t10a"), "C", "C$$$anonfun$15")
+ assertDoesNotInvoke(getSingleMethod(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(getSingleMethod(c, "t1"), List(Op(ICONST_0), Op(ICONST_1), Op(IADD), Op(IRETURN)))
+ assertEquals(getSingleMethod(c, "t2").instructions collect { case i: Invoke => i.owner +"."+ i.name }, List(
+ "scala/runtime/IntRef.create", "C.C$$$anonfun$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(getSingleMethod(c, "t1"), List(Op(ICONST_3), Op(ICONST_4), Op(IADD), Op(IRETURN)))
+ assertSameCode(getSingleMethod(c, "t2"), List(Op(ICONST_1), Op(ICONST_2), Op(IADD), Op(IRETURN)))
+ assertSameCode(getSingleMethod(c, "t3"), List(Op(ICONST_1), Op(ICONST_3), Op(ISUB), Op(IRETURN)))
+ assertNoInvoke(getSingleMethod(c, "t4"))
+ assertNoInvoke(getSingleMethod(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) = getSingleMethod(c, m).instructions collect { case TypeOp(CHECKCAST, tp) => tp }
+ assertSameCode(getSingleMethod(c, "t1"), List(VarOp(ALOAD, 1), Op(ARETURN)))
+ assertSameCode(getSingleMethod(c, "t2"), List(VarOp(ALOAD, 1), Op(ARETURN)))
+ assertSameCode(getSingleMethod(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 = cls.find(_.name == "Test$").get
+ assertSameSummary(getSingleMethod(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(getSingleMethod(c, "t"), List(
+ ALOAD, "C$$$anonfun$1", IFEQ /*A*/,
+ "C$$$anonfun$2", IRETURN,
+ -1 /*A*/, "C$$$anonfun$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 = "-Yopt:l:project -Yopt-warnings")
+ assertInvoke(getSingleMethod(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)
+ // the forwarder C.f is inlined, so there's no invocation
+ assertSameSummary(getSingleMethod(c, "f"), List(ICONST_1, IRETURN))
+ }
+
+ @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(getSingleMethod(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 = getSingleMethod(c, "t")
+ assertNoIndy(t)
+ assertInvoke(t, "C", "C$$$anonfun$1")
+ }
}