diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2015-12-11 09:37:02 +0100 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2015-12-13 09:41:51 +0100 |
commit | 93de014be97c5f5a707a36ce10eeaab68037fda7 (patch) | |
tree | b94ea4b1c10a053160a350f8bdbeaac222d1bf36 /test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala | |
parent | 34fcee8f7dd7a9128ab7a14f97d5dd8e89ab1b4d (diff) | |
download | scala-93de014be97c5f5a707a36ce10eeaab68037fda7.tar.gz scala-93de014be97c5f5a707a36ce10eeaab68037fda7.tar.bz2 scala-93de014be97c5f5a707a36ce10eeaab68037fda7.zip |
Rename test MethodLevelOpts -> MethodLevelOptsTest
Diffstat (limited to 'test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala')
-rw-r--r-- | test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala new file mode 100644 index 0000000000..f4524f0bb1 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/MethodLevelOptsTest.scala @@ -0,0 +1,255 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.nsc.backend.jvm.AsmUtils._ +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ +import scala.tools.testing.ClearAfterClass + +object MethodLevelOptsTest extends ClearAfterClass.Clearable { + var methodOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:method") + def clear(): Unit = { methodOptCompiler = null } +} + +@RunWith(classOf[JUnit4]) +class MethodLevelOptsTest extends ClearAfterClass { + ClearAfterClass.stateToClear = MethodLevelOptsTest + + val methodOptCompiler = MethodLevelOptsTest.methodOptCompiler + + def wrapInDefault(code: Instruction*) = List(Label(0), LineNumber(1, Label(0))) ::: code.toList ::: List(Label(1)) + + @Test + def eliminateEmptyTry(): Unit = { + val code = "def f = { try {} catch { case _: Throwable => 0; () }; 1 }" + val warn = "a pure expression does nothing in statement position" + assertSameCode(singleMethodInstructions(methodOptCompiler)(code, allowMessage = _.msg contains warn), wrapInDefault(Op(ICONST_1), Op(IRETURN))) + } + + @Test + def 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 = singleMethod(methodOptCompiler)(code) + assertTrue(m.handlers.length == 0) + assertSameCode(m.instructions.dropNonOp, + 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 = singleMethod(methodOptCompiler)(code) + assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5) + assertSameCode(m.instructions, + wrapInDefault(VarOp(ALOAD, 1), Label(3), Op(ATHROW), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), VarOp(ALOAD, 1), Op(ATHROW)) + ) + } + + @Test + def inlineReturnInCatchNotTry(): Unit = { + val code = "def f: Int = return { try 1 catch { case _: Throwable => 2 } }" + // cannot inline the IRETURN into the try block (because RETURN may throw IllegalMonitorState) + val m = singleMethod(methodOptCompiler)(code) + assertHandlerLabelPostions(m.handlers.head, m.instructions, 0, 3, 5) + assertSameCode(m.instructions, + wrapInDefault(Op(ICONST_1), Label(3), Op(IRETURN), Label(5), FrameEntry(4, List(), List("java/lang/Throwable")), Op(POP), Op(ICONST_2), Op(IRETURN))) + } + + @Test + def simplifyJumpsInTryCatchFinally(): Unit = { + val code = + """def f: Int = + | try { + | return 1 + | } catch { + | case _: Throwable => + | return 2 + | } finally { + | return 3 + | // dead + | val x = try 10 catch { case _: Throwable => 11 } + | println(x) + | } + """.stripMargin + val m = singleMethod(methodOptCompiler)(code) + assertTrue(m.handlers.isEmpty) + assertSameCode(m.instructions.dropNonOp, 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 List(c) = compileClasses(methodOptCompiler)(code) + assertSameCode(getSingleMethod(c, "t").instructions.dropNonOp, + 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 List(c) = compileClasses(methodOptCompiler)(code) + assertSameCode( + getSingleMethod(c, "t").instructions.dropNonOp, + 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 List(c) = compileClasses(methodOptCompiler)(code) + + assertEquals( + getSingleMethod(c, "t").instructions.dropNonOp, + 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)) + | val f = new scala.runtime.IntRef(11) + | x + y + | } + |} + """.stripMargin + val List(c) = compileClasses(methodOptCompiler)(code) + assertEquals(getSingleMethod(c, "t").instructions.dropNonOp, + List(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 List(c) = compileClasses(methodOptCompiler)(code) + assertEquals(getSingleMethod(c, "t").instructions.dropNonOp, + 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 List(c) = compileClasses(methodOptCompiler)(code) + assertEquals(getSingleMethod(c, "t").instructions.dropNonOp, + 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 List(c) = compileClasses(methodOptCompiler)(code) + assertEquals( + getSingleMethod(c, "t").instructions.dropNonOp, + 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", "C$$$anonfun$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 List(c) = compileClasses(methodOptCompiler)(code) + val t = getSingleMethod(c, "t") + assert(!t.instructions.exists(_.opcode == INVOKEDYNAMIC), t) + } +} |