com.google.gwt.dev.jjs.impl.MethodCallTightener Maven / Gradle / Ivy
/*
* 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.Context;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JNewInstance;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JReferenceType;
import com.google.gwt.dev.jjs.ast.JRunAsync;
import com.google.gwt.dev.jjs.ast.JType;
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;
/**
* Update polymorphic method calls to tighter bindings based on the type of the
* qualifier. For a given polymorphic method call to a non-final target, see if
* the static type of the qualifer would let us target an override instead.
*
* This is possible because the qualifier might have been tightened by
* {@link com.google.gwt.dev.jjs.impl.TypeTightener}.
*
* For example, given the code:
*
*
* List foo = new ArrayList();
* foo.add("bar");
*
*
* The type of foo is tightened by TypeTightener from type List to be of type
* ArrayList. This means that MethodCallTightener can analyze the polymorphic
* call List.add() on foo and tighten it to the more specific ArrayList.add().
*/
public class MethodCallTightener {
/**
* Updates polymorphic method calls to tighter bindings based on the type of
* the qualifier.
*/
public class MethodCallTighteningVisitor extends JChangeTrackingVisitor {
public MethodCallTighteningVisitor(OptimizerContext optimizerCtx) {
super(optimizerCtx);
}
@Override
public void endVisit(JMethodCall x, Context ctx) {
// The method call is already known statically
if (x.isVolatile() || !x.canBePolymorphic()) {
return;
}
JType instanceType = x.getInstance().getType().getUnderlyingType();
if (!(instanceType instanceof JClassType)) {
// Cannot tighten.
return;
}
JMethod mostSpecificTarget = getMostSpecificOverride(x);
if (mostSpecificTarget.getEnclosingType().isJsNative()) {
// Never tighten to instance methods in native types. This done because java.lang.Object
// methods are implicitly implemented by all objects but may or may not be present in the
// native type implementation. The dispatch for these is eventually done through a
// trampoline {@see Devirtualizer} that makes the proper checks and invokes the native
// implementation if present.
assert x.getTarget().getEnclosingType().isJavaLangObject()
|| x.getTarget().getEnclosingType().isJsNative();
return;
}
// Tighten the method call if a more specific override is available.
JMethodCall newCall = maybeReplaceTargetMethod(x, mostSpecificTarget);
maybeUpgradeToNonPolymorphicCall(newCall);
if (newCall != x) {
ctx.replaceMe(newCall);
}
}
private JMethod getMostSpecificOverride(final JMethodCall methodCall) {
JMethod original = methodCall.getTarget();
JClassType underlyingType =
(JClassType) methodCall.getInstance().getType().getUnderlyingType();
return program.typeOracle.findMostSpecificOverride(underlyingType, original);
}
private JMethodCall maybeReplaceTargetMethod(JMethodCall methodCall, JMethod newTargetMethod) {
if (methodCall.getTarget() == newTargetMethod) {
return methodCall;
}
return new JMethodCall(
methodCall.getSourceInfo(),
methodCall.getInstance(),
newTargetMethod,
methodCall.getArgs());
}
private void maybeUpgradeToNonPolymorphicCall(JMethodCall x) {
JReferenceType instanceType = (JReferenceType) x.getInstance().getType();
if (!instanceType.canBeSubclass() || !hasPotentialOverride(instanceType, x.getTarget())) {
assert getMostSpecificOverride(x) == x.getTarget();
// Mark a call as non-polymorphic if the targeted type is guaranteed to be not a subclass
// or there are no overriding implementations.
x.setCannotBePolymorphic();
madeChanges();
}
}
private boolean hasPotentialOverride(JReferenceType instanceType, JMethod target) {
if (target.isAbstract()) {
return true;
}
for (JMethod override : target.getOverridingMethods()) {
JReferenceType overrideType = override.getEnclosingType();
if (!program.typeOracle.castFailsTrivially(instanceType, overrideType)) {
// This call is truly polymorphic.
return true;
}
}
return false;
}
@Override
public void endVisit(JNewInstance x, Context ctx) {
// Do not tighten new operations.
}
@Override
public boolean visit(JRunAsync x, Context ctx) {
x.traverseOnSuccess(this);
return super.visit(x, ctx);
}
}
public static final String NAME = MethodCallTightener.class.getSimpleName();
@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 MethodCallTightener(program).execImpl(optimizerCtx);
optimizerCtx.incOptimizationStep();
optimizeEvent.end("didChange", "" + stats.didChange());
JavaAstVerifier.assertProgramIsConsistent(program);
return stats;
}
private final JProgram program;
private MethodCallTightener(JProgram program) {
this.program = program;
}
private OptimizerStats execImpl(OptimizerContext optimizerCtx) {
MethodCallTighteningVisitor tightener = new MethodCallTighteningVisitor(optimizerCtx);
tightener.accept(program);
return new OptimizerStats(NAME).recordModified(tightener.getNumMods());
}
}