From 5675d2b26b27688ea9aefafac7f5f37f14c87750 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Tue, 30 Dec 2014 14:55:45 +0300 Subject: [PATCH] Dead code elimination #KT-6602 Fixed #KT-6305 Fixed #KT-5656 Fixed --- .../DeadCodeEliminationMethodTransformer.kt | 37 ++++++++++ .../OptimizationMethodVisitor.java | 9 ++- .../RedundantGotoMethodTransformer.kt | 7 +- .../codegen/optimization/common/Util.kt | 71 +++++++++++++++++++ .../deadCodeElimination/emptyVariableRange.kt | 12 ++++ .../deadCodeElimination/boxing.kt | 16 +++++ .../deadCodeElimination/emptyVariableRange.kt | 7 ++ .../deadCodeElimination/lastReturn.kt | 23 ++++++ .../codegen/BytecodeTextTestGenerated.java | 29 +++++++- ...lackBoxWithStdlibCodegenTestGenerated.java | 17 ++++- 10 files changed, 218 insertions(+), 10 deletions(-) create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt create mode 100644 compiler/testData/codegen/boxWithStdlib/deadCodeElimination/emptyVariableRange.kt create mode 100644 compiler/testData/codegen/bytecodeText/deadCodeElimination/boxing.kt create mode 100644 compiler/testData/codegen/bytecodeText/deadCodeElimination/emptyVariableRange.kt create mode 100644 compiler/testData/codegen/bytecodeText/deadCodeElimination/lastReturn.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt new file mode 100644 index 00000000000..86b08a18827 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen.optimization + +import org.jetbrains.org.objectweb.asm.tree.MethodNode +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter +import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful + +public class DeadCodeEliminationMethodTransformer : MethodTransformer() { + override fun transform(internalClassName: String, methodNode: MethodNode) { + val frames = MethodTransformer.analyze(internalClassName, methodNode, OptimizationBasicInterpreter()) + val insnList = methodNode.instructions + val insnsArray = insnList.toArray() + + // Do not remove not meaningful nodes (labels/linenumbers) because they can be referred + // by try/catch blocks or local variables table + // We remove unneeded ones further after all optimizations by calling CommonPackage.prepareForEmitting(methodNode) + insnsArray.zip(frames).filter { + it.second == null && it.first.isMeaningful + }.forEach { insnList.remove(it.first) } + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java index 8c60a1dc746..7525cca74c7 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java @@ -21,6 +21,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil; import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTransformer; import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantNullCheckMethodTransformer; +import org.jetbrains.kotlin.codegen.optimization.common.CommonPackage; import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer; import org.jetbrains.org.objectweb.asm.MethodVisitor; import org.jetbrains.org.objectweb.asm.Opcodes; @@ -35,8 +36,11 @@ import java.util.List; public class OptimizationMethodVisitor extends MethodVisitor { private static final int MEMORY_LIMIT_BY_METHOD_MB = 50; private static final MethodTransformer[] TRANSFORMERS = new MethodTransformer[]{ - new RedundantNullCheckMethodTransformer(), new RedundantBoxingMethodTransformer(), - new RedundantGotoMethodTransformer(), new StoreStackBeforeInlineMethodTransformer() + new RedundantNullCheckMethodTransformer(), + new RedundantBoxingMethodTransformer(), + new DeadCodeEliminationMethodTransformer(), + new RedundantGotoMethodTransformer(), + new StoreStackBeforeInlineMethodTransformer() }; private final MethodNode methodNode; @@ -70,6 +74,7 @@ public class OptimizationMethodVisitor extends MethodVisitor { for (MethodTransformer transformer : TRANSFORMERS) { transformer.transform("fake", methodNode); } + CommonPackage.prepareForEmitting(methodNode); } methodNode.accept(new EndIgnoringMethodVisitorDecorator(Opcodes.ASM5, delegate)); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantGotoMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantGotoMethodTransformer.kt index 1558f7d0f73..f28eacb676b 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantGotoMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/RedundantGotoMethodTransformer.kt @@ -22,6 +22,7 @@ import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode import org.jetbrains.org.objectweb.asm.tree.LabelNode import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode +import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful public class RedundantGotoMethodTransformer : MethodTransformer() { /** @@ -53,9 +54,3 @@ public class RedundantGotoMethodTransformer : MethodTransformer() { } } } - -private val AbstractInsnNode.isMeaningful : Boolean get() = - when (this.getType()) { - AbstractInsnNode.LABEL, AbstractInsnNode.LINE, AbstractInsnNode.FRAME -> false - else -> true - } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt new file mode 100644 index 00000000000..4e2cdfbc65f --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen.optimization.common + +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue +import org.jetbrains.org.objectweb.asm.tree.MethodNode + +val AbstractInsnNode.isMeaningful : Boolean get() = + when (this.getType()) { + AbstractInsnNode.LABEL, AbstractInsnNode.LINE, AbstractInsnNode.FRAME -> false + else -> true + } + +class InsnStream(val from: AbstractInsnNode, val to: AbstractInsnNode?) : Stream { + override fun iterator(): Iterator { + return object : Iterator { + var current = from + override fun next(): AbstractInsnNode { + val result = current + current = current.getNext() + return result + } + override fun hasNext() = current != to + } + } +} + +fun MethodNode.prepareForEmitting() { + tryCatchBlocks = tryCatchBlocks.filter { tcb -> + InsnStream(tcb.start, tcb.end).any { insn -> + insn.isMeaningful + } + } + + // local variables with live ranges starting after last meaningful instruction lead to VerifyError + localVariables = localVariables.filter { lv -> + InsnStream(lv.start, instructions.getLast()).any { insn -> + insn.isMeaningful + } + } + + // We should remove linenumbers after last meaningful instruction + // because they point to index of non-existing instruction and it leads to VerifyError + var current = instructions.getLast() + while (!current.isMeaningful) { + val prev = current.getPrevious() + + if (current.getType() == AbstractInsnNode.LINE) { + instructions.remove(current) + } + + current = prev + } +} diff --git a/compiler/testData/codegen/boxWithStdlib/deadCodeElimination/emptyVariableRange.kt b/compiler/testData/codegen/boxWithStdlib/deadCodeElimination/emptyVariableRange.kt new file mode 100644 index 00000000000..2948d8b48a6 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/deadCodeElimination/emptyVariableRange.kt @@ -0,0 +1,12 @@ +import kotlin.test.assertEquals + +fun foo(): Int { + return 1 + // val xyz has empty live range because everything after return will be removed as dead + val xyz = 1 +} + +fun box(): String { + assertEquals(1, foo()) + return "OK" +} diff --git a/compiler/testData/codegen/bytecodeText/deadCodeElimination/boxing.kt b/compiler/testData/codegen/bytecodeText/deadCodeElimination/boxing.kt new file mode 100644 index 00000000000..12165e0962b --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/deadCodeElimination/boxing.kt @@ -0,0 +1,16 @@ +fun foo(x: Any?) {} + +fun box() { + val x: Int? = 1 + x!! + + val z: Int? = if (1 == 1) x else null + z!! + + foo(1 as java.lang.Integer) +} + +// 0 IFNULL +// 1 IFNONNULL +// 1 throwNpe +// 0 ATHROW diff --git a/compiler/testData/codegen/bytecodeText/deadCodeElimination/emptyVariableRange.kt b/compiler/testData/codegen/bytecodeText/deadCodeElimination/emptyVariableRange.kt new file mode 100644 index 00000000000..741b2657187 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/deadCodeElimination/emptyVariableRange.kt @@ -0,0 +1,7 @@ +fun foo() { + return + // val xyz has empty live range because everything after return will be removed as dead + val xyz = 1 +} + +// 0 xyz diff --git a/compiler/testData/codegen/bytecodeText/deadCodeElimination/lastReturn.kt b/compiler/testData/codegen/bytecodeText/deadCodeElimination/lastReturn.kt new file mode 100644 index 00000000000..bdc6666bb85 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/deadCodeElimination/lastReturn.kt @@ -0,0 +1,23 @@ +import kotlin.test.assertEquals + +fun foo(value: Boolean): Int { + if (value) + return 1 + else + return 2 +} + +fun box(): String { + assertEquals(1, foo(true)) + assertEquals(2, foo(false)) + + return "OK" +} + +/* + 3 return's are defined in functions + 2 are from package facade + 1 is from package clinit +*/ + +// 6 RETURN diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 3285eb8349e..e4e8cdbe8f3 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -30,7 +30,7 @@ import java.util.regex.Pattern; @SuppressWarnings("all") @TestMetadata("compiler/testData/codegen/bytecodeText") @TestDataPath("$PROJECT_ROOT") -@InnerTestClasses({BytecodeTextTestGenerated.BoxingOptimization.class, BytecodeTextTestGenerated.Constants.class, BytecodeTextTestGenerated.DirectInvoke.class, BytecodeTextTestGenerated.Inline.class, BytecodeTextTestGenerated.LazyCodegen.class, BytecodeTextTestGenerated.LineNumbers.class, BytecodeTextTestGenerated.Statements.class, BytecodeTextTestGenerated.StaticFields.class, BytecodeTextTestGenerated.StoreStackBeforeInline.class, BytecodeTextTestGenerated.When.class, BytecodeTextTestGenerated.WhenEnumOptimization.class, BytecodeTextTestGenerated.WhenStringOptimization.class}) +@InnerTestClasses({BytecodeTextTestGenerated.BoxingOptimization.class, BytecodeTextTestGenerated.Constants.class, BytecodeTextTestGenerated.DeadCodeElimination.class, BytecodeTextTestGenerated.DirectInvoke.class, BytecodeTextTestGenerated.Inline.class, BytecodeTextTestGenerated.LazyCodegen.class, BytecodeTextTestGenerated.LineNumbers.class, BytecodeTextTestGenerated.Statements.class, BytecodeTextTestGenerated.StaticFields.class, BytecodeTextTestGenerated.StoreStackBeforeInline.class, BytecodeTextTestGenerated.When.class, BytecodeTextTestGenerated.WhenEnumOptimization.class, BytecodeTextTestGenerated.WhenStringOptimization.class}) @RunWith(JUnit3RunnerWithInners.class) public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { @TestMetadata("accessorForProtected.kt") @@ -301,6 +301,33 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { } } + @TestMetadata("compiler/testData/codegen/bytecodeText/deadCodeElimination") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class DeadCodeElimination extends AbstractBytecodeTextTest { + public void testAllFilesPresentInDeadCodeElimination() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/deadCodeElimination"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("boxing.kt") + public void testBoxing() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/deadCodeElimination/boxing.kt"); + doTest(fileName); + } + + @TestMetadata("emptyVariableRange.kt") + public void testEmptyVariableRange() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/deadCodeElimination/emptyVariableRange.kt"); + doTest(fileName); + } + + @TestMetadata("lastReturn.kt") + public void testLastReturn() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/deadCodeElimination/lastReturn.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/bytecodeText/directInvoke") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java index fc065c8e328..b4db5bc09b7 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java @@ -30,7 +30,7 @@ import java.util.regex.Pattern; @SuppressWarnings("all") @TestMetadata("compiler/testData/codegen/boxWithStdlib") @TestDataPath("$PROJECT_ROOT") -@InnerTestClasses({BlackBoxWithStdlibCodegenTestGenerated.Annotations.class, BlackBoxWithStdlibCodegenTestGenerated.Arrays.class, BlackBoxWithStdlibCodegenTestGenerated.BoxingOptimization.class, BlackBoxWithStdlibCodegenTestGenerated.CallableReference.class, BlackBoxWithStdlibCodegenTestGenerated.Casts.class, BlackBoxWithStdlibCodegenTestGenerated.Classes.class, BlackBoxWithStdlibCodegenTestGenerated.DataClasses.class, BlackBoxWithStdlibCodegenTestGenerated.DefaultArguments.class, BlackBoxWithStdlibCodegenTestGenerated.Enum.class, BlackBoxWithStdlibCodegenTestGenerated.Evaluate.class, BlackBoxWithStdlibCodegenTestGenerated.FullJdk.class, BlackBoxWithStdlibCodegenTestGenerated.HashPMap.class, BlackBoxWithStdlibCodegenTestGenerated.Intrinsics.class, BlackBoxWithStdlibCodegenTestGenerated.JdkAnnotations.class, BlackBoxWithStdlibCodegenTestGenerated.LazyCodegen.class, BlackBoxWithStdlibCodegenTestGenerated.LocalFunInLambda.class, BlackBoxWithStdlibCodegenTestGenerated.MultiDeclForArray.class, BlackBoxWithStdlibCodegenTestGenerated.Native.class, BlackBoxWithStdlibCodegenTestGenerated.NonLocalReturns.class, BlackBoxWithStdlibCodegenTestGenerated.PlatformNames.class, BlackBoxWithStdlibCodegenTestGenerated.PlatformStatic.class, BlackBoxWithStdlibCodegenTestGenerated.PlatformTypes.class, BlackBoxWithStdlibCodegenTestGenerated.Ranges.class, BlackBoxWithStdlibCodegenTestGenerated.Reflection.class, BlackBoxWithStdlibCodegenTestGenerated.Regressions.class, BlackBoxWithStdlibCodegenTestGenerated.Reified.class, BlackBoxWithStdlibCodegenTestGenerated.StoreStackBeforeInline.class, BlackBoxWithStdlibCodegenTestGenerated.Strings.class, BlackBoxWithStdlibCodegenTestGenerated.ToArray.class, BlackBoxWithStdlibCodegenTestGenerated.Vararg.class, BlackBoxWithStdlibCodegenTestGenerated.When.class, BlackBoxWithStdlibCodegenTestGenerated.WhenEnumOptimization.class, BlackBoxWithStdlibCodegenTestGenerated.WhenStringOptimization.class}) +@InnerTestClasses({BlackBoxWithStdlibCodegenTestGenerated.Annotations.class, BlackBoxWithStdlibCodegenTestGenerated.Arrays.class, BlackBoxWithStdlibCodegenTestGenerated.BoxingOptimization.class, BlackBoxWithStdlibCodegenTestGenerated.CallableReference.class, BlackBoxWithStdlibCodegenTestGenerated.Casts.class, BlackBoxWithStdlibCodegenTestGenerated.Classes.class, BlackBoxWithStdlibCodegenTestGenerated.DataClasses.class, BlackBoxWithStdlibCodegenTestGenerated.DeadCodeElimination.class, BlackBoxWithStdlibCodegenTestGenerated.DefaultArguments.class, BlackBoxWithStdlibCodegenTestGenerated.Enum.class, BlackBoxWithStdlibCodegenTestGenerated.Evaluate.class, BlackBoxWithStdlibCodegenTestGenerated.FullJdk.class, BlackBoxWithStdlibCodegenTestGenerated.HashPMap.class, BlackBoxWithStdlibCodegenTestGenerated.Intrinsics.class, BlackBoxWithStdlibCodegenTestGenerated.JdkAnnotations.class, BlackBoxWithStdlibCodegenTestGenerated.LazyCodegen.class, BlackBoxWithStdlibCodegenTestGenerated.LocalFunInLambda.class, BlackBoxWithStdlibCodegenTestGenerated.MultiDeclForArray.class, BlackBoxWithStdlibCodegenTestGenerated.Native.class, BlackBoxWithStdlibCodegenTestGenerated.NonLocalReturns.class, BlackBoxWithStdlibCodegenTestGenerated.PlatformNames.class, BlackBoxWithStdlibCodegenTestGenerated.PlatformStatic.class, BlackBoxWithStdlibCodegenTestGenerated.PlatformTypes.class, BlackBoxWithStdlibCodegenTestGenerated.Ranges.class, BlackBoxWithStdlibCodegenTestGenerated.Reflection.class, BlackBoxWithStdlibCodegenTestGenerated.Regressions.class, BlackBoxWithStdlibCodegenTestGenerated.Reified.class, BlackBoxWithStdlibCodegenTestGenerated.StoreStackBeforeInline.class, BlackBoxWithStdlibCodegenTestGenerated.Strings.class, BlackBoxWithStdlibCodegenTestGenerated.ToArray.class, BlackBoxWithStdlibCodegenTestGenerated.Vararg.class, BlackBoxWithStdlibCodegenTestGenerated.When.class, BlackBoxWithStdlibCodegenTestGenerated.WhenEnumOptimization.class, BlackBoxWithStdlibCodegenTestGenerated.WhenStringOptimization.class}) @RunWith(JUnit3RunnerWithInners.class) public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCodegenTest { public void testAllFilesPresentInBoxWithStdlib() throws Exception { @@ -1186,6 +1186,21 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode } } + @TestMetadata("compiler/testData/codegen/boxWithStdlib/deadCodeElimination") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class DeadCodeElimination extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInDeadCodeElimination() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/deadCodeElimination"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("emptyVariableRange.kt") + public void testEmptyVariableRange() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/deadCodeElimination/emptyVariableRange.kt"); + doTestWithStdlib(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxWithStdlib/defaultArguments") @TestDataPath("$PROJECT_ROOT") @InnerTestClasses({DefaultArguments.Constructor.class})