/* * Copyright 2009 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.gflow; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.impl.DeadCodeElimination; import com.google.gwt.dev.jjs.impl.MethodInliner; import com.google.gwt.dev.jjs.impl.OptimizerTestBase; /** * Tests for {@link DataflowOptimizer} */ public class DataflowOptimizerTest extends OptimizerTestBase { @Override protected void setUp() throws Exception { super.setUp(); /* * TODO: Each of these snippets shouldn't be setup for every test, and thus should be moved * to the individual test cases they are needed for (or to shared methods if needed). */ addSnippetClassDecl("static void foo(int i) { }"); addSnippetClassDecl("static boolean bar() { return true; }"); addSnippetClassDecl("static void baz(boolean b) { }"); addSnippetClassDecl("static int genInt() { return 1; }"); addSnippetClassDecl("static int multiply(int i, int j) { return i * j; }"); addSnippetClassDecl("static class CheckedException extends Exception {}"); addSnippetClassDecl("static class UncheckedException1 extends RuntimeException {}"); addSnippetClassDecl("static class UncheckedException2 extends RuntimeException {}"); addSnippetClassDecl("static void throwUncheckedException1() " + "throws UncheckedException1 {}"); addSnippetClassDecl("static void throwCheckedException() " + "throws CheckedException {}"); addSnippetClassDecl("static void throwSeveralExceptions() " + "throws CheckedException, UncheckedException1 {}"); addSnippetClassDecl("static class Foo { int i; int j; int k; }"); addSnippetClassDecl("static Foo createFoo() {return null;}"); addSnippetClassDecl("static Foo staticFooInstance;"); runMethodInliner = false; runDCE = false; } public void testLinearStatements1() throws Exception { optimize("int", "int i = 1; int j = i; return i;").into( "int i; int j; return 1;"); } public void testLinearStatements2() throws Exception { optimize("int", "int i = 1; int j = i; return j;").into( "int i; int j; return 1;"); } public void testLinearStatements3() throws Exception { optimize("void", "int i = 1; int j = 1; foo(j);").into( "int i; int j; foo(1);"); } /** * Test case for issue 8115 (http://code.google.com/p/google-web-toolkit/issues/detail?id=8115) * @throws Exception */ public void testCatchThrowExceptionFinally() throws Exception { addSnippetClassDecl("static boolean b;"); addSnippetClassDecl("static int i;"); addSnippetClassDecl("static int j;"); optimize("void", "" + "Object o = null;" + "try {" + " if (b) throw new UncheckedException1();" + "} catch (UncheckedException1 e) {" + " o = e;" + " throw e;" + "} finally {" + " if (o == null)" + " j++;" + "}").into("" + "Object o = null;" + "try {" + " if (b) throw new UncheckedException1();" + "} catch (UncheckedException1 e) {" + " o = e;" + " throw e;" + "} finally {" + " if (o == null)" // the bug would cause this if to be optimized away as always true. + " j++;" + "}"); } public void testDeadIfBranch1() throws Exception { optimize("void", "int i = 1; if (i == 1) { int j = 2; } else { int j = 3; }").into( "int i; { int j; } "); } public void testDeadIfBranch2() throws Exception { optimize("void", "int i = 1; if (i != 1) { int j = 2; } else { int j = 3; }").into( "int i; { int j; } "); } public void testDeadIfBranch3() throws Exception { optimize("int", "int i = 1; int j = 0; if (i != 1) { j = 2; } else { j = 3; } return j;").into( "int i; int j; return 3; "); } public void testDeadIfBranch4() throws Exception { addSnippetClassDecl("static Object f = null;"); optimize("void", "Object e = null;" + "if (e == null && f == null) {" + " return;" + "}" + "boolean b = e == null;").into( "Object e; if (EntryPoint.f == null) { return; } boolean b;" ); } public void testDeadWhile() throws Exception { optimize("void", "int j = 0; while (j > 0) { }").into( "int j;"); } // Various complex stuff public void testComplexCode1() throws Exception { optimize("int", "int i = 1; int j = 0; while (j > 0) { if (i != 1) { i++; j++; } } return i;").into( "int i; int j; return 1;"); } public void testComplexCode2() throws Exception { optimize("void", "boolean b = bar(); if (b) { baz(b); return; }").into( "boolean b = bar(); if (b) { baz(true); return; }"); } public void testAssert() throws Exception { optimize("void", "boolean b = true;", "assert b;").into( "boolean b;"); } public void testDebuggerStatement() throws Exception { addSnippetImport("com.google.gwt.core.shared.GWT"); optimize("void", "GWT.debugger();").noChange(); } public void testDeadCodeRemovalNearDebuggerStatement() throws Exception { addSnippetImport("com.google.gwt.core.shared.GWT"); optimize("void", "int i = 1; if (i==1) { GWT.debugger(); } else { GWT.debugger(); }").into( "int i; GWT.debugger();" ); } public void testNoChange() throws Exception { optimize("void", "try {", " foo(1);", "} catch (RuntimeException e) { }").noChange(); } public void testAssignToField() throws Exception { optimize("void", "Foo foo = createFoo();", "foo.i = 1;" ).noChange(); } public void testSwapValues() throws Exception { optimize("int", "int i = genInt(); int j = genInt(); int t;", "if (i > j) { t = i; i = j; j = t; }", "return multiply(i, j);" ).into( "int i = genInt(); int j = genInt(); int t;", "if (i > j) { t = i; i = j; j = t; }", "return multiply(i, j);" ); } public void testSwapValuesMethodParameter() throws Exception { addSnippetClassDecl("int calculate(int i, int j) {" + "int t;" + "if (i > j) { t = i; i = j; j = t; }" + "return multiply(i, j);" + "}"); optimizeMethod("calculate", "int", "return 1;" ).intoString( "int t;", "if (i > j) {", " t = i;", " i = j;", " j = t;", "}", "return EntryPoint.multiply(i, j);" ); } public void testComplexCode3() throws Exception { addSnippetClassDecl("static final int SPLIT_LOOKING_FOR_COMMA = 0;"); addSnippetClassDecl("static final int SPLIT_IN_STRING = 1;"); addSnippetClassDecl("static final int SPLIT_IN_ESCAPE = 2;"); addSnippetClassDecl("static String getCsvString() { return null; }"); addSnippetClassDecl( "static class JsArrayString {" + " static JsArrayString createArray() { return null; }" + " JsArrayString cast() { return this; }" + " void push(String s) { }" + "}"); addSnippetClassDecl( "static class StringBuilder {" + " void append(char c) { }" + "}"); optimize("JsArrayString", "int state;", "String csvString = getCsvString();", "JsArrayString results = JsArrayString.createArray().cast();", "int index = 0;", "StringBuilder field = new StringBuilder();", "state = SPLIT_LOOKING_FOR_COMMA;", "while (index < csvString.length()) {", " char nextCharacter = csvString.charAt(index);", " switch (state) {", " case SPLIT_LOOKING_FOR_COMMA:", " switch (nextCharacter) {", " case ',':", " results.push(field.toString());", " field = new StringBuilder();", " break;", " case '\"':", " state = SPLIT_IN_STRING;", " break;", " default:", " field.append(nextCharacter);", " }", " break;", " case SPLIT_IN_STRING:", " switch (nextCharacter) {", " case '\"':", " state = SPLIT_LOOKING_FOR_COMMA;", " break;", " case '\\\\':", " state = SPLIT_IN_ESCAPE;", " field.append(nextCharacter);", " break;", " default:", " field.append(nextCharacter);", " }", " break;", " case SPLIT_IN_ESCAPE:", " state = SPLIT_IN_STRING;", " field.append(nextCharacter);", " break;", " default:", " field.append(nextCharacter);", " }", " index++;", "}", "results.push(field.toString());", "return results;" ).into( "int state;", "String csvString = getCsvString();", "JsArrayString results = JsArrayString.createArray().cast();", "int index = 0;", "StringBuilder field = new StringBuilder();", "state = 0;", "while (index < csvString.length()) {", " char nextCharacter = csvString.charAt(index);", " switch (state) {", " case 0:", " switch (nextCharacter) {", " case ',':", " results.push(field.toString());", " field = new StringBuilder();", " break;", " case '\"':", " state = 1;", " break;", " default:", " field.append(nextCharacter);", " }", " break;", " case 1:", " switch (nextCharacter) {", " case '\"':", " state = 0;", " break;", " case '\\\\':", " state = 2;", " field.append('\\\\');", " break;", " default:", " field.append(nextCharacter);", " }", " break;", " case 2:", " state = 1;", " field.append(nextCharacter);", " break;", " default:", " field.append(nextCharacter);", " }", " ++index;", "}", "results.push(field.toString());", "return results;" ); } public void testComplexCode4() throws Exception { addSnippetClassDecl("static boolean confirm() { return true; }"); optimize("int", "int n = 0;", "for (; ; ) {", " if (confirm()) {", " break;", " } else {", " for (int i = 0; i < 2; i++) {", " n = i;", " }", " }", "}", "return n;" ).into( "int n = 0;", "for (; ; ) {", " if (confirm()) {", " break;", " } else {", " for (int i = 0; i < 2; i++) {", " n = i;", " }", " }", "}", "return n;" ); } /** * Test fix for http://code.google.com/p/google-web-toolkit/issues/detail?id=5739 */ public void testExceptionInitializerFlow() throws Exception { addSnippetClassDecl("static int foo() { return 0; }"); optimize("boolean", " int size = -1;\n" + " try {\n" + " size = ((Object[]) (Object)\"aaa\").length;\n" + " } catch (final Exception ex) {\n" + " }\n" + " if (size < 0) {\n" + " try {\n" + " size = 3;\n" + " } catch (final Exception ex) {\n" + " }\n" + " }\n" + " return size > 0;").into("int size = -1;\n" + " try {\n" + " size = ((Object[]) (Object)\"aaa\").length;\n" + " } catch (final Exception ex) {\n" + " }\n" + " if (size < 0) {\n" + " try {\n" + " size = 3;\n" + " } catch (final Exception ex) {\n" + " }\n" + " } return size > 0;"); } public void testImplicitConversion() throws Exception { optimize("long", "int bar = 0x12345678;", "bar = bar * 1234;", "long lng = bar;", "long lng8 = lng << 8;", "return lng8;" ).into( " int bar;", " long lng = -1068970384;", " long lng8 = lng << 8;", " return lng8;"); } /* * This test is a regression for an issue where inlined multiexpressions were getting removed * by the ConstantsTransformationFunction, based on the constant value of the multi-expression, * despite there being side-effects of the multi-expression. So, we want to test that inlining * proceeds, but not further constant transformation. * * TODO(rluble): This test may need to evolve over time, as the specifics of the * optimizers change. */ public void testInlinedConstantExpressionWithSideEffects() throws Exception { runDCE = true; runMethodInliner = true; addSnippetClassDecl("static void fail() {" + " throw new RuntimeException();" + "}"); addSnippetClassDecl("static Integer x;"); addSnippetClassDecl("static boolean copy(Number n) {" + " x = (Integer) n;" + " return true;" + "}"); optimize("int", "Integer n = new Integer(1);", "if (!copy(n)) {", " fail();", "}", "return x;") .into("Integer n = new Integer(1);", "x = n;", "return x;"); } private boolean runDCE; private boolean runMethodInliner; @Override protected boolean doOptimizeMethod(TreeLogger logger, JProgram program, JMethod method) { boolean didChange = false; boolean optimizeChange; // Run optimizations in a loop to make the tests more robusts to unrelated // changes. do { optimizeChange = false; if (runDCE) { optimizeChange = DeadCodeElimination.exec(program).didChange() || optimizeChange; } if (runMethodInliner) { optimizeChange = MethodInliner.exec(program).didChange() || optimizeChange; } didChange = didChange || optimizeChange; } while (optimizeChange); didChange = DataflowOptimizer.exec(program, method).didChange() || didChange; return didChange; } }