summaryrefslogtreecommitdiff
path: root/test/junit/scala/tools/nsc/backend/jvm
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2014-08-25 23:13:05 +0200
committerLukas Rytz <lukas.rytz@gmail.com>2014-09-10 00:05:03 +0200
commit44b5c261a8e585c5747380895aa06c84f9d63f6c (patch)
treebebacf259c0d62d4b9298b4892cc5c2c309cc857 /test/junit/scala/tools/nsc/backend/jvm
parentd3cfbb1db68dab3308edfa66bbdb497c5d3f0cb2 (diff)
downloadscala-44b5c261a8e585c5747380895aa06c84f9d63f6c.tar.gz
scala-44b5c261a8e585c5747380895aa06c84f9d63f6c.tar.bz2
scala-44b5c261a8e585c5747380895aa06c84f9d63f6c.zip
JUnit tests for dead code elimination.
JUnit tests may use tools from partest-extras (ASMConverters)
Diffstat (limited to 'test/junit/scala/tools/nsc/backend/jvm')
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala1
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala27
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala162
3 files changed, 189 insertions, 1 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
index cb7e7050b0..221aad6536 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala
@@ -1,7 +1,6 @@
package scala.tools.nsc
package backend.jvm
-import scala.tools.testing.AssertUtil._
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.Test
diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
new file mode 100644
index 0000000000..256dff85c3
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala
@@ -0,0 +1,27 @@
+package scala.tools.nsc.backend.jvm
+
+import scala.tools.asm.Opcodes
+import scala.tools.asm.tree.{AbstractInsnNode, LabelNode, ClassNode, MethodNode}
+import scala.tools.partest.ASMConverters
+import scala.collection.JavaConverters._
+
+object CodeGenTools {
+ import ASMConverters._
+
+ def genMethod( flags: Int = Opcodes.ACC_PUBLIC,
+ name: String = "m",
+ descriptor: String = "()V",
+ genericSignature: String = null,
+ throwsExceptions: Array[String] = null)(body: Instruction*): MethodNode = {
+ val node = new MethodNode(flags, name, descriptor, genericSignature, throwsExceptions)
+ applyToMethod(node, body.toList)
+ node
+ }
+
+ def wrapInClass(method: MethodNode): ClassNode = {
+ val cls = new ClassNode()
+ cls.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "C", null, "java/lang/Object", null)
+ cls.methods.add(method)
+ cls
+ }
+}
diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
new file mode 100644
index 0000000000..4e62cc64e4
--- /dev/null
+++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala
@@ -0,0 +1,162 @@
+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.testing.AssertUtil._
+
+import CodeGenTools._
+import scala.tools.partest.ASMConverters
+import ASMConverters._
+
+@RunWith(classOf[JUnit4])
+class UnreachableCodeTest {
+ import UnreachableCodeTest._
+
+ @Test
+ def basicElimination(): Unit = {
+ assertEliminateDead(
+ Op(ACONST_NULL),
+ Op(ATHROW),
+ Op(RETURN).dead
+ )
+
+ assertEliminateDead(
+ Op(RETURN)
+ )
+
+ assertEliminateDead(
+ Op(RETURN),
+ Op(ACONST_NULL).dead,
+ Op(ATHROW).dead
+ )
+ }
+
+ @Test
+ def eliminateNop(): Unit = {
+ assertEliminateDead(
+ // not dead, since visited by data flow analysis. need a different opt to eliminate it.
+ Op(NOP),
+ Op(RETURN),
+ Op(NOP).dead
+ )
+ }
+
+ @Test
+ def eliminateBranchOver(): Unit = {
+ assertEliminateDead(
+ Jump(GOTO, Label(1)),
+ Op(ACONST_NULL).dead,
+ Op(ATHROW).dead,
+ Label(1),
+ Op(RETURN)
+ )
+
+ assertEliminateDead(
+ Jump(GOTO, Label(1)),
+ Label(1),
+ Op(RETURN)
+ )
+ }
+
+ @Test
+ def deadLabelsRemain(): Unit = {
+ assertEliminateDead(
+ Op(RETURN),
+ Jump(GOTO, Label(1)).dead,
+ // not dead - labels may be referenced from other places in a classfile (eg exceptions table).
+ // will need a different opt to get rid of them
+ Label(1)
+ )
+ }
+
+ @Test
+ def pushPopNotEliminated(): Unit = {
+ assertEliminateDead(
+ // not dead, visited by data flow analysis.
+ Op(ACONST_NULL),
+ Op(POP),
+ Op(RETURN)
+ )
+ }
+
+ @Test
+ def nullnessNotConsidered(): Unit = {
+ assertEliminateDead(
+ Op(ACONST_NULL),
+ Jump(IFNULL, Label(1)),
+ Op(RETURN), // not dead
+ Label(1),
+ Op(RETURN)
+ )
+ }
+
+ @Test
+ def metaTest(): Unit = {
+ assertEliminateDead() // no instructions
+
+ assertThrows[AssertionError](
+ assertEliminateDead(Op(RETURN).dead),
+ _.contains("Expected: List()\nActual : List(Op(RETURN))")
+ )
+
+ assertThrows[AssertionError](
+ assertEliminateDead(Op(RETURN), Op(RETURN)),
+ _.contains("Expected: List(Op(RETURN), Op(RETURN))\nActual : List(Op(RETURN))")
+ )
+ }
+
+ @Test
+ def bytecodeEquivalence: Unit = {
+ assertTrue(List(VarOp(ILOAD, 1)) ===
+ List(VarOp(ILOAD, 2)))
+ assertTrue(List(VarOp(ILOAD, 1), VarOp(ISTORE, 1)) ===
+ List(VarOp(ILOAD, 2), VarOp(ISTORE, 2)))
+
+ // the first Op will associate 1->2, then the 2->2 will fail
+ assertFalse(List(VarOp(ILOAD, 1), VarOp(ISTORE, 2)) ===
+ List(VarOp(ILOAD, 2), VarOp(ISTORE, 2)))
+
+ // will associate 1->2 and 2->1, which is OK
+ assertTrue(List(VarOp(ILOAD, 1), VarOp(ISTORE, 2)) ===
+ List(VarOp(ILOAD, 2), VarOp(ISTORE, 1)))
+
+ assertTrue(List(Label(1), Label(2), Label(1)) ===
+ List(Label(2), Label(4), Label(2)))
+ assertTrue(List(LineNumber(1, Label(1)), Label(1)) ===
+ List(LineNumber(1, Label(3)), Label(3)))
+ assertFalse(List(LineNumber(1, Label(1)), Label(1)) ===
+ List(LineNumber(1, Label(3)), Label(1)))
+
+ assertTrue(List(TableSwitch(TABLESWITCH, 1, 3, Label(4), List(Label(5), Label(6))), Label(4), Label(5), Label(6)) ===
+ List(TableSwitch(TABLESWITCH, 1, 3, Label(9), List(Label(3), Label(4))), Label(9), Label(3), Label(4)))
+
+ assertTrue(List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(3)), List("java/lang/Object", Label(4))), Label(3), Label(4)) ===
+ List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(1)), List("java/lang/Object", Label(3))), Label(1), Label(3)))
+ }
+}
+
+object UnreachableCodeTest {
+ import scala.language.implicitConversions
+ implicit def aliveInstruction(ins: Instruction): (Instruction, Boolean) = (ins, true)
+
+ implicit class MortalInstruction(val ins: Instruction) extends AnyVal {
+ def dead: (Instruction, Boolean) = (ins, false)
+ }
+
+ def assertEliminateDead(code: (Instruction, Boolean)*): Unit = {
+ val cls = wrapInClass(genMethod()(code.map(_._1): _*))
+ LocalOpt.removeUnreachableCode(cls)
+ val nonEliminated = instructionsFromMethod(cls.methods.get(0))
+
+ val expectedLive = code.filter(_._2).map(_._1).toList
+
+ assertTrue(s"\nExpected: $expectedLive\nActual : $nonEliminated", nonEliminated === expectedLive)
+ }
+
+}