package scala.tools.nsc
package backend.jvm
package opt
import org.junit.Assert._
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import scala.collection.JavaConverters._
import scala.tools.asm.Opcodes._
import scala.tools.asm.tree.ClassNode
import scala.tools.nsc.backend.jvm.AsmUtils._
import scala.tools.partest.ASMConverters._
import scala.tools.testing.BytecodeTesting
import scala.tools.testing.BytecodeTesting._
@RunWith(classOf[JUnit4])
class MethodLevelOptsTest extends BytecodeTesting {
override def compilerArgs = "-opt:l:method"
import compiler._
def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1))
def locals(c: ClassNode, m: String) = getAsmMethod(c, m).localVariables.asScala.toList.map(l => (l.name, l.index)).sortBy(_._2)
@Test
def eliminateEmptyTry(): Unit = {
val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }"
val warn = "a pure expression does nothing in statement position"
assertSameCode(compileInstructions(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN)))
}
@Test
def eliminateLoadBoxedUnit(): Unit = {
// the compiler inserts a boxed into the try block. it's therefore non-empty (and live) and not eliminated.
val code = "def f = { try {} catch { case _: Throwable => 0 }; 1 }"
val m = compileMethod(code)
assertTrue(m.handlers.length == 0)
assertSameCode(m, List(Op(ICONST_1), Op(IRETURN)))
}
@Test
def inlineThrowInCatchNotTry(): Unit = {
// the try block does not contain the `ATHROW` instruction, but in the catch block, `ATHROW` is inlined
val code = "def f(e: Exception) = throw { try e catch { case _: Throwable => e } }"
val m = compileMethod(code)
assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
assertSameCode(m.instructions,
wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW))
)
}
@Test
def inlineReturnInCatchNotTry(): Unit = {
val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }"
// cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState)
val m = compileMethod(code)
assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5)
assertSameCode(m.instructions,
wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN)))
}
@Test
def simplifyJumpsInTryCatchFinally(): Unit = {
val code =
"""def f: Int =
| try {
| return 1
| } catch {
| case _: Throwable =>
| return 2
| } finally {
| return 3
| // dead
| val x = try 10 catch { case _: Throwable => 11 }
| println(x)
| }
""".stripMargin
val m = compileMethod(code)
assertTrue(m.handlers.isEmpty)
assertSameCode(m, List(Op(ICONST_3), Op(IRETURN)))
}
@Test
def nullStoreLoadElim(): Unit = {
// point of this test: we have two cleanups
// - remove `ACONST_NULL; ASTORE x` if x is otherwise not live
// - remove `ASTORE x; ALOAD x` if x is otherwise not live
// in the example below, we have `ACONST_NULL; ASTORE x; ALOAD x`. in this case the store-load
// should be removed (even though it looks like a null-store at first).
val code =
"""class C {
| def t = {
| val x = null
| x.toString
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameCode(getMethod(c, "t"), List(
Op(ACONST_NULL), Invoke(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false), Op(ARETURN)))
}
@Test
def deadStoreReferenceElim(): Unit = {
val code =
"""class C {
| def t = {
| var a = "a" // assign to non-initialized, directly removed by dead store
| a = "b" // assign to initialized, replaced by null-store, which is then removed: the var is not live, the uses are null-store or store-load
| a = "c"
| a // store-load pair will be eliminated
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameCode(
getMethod(c, "t"), List(Ldc(LDC, "c"), Op(ARETURN)))
}
@Test
def deadStoreReferenceKeepNull(): Unit = {
val code =
"""class C {
| def t = {
| var a = "el" // this store is live, used in the println.
| println(a)
| a = "met" // since it's an ASTORE to a live variable, cannot elim the store (SI-5313), but store null instead.
| // so we get `LDC met; POP; ACONST_NULL; ASTORE 1`. the `LDC met; POP` is eliminated by push-pop.
| a = "zit" // this store is live, so we get `LDC zit; ASOTRE 1; ALOAD 1; ARETURN`.
| // we cannot eliminated the store-load sequence, because the local is live (again SI-5313).
| a
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameCode(getMethod(c, "t"), List(
Ldc(LDC, "el"), VarOp(ASTORE, 1),
Field(GETSTATIC, "scala/Predef$", "MODULE$", "Lscala/Predef$;"), VarOp(ALOAD, 1), Invoke(INVOKEVIRTUAL, "scala/Predef$", "println", "(Ljava/lang/Object;)V", false),
Op(ACONST_NULL), VarOp(ASTORE, 1),
Ldc(LDC, "zit"), VarOp(ASTORE, 1), VarOp(ALOAD, 1), Op(ARETURN)))
}
@Test
def elimUnusedTupleObjectStringBox(): Unit = {
val code =
"""class C {
| def t(x: Int, y: Int): Int = {
| val a = (x, y) // Tuple2$mcII$sp
| val b = (a, y) // Tuple2
| val c = (new Object, "krik", new String) // unused java/lang/Object, java/lang/String allocation and string constant is also eliminated
| val d = new java.lang.Integer(x)
| val e = new String(new Array[Char](23)) // array allocation not eliminated, as it may throw (negative size, SI-8601)
| val f = new scala.runtime.IntRef(11)
| x + y
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameCode(getMethod(c, "t"), List(
IntOp(BIPUSH, 23), IntOp(NEWARRAY, 5), Op(POP), VarOp(ILOAD, 1), VarOp(ILOAD, 2), Op(IADD), Op(IRETURN)))
}
@Test
def noElimImpureConstructor(): Unit = {
val code =
"""class C {
| def t(x: Int, y: Int): Int = {
| val a = new java.lang.Integer("nono")
| x + y
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameCode(getMethod(c, "t"), List(
TypeOp(NEW, "java/lang/Integer"), Ldc(LDC, "nono"), Invoke(INVOKESPECIAL, "java/lang/Integer", "<init>", "(Ljava/lang/String;)V", false),
VarOp(ILOAD, 1), VarOp(ILOAD, 2), Op(IADD), Op(IRETURN)))
}
@Test
def elimUnusedBoxUnbox(): Unit = {
val code =
"""class C {
| def t(a: Long): Int = {
| val t = 3 + a
| val u = a + t
| val v: Any = u // scala/runtime/BoxesRunTime.boxToLong
|
| val w = (v, a) // a Tuple2 (not specialized because first value is Any)
| // so calls scala/runtime/BoxesRunTime.boxToLong on the second value
|
| val x = v.asInstanceOf[Long] // scala/runtime/BoxesRunTime.unboxToLong
|
| val z = (java.lang.Long.valueOf(a), t) // java box call on the left, scala/runtime/BoxesRunTime.boxToLong on the right
|
| 0
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameCode(getMethod(c, "t"), List(Op(ICONST_0), Op(IRETURN)))
}
@Test
def elimUnusedClosure(): Unit = {
val code =
"""class C {
| def t(x: Int, y: Int): Int = {
| val f = (a: Int) => a + x + y
| val g = (b: Int) => b - x
| val h = (s: String) => println(s)
| f(30)
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameCode(getMethod(c, "t"), List(
IntOp(BIPUSH, 30), VarOp(ISTORE, 3), // no constant propagation, so we keep the store (and load below) of a const
VarOp(ILOAD, 1),
VarOp(ILOAD, 2),
VarOp(ILOAD, 3),
Invoke(INVOKESTATIC, "C", "$anonfun$t$1", "(III)I", false), Op(IRETURN)))
}
@Test
def rewriteSpecializedClosureCall(): Unit = {
val code =
"""class C {
| def t = {
| val f1 = (x: Int) => println(x) // int-unit specialization
| val f2 = (x: Int, y: Long) => x == y // int-long-boolean
| f1(1)
| f2(3, 4)
| }
|}
""".stripMargin
val c = compileClass(code)
val t = getMethod(c, "t")
assert(!t.instructions.exists(_.opcode == INVOKEDYNAMIC), t)
}
@Test
def boxUnboxPrimitive(): Unit = {
val code =
"""class C {
| def t1 = {
| val a: Any = runtime.BoxesRunTime.boxToInteger(1)
| runtime.BoxesRunTime.unboxToInt(a) + 1
| }
|
| // two box and two unbox operations
| def t2(b: Boolean) = {
| val a = if (b) (3l: Any) else 2l
| a.asInstanceOf[Long] + 1 + a.asInstanceOf[Long]
| }
|
| def t3(i: Integer): Int = i.asInstanceOf[Int]
|
| def t4(l: Long): Any = l
|
| def t5(i: Int): Int = {
| val b = Integer.valueOf(i)
| val c: Integer = i
| b.asInstanceOf[Int] + c.intValue
| }
|
| def t6: Long = {
| val y = new java.lang.Boolean(true)
| val i: Integer = if (y) new Integer(10) else 13
| val j: java.lang.Long = 3l
| j + i
| }
|
| def t7: Int = {
| val a: Any = 3
| a.asInstanceOf[Int] + a.asInstanceOf[Int]
| }
|
| def t8 = null.asInstanceOf[Int]
|
| def t9: Int = {
| val a = Integer.valueOf(10)
| val b = runtime.BoxesRunTime.unboxToInt(a)
| a + b
| }
|
| @noinline def escape(a: Any) = ()
|
| // example E4 in BoxUnbox doc comment
| def t10: Int = {
| val a = Integer.valueOf(10) // int 10 is stored into local
| escape(a)
| a // no unbox, 10 is read from local
| }
|
| // the boxes here cannot be eliminated. see doc comment in BoxUnbox, example E1.
| def t11(b: Boolean): Int = {
| val i = Integer.valueOf(10)
| val j = Integer.valueOf(41)
| escape(i) // force rewrite method M1 (see doc in BoxUnbox)
| val res: Integer = if (b) i else j
| res.toInt // cannot be re-written to a local variable read - we don't know which local to read
| }
|
| // both boxes have a single unboxing consumer, and the escape. note that the escape does
| // NOT put the two boxes into the same set of rewrite operations: we can rewrite both
| // boxes with their unbox individually. in both cases the box also escapes, so method
| // M1 will keep the box around.
| def t12(b: Boolean): Int = {
| val i = Integer.valueOf(10)
| val j = Integer.valueOf(32)
| escape(if (b) i else j) // force method M1. the escape here is a consumer for both boxes
| if (b) i.toInt else j.toInt // both boxes (i, j) have their own unboxing consumer
| }
|}
""".stripMargin
val c = compileClass(code)
assertNoInvoke(getMethod(c, "t1"))
assertNoInvoke(getMethod(c, "t2"))
assertInvoke(getMethod(c, "t3"), "scala/runtime/BoxesRunTime", "unboxToInt")
assertInvoke(getMethod(c, "t4"), "scala/runtime/BoxesRunTime", "boxToLong")
assertNoInvoke(getMethod(c, "t5"))
assertNoInvoke(getMethod(c, "t6"))
assertNoInvoke(getMethod(c, "t7"))
assertSameSummary(getMethod(c, "t8"), List(ICONST_0, IRETURN))
assertNoInvoke(getMethod(c, "t9"))
// t10: no invocation of unbox
assertEquals(getInstructions(c, "t10") collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List(
("java/lang/Integer", "valueOf"),
("C", "escape")))
assertSameSummary(getMethod(c, "t11"), List(
BIPUSH, "valueOf", ASTORE /*2*/,
BIPUSH, "valueOf", ASTORE /*3*/,
ALOAD /*0*/, ALOAD /*2*/, "escape",
ILOAD /*1*/, IFEQ /*L1*/, ALOAD /*2*/, GOTO /*L2*/, /*Label L1*/ -1, ALOAD /*3*/, /*Label L2*/ -1,
ASTORE /*4*/, GETSTATIC /*Predef*/, ALOAD /*4*/, "Integer2int", IRETURN))
// no unbox invocations
assertEquals(getInstructions(c, "t12") collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List(
("java/lang/Integer", "valueOf"),
("java/lang/Integer", "valueOf"),
("C", "escape")))
}
@Test
def refEliminiation(): Unit = {
val code =
"""class C {
| import runtime._
| @noinline def escape(a: Any) = ()
|
| def t1 = { // box eliminated
| val r = new IntRef(0)
| r.elem
| }
|
| def t2(b: Boolean) = {
| val r1 = IntRef.zero() // both eliminated
| val r2 = IntRef.create(1)
| val res: IntRef = if (b) r1 else r2
| res.elem
| }
|
| def t3 = {
| val r = LongRef.create(10l) // eliminated
| r.elem += 3
| r.elem
| }
|
| def t4(b: Boolean) = {
| val x = BooleanRef.create(false) // eliminated
| if (b) x.elem = true
| if (x.elem) "a" else "b"
| }
|
| def t5 = {
| val r = IntRef.create(10) // not eliminated: the box might be modified in the escape
| escape(r)
| r.elem
| }
|
| def t6(b: Boolean) = {
| val r1 = IntRef.zero()
| val r2 = IntRef.create(1)
| r1.elem = 39
| val res: IntRef = if (b) r1 else r2
| res.elem // boxes remain: can't rewrite this read, don't know which local
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameSummary(getMethod(c, "t1"), List(ICONST_0, IRETURN))
assertNoInvoke(getMethod(c, "t2"))
assertSameSummary(getMethod(c, "t3"), List(LDC, LDC, LADD, LRETURN))
assertNoInvoke(getMethod(c, "t4"))
assertEquals(getInstructions(c, "t5") collect { case Field(_, owner, name, _) => s"$owner.$name" },
List("scala/runtime/IntRef.elem"))
assertEquals(getInstructions(c, "t6") collect { case Field(op, owner, name, _) => s"$op $owner.$name" },
List(s"$PUTFIELD scala/runtime/IntRef.elem", s"$GETFIELD scala/runtime/IntRef.elem"))
}
@Test
def tupleElimination(): Unit = {
val code =
"""class C {
| def t1(b: Boolean) = {
| val t = ("hi", "fish")
| if (b) t._1 else t._2
| }
|
| def t2 = {
| val t = (1, 3) // specialized tuple
| t._1 + t._2 // specialized accessors (_1$mcII$sp)
| }
|
| def t3 = {
| // boxed before tuple creation, a non-specialized tuple is created
| val t = (new Integer(3), Integer.valueOf(4))
| t._1 + t._2 // invokes the generic `_1` / `_2` getters, both values unboxed by Integer2int
| }
|
| def t4: Any = {
| val t = (3, 3) // specialized tuple is created, ints are not boxed
| (t: Tuple2[Any, Any])._1 // when eliminating the _1 call, need to insert a boxing operation
| }
|
| // the inverse of t4 also happens: an Tuple[Integer] where _1$mcI$sp is invoked. In this
| // case, an unbox operation needs to be added when eliminating the extraction. The only
| // way I found to test this is with an inlined generic method, see InlinerTest.tupleElimination.
| def tpl[A, B](a: A, b: B) = (a, b)
| def t5: Int = tpl(1, 2)._1 // invokes _1$mcI$sp
|
| def t6 = {
| val (a, b) = (1, 2)
| a - b
| }
|
| def t7 = {
| // this example is more tricky to handle than it looks, see doc comment in BoxUnbox.
| val ((a, b), c) = ((1, 2), 3)
| a + b + c
| }
|
| def t8 = {
| val ((a, b), (c, d)) = ((1, 2), (3, Integer.valueOf(10)))
| a + b + c + d
| }
|
| def t9(a: Int, b: Int) = (a, b) match { // tuple is optimized away
| case (x, y) if x == y => 0
| case (x, y) => x + y
| }
|}
""".stripMargin
val c = compileClass(code)
assertNoInvoke(getMethod(c, "t1"))
assertSameSummary(getMethod(c, "t2"), List(ICONST_1, ICONST_3, IADD, IRETURN))
assertSameSummary(getMethod(c, "t3"), List(ICONST_3, ICONST_4, IADD, IRETURN))
assertSameSummary(getMethod(c, "t4"), List(ICONST_3, "boxToInteger", ARETURN))
assertEquals(getInstructions(c, "t5") collect { case Invoke(_, owner, name, _, _) => (owner, name) }, List(
("scala/runtime/BoxesRunTime", "boxToInteger"),
("scala/runtime/BoxesRunTime", "boxToInteger"),
("C", "tpl"),
("scala/Tuple2", "_1$mcI$sp")))
assertSameSummary(getMethod(c, "t6"), List(ICONST_1, ICONST_2, ISUB, IRETURN))
assertSameSummary(getMethod(c, "t7"), List(
ICONST_1, ICONST_2, ISTORE, ISTORE,
ICONST_3, ISTORE,
ILOAD, ILOAD, IADD, ILOAD, IADD, IRETURN))
assertNoInvoke(getMethod(c, "t8"))
assertNoInvoke(getMethod(c, "t9"))
}
@Test
def nullnessOpts(): Unit = {
val code =
"""class C {
| def t1 = {
| val a = new C
| if (a == null)
| println() // eliminated
| a
| }
|
| def t2 = null.asInstanceOf[Long] // replaced by zero value
|
| def t3 = {
| val t = (1, 3)
| val a = null
| if (t ne a) t._1
| else throw new Error()
| }
|
| def t4 = {
| val i = Integer.valueOf(1)
| val a = null
| if (i eq a) throw new Error()
| else i.toInt
| }
|
| def t5 = {
| val i = runtime.DoubleRef.zero()
| if (i == null) throw new Error()
| else i.elem
| }
|
| def t6 = {
| var a = null
| var i = null
| a = i // eliminated (store of null to variable that is already null)
| a // replaced by ACONST_NULL (load of variable that is known null)
| }
|
| def t7 = {
| val a = null
| a.isInstanceOf[String] // eliminated, replaced by 0 (null.isInstanceOf is always false)
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameSummary(getMethod(c, "t1"), List(NEW, DUP, "<init>", ARETURN))
assertSameCode(getMethod(c, "t2"), List(Op(LCONST_0), Op(LRETURN)))
assertSameCode(getMethod(c, "t3"), List(Op(ICONST_1), Op(IRETURN)))
assertSameCode(getMethod(c, "t4"), List(Op(ICONST_1), Op(IRETURN)))
assertSameCode(getMethod(c, "t5"), List(Op(DCONST_0), Op(DRETURN)))
assertSameCode(getMethod(c, "t6"), List(Op(ACONST_NULL), Op(ARETURN)))
assertSameCode(getMethod(c, "t7"), List(Op(ICONST_0), Op(IRETURN)))
}
@Test
def elimRedundantNullCheck(): Unit = {
val code =
"""class C {
| def t(x: Object) = {
| val bool = x == null
| if (x != null) 1 else 0
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameCode(
getMethod(c, "t"), List(
VarOp(ALOAD, 1), Jump(IFNULL, Label(6)), Op(ICONST_1), Op(IRETURN), Label(6), Op(ICONST_0), Op(IRETURN)))
}
@Test
def t5313(): Unit = {
val code =
"""class C {
| def randomBoolean = scala.util.Random.nextInt % 2 == 0
|
| // 3 stores to kept1 (slot 1), 1 store to result (slot 2)
| def t1 = {
| var kept1 = new Object
| val result = new java.lang.ref.WeakReference(kept1)
| kept1 = null // we can't eliminate this assignment because result can observe
| // when the object has no more references. See SI-5313
| kept1 = new Object // could eliminate this one with a more elaborate analysis (we know it contains null)
| // however, such is not implemented: if a var is live, then stores are kept.
| result
| }
|
| // only two variables are live: kept2 and kept3. they end up on slots 1 and 2.
| // kept2 has 2 stores, kept3 has 1 store.
| def t2 = {
| var erased2 = null // we can eliminate this store because it's never used
| val erased3 = erased2 // and this
| var erased4 = erased2 // and this
| val erased5 = erased4 // and this
| var kept2: Object = new Object // ultimately can't be eliminated
| while(randomBoolean) {
| val kept3 = kept2
| kept2 = null // this can't, because it clobbers kept2, which is used
| erased4 = null // safe to eliminate
| println(kept3)
| }
| 0
| }
|
| def t3 = {
| var kept4 = new Object // have to keep, it's used
| try
| println(kept4)
| catch {
| case _ : Throwable => kept4 = null // have to keep, it clobbers kept4 which is used
| }
| 0
| }
|
| def t4 = {
| var kept5 = new Object
| print(kept5)
| kept5 = null // can't eliminate it's a clobber and it's used
| print(kept5)
| kept5 = null // eliminated by nullness analysis (store null to a local that is known to be null)
| 0
| }
|
| def t5 = {
| while(randomBoolean) {
| var kept6: AnyRef = null // not used, but have to keep because it clobbers the next used store
| // on the back edge of the loop
| kept6 = new Object // used
| println(kept6)
| }
| 0
| }
|}
""".stripMargin
val c = compileClass(code)
def stores(m: String) = getInstructions(c, m).filter(_.opcode == ASTORE)
assertEquals(locals(c, "t1"), List(("this",0), ("kept1",1), ("result",2)))
assert(stores("t1") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 2), VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
textify(getAsmMethod(c, "t1")))
assertEquals(locals(c, "t2"), List(("this",0), ("kept2",1), ("kept3",2)))
assert(stores("t2") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 2), VarOp(ASTORE, 1)),
textify(getAsmMethod(c, "t2")))
assertEquals(locals(c, "t3"), List(("this",0), ("kept4",1)))
assert(stores("t3") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
textify(getAsmMethod(c, "t3")))
assertEquals(locals(c, "t4"), List(("this",0), ("kept5",1)))
assert(stores("t4") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
textify(getAsmMethod(c, "t4")))
assertEquals(locals(c, "t5"), List(("this",0), ("kept6",1)))
assert(stores("t5") == List(VarOp(ASTORE, 1), VarOp(ASTORE, 1)),
textify(getAsmMethod(c, "t5")))
}
@Test
def testCpp(): Unit = {
// copied from an old test (run/test-cpp.scala)
val code =
"""class C {
| import scala.util.Random._
|
| def t1(x: Int) = {
| val y = x
| println(y)
| }
|
| def t2 = {
| val x = 2
| val y = x
| println(y)
| }
|
| def t3 = {
| val x = this
| val y = x
| println(y)
| }
|
| def f = nextInt
|
| def t4 = {
| val x = f
| val y = x
| println(y)
| }
|
| def t5 = {
| var x = nextInt
| var y = x
| println(y)
|
| y = nextInt
| x = y
| println(x)
| }
|}
""".stripMargin
val c = compileClass(code)
assertEquals(locals(c, "t1"), List(("this", 0), ("x", 1)))
assertEquals(locals(c, "t2"), List(("this", 0), ("x", 1)))
// we don't have constant propagation (yet).
// the local var can't be optimized as a store;laod sequence, there's a GETSTATIC between the two
assertSameSummary(getMethod(c, "t2"), List(
ICONST_2, ISTORE, GETSTATIC, ILOAD, "boxToInteger", "println", RETURN))
assertEquals(locals(c, "t3"), List(("this", 0)))
assertEquals(locals(c, "t4"), List(("this", 0), ("x", 1)))
assertEquals(locals(c, "t5"), List(("this", 0), ("x", 1)))
}
@Test
def t7006(): Unit = {
val code =
"""class C {
| def t: Unit = {
| try {
| val x = 3
| } finally {
| print("hello")
| }
| while(true) { }
| }
|}
""".stripMargin
val c = compileClass(code)
val t = getMethod(c, "t")
assertEquals(t.handlers, Nil)
assertEquals(locals(c, "t"), List(("this", 0)))
assertSameSummary(t, List(GETSTATIC, LDC, "print", -1, GOTO))
}
@Test
def booleanOrderingCompare(): Unit = {
val code =
"""class C {
| def compare(x: Boolean, y: Boolean) = (x, y) match {
| case (false, true) => -1
| case (true, false) => 1
| case _ => 0
| }
|}
""".stripMargin
val c = compileClass(code)
assertNoInvoke(getMethod(c, "compare"))
}
@Test
def t8790(): Unit = {
val code =
"""class C {
| def t(x: Int, y: Int): String = (x, y) match {
| case (7, 8) => "a"
| case _ => "b"
| }
|}
""".stripMargin
val c = compileClass(code)
assertSameSummary(getMethod(c, "t"), List(
BIPUSH, ILOAD, IF_ICMPNE,
BIPUSH, ILOAD, IF_ICMPNE,
LDC, ASTORE, GOTO,
-1, LDC, ASTORE,
-1, ALOAD, ARETURN))
}
@Test
def elimSamLambda(): Unit = {
val code =
"""class C {
| def t1(x: Int) = {
| val fun: java.util.function.IntFunction[Int] = y => y + 1
| fun(x)
| }
| def t2(x: Int) = {
| val fun: T = i => i + 1
| fun.f(x)
| }
|}
|trait T { def f(x: Int): Int }
""".stripMargin
val List(c, t) = compileClasses(code)
assertSameSummary(getMethod(c, "t1"), List(ILOAD, "$anonfun$t1$1", IRETURN))
assertSameSummary(getMethod(c, "t2"), List(ILOAD, "$anonfun$t2$1", IRETURN))
}
}