blob: ed0b941823961ba59c6ca8cfd899c0979998c32c [file] [log] [blame]
/*
* 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();
}
}