blob: bebe4ea4bec36e282f957d7bd26704f969ab3737 [file] [log] [blame]
/*
* Copyright 2007 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.CanBeSetFinal;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConstructor;
import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JFieldRef;
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JPostfixOperation;
import com.google.gwt.dev.jjs.ast.JPrefixOperation;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JVariable;
import com.google.gwt.dev.jjs.ast.JVariableRef;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
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 com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import java.util.Set;
/**
* Finds all items are effectively final. That is, methods that are never overridden and variables
* that are never reassigned.
* <p>
* NOTE: Classes should not be marked final, since the analysis for classes is done through
* {@link JAnalysisDecoratedType}.
*/
public class Finalizer {
/**
* Any items that weren't marked during MarkVisitor can be set final.
*
* Open question: What does it mean if an interface/abstract method becomes
* final? Is this possible after Pruning? I guess it means that someone tried
* to make a call to method that wasn't actually implemented anywhere in the
* program. But if it wasn't implemented, then the enclosing class should have
* come up as not instantiated and been culled. So I think it's not possible.
*/
private class FinalizeVisitor extends JChangeTrackingVisitor {
public FinalizeVisitor(OptimizerContext optimizerCtx) {
super(optimizerCtx);
}
@Override
public void exit(JConstructor x, Context ctx) {
// Not applicable.
}
@Override
public void exit(JField x, Context ctx) {
if (x.isVolatile() || x.canBeImplementedExternally() || x.canBeReferencedExternally()) {
return;
}
maybeFinalize(x);
}
@Override
public void endVisit(JLocal x, Context ctx) {
maybeFinalize(x);
}
@Override
public void exit(JMethod x, Context ctx) {
if (!x.isFinal() && x.getOverridingMethods().isEmpty()) {
setFinal(x);
}
}
@Override
public void endVisit(JParameter x, Context ctx) {
maybeFinalize(x);
}
@Override
public boolean visit(JClassType x, Context ctx) {
// Don't visit external types, because we can't change their final
// specifiers.
return !x.isExternal();
}
@Override
public boolean visit(JMethodBody x, Context ctx) {
for (JLocal local : x.getLocals()) {
maybeFinalize(local);
}
return false;
}
private void maybeFinalize(JVariable x) {
if (!x.isFinal() && !isReassigned.contains(x)) {
setFinal(x);
}
}
private void setFinal(CanBeSetFinal x) {
x.setFinal();
assert x.isFinal();
madeChanges();
}
}
/**
* Find all items that ARE overridden/subclassed/reassigned.
*/
private class MarkVisitor extends JVisitor {
@Override
public void endVisit(JBinaryOperation x, Context ctx) {
if (x.getOp().isAssignment()) {
recordAssignment(x.getLhs());
}
}
@Override
public void endVisit(JConstructor x, Context ctx) {
// Never overridden.
}
@Override
public void endVisit(JDeclarationStatement x, Context ctx) {
// This is should only be considered a reassignment if the uninitialized value for this field
// is observable.
// TODO(rluble): do static analysis to improve this pass.
if (x.getVariableRef() instanceof JFieldRef) {
JField field = ((JFieldRef) x.getVariableRef()).getField();
if (field.getLiteralInitializer() != null &&
!field.getLiteralInitializer().equals(field.getType().getDefaultValue())) {
recordAssignment(x.getVariableRef());
}
}
}
@Override
public void endVisit(JPostfixOperation x, Context ctx) {
if (x.getOp().isModifying()) {
recordAssignment(x.getArg());
}
}
@Override
public void endVisit(JPrefixOperation x, Context ctx) {
if (x.getOp().isModifying()) {
recordAssignment(x.getArg());
}
}
@Override
public void endVisit(JsniFieldRef x, Context ctx) {
if (x.isLvalue()) {
recordAssignment(x);
}
}
@Override
public boolean visit(JClassType x, Context ctx) {
// Don't visit external types, because we can't change their final
// specifiers.
return !x.isExternal();
}
private void recordAssignment(JExpression lhs) {
if (lhs instanceof JVariableRef) {
JVariableRef variableRef = (JVariableRef) lhs;
isReassigned.add(variableRef.getTarget());
}
}
}
private static final String NAME = Finalizer.class.getSimpleName();
private Finalizer(JProgram program) {
this.program = program;
}
@VisibleForTesting
static OptimizerStats exec(JProgram program) {
return exec(program, OptimizerContext.NULL_OPTIMIZATION_CONTEXT);
}
public static OptimizerStats exec(JProgram program, OptimizerContext optimizerCtx) {
Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME);
OptimizerStats stats = new Finalizer(program).execImpl(optimizerCtx);
optimizerCtx.incOptimizationStep();
optimizeEvent.end("didChange", "" + stats.didChange());
return stats;
}
private final Set<JVariable> isReassigned = Sets.newHashSet();
private final JProgram program;
private OptimizerStats execImpl(OptimizerContext optimizerCtx) {
MarkVisitor marker = new MarkVisitor();
marker.accept(program);
FinalizeVisitor finalizer = new FinalizeVisitor(optimizerCtx);
finalizer.accept(program);
JavaAstVerifier.assertProgramIsConsistent(program);
return new OptimizerStats(NAME).recordModified(finalizer.getNumMods());
}
}