summaryrefslogtreecommitdiff
path: root/test/junit
diff options
context:
space:
mode:
Diffstat (limited to 'test/junit')
-rw-r--r--test/junit/scala/StringContextTest.scala160
-rw-r--r--test/junit/scala/collection/mutable/OpenHashMapTest.scala42
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala3
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala4
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala19
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala347
6 files changed, 426 insertions, 149 deletions
diff --git a/test/junit/scala/StringContextTest.scala b/test/junit/scala/StringContextTest.scala
index 7e9e775d58..900852fcc6 100644
--- a/test/junit/scala/StringContextTest.scala
+++ b/test/junit/scala/StringContextTest.scala
@@ -1,6 +1,8 @@
package scala
+import language.implicitConversions
+
import org.junit.Test
import org.junit.Assert._
import org.junit.runner.RunWith
@@ -84,4 +86,162 @@ class StringContextTest {
// Use this method to avoid problems with a locale-dependent decimal mark.
// The string interpolation is not used here intentionally as this method is used to test string interpolation.
private def formatUsingCurrentLocale(number: Double, decimalPlaces: Int = 2) = ("%." + decimalPlaces + "f").format(number)
+
+ @Test def `f interpolator baseline`(): Unit = {
+
+ implicit def stringToBoolean(s: String): Boolean = java.lang.Boolean.parseBoolean(s)
+ implicit def stringToChar(s: String): Char = s(0)
+ implicit def str2fmt(s: String): java.util.Formattable = new java.util.Formattable {
+ def formatTo(f: java.util.Formatter, g: Int, w: Int, p: Int) = f.format("%s", s)
+ }
+
+ val b_true = true
+ val b_false = false
+
+ val i = 42
+
+ val f_zero = 0.0
+ val f_zero_- = -0.0
+
+ val s = "Scala"
+
+ val fff = new java.util.Formattable {
+ def formatTo(f: java.util.Formatter, g: Int, w: Int, p: Int) = f.format("4")
+ }
+ import java.util.{ Calendar, Locale }
+ val c = Calendar.getInstance(Locale.US)
+ c.set(2012, Calendar.MAY, 26)
+ implicit def strToDate(x: String): Calendar = c
+
+ val ss = List[(String, String)] (
+ // 'b' / 'B' (category: general)
+ // -----------------------------
+ f"${b_false}%b" -> "false",
+ f"${b_true}%b" -> "true",
+
+ f"${null}%b" -> "false",
+ f"${false}%b" -> "false",
+ f"${true}%b" -> "true",
+ f"${true && false}%b" -> "false",
+ f"${new java.lang.Boolean(false)}%b" -> "false",
+ f"${new java.lang.Boolean(true)}%b" -> "true",
+
+ f"${null}%B" -> "FALSE",
+ f"${false}%B" -> "FALSE",
+ f"${true}%B" -> "TRUE",
+ f"${new java.lang.Boolean(false)}%B" -> "FALSE",
+ f"${new java.lang.Boolean(true)}%B" -> "TRUE",
+
+ f"${"true"}%b" -> "true",
+ f"${"false"}%b"-> "false",
+
+ // 'h' | 'H' (category: general)
+ // -----------------------------
+ f"${null}%h" -> "null",
+ f"${f_zero}%h" -> "0",
+ f"${f_zero_-}%h" -> "80000000",
+ f"${s}%h" -> "4c01926",
+
+ f"${null}%H" -> "NULL",
+ f"${s}%H" -> "4C01926",
+
+ // 's' | 'S' (category: general)
+ // -----------------------------
+ f"${null}%s" -> "null",
+ f"${null}%S" -> "NULL",
+ f"${s}%s" -> "Scala",
+ f"${s}%S" -> "SCALA",
+ f"${5}" -> "5",
+ f"${i}" -> "42",
+ f"${'foo}" -> "'foo",
+
+ f"${Thread.State.NEW}" -> "NEW",
+
+ // 'c' | 'C' (category: character)
+ // -------------------------------
+ f"${120:Char}%c" -> "x",
+ f"${120:Byte}%c" -> "x",
+ f"${120:Short}%c" -> "x",
+ f"${120:Int}%c" -> "x",
+ f"${new java.lang.Character('x')}%c" -> "x",
+ f"${new java.lang.Byte(120:Byte)}%c" -> "x",
+ f"${new java.lang.Short(120:Short)}%c" -> "x",
+ f"${new java.lang.Integer(120)}%c" -> "x",
+
+ f"${'x' : java.lang.Character}%c" -> "x",
+ f"${(120:Byte) : java.lang.Byte}%c" -> "x",
+ f"${(120:Short) : java.lang.Short}%c" -> "x",
+ f"${120 : java.lang.Integer}%c" -> "x",
+
+ f"${"Scala"}%c" -> "S",
+
+ // 'd' | 'o' | 'x' | 'X' (category: integral)
+ // ------------------------------------------
+ f"${120:Byte}%d" -> "120",
+ f"${120:Short}%d" -> "120",
+ f"${120:Int}%d" -> "120",
+ f"${120:Long}%d" -> "120",
+ f"${60 * 2}%d" -> "120",
+ f"${new java.lang.Byte(120:Byte)}%d" -> "120",
+ f"${new java.lang.Short(120:Short)}%d" -> "120",
+ f"${new java.lang.Integer(120)}%d" -> "120",
+ f"${new java.lang.Long(120)}%d" -> "120",
+ f"${120 : java.lang.Integer}%d" -> "120",
+ f"${120 : java.lang.Long}%d" -> "120",
+ f"${BigInt(120)}%d" -> "120",
+
+ f"${new java.math.BigInteger("120")}%d" -> "120",
+
+ f"${4}%#10X" -> " 0X4",
+
+ f"She is ${fff}%#s feet tall." -> "She is 4 feet tall.",
+
+ f"Just want to say ${"hello, world"}%#s..." -> "Just want to say hello, world...",
+
+ { implicit val strToShort = (s: String) => java.lang.Short.parseShort(s) ; f"${"120"}%d" } -> "120",
+ { implicit val strToInt = (s: String) => 42 ; f"${"120"}%d" } -> "42",
+
+ // 'e' | 'E' | 'g' | 'G' | 'f' | 'a' | 'A' (category: floating point)
+ // ------------------------------------------------------------------
+ f"${3.4f}%e" -> "3.400000e+00",
+ f"${3.4}%e" -> "3.400000e+00",
+ f"${3.4f : java.lang.Float}%e" -> "3.400000e+00",
+ f"${3.4 : java.lang.Double}%e" -> "3.400000e+00",
+
+ f"${BigDecimal(3.4)}%e" -> "3.400000e+00",
+
+ f"${new java.math.BigDecimal(3.4)}%e" -> "3.400000e+00",
+
+ f"${3}%e" -> "3.000000e+00",
+ f"${3L}%e" -> "3.000000e+00",
+
+ // 't' | 'T' (category: date/time)
+ // -------------------------------
+ f"${c}%TD" -> "05/26/12",
+ f"${c.getTime}%TD" -> "05/26/12",
+ f"${c.getTime.getTime}%TD" -> "05/26/12",
+ f"""${"1234"}%TD""" -> "05/26/12",
+
+ // literals and arg indexes
+ f"%%" -> "%",
+ f" mind%n------%nmatter%n" ->
+ """| mind
+ |------
+ |matter
+ |""".stripMargin,
+ f"${i}%d %<d ${9}%d" -> "42 42 9",
+ f"${7}%d %<d ${9}%d" -> "7 7 9",
+ f"${7}%d %2$$d ${9}%d" -> "7 9 9",
+
+ f"${null}%d %<B" -> "null FALSE",
+
+ f"${5: Any}" -> "5",
+ f"${5}%s%<d" -> "55",
+ f"${3.14}%s,%<f" -> "3.14,3.140000",
+
+ f"z" -> "z"
+ )
+
+ for ((f, s) <- ss) assertEquals(s, f)
+ }
}
diff --git a/test/junit/scala/collection/mutable/OpenHashMapTest.scala b/test/junit/scala/collection/mutable/OpenHashMapTest.scala
new file mode 100644
index 0000000000..9b5c20e01a
--- /dev/null
+++ b/test/junit/scala/collection/mutable/OpenHashMapTest.scala
@@ -0,0 +1,42 @@
+package scala.collection.mutable
+
+import org.junit.Test
+import org.junit.Assert._
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/** Tests for [[OpenHashMap]]. */
+@RunWith(classOf[JUnit4])
+class OpenHashMapTest {
+ /** Test that an [[OpenHashMap]] correctly maintains its internal `deleted` count. */
+ @Test
+ def maintainsDeletedCount {
+ val m = OpenHashMap.empty[Int, Int]
+
+ // Reflect to get the private `deleted` field's value, which should be zero.
+
+ /* TODO Doesn't work, due to SI-9306.
+ import scala.reflect.runtime.{universe => ru}
+
+ val mirror = ru.runtimeMirror(m.getClass.getClassLoader)
+ val mapType = ru.typeOf[OpenHashMap[Int, Int]]
+ val termSym = mapType.decls
+ .filterNot { s => s.isMethod }
+ .filter { s => s.fullName.endsWith("deleted") }
+ .head.asTerm
+
+ val fieldMirror = mirror.reflect(m).reflectField(termSym)
+ */
+ // Use Java reflection instead for now.
+ val field = m.getClass.getDeclaredField("deleted")
+ field.setAccessible(true)
+
+ m.put(0, 0)
+ m.remove(0)
+ assertEquals(1, field.getInt(m))
+
+ m.put(0, 0) // Add an entry with the same key
+ // TODO assertEquals(0, fieldMirror.get.asInstanceOf[Int])
+ assertEquals(0, field.getInt(m))
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
index 769236ae49..1f2ec274d3 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
@@ -169,6 +169,9 @@ object CodeGenTools {
def getSingleMethod(classNode: ClassNode, name: String): Method =
convertMethod(classNode.methods.asScala.toList.find(_.name == name).get)
+ def findAsmMethods(c: ClassNode, p: String => Boolean) = c.methods.iterator.asScala.filter(m => p(m.name)).toList.sortBy(_.name)
+ def findAsmMethod(c: ClassNode, name: String) = findAsmMethods(c, _ == name).head
+
/**
* Instructions that match `query` when textified.
* If `query` starts with a `+`, the next instruction is returned.
diff --git a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
index f9a55bb26e..e57e95bac4 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/DefaultMethodTest.scala
@@ -16,8 +16,8 @@ object DefaultMethodTest extends ClearAfterClass.Clearable {
}
class DefaultMethodTest extends ClearAfterClass {
- ClearAfterClass.stateToClear = DirectCompileTest
- val compiler = DirectCompileTest.compiler
+ ClearAfterClass.stateToClear = DefaultMethodTest
+ val compiler = DefaultMethodTest.compiler
@Test
def defaultMethodsViaGenBCode(): Unit = {
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 efd88f10c3..3857d3e8ce 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala
@@ -50,9 +50,6 @@ class CallGraphTest extends ClearAfterClass {
compileClasses(compiler)(code, allowMessage = allowMessage).map(c => byteCodeRepository.classNode(c.name).get)
}
- def getMethods(c: ClassNode, p: String => Boolean) = c.methods.iterator.asScala.filter(m => p(m.name)).toList.sortBy(_.name)
- def getMethod(c: ClassNode, name: String) = getMethods(c, _ == name).head
-
def callsInMethod(methodNode: MethodNode): List[MethodInsnNode] = methodNode.instructions.iterator.asScala.collect({
case call: MethodInsnNode => call
}).toList
@@ -121,10 +118,10 @@ class CallGraphTest extends ClearAfterClass {
val List(cCls, cMod, dCls, testCls) = compile(code, checkMsg)
assert(msgCount == 6, msgCount)
- val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = getMethods(cCls, _.startsWith("f"))
- val List(df1, df3) = getMethods(dCls, _.startsWith("f"))
- val g1 = getMethod(cMod, "g1")
- val List(t1, t2) = getMethods(testCls, _.startsWith("t"))
+ val List(cf1, cf2, cf3, cf4, cf5, cf6, cf7) = findAsmMethods(cCls, _.startsWith("f"))
+ val List(df1, df3) = findAsmMethods(dCls, _.startsWith("f"))
+ val g1 = findAsmMethod(cMod, "g1")
+ val List(t1, t2) = findAsmMethods(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)
@@ -160,7 +157,7 @@ class CallGraphTest extends ClearAfterClass {
|}
""".stripMargin
val List(c) = compile(code)
- val m = getMethod(c, "m")
+ val m = findAsmMethod(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")
@@ -168,12 +165,6 @@ class CallGraphTest extends ClearAfterClass {
checkCallsite(fn, m, forNameMeth, classTp, safeToInline = false, atInline = false, atNoInline = false)
}
- /**
- * NOTE: if this test fails for you when running within the IDE, it's probably because you're
- * using 2.12.0-M2 for compilining within the IDE, which doesn't add SAM information to the
- * InlineInfo attribute. So the InlineInfo in the classfile for Function1 doesn't say that
- * it's a SAM type. The test passes when running with ant (which does a full bootstrap).
- */
@Test
def checkArgInfos(): Unit = {
val code =
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 1108a37266..1e01627969 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.immutable.IntMap
-import scala.collection.mutable.ListBuffer
-import scala.reflect.internal.util.{NoPosition, 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.io._
import scala.tools.nsc.reporters.StoreReporter
-import scala.tools.testing.AssertUtil._
import CodeGenTools._
import scala.tools.partest.ASMConverters
@@ -69,6 +63,7 @@ class InlinerTest extends ClearAfterClass {
val compiler = InlinerTest.compiler
import compiler.genBCode.bTypes._
import compiler.genBCode.bTypes.backendUtils._
+ import inlinerHeuristics._
def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = {
InlinerTest.notPerRun.foreach(_.clear())
@@ -88,44 +83,25 @@ class InlinerTest extends ClearAfterClass {
assert(callsite.callee.get.callee == callee, callsite.callee.get.callee.name)
}
- def makeInlineRequest( callsiteInstruction: MethodInsnNode, callsiteMethod: MethodNode, callsiteClass: ClassBType,
- callee: MethodNode, calleeDeclarationClass: ClassBType,
- callsiteStackHeight: Int, receiverKnownNotNull: Boolean,
- post: List[inlinerHeuristics.PostInlineRequest] = Nil) = inlinerHeuristics.InlineRequest(
- callsite = callGraph.Callsite(
- callsiteInstruction = callsiteInstruction,
- callsiteMethod = callsiteMethod,
- callsiteClass = callsiteClass,
- callee = Right(callGraph.Callee(callee = callee, calleeDeclarationClass = calleeDeclarationClass, safeToInline = true, safeToRewrite = false, annotatedInline = false, annotatedNoInline = false, samParamTypes = IntMap.empty, calleeInfoWarning = None)),
- argInfos = IntMap.empty,
- callsiteStackHeight = callsiteStackHeight,
- receiverKnownNotNull = receiverKnownNotNull,
- callsitePosition = NoPosition),
- post = post)
-
- // inline first invocation of f into g in class C
- def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): (MethodNode, List[CannotInlineWarning]) = {
- val List(cls) = compile(code)
- mod(cls)
- val clsBType = classBTypeFromParsedClassfile(cls.name)
-
- val List(f, g) = cls.methods.asScala.filter(m => Set("f", "g")(m.name)).toList.sortBy(_.name)
- val fCall = g.instructions.iterator.asScala.collect({ case i: MethodInsnNode if i.name == "f" => i }).next()
-
- val analyzer = new AsmAnalyzer(g, clsBType.internalName)
-
- val request = makeInlineRequest(
- callsiteInstruction = fCall,
- callsiteMethod = g,
- callsiteClass = clsBType,
- callee = f,
- calleeDeclarationClass = clsBType,
- callsiteStackHeight = analyzer.frameAt(fCall).getStackSize,
- receiverKnownNotNull = true
- )
-
- val r = inliner.inline(request)
- (g, r)
+ def getCallsite(method: MethodNode, calleeName: String) = callGraph.callsites(method).valuesIterator.find(_.callee.get.callee.name == calleeName).get
+
+ 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)
+ }
+
+ def canInlineTest(code: String, mod: ClassNode => Unit = _ => ()): Option[OptimizerWarning] = {
+ val cs = gMethAndFCallsite(code, mod)._2
+ inliner.earlyCanInlineCheck(cs) orElse inliner.canInlineBody(cs)
+ }
+
+ def inlineTest(code: String, mod: ClassNode => Unit = _ => ()): MethodNode = {
+ val (gMethod, fCall) = gMethAndFCallsite(code, mod)
+ inliner.inline(InlineRequest(fCall, Nil))
+ gMethod
}
@Test
@@ -137,7 +113,7 @@ class InlinerTest extends ClearAfterClass {
|}
""".stripMargin
- val (g, _) = inlineTest(code)
+ val g = inlineTest(code)
val gConv = convertMethod(g)
assertSameCode(gConv.instructions.dropNonOp,
@@ -171,7 +147,7 @@ class InlinerTest extends ClearAfterClass {
// See also discussion around ATHROW in BCodeBodyBuilder
- val (g, _) = inlineTest(code)
+ 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 ???
@@ -192,11 +168,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.length == 1 && can.head.isInstanceOf[SynchronizedMethod], can)
+ assert(can.nonEmpty && can.get.isInstanceOf[SynchronizedMethod], can)
}
@Test
@@ -207,7 +183,7 @@ class InlinerTest extends ClearAfterClass {
| def g = f + 1
|}
""".stripMargin
- val (_, r) = inlineTest(code)
+ val r = canInlineTest(code)
assert(r.isEmpty, r)
}
@@ -221,8 +197,8 @@ class InlinerTest extends ClearAfterClass {
| def g = println(f)
|}
""".stripMargin
- val (_, r) = inlineTest(code)
- assert(r.length == 1 && r.head.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r)
+ val r = canInlineTest(code)
+ assert(r.nonEmpty && r.get.isInstanceOf[MethodWithHandlerCalledOnNonEmptyStack], r)
}
@Test
@@ -242,31 +218,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 request = makeInlineRequest(
- callsiteInstruction = gCall,
- callsiteMethod = h,
- callsiteClass = dTp,
- callee = g,
- calleeDeclarationClass = cTp,
- callsiteStackHeight = analyzer.frameAt(gCall).getStackSize,
- receiverKnownNotNull = true
- )
-
- val r = inliner.inline(request)
-
- assert(r.length == 1 && r.head.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
@@ -403,27 +358,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 request = makeInlineRequest(
- callsiteInstruction = callsiteIns,
- callsiteMethod = f,
- callsiteClass = clsBType,
- callee = lowestOneBitMethod,
- calleeDeclarationClass = integerClassBType,
- callsiteStackHeight = analyzer.frameAt(callsiteIns).getStackSize,
- receiverKnownNotNull = false
- )
-
- val r = inliner.inline(request)
- assert(r.isEmpty, r)
- val ins = instructionsFromMethod(f)
+ val fMeth = findAsmMethod(c, "f")
+ val call = getCallsite(fMeth, "lowestOneBit")
+
+ 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)
@@ -1065,49 +1007,124 @@ class InlinerTest extends ClearAfterClass {
| 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
+ }
- val cTp = classBTypeFromParsedClassfile(c.name)
-
- val f = c.methods.asScala.find(_.name == "f").get
- val g = c.methods.asScala.find(_.name == "g").get
- val h = c.methods.asScala.find(_.name == "h").get
-
- val gCall = h.instructions.iterator.asScala.collect({
- case m: MethodInsnNode if m.name == "g" => m
- }).next()
- val fCall = g.instructions.iterator.asScala.collect({
- case m: MethodInsnNode if m.name == "f" => m
- }).next()
-
- val analyzer = new AsmAnalyzer(h, cTp.internalName)
-
- val request = makeInlineRequest(
- callsiteInstruction = gCall,
- callsiteMethod = h,
- callsiteClass = cTp,
- callee = g,
- calleeDeclarationClass = cTp,
- callsiteStackHeight = analyzer.frameAt(gCall).getStackSize,
- receiverKnownNotNull = false,
- post = List(inlinerHeuristics.PostInlineRequest(fCall, Nil))
- )
-
- val r = inliner.inline(request)
- assertNoInvoke(getSingleMethod(c, "h")) // no invoke in h: first g is inlined, then the inlined call to f is also inlined
- assertInvoke(getSingleMethod(c, "g"), "C", "f") // g itself still has the call to f
- assert(r.isEmpty, r)
+ @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"))
}
- /**
- * NOTE: if this test fails for you when running within the IDE, it's probably because you're
- * using 2.12.0-M2 for compilining within the IDE, which doesn't add SAM information to the
- * InlineInfo attribute. So the InlineInfo in the classfile for Function1 doesn't say that
- * it's a SAM type. The test passes when running with ant (which does a full bootstrap).
- */
@Test
def inlineHigherOrder(): Unit = {
val code =
@@ -1134,4 +1151,68 @@ class InlinerTest extends ClearAfterClass {
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"))
+ }
}