| /* |
| * Copyright 2008 Google Inc. |
| * |
| * 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 com.google.gwt.dev.jjs.impl; |
| |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| |
| /** |
| * Tests {@link DeadCodeElimination}. |
| */ |
| public class DeadCodeEliminationTest extends OptimizerTestBase { |
| /* |
| * TODO: this class needs more tests, and more sophisticated cases. Especially |
| * to ensure we converge in a single pass. |
| */ |
| |
| @Override |
| public void setUp() throws Exception { |
| addSnippetClassDecl("static volatile boolean b;"); |
| addSnippetClassDecl("static volatile boolean b1;"); |
| addSnippetClassDecl("static volatile int i;"); |
| addSnippetClassDecl("static volatile long l;"); |
| addSnippetClassDecl("static volatile float f;"); |
| addSnippetClassDecl("static volatile double d;"); |
| addSnippetClassDecl("static volatile String s;"); |
| addSnippetClassDecl("static volatile Object o;"); |
| |
| runMethodInliner = false; |
| runSpecializer = false; |
| } |
| |
| public void testConditionalOptimizations() throws Exception { |
| optimize("int", "return true ? 3 : 4;").into("return 3;"); |
| optimize("int", "return false ? 3 : 4;").into("return 4;"); |
| |
| optimize("boolean", "return b ? true : b1;").into("return b || b1;"); |
| optimize("boolean", "return b ? false : b1;").into("return !b && b1;"); |
| optimize("boolean", "return b ? b1 : true;").into("return !b || b1;"); |
| optimize("boolean", "return b ? b1 : false;").into("return b && b1;"); |
| } |
| |
| public void testSwitchOverConstant_noMatchingCase() throws Exception { |
| optimize("int", "switch (0) { case 1: return 1; } return 0;") |
| .into("return 0;"); |
| } |
| |
| public void testSwitchOverConstant_MatchingCase() throws Exception { |
| optimize("int", |
| "switch (1) { case 1: return 1; } return 0;") |
| .into("return 1;"); |
| |
| // The if isn't really the focus of the optimization, but the `into` |
| // string will not compile if it is invalid Java. It makes the opt form |
| // valid java. |
| optimize("int", |
| "int j = 1;", |
| "if (b) {", |
| " switch (1) {", |
| " case 0:", |
| " case 1:", |
| " case 2:", |
| " j = 5;", |
| " case 3:", |
| " return 1;", |
| " default:", |
| " return j;", |
| " }", |
| "}", |
| "return -1;") |
| .into( |
| "int j = 1;", |
| "if (b) {", |
| " switch (1) {", |
| " case 1:", // All of the other cases and the default are gone |
| " j = 5;", // this is a dead-store but is currently retained |
| " return 1;", |
| " }", |
| "}", |
| "return -1;"); |
| } |
| |
| public void testSwitchOverConstant_NonConstant() throws Exception { |
| // doesn't optimize when there is a non-constant switch expr |
| // (though, in this case, it's easy to imagine that it could) |
| String[] nonConstantSwitch = new String[] { |
| "int j = 1;", |
| "switch (j) {", |
| " case 0: ", |
| " case 1: ", |
| " case 2: ", |
| " j = 5; ", |
| " case 3: ", |
| " return 1; ", |
| " default: ", |
| " return j; ", |
| "}" |
| }; |
| optimize("int", nonConstantSwitch).into(nonConstantSwitch); |
| } |
| |
| public void testIfOptimizations() throws Exception { |
| optimize("int", "if (true) return 1; return 0;").into("return 1;"); |
| optimize("int", "if (false) return 1; return 0;").into("return 0;"); |
| optimize("int", "if (true) return 1; else return 2;").into("return 1;"); |
| optimize("int", "if (false) return 1; else return 2;").into("return 2;"); |
| |
| optimize("int", "if (true) {} else return 4; return 0;").into("return 0;"); |
| |
| addSnippetClassDecl("static boolean test() { return b; }"); |
| optimize("int", "if (test()) {} else {}; return 0;").into( |
| "test(); return 0;"); |
| } |
| |
| public void testIfStatementToBoolean_NotOptimization() throws Exception { |
| optimize("void", "if (!b) i = 1;").intoString( |
| "EntryPoint.b || (EntryPoint.i = 1);"); |
| optimize("void", "if (!b) i = 1; else i = 2;").intoString( |
| "EntryPoint.b ? (EntryPoint.i = 2) : (EntryPoint.i = 1);"); |
| optimize("int", "if (!b) { return 1;} else {return 2;}").into( |
| "return b ? 2 : 1;"); |
| } |
| |
| public void testIfStatementToBoolean_ReturnLifting() throws Exception { |
| optimize("int", "if (b) return 1; return 2;").into( |
| "if (b) return 1; return 2;"); |
| optimize("int", "if (b) { return 1; } return 2;").into( |
| "if (b) { return 1; } return 2;"); |
| optimize("int", "if (b) { return 1;} else {return 2;}").into( |
| "return b ? 1 : 2;"); |
| optimize("int", "if (b) return 1; else {return 2;}").into( |
| "return b ? 1 : 2;"); |
| optimize("int", "if (b) return 1; else return 2;").into("return b ? 1 : 2;"); |
| optimize("void", "if (b) return; else return;").into( |
| "if (b) return; else return;"); |
| } |
| |
| public void testIfStatementToBoolean_ThenElseOptimization() throws Exception { |
| optimize("void", "if (b) i = 1; else i = 2;").intoString( |
| "EntryPoint.b ? (EntryPoint.i = 1) : (EntryPoint.i = 2);"); |
| optimize("void", "if (b) {i = 1;} else {i = 2;}").intoString( |
| "EntryPoint.b ? (EntryPoint.i = 1) : (EntryPoint.i = 2);"); |
| } |
| |
| public void testIfStatementToBoolean_ThenOptimization() throws Exception { |
| optimize("void", "if (b) i = 1;").intoString( |
| "EntryPoint.b && (EntryPoint.i = 1);"); |
| optimize("void", "if (b) {i = 1;}").intoString( |
| "EntryPoint.b && (EntryPoint.i = 1);"); |
| } |
| |
| /** |
| * BUG: JInstance was marked as not having side effects whereas it all depends on the |
| * whether the expression on the left has side effects. |
| * |
| * Reproduces Issue:7818. |
| */ |
| public void testInstanceOfOptimization() throws Exception { |
| runMethodInliner = true; |
| addSnippetClassDecl( |
| "static class A { " |
| + "static int f1;" |
| + "static A createA() { A.f1 = 1; return new A(); } " |
| + "static boolean instanceofMulti() { return (createA() instanceof A); } " |
| + "static boolean inlineable() { instanceofMulti(); return true;}" |
| + "}"); |
| |
| optimizeExpressions(false, "void", "A.inlineable()") |
| .into("A.f1 = 1; new A();"); |
| } |
| |
| public void testCommuteMultiExpression() throws Exception { |
| runMethodInliner = true; |
| addSnippetClassDecl( |
| "static class A { " |
| + "static int f1;" |
| + "static A createA() { A.f1 = 1; return new A(); } " |
| + "static boolean booleanWithSideEffects() { createA(); return true;}" |
| + "static boolean booleanWithoutSideEffects() { return true;}" |
| + "static int arithmeticWithSideEffects() { createA(); return 4;}" |
| + "}"); |
| |
| optimizeExpressions(false, "boolean", "true && A.booleanWithoutSideEffects()") |
| .intoString("return true;"); |
| |
| optimizeExpressions(false, "boolean", "true && A.booleanWithSideEffects()") |
| .intoString("return (EntryPoint$A.f1 = 1, new EntryPoint$A(), true);"); |
| |
| optimizeExpressions(false, "boolean", "false && A.booleanWithSideEffects()") |
| .intoString("return false;"); |
| |
| optimizeExpressions(false, "int", "3 + A.arithmeticWithSideEffects()") |
| .intoString("return (EntryPoint$A.f1 = 1, new EntryPoint$A(), 7);"); |
| } |
| |
| public void testStringOptimizations() throws Exception { |
| runMethodInliner = true; |
| addSnippetClassDecl( |
| "static class A { ", |
| " final static String s1 = \"a\";", |
| " final static String s2 = \"a\";", |
| " final static String s3 = \"b\";", |
| " final static String s4 = null;", |
| "}"); |
| |
| // TODO(rluble): This test is not 100% meaninful as the JDT performs some optimizations for us. |
| optimizeExpressions(false, "boolean", "\"a\".equals(\"a\")") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "\"a\" == \"a\"") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "\"a\" != \"b\"") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "A.s1.equals(A.s1)") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "A.s1 == A.s1") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "A.s1 == \"a\"") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "\"a\" != null") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "\"a\".equals(A.s2)") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "!\"a\".equals(A.s3)") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "\"a\" == A.s2") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "\"a\" != A.s3") |
| .into("return true;"); |
| optimizeExpressions(false, "boolean", "A.s1.equals(A.s2)") |
| .into("return true;"); |
| // Next two are not directly optimizable because of inserted clinits. |
| // optimizeExpressions(false, "boolean", "\"a\" != A.s4") |
| // .into("return true;"); |
| // optimizeExpressions(false, "boolean", "A.s4 == null") |
| // .into("return true;"); |
| } |
| |
| public void testStringOptimizations_withSpecializer() throws Exception { |
| runSpecializer = true; |
| testStringOptimizations(); |
| } |
| |
| public void testDoOptimization() throws Exception { |
| optimize("void", "do {} while (b);").intoString( |
| "do;", |
| "while (EntryPoint.b);"); |
| optimize("void", "do {} while (true);").intoString( |
| "do;", |
| "while (true);"); |
| optimize("void", "do {} while (false);").intoString(""); |
| optimize("void", "do { i++; } while (false);").intoString("++EntryPoint.i;"); |
| optimize("void", "do { break; } while (false);").intoString( |
| "do {", |
| " break;", |
| "} while (false);"); |
| } |
| |
| public void testMultiExpressionOptimization() throws Exception { |
| runMethodInliner = true; |
| addSnippetClassDecl( |
| "static class A { ", |
| " static int f;", |
| " static { if (4-f ==0) f=4; }", |
| " static boolean t() { return true; }", |
| " static boolean f() { return false; }", |
| " static boolean notInlineable() { if (4-f == 0) return true;return false;}", |
| "}"); |
| |
| addSnippetClassDecl( |
| "static class B { ", |
| " static boolean inlineableOr() { return A.t() || A.notInlineable(); }", |
| " static boolean inlineableAnd() { return A.t() && A.notInlineable(); }", |
| "}"); |
| |
| optimize("void", "B.inlineableAnd();") |
| .intoString("EntryPoint$A.$clinit();\nEntryPoint$A.notInlineable();"); |
| optimize("void", "B.inlineableOr();") |
| .intoString("EntryPoint$A.$clinit();"); |
| } |
| |
| public void testOptimizeStringCalls() throws Exception { |
| // Note: we're limited here by the methods declared in the mock String in |
| // JJSTestBase#addBuiltinClasses |
| |
| // String.length |
| optimize("int", "return \"abc\".length();").intoString("return 3;"); |
| optimize("int", "return s.length();").intoString("return EntryPoint.s.length();"); |
| |
| // String.charAt |
| optimize("char", "return \"abc\".charAt(1);").intoString("return 'b';"); |
| optimize("char", "return s.charAt(1);").intoString("return EntryPoint.s.charAt(1);"); |
| |
| // String.toString |
| optimize("String", "return s.toString();").intoString("return EntryPoint.s;"); |
| optimize("String", "return o.toString();").intoString("return EntryPoint.o.toString();"); |
| |
| // String.hashCode: never optimized |
| optimize("int", "return \"abc\".hashCode();").intoString("return \"abc\".hashCode();"); |
| optimize("int", "return s.hashCode();").intoString("return EntryPoint.s.hashCode();"); |
| |
| // String.equals |
| optimize("boolean", "return \"a\".equals(\"a\");").intoString("return true;"); |
| optimize("boolean", "return \"a\".equals(\"b\");").intoString("return false;"); |
| optimize("boolean", "return s.equals(\"a\");") |
| .intoString("return EntryPoint.s.equals(\"a\");"); |
| |
| // String concat |
| optimize("String", "return \"a\" + \"a\";").intoString("return \"aa\";"); |
| optimize("String", "return \"a\" + 1;").intoString("return \"a1\";"); |
| optimize("String", "return \"a\" + '1';").intoString("return \"a1\";"); |
| optimize("String", "return \"a\" + 1L;").intoString("return \"a1\";"); |
| } |
| |
| public void testSubtractFromZero() throws Exception { |
| optimize("int", "return 0 - i;").intoString("return -EntryPoint.i;"); |
| optimize("long", "return 0 - l;").intoString("return -EntryPoint.l;"); |
| // Verify that float/double subtracts from zero aren't replaced, since they |
| // are needed for obscure IEEE754 functionality -- specifically, converting |
| // 0.0 - v into -v means the sign of the result is the opposite of the input |
| // rather than always being positive. |
| optimize("float", "return 0.0F - f;").intoString("return 0.0 - EntryPoint.f;"); |
| optimize("double", "return 0.0 - d;").intoString("return 0.0 - EntryPoint.d;"); |
| } |
| |
| public void testFloatingPoint() throws Exception { |
| // Internally we represent float literals as double, so here we make sure that 1.1f is |
| // is printed as a double with the right precision. |
| optimize("float", "return 1.1f;").intoString("return " + String.format("%.16g", (double) 1.1f) + |
| ";"); |
| } |
| |
| public void testMultiExpression_RedundantClinitRemoval() throws Exception { |
| addSnippetClassDecl( |
| "static class A { " |
| + "static int f1;" |
| + "static int f2;" |
| + "static { f1 = 1; }" |
| + "static void m1() { } " |
| + "}" + |
| "static class B extends A { " |
| + "static int f3;" |
| + "static int f4;" |
| + "static { f3 = 1; }" |
| + "static void m2() { } " |
| + "}"); |
| |
| optimizeExpressions(true, "void", "A.m1()", "A.m1()").intoString("EntryPoint$A.$clinit();\n" |
| + "EntryPoint$A.m1();\n" |
| + "EntryPoint$A.m1();"); |
| optimizeExpressions(true, "void", "B.m2()", "A.m1()").intoString("EntryPoint$B.$clinit();\n" |
| + "EntryPoint$B.m2();\n" |
| + "EntryPoint$A.m1();"); |
| optimizeExpressions(true, "void", "A.m1()", "B.m2()").intoString("EntryPoint$A.$clinit();\n" |
| + "EntryPoint$A.m1();\n" |
| + "EntryPoint$B.$clinit();\n" |
| + "EntryPoint$B.m2();"); |
| } |
| |
| private boolean runMethodInliner; |
| private boolean runSpecializer; |
| |
| @Override |
| protected boolean optimizeMethod(JProgram program, JMethod method) { |
| |
| if (runMethodInliner) { |
| MethodInliner.exec(program); |
| } |
| if (runSpecializer) { |
| Finalizer.exec(program); // required so that method is marked final |
| MakeCallsStatic.exec(program, false); // required so that method is static |
| TypeTightener.exec(program); // required so that the parameter types are tightened |
| MethodCallSpecializer.exec(program); |
| } |
| |
| OptimizerStats result = DeadCodeElimination.exec(program, method); |
| if (result.didChange()) { |
| // Make sure we converge in one pass. |
| // |
| // TODO(rluble): It does not appear to be true in general unless we iterate until a |
| // fixpoint in exec(). |
| // |
| // Example: |
| // |
| // Constructor( ) { deadcode } |
| // m( new Constructor(); } |
| // |
| // If m is processed first, it will see the constructor as having side effects. |
| // Then the constructor will become empty enabling m() become empty in the next round. |
| // |
| assertFalse(DeadCodeElimination.exec(program, method).didChange()); |
| } |
| return result.didChange(); |
| } |
| } |