/* * Copyright 2011 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.js; import com.google.gwt.dev.js.ast.JsBlock; import com.google.gwt.dev.js.ast.JsContext; import com.google.gwt.dev.js.ast.JsModVisitor; import com.google.gwt.dev.js.ast.JsProgram; import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.js.ast.JsSwitch; import com.google.gwt.dev.js.ast.JsSwitchMember; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Combine case labels with identical bodies. Case bodies that may fall through * to the following case label and case bodies following a possible fallthrough * are left undisturbed. * * For example, consider the following input: * * <pre> * switch (x) { * case 0: y = 17; break; * case 1: if (z == 0) { y = 18; break; } else { y = 19 } // fallthrough else * case 2: return 22; * case 3: if (z == 0) { y = 18; break; } else { y = 19 } // fallthrough else * case 4: y = 17; break; * case 5: y = 17; break; * case 6: return 22; * } * </pre> * * This will be transformed into: * * <pre> * switch (x) { * case 0: y = 17; break; * case 1: if (z == 0) { y = 18; break; } else { y = 19 } * case 6: case 2: return 22; * case 3: if (z == 0) { y = 18; break; } else { y = 19 } * case 5: case 4: y = 17; break; * } * * <pre> * * Cases (2, 6) and (4, 5) have been coalesced. Note that case 0 has not been * combined with cases 4 and 5 since case 4 cannot be moved due to the potential * fallthrough from case 3, and we currently only coalesce a given cases with a * preceding case and so cannot move case 0 downward. * * Although this pattern is unlikely to occur frequently in hand-written code, * it can account for a significant amount of space in generated code. */ public class JsDuplicateCaseFolder { private class DuplicateCaseFolder extends JsModVisitor { public DuplicateCaseFolder() { } @Override public boolean visit(JsSwitch x, JsContext ctx) { boolean modified = false; // A map from case body source code to the original case label // in which they appeared Map<String, JsSwitchMember> seen = new HashMap<String, JsSwitchMember>(); // Original list of members List<JsSwitchMember> cases = x.getCases(); // Coalesced list of members List<JsSwitchMember> newCases = new LinkedList<JsSwitchMember>(); // Keep track of whether the previous case can fall through // to the current case boolean hasPreviousFallthrough = false; // Iterate over members and locate ones with bodies identical to // previous members for (JsSwitchMember member : cases) { List<JsStatement> stmts = member.getStmts(); // Don't rewrite any cases that might fall through if (!unconditionalControlBreak(stmts)) { hasPreviousFallthrough = true; // copy the case into the output newCases.add(member); continue; } String body = toSource(stmts); JsSwitchMember previousCase = seen.get(body); if (previousCase == null || hasPreviousFallthrough) { // Don't coalesce a case that can be reached via fallthrough // from the previous case newCases.add(member); seen.put(body, member); } else { // Locate the position of the case that this case is to be // coalesced with. Note: linear search in output list int index = newCases.indexOf(previousCase); // Empty the case body and insert the case label into the output member.getStmts().clear(); newCases.add(index, member); modified = true; } hasPreviousFallthrough = false; } // Rewrite the AST if any cases have changed if (modified) { didChange = true; cases.clear(); cases.addAll(newCases); } return true; } private String toSource(List<JsStatement> stmts) { StringBuilder sb = new StringBuilder(); for (JsStatement stmt : stmts) { sb.append(stmt.toSource(true)); sb.append("\n"); // separate statements } return sb.toString(); } /** * See {@link JsStatement#unconditionalControlBreak()}. */ private boolean unconditionalControlBreak(List<JsStatement> stmts) { for (JsStatement stmt : stmts) { if (stmt.unconditionalControlBreak()) { return true; } } return false; } } // Needed for OptimizerTestBase public static boolean exec(JsProgram program) { return new JsDuplicateCaseFolder().execImpl(program.getFragmentBlock(0)); } public JsDuplicateCaseFolder() { } private boolean execImpl(JsBlock fragment) { DuplicateCaseFolder dcf = new DuplicateCaseFolder(); dcf.accept(fragment); return dcf.didChange(); } }