From bdac88a885c1c7731d5a81acceac75513baea536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 28 Aug 2018 19:56:53 +0200 Subject: [PATCH 1/3] Better dead-code-elimination inside PatternMatcher. If the head of a `SeqPlan` cannot fall through, the tail is unreachable and can be removed. --- .../tools/dotc/transform/PatternMatcher.scala | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index d4a9331db261..745c1787f5ed 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -457,6 +457,12 @@ object PatternMatcher { apply(initializer(plan.sym)) plan } + override def apply(plan: SeqPlan): Plan = { + apply(plan.head) + if (canFallThrough(plan.head)) + apply(plan.tail) + plan + } } refCounter(plan) refCounter.count @@ -564,8 +570,10 @@ object PatternMatcher { new MergeTests()(plan) } - /** Inline let-bound trees that are referenced only once. - * Drop all variables that are not referenced anymore after this. + /** Inline let-bound trees that are referenced only once and eliminate dead code. + * + * - Drop all variables that are not referenced anymore after inlining. + * - Drop the `tail` of `SeqPlan`s whose `head` cannot fall through. */ private def inlineVars(plan: Plan): Plan = { val refCount = varRefCount(plan) @@ -597,6 +605,18 @@ object PatternMatcher { plan } } + override def apply(plan: SeqPlan): Plan = { + val newHead = apply(plan.head) + if (!canFallThrough(newHead)) { + // If the head cannot fall through, the tail is dead code + newHead + } + else { + plan.head = newHead + plan.tail = apply(plan.tail) + plan + } + } } Inliner(plan) } From b3ff28a371d9c4b1b85641a616ca84534c8d251a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 28 Aug 2018 20:58:35 +0200 Subject: [PATCH 2/3] New version of the back-end, which is marter about `()` in else branches. This removes the last nop's generating by the `Labeled`-based pattern matcher. --- scala-backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scala-backend b/scala-backend index 011d5c333d52..0e7ec5de6024 160000 --- a/scala-backend +++ b/scala-backend @@ -1 +1 @@ -Subproject commit 011d5c333d52fc6d9e45b9e33614abe9ac19a851 +Subproject commit 0e7ec5de60247d44aa4609bb8e194437a36596e8 From 6e0864993affa8542680d6f81ce4937599ef4bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 30 Aug 2018 20:28:30 +0200 Subject: [PATCH 3/3] Add some bytecode tests for the pattern matcher. * Switch with alternatives * Switch with guards * No `throw` in a match with `case _ =>` --- .../backend/jvm/DottyBytecodeTests.scala | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index 6a92872e5213..fc76948490df 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -50,7 +50,7 @@ class TestBCode extends DottyBytecodeTest { /** This test verifies that simple matches with `@switch` annotations are * indeed transformed to a switch */ - @Test def basicTransfromAnnotated = { + @Test def basicSwitch = { val source = """ |object Foo { | import scala.annotation.switch @@ -69,6 +69,71 @@ class TestBCode extends DottyBytecodeTest { } } + @Test def switchWithAlternatives = { + val source = + """ + |object Foo { + | import scala.annotation.switch + | def foo(i: Int) = (i: @switch) match { + | case 2 => println(2) + | case 1 | 3 | 5 => println(1) + | case 0 => println(0) + | } + |} + """.stripMargin + + checkBCode(source) { dir => + val moduleIn = dir.lookupName("Foo$.class", directory = false) + val moduleNode = loadClassNode(moduleIn.input) + val methodNode = getMethod(moduleNode, "foo") + assert(verifySwitch(methodNode)) + } + } + + @Test def switchWithGuards = { + val source = + """ + |object Foo { + | import scala.annotation.switch + | def foo(i: Int, b: Boolean) = (i: @switch) match { + | case 2 => println(3) + | case 1 if b => println(2) + | case 1 => println(1) + | case 0 => println(0) + | } + |} + """.stripMargin + + checkBCode(source) { dir => + val moduleIn = dir.lookupName("Foo$.class", directory = false) + val moduleNode = loadClassNode(moduleIn.input) + val methodNode = getMethod(moduleNode, "foo") + assert(verifySwitch(methodNode)) + } + } + + @Test def matchWithDefaultNoThrowMatchError = { + val source = + """class Test { + | def test(s: String) = s match { + | case "Hello" => 1 + | case _ => 2 + | } + |} + """.stripMargin + + checkBCode(source) { dir => + val clsIn = dir.lookupName("Test.class", directory = false) + val clsNode = loadClassNode(clsIn.input) + val method = getMethod(clsNode, "test") + val throwMatchError = instructionsFromMethod(method).exists { + case Op(Opcodes.ATHROW) => true + case _ => false + } + assertFalse(throwMatchError) + } + } + @Test def failTransform = { val source = """ |object Foo {