001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.jjs.impl;
017:
018: import com.google.gwt.dev.jjs.SourceInfo;
019: import com.google.gwt.dev.jjs.ast.Context;
020: import com.google.gwt.dev.jjs.ast.JAbstractMethodBody;
021: import com.google.gwt.dev.jjs.ast.JClassType;
022: import com.google.gwt.dev.jjs.ast.JMethod;
023: import com.google.gwt.dev.jjs.ast.JMethodBody;
024: import com.google.gwt.dev.jjs.ast.JMethodCall;
025: import com.google.gwt.dev.jjs.ast.JModVisitor;
026: import com.google.gwt.dev.jjs.ast.JParameter;
027: import com.google.gwt.dev.jjs.ast.JParameterRef;
028: import com.google.gwt.dev.jjs.ast.JProgram;
029: import com.google.gwt.dev.jjs.ast.JReturnStatement;
030: import com.google.gwt.dev.jjs.ast.JStatement;
031: import com.google.gwt.dev.jjs.ast.JThisRef;
032: import com.google.gwt.dev.jjs.ast.JType;
033: import com.google.gwt.dev.jjs.ast.JVisitor;
034: import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
035: import com.google.gwt.dev.js.ast.JsContext;
036: import com.google.gwt.dev.js.ast.JsExpression;
037: import com.google.gwt.dev.js.ast.JsFunction;
038: import com.google.gwt.dev.js.ast.JsModVisitor;
039: import com.google.gwt.dev.js.ast.JsName;
040: import com.google.gwt.dev.js.ast.JsParameter;
041: import com.google.gwt.dev.js.ast.JsThisRef;
042:
043: import java.util.HashSet;
044: import java.util.IdentityHashMap;
045: import java.util.Map;
046: import java.util.Set;
047:
048: /**
049: * This is an interesting "optimization". It's not really an optimization in and
050: * of itself, but it opens the door to other optimizations. The basic idea is
051: * that you look for calls to instance methods that are not actually
052: * polymorphic. In other words, the target method is (effectively) final, not
053: * overriden anywhere in the compilation. We rewrite the single instance method
054: * as a static method that contains the implementation plus an instance method
055: * that delegates to the static method. Then we update any call sites to call
056: * the static method instead. This opens the door to further optimizations,
057: * reduces use of the long "this" keyword in the resulting JavaScript, and in
058: * most cases the polymorphic version can be pruned later.
059: *
060: * TODO(later): make this work on JSNI methods!
061: */
062: public class MakeCallsStatic {
063:
064: /**
065: * For all methods that should be made static, move the contents of the method
066: * to a new static method, and have the original (instance) method delegate to
067: * it. Sometimes the instance method can be pruned later since we update all
068: * non-polymorphic call sites.
069: */
070: private class CreateStaticImplsVisitor extends JVisitor {
071:
072: /**
073: * When code is moved from an instance method to a static method, all
074: * thisRefs must be replaced with paramRefs to the synthetic this param.
075: */
076: private class RewriteJsniMethodBody extends JsModVisitor {
077:
078: private final JsName this Param;
079:
080: public RewriteJsniMethodBody(JsName this Param) {
081: this .this Param = this Param;
082: }
083:
084: @Override
085: public void endVisit(JsThisRef x,
086: JsContext<JsExpression> ctx) {
087: ctx.replaceMe(this Param.makeRef());
088: }
089:
090: @Override
091: public boolean visit(JsFunction x,
092: JsContext<JsExpression> ctx) {
093: // Don't recurse into nested functions!
094: return false;
095: }
096: }
097:
098: /**
099: * When code is moved from an instance method to a static method, all
100: * thisRefs must be replaced with paramRefs to the synthetic this param.
101: * ParameterRefs also need to be targeted to the params in the new method.
102: */
103: private class RewriteMethodBody extends JModVisitor {
104:
105: private final JParameter this Param;
106: private final Map<JParameter, JParameter> varMap;
107:
108: public RewriteMethodBody(JParameter this Param,
109: Map<JParameter, JParameter> varMap) {
110: this .this Param = this Param;
111: this .varMap = varMap;
112: }
113:
114: @Override
115: public void endVisit(JParameterRef x, Context ctx) {
116: JParameter param = varMap.get(x.getTarget());
117: JParameterRef paramRef = new JParameterRef(program, x
118: .getSourceInfo(), param);
119: ctx.replaceMe(paramRef);
120: }
121:
122: @Override
123: public void endVisit(JThisRef x, Context ctx) {
124: JParameterRef paramRef = new JParameterRef(program, x
125: .getSourceInfo(), this Param);
126: ctx.replaceMe(paramRef);
127: }
128: }
129:
130: @Override
131: public boolean visit(JMethod x, Context ctx) {
132: // Let's do it!
133: JClassType enclosingType = (JClassType) x
134: .getEnclosingType();
135: JType returnType = x.getType();
136: SourceInfo sourceInfo = x.getSourceInfo();
137: int myIndexInClass = enclosingType.methods.indexOf(x);
138: assert (myIndexInClass >= 0);
139:
140: // Create the new static method
141: String newName = "$" + x.getName();
142:
143: /*
144: * Don't use the JProgram helper because it auto-adds the new method to
145: * its enclosing class.
146: */
147: JMethod newMethod = new JMethod(program, sourceInfo,
148: newName, enclosingType, returnType, false, true,
149: true, x.isPrivate());
150:
151: // Setup parameters; map from the old params to the new params
152: JParameter this Param = program.createParameter(null,
153: "this$static".toCharArray(), enclosingType, true,
154: true, newMethod);
155: Map<JParameter, JParameter> varMap = new IdentityHashMap<JParameter, JParameter>();
156: for (int i = 0; i < x.params.size(); ++i) {
157: JParameter oldVar = x.params.get(i);
158: JParameter newVar = program.createParameter(oldVar
159: .getSourceInfo(), oldVar.getName()
160: .toCharArray(), oldVar.getType(), oldVar
161: .isFinal(), false, newMethod);
162: varMap.put(oldVar, newVar);
163: }
164: newMethod.freezeParamTypes();
165:
166: // Move the body of the instance method to the static method
167: JAbstractMethodBody movedBody = x.getBody();
168: newMethod.setBody(movedBody);
169:
170: // Create a new body for the instance method that delegates to the static
171: JMethodBody newBody = new JMethodBody(program, sourceInfo);
172: x.setBody(newBody);
173: JMethodCall newCall = new JMethodCall(program, sourceInfo,
174: null, newMethod);
175: newCall.getArgs().add(
176: program.getExprThisRef(sourceInfo, enclosingType));
177: for (int i = 0; i < x.params.size(); ++i) {
178: JParameter param = x.params.get(i);
179: newCall.getArgs().add(
180: new JParameterRef(program, sourceInfo, param));
181: }
182: JStatement statement;
183: if (returnType == program.getTypeVoid()) {
184: statement = newCall.makeStatement();
185: } else {
186: statement = new JReturnStatement(program, sourceInfo,
187: newCall);
188: }
189: newBody.getStatements().add(statement);
190:
191: /*
192: * Rewrite the method body. Update all thisRefs to paramRefs. Update
193: * paramRefs and localRefs to target the params/locals in the new method.
194: */
195: if (newMethod.isNative()) {
196: // For natives, we also need to create the JsParameter for this$static,
197: // because the jsFunc already has parameters.
198: // TODO: Do we really need to do that in BuildTypeMap?
199: JsFunction jsFunc = ((JsniMethodBody) movedBody)
200: .getFunc();
201: JsName paramName = jsFunc.getScope().declareName(
202: "this$static");
203: jsFunc.getParameters().add(0,
204: new JsParameter(paramName));
205: RewriteJsniMethodBody rewriter = new RewriteJsniMethodBody(
206: paramName);
207: // Accept the body to avoid the recursion blocker.
208: rewriter.accept(jsFunc.getBody());
209: } else {
210: RewriteMethodBody rewriter = new RewriteMethodBody(
211: this Param, varMap);
212: rewriter.accept(movedBody);
213: }
214:
215: // Add the new method as a static impl of the old method
216: program.putStaticImpl(x, newMethod);
217: enclosingType.methods.add(myIndexInClass + 1, newMethod);
218: return false;
219: }
220: }
221:
222: /**
223: * Look for any places where instance methods are called in a static manner.
224: * Record this fact so we can create static dispatch implementations.
225: */
226: private class FindStaticDispatchSitesVisitor extends JVisitor {
227:
228: @Override
229: public void endVisit(JMethodCall x, Context ctx) {
230: JMethod method = x.getTarget();
231:
232: // Did we already do this one?
233: if (program.getStaticImpl(method) != null
234: || toBeMadeStatic.contains(method)) {
235: return;
236: }
237:
238: // Must be instance and final
239: if (x.canBePolymorphic()) {
240: return;
241: }
242: if (method.isStatic()) {
243: return;
244: }
245: if (method.isAbstract()) {
246: return;
247: }
248: if (method == program.getNullMethod()) {
249: // Special case: we don't make calls to this method static.
250: return;
251: }
252:
253: // Let's do it!
254: toBeMadeStatic.add(method);
255: }
256: }
257:
258: /**
259: * For any method calls to methods we updated during
260: * CreateStaticMethodVisitor, go and rewrite the call sites to call the static
261: * method instead.
262: */
263: private class RewriteCallSites extends JModVisitor {
264:
265: /*
266: * In cases where callers are directly referencing (effectively) final
267: * instance methods, rewrite the call site to reference the newly-generated
268: * static method instead.
269: */
270: @Override
271: public void endVisit(JMethodCall x, Context ctx) {
272: JMethod oldMethod = x.getTarget();
273: JMethod newMethod = program.getStaticImpl(oldMethod);
274: if (newMethod == null || x.canBePolymorphic()) {
275: return;
276: }
277:
278: // Update the call site
279: JMethodCall newCall = new JMethodCall(program, x
280: .getSourceInfo(), null, newMethod);
281:
282: // The qualifier becomes the first arg
283: newCall.getArgs().add(x.getInstance());
284: // Copy the rest of the args
285: for (int i = 0; i < x.getArgs().size(); ++i) {
286: newCall.getArgs().add(x.getArgs().get(i));
287: }
288: ctx.replaceMe(newCall);
289: }
290: }
291:
292: public static boolean exec(JProgram program) {
293: return new MakeCallsStatic(program).execImpl();
294: }
295:
296: public Set<JMethod> toBeMadeStatic = new HashSet<JMethod>();
297:
298: private final JProgram program;
299:
300: private MakeCallsStatic(JProgram program) {
301: this .program = program;
302: }
303:
304: private boolean execImpl() {
305: FindStaticDispatchSitesVisitor finder = new FindStaticDispatchSitesVisitor();
306: finder.accept(program);
307: if (toBeMadeStatic.isEmpty()) {
308: return false;
309: }
310:
311: CreateStaticImplsVisitor creator = new CreateStaticImplsVisitor();
312: for (JMethod method : toBeMadeStatic) {
313: creator.accept(method);
314: }
315:
316: RewriteCallSites rewriter = new RewriteCallSites();
317: rewriter.accept(program);
318: assert (rewriter.didChange());
319: return true;
320: }
321:
322: }
|