| /* |
| * 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.JArrayType; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JInterfaceType; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JModVisitor; |
| import com.google.gwt.dev.jjs.ast.JNewInstance; |
| import com.google.gwt.dev.jjs.ast.JNode; |
| import com.google.gwt.dev.jjs.ast.JNullType; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReferenceType; |
| 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; |
| |
| /** |
| * 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}. |
| */ |
| public class MethodCallTightener { |
| public static final String NAME = MethodCallTightener.class.getSimpleName(); |
| |
| /** |
| * Updates polymorphic method calls to tighter bindings based on the type of |
| * the qualifier. |
| */ |
| public class MethodCallTighteningVisitor extends JModVisitor { |
| |
| @Override |
| public void endVisit(JNewInstance x, Context ctx) { |
| // Do not tighten new operations. |
| } |
| |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| JMethod method = x.getTarget(); |
| JExpression instance = x.getInstance(); |
| |
| // The method call is already known statically |
| if (!x.canBePolymorphic()) { |
| return; |
| } |
| |
| JReferenceType instanceType = ((JReferenceType) instance.getType()).getUnderlyingType(); |
| JReferenceType enclosingType = method.getEnclosingType(); |
| |
| if (instanceType == enclosingType |
| || instanceType instanceof JInterfaceType) { |
| // This method call is as tight as it can be for the type of the |
| // qualifier |
| return; |
| } |
| |
| if (instanceType instanceof JArrayType) { |
| // shouldn't get here; arrays don't have extra methods |
| return; |
| } |
| |
| if (instanceType instanceof JNullType) { |
| // TypeTightener will handle this case |
| return; |
| } |
| |
| assert (instanceType instanceof JClassType); |
| |
| /* |
| * Search myself and all my super types to find a tighter implementation |
| * of the called method, if possible. |
| */ |
| JMethod foundMethod = null; |
| JClassType type; |
| outer : for (type = (JClassType) instanceType; type != null |
| && type != enclosingType; type = type.getSuperClass()) { |
| for (JMethod methodIt : type.getMethods()) { |
| if (methodOverrides(methodIt, method)) { |
| foundMethod = methodIt; |
| break outer; |
| } |
| } |
| } |
| |
| if (foundMethod == null) { |
| return; |
| } |
| |
| /* |
| * Replace the call to the original method with a call to the same method |
| * on the tighter type. |
| */ |
| JMethodCall call = new JMethodCall(x.getSourceInfo(), x.getInstance(), |
| foundMethod); |
| call.addArgs(x.getArgs()); |
| ctx.replaceMe(call); |
| } |
| |
| /** |
| * Check whether <code>subMethod</code> overrides <code>supMethod</code>. |
| * For the purposes of this method, indirect overrides are considered |
| * overrides. For example, if method A.m overrides B.m, and B.m overrides |
| * C.m, then A.m is considered to override C.m. Additionally, implementing |
| * an interface is considered |
| * <q>overriding</q> |
| * for the purposes of this method. |
| * |
| */ |
| private boolean methodOverrides(JMethod subMethod, JMethod supMethod) { |
| if (subMethod.getParams().size() != supMethod.getParams().size()) { |
| // short cut: check the number of parameters |
| return false; |
| } |
| |
| if (!subMethod.getName().equals(supMethod.getName())) { |
| // short cut: check the method names |
| return false; |
| } |
| |
| // long way: get all overrides and see if supMethod is included |
| return program.typeOracle.getAllOverrides(subMethod).contains(supMethod); |
| } |
| } |
| |
| public static OptimizerStats exec(JProgram program) { |
| Event optimizeEvent = SpeedTracerLogger.start( |
| CompilerEventType.OPTIMIZE, "optimizer", NAME); |
| OptimizerStats stats = new MethodCallTightener(program).execImpl(); |
| optimizeEvent.end("didChange", "" + stats.didChange()); |
| return stats; |
| } |
| |
| /** |
| * Tighten method calls that occur within <code>node</code> and its children. |
| */ |
| public static void exec(JProgram program, JNode node) { |
| new MethodCallTightener(program).execImpl(node); |
| } |
| |
| private final JProgram program; |
| |
| private MethodCallTightener(JProgram program) { |
| this.program = program; |
| } |
| |
| private OptimizerStats execImpl() { |
| MethodCallTighteningVisitor tightener = new MethodCallTighteningVisitor(); |
| tightener.accept(program); |
| return new OptimizerStats(NAME).recordModified(tightener.getNumMods()); |
| } |
| |
| private void execImpl(JNode node) { |
| MethodCallTighteningVisitor tightener = new MethodCallTighteningVisitor(); |
| tightener.accept(node); |
| } |
| } |