| /* |
| * Copyright 2008 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.InternalCompilerException; |
| import com.google.gwt.dev.jjs.SourceInfo; |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.JAbstractMethodBody; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JConstructor; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodBody; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JModVisitor; |
| import com.google.gwt.dev.jjs.ast.JParameter; |
| import com.google.gwt.dev.jjs.ast.JParameterRef; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReturnStatement; |
| import com.google.gwt.dev.jjs.ast.JStatement; |
| import com.google.gwt.dev.jjs.ast.JThisRef; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JVisitor; |
| import com.google.gwt.dev.jjs.ast.js.JMultiExpression; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsFunction; |
| import com.google.gwt.dev.js.ast.JsModVisitor; |
| import com.google.gwt.dev.js.ast.JsName; |
| import com.google.gwt.dev.js.ast.JsParameter; |
| import com.google.gwt.dev.js.ast.JsThisRef; |
| 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.ArrayList; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * This is an interesting "optimization". It's not really an optimization in and |
| * of itself, but it opens the door to other optimizations. The basic idea is |
| * that you look for calls to instance methods that are not actually |
| * polymorphic. In other words, the target method is (effectively) final, not |
| * overridden anywhere in the compilation. We rewrite the single instance method |
| * as a static method that contains the implementation plus an instance method |
| * that delegates to the static method. Then we update any call sites to call |
| * the static method instead. This opens the door to further optimizations, |
| * reduces use of the long "this" keyword in the resulting JavaScript, and in |
| * most cases the polymorphic version can be pruned later. |
| */ |
| public class MakeCallsStatic { |
| /** |
| * For all methods that should be made static, move the contents of the method |
| * to a new static method, and have the original (instance) method delegate to |
| * it. Sometimes the instance method can be pruned later since we update all |
| * non-polymorphic call sites. |
| */ |
| static class CreateStaticImplsVisitor extends JVisitor { |
| /** |
| * When code is moved from an instance method to a static method, all |
| * thisRefs must be replaced with paramRefs to the synthetic this param. |
| */ |
| private class RewriteJsniMethodBody extends JsModVisitor { |
| |
| private final JsName thisParam; |
| |
| public RewriteJsniMethodBody(JsName thisParam) { |
| this.thisParam = thisParam; |
| } |
| |
| @Override |
| public void endVisit(JsThisRef x, JsContext ctx) { |
| ctx.replaceMe(thisParam.makeRef(x.getSourceInfo())); |
| } |
| |
| @Override |
| public boolean visit(JsFunction x, JsContext ctx) { |
| // Don't recurse into nested functions! |
| return false; |
| } |
| } |
| |
| /** |
| * When code is moved from an instance method to a static method, all |
| * thisRefs must be replaced with paramRefs to the synthetic this param. |
| * ParameterRefs also need to be targeted to the params in the new method. |
| */ |
| private class RewriteMethodBody extends JModVisitor { |
| |
| private final JParameter thisParam; |
| private final Map<JParameter, JParameter> varMap; |
| |
| public RewriteMethodBody(JParameter thisParam, Map<JParameter, JParameter> varMap) { |
| this.thisParam = thisParam; |
| this.varMap = varMap; |
| } |
| |
| @Override |
| public void endVisit(JParameterRef x, Context ctx) { |
| JParameter param = varMap.get(x.getTarget()); |
| JParameterRef paramRef = new JParameterRef(x.getSourceInfo(), param); |
| ctx.replaceMe(paramRef); |
| } |
| |
| @Override |
| public void endVisit(JThisRef x, Context ctx) { |
| JParameterRef paramRef = new JParameterRef(x.getSourceInfo(), thisParam); |
| ctx.replaceMe(paramRef); |
| } |
| } |
| |
| private final JProgram program; |
| |
| CreateStaticImplsVisitor(JProgram program) { |
| this.program = program; |
| } |
| |
| @Override |
| public boolean visit(JConstructor x, Context ctx) { |
| throw new InternalCompilerException("Should not try to staticify constructors"); |
| } |
| |
| @Override |
| public boolean visit(JMethod x, Context ctx) { |
| // Let's do it! |
| JClassType enclosingType = (JClassType) x.getEnclosingType(); |
| JType returnType = x.getType(); |
| SourceInfo sourceInfo = x.getSourceInfo().makeChild(); |
| int myIndexInClass = enclosingType.getMethods().indexOf(x); |
| assert (myIndexInClass > 0); |
| |
| // Create the new static method |
| String newName = "$" + x.getName(); |
| |
| /* |
| * Don't use the JProgram helper because it auto-adds the new method to |
| * its enclosing class. |
| */ |
| JMethod newMethod = |
| new JMethod(sourceInfo, newName, enclosingType, returnType, false, true, true, x |
| .isPrivate()); |
| newMethod.setSynthetic(); |
| newMethod.addThrownExceptions(x.getThrownExceptions()); |
| |
| // Setup parameters; map from the old params to the new params |
| JParameter thisParam = |
| JParameter.create(sourceInfo, "this$static", enclosingType.getNonNull(), true, true, |
| newMethod); |
| Map<JParameter, JParameter> varMap = new IdentityHashMap<JParameter, JParameter>(); |
| for (int i = 0; i < x.getParams().size(); ++i) { |
| JParameter oldVar = x.getParams().get(i); |
| JParameter newVar = |
| JParameter.create(oldVar.getSourceInfo(), oldVar.getName(), oldVar.getType(), oldVar |
| .isFinal(), false, newMethod); |
| varMap.put(oldVar, newVar); |
| } |
| |
| // Set the new original param types based on the old original param types |
| List<JType> originalParamTypes = new ArrayList<JType>(); |
| originalParamTypes.add(enclosingType.getNonNull()); |
| originalParamTypes.addAll(x.getOriginalParamTypes()); |
| newMethod.setOriginalTypes(x.getOriginalReturnType(), originalParamTypes); |
| |
| // Move the body of the instance method to the static method |
| JAbstractMethodBody movedBody = x.getBody(); |
| newMethod.setBody(movedBody); |
| |
| JMethodBody newBody = new JMethodBody(sourceInfo); |
| x.setBody(newBody); |
| JMethodCall newCall = new JMethodCall(sourceInfo, null, newMethod); |
| newCall.addArg(new JThisRef(sourceInfo, enclosingType)); |
| for (int i = 0; i < x.getParams().size(); ++i) { |
| JParameter param = x.getParams().get(i); |
| newCall.addArg(new JParameterRef(sourceInfo, param)); |
| } |
| JStatement statement; |
| if (returnType == program.getTypeVoid()) { |
| statement = newCall.makeStatement(); |
| } else { |
| statement = new JReturnStatement(sourceInfo, newCall); |
| } |
| newBody.getBlock().addStmt(statement); |
| |
| /* |
| * Rewrite the method body. Update all thisRefs to paramRefs. Update |
| * paramRefs and localRefs to target the params/locals in the new method. |
| */ |
| if (newMethod.isNative()) { |
| // For natives, we also need to create the JsParameter for this$static, |
| // because the jsFunc already has parameters. |
| // TODO: Do we really need to do that in BuildTypeMap? |
| JsFunction jsFunc = ((JsniMethodBody) movedBody).getFunc(); |
| JsName paramName = jsFunc.getScope().declareName("this$static"); |
| jsFunc.getParameters().add(0, new JsParameter(sourceInfo, paramName)); |
| RewriteJsniMethodBody rewriter = new RewriteJsniMethodBody(paramName); |
| // Accept the body to avoid the recursion blocker. |
| rewriter.accept(jsFunc.getBody()); |
| } else { |
| RewriteMethodBody rewriter = new RewriteMethodBody(thisParam, varMap); |
| rewriter.accept(movedBody); |
| } |
| |
| // Add the new method as a static impl of the old method |
| program.putStaticImpl(x, newMethod); |
| enclosingType.getMethods().add(myIndexInClass + 1, newMethod); |
| return false; |
| } |
| } |
| |
| /** |
| * Look for any places where instance methods are called in a static manner. |
| * Record this fact so we can create static dispatch implementations. |
| */ |
| private class FindStaticDispatchSitesVisitor extends JVisitor { |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| JMethod method = x.getTarget(); |
| |
| if (method.isExternal()) { |
| // Staticifying a method requires modifying the type, which we can't |
| // do for external types. Theoretically we could put the static method |
| // in some generated code, but what does that really buy us? |
| return; |
| } |
| |
| // Did we already do this one? |
| if (program.getStaticImpl(method) != null || toBeMadeStatic.contains(method)) { |
| return; |
| } |
| |
| // Must be instance and final |
| if (x.canBePolymorphic()) { |
| return; |
| } |
| if (!method.needsVtable()) { |
| return; |
| } |
| if (method.isAbstract()) { |
| return; |
| } |
| if (method == program.getNullMethod()) { |
| // Special case: we don't make calls to this method static. |
| return; |
| } |
| |
| if (!method.getEnclosingType().getMethods().contains(method)) { |
| // The target method was already pruned (TypeTightener will fix this). |
| return; |
| } |
| |
| // Let's do it! |
| toBeMadeStatic.add(method); |
| } |
| } |
| |
| /** |
| * For any method calls to methods we updated during |
| * CreateStaticMethodVisitor, go and rewrite the call sites to call the static |
| * method instead. |
| */ |
| private class RewriteCallSites extends JModVisitor { |
| private boolean currentMethodIsInitiallyLive; |
| private ControlFlowAnalyzer initiallyLive; |
| |
| /** |
| * In cases where callers are directly referencing (effectively) final |
| * instance methods, rewrite the call site to reference the newly-generated |
| * static method instead. |
| */ |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| JMethod oldMethod = x.getTarget(); |
| JMethod newMethod = program.getStaticImpl(oldMethod); |
| if (newMethod == null || x.canBePolymorphic()) { |
| return; |
| } |
| |
| if (currentMethodIsInitiallyLive |
| && !initiallyLive.getLiveFieldsAndMethods().contains(x.getTarget())) { |
| /* |
| * Don't devirtualize calls from initial code to non-initial code. |
| * |
| * TODO(spoon): similar prevention when the callee is exclusive to some |
| * split point and the caller is not. |
| */ |
| return; |
| } |
| ctx.replaceMe(makeStaticCall(x, newMethod)); |
| } |
| |
| @Override |
| public boolean visit(JMethod x, Context ctx) { |
| currentMethodIsInitiallyLive = initiallyLive.getLiveFieldsAndMethods().contains(x); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JProgram x, Context ctx) { |
| initiallyLive = CodeSplitter.computeInitiallyLive(x); |
| return true; |
| } |
| } |
| |
| private static final String NAME = MakeCallsStatic.class.getSimpleName(); |
| |
| public static OptimizerStats exec(JProgram program) { |
| Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME); |
| OptimizerStats stats = new MakeCallsStatic(program).execImpl(); |
| optimizeEvent.end("didChange", "" + stats.didChange()); |
| return stats; |
| } |
| |
| static JExpression makeStaticCall(JMethodCall x, JMethod newMethod) { |
| // Update the call site |
| JMethodCall newCall = new JMethodCall(x.getSourceInfo(), null, newMethod); |
| |
| /* |
| * If the qualifier is a JMultiExpression, invoke on the last value. This |
| * ensures that clinits maintain the same execution order relative to |
| * parameters in deeply-inlined scenarios. |
| */ |
| // (a, b).foo() --> (a, foo(b)) |
| if (x.getInstance() instanceof JMultiExpression) { |
| JMultiExpression multi = (JMultiExpression) x.getInstance(); |
| int lastIndex = multi.exprs.size() - 1; |
| newCall.addArg(multi.exprs.get(lastIndex)); |
| newCall.addArgs(x.getArgs()); |
| multi.exprs.set(lastIndex, newCall); |
| return multi; |
| } else { |
| // The qualifier becomes the first arg |
| // a.foo(b) --> foo(a,b) |
| newCall.addArg(x.getInstance()); |
| newCall.addArgs(x.getArgs()); |
| return newCall; |
| } |
| } |
| |
| protected Set<JMethod> toBeMadeStatic = new HashSet<JMethod>(); |
| |
| private final JProgram program; |
| |
| private MakeCallsStatic(JProgram program) { |
| this.program = program; |
| } |
| |
| private OptimizerStats execImpl() { |
| OptimizerStats stats = new OptimizerStats(NAME); |
| FindStaticDispatchSitesVisitor finder = new FindStaticDispatchSitesVisitor(); |
| finder.accept(program); |
| |
| CreateStaticImplsVisitor creator = new CreateStaticImplsVisitor(program); |
| for (JMethod method : toBeMadeStatic) { |
| creator.accept(method); |
| } |
| |
| /* |
| * Run the rewriter even if we didn't make any new static methods; other |
| * optimizations can unlock devirtualizations even if no more static impls |
| * are created. |
| */ |
| RewriteCallSites rewriter = new RewriteCallSites(); |
| rewriter.accept(program); |
| stats.recordModified(rewriter.getNumMods()); |
| assert (rewriter.didChange() || toBeMadeStatic.isEmpty()); |
| return stats; |
| } |
| } |