/* * 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: * * <pre> * List foo = new ArrayList<String>(); * foo.add("bar"); * </pre> * * 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()); } }