blob: 58b2934ae86143ad437eb81f649bd58fc02e026c [file] [log] [blame]
/*
* Copyright 2014 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.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.impl.OptimizerStats;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsBinaryOperator;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsCase;
import com.google.gwt.dev.js.ast.JsConditional;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsDefault;
import com.google.gwt.dev.js.ast.JsEmpty;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.dev.js.ast.JsFor;
import com.google.gwt.dev.js.ast.JsForIn;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsIf;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsNode;
import com.google.gwt.dev.js.ast.JsNullLiteral;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsWhile;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This is used to clean up duplication invocations of clinit function. Whenever there is a
* possible branch in program flow, the remover will create a new instance of
* itself to handle the possible branches.
*
* We don't look at combining branch choices. This will not produce the most
* efficient elimination of duplicated calls, but it handles the general case
* and is simple to verify.
*/
public class DuplicateClinitRemover extends JsModVisitor {
private static final String NAME = JsInliner.class.getSimpleName();
/*
* TODO: Most of the special casing below can be removed if complex
* statements always use blocks, rather than plain statements.
*/
/**
* Retains the functions that we know have been called.
*/
private final Set<JsFunction> called;
private final JsProgram program;
public DuplicateClinitRemover(JsProgram program) {
this.program = program;
called = new HashSet<JsFunction>();
}
public DuplicateClinitRemover(JsProgram program, Set<JsFunction> alreadyCalled) {
this.program = program;
called = new HashSet<JsFunction>(alreadyCalled);
}
/**
* Look for comma expressions that contain duplicate calls and handle the
* conditional-evaluation case of logical and/or operations.
*/
@Override
public boolean visit(JsBinaryOperation x, JsContext ctx) {
if (x.getOperator() == JsBinaryOperator.COMMA) {
boolean left = isDuplicateCall(x.getArg1());
boolean right = isDuplicateCall(x.getArg2());
if (left && right) {
/*
* (clinit(), clinit()) --> delete or null.
*
* This construct is very unlikely since the InliningVisitor builds
* the comma expressions in a right-nested manner.
*/
if (ctx.canRemove()) {
ctx.removeMe();
return false;
} else {
// The return value from an XO function is never used
ctx.replaceMe(JsNullLiteral.INSTANCE);
return false;
}
} else if (left) {
// (clinit(), xyz) --> xyz
// This is the common case
ctx.replaceMe(accept(x.getArg2()));
return false;
} else if (right) {
// (xyz, clinit()) --> xyz
// Possible if a clinit() were the last element
ctx.replaceMe(accept(x.getArg1()));
return false;
}
} else if (x.getOperator().equals(JsBinaryOperator.AND)
|| x.getOperator().equals(JsBinaryOperator.OR)) {
x.setArg1(accept(x.getArg1()));
// Possibility of conditional evaluation of second parameter
x.setArg2(branch(x.getArg2()));
return false;
}
return true;
}
/**
* Most of the branching statements (as well as JsFunctions) will visit with
* a JsBlock, so we don't need to explicitly enumerate all JsStatement
* subtypes.
*/
@Override
public boolean visit(JsBlock x, JsContext ctx) {
branch(x.getStatements());
return false;
}
@Override
public boolean visit(JsCase x, JsContext ctx) {
x.setCaseExpr(accept(x.getCaseExpr()));
branch(x.getStmts());
return false;
}
@Override
public boolean visit(JsConditional x, JsContext ctx) {
x.setTestExpression(accept(x.getTestExpression()));
x.setThenExpression(branch(x.getThenExpression()));
x.setElseExpression(branch(x.getElseExpression()));
return false;
}
@Override
public boolean visit(JsDefault x, JsContext ctx) {
branch(x.getStmts());
return false;
}
@Override
public boolean visit(JsExprStmt x, JsContext ctx) {
if (isDuplicateCall(x.getExpression())) {
if (ctx.canRemove()) {
ctx.removeMe();
} else {
ctx.replaceMe(new JsEmpty(x.getSourceInfo()));
}
return false;
} else {
return true;
}
}
@Override
public boolean visit(JsFor x, JsContext ctx) {
// The JsFor may have an expression xor a variable declaration.
if (x.getInitExpr() != null) {
x.setInitExpr(accept(x.getInitExpr()));
} else if (x.getInitVars() != null) {
x.setInitVars(accept(x.getInitVars()));
}
// The condition is optional
if (x.getCondition() != null) {
x.setCondition(accept(x.getCondition()));
}
// The increment expression is optional
if (x.getIncrExpr() != null) {
x.setIncrExpr(branch(x.getIncrExpr()));
}
// The body is not guaranteed to be a JsBlock
x.setBody(branch(x.getBody()));
return false;
}
@Override
public boolean visit(JsForIn x, JsContext ctx) {
if (x.getIterExpr() != null) {
x.setIterExpr(accept(x.getIterExpr()));
}
x.setObjExpr(accept(x.getObjExpr()));
// The body is not guaranteed to be a JsBlock
x.setBody(branch(x.getBody()));
return false;
}
@Override
public boolean visit(JsIf x, JsContext ctx) {
x.setIfExpr(accept(x.getIfExpr()));
x.setThenStmt(branch(x.getThenStmt()));
if (x.getElseStmt() != null) {
x.setElseStmt(branch(x.getElseStmt()));
}
return false;
}
/**
* Possibly record that we've seen a call in the current context.
*/
@Override
public boolean visit(JsInvocation x, JsContext ctx) {
JsFunction func = JsUtils.isExecuteOnce(x);
while (func != null) {
called.add(func);
func = func.getSuperClinit();
}
return true;
}
@Override
public boolean visit(JsWhile x, JsContext ctx) {
x.setCondition(accept(x.getCondition()));
// The body is not guaranteed to be a JsBlock
x.setBody(branch(x.getBody()));
return false;
}
/**
* Static entry point used by JavaToJavaScriptCompiler.
*/
public static OptimizerStats exec(JsProgram program) {
Event optimizeJsEvent = SpeedTracerLogger.start(
CompilerEventType.OPTIMIZE_JS, "duplicateXOremover", NAME);
OptimizerStats stats = execImpl(program);
optimizeJsEvent.end("didChange", "" + stats.didChange());
return stats;
}
private static OptimizerStats execImpl(JsProgram program) {
OptimizerStats stats = new OptimizerStats(NAME);
DuplicateClinitRemover r = new DuplicateClinitRemover(program);
r.accept(program);
if (r.didChange()) {
stats.recordModified();
}
return stats;
}
private <T extends JsNode> void branch(List<T> x) {
DuplicateClinitRemover dup = new DuplicateClinitRemover(program, called);
dup.acceptWithInsertRemove(x);
didChange |= dup.didChange();
}
private <T extends JsNode> T branch(T x) {
DuplicateClinitRemover dup = new DuplicateClinitRemover(program, called);
T toReturn = dup.accept(x);
if ((toReturn != x) && !dup.didChange()) {
throw new InternalCompilerException(
"node replacement should imply didChange()");
}
didChange |= dup.didChange();
return toReturn;
}
private boolean isDuplicateCall(JsExpression x) {
if (!(x instanceof JsInvocation)) {
return false;
}
JsFunction func = JsUtils.isExecuteOnce((JsInvocation) x);
return (func != null && called.contains(func));
}
}