blob: ef7784306e41f80cc6931a611082ea0de8cf0adf [file] [log] [blame]
/*
* 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.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConditional;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JLocalRef;
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.JType;
import com.google.gwt.dev.jjs.ast.JTypeOracle;
import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
import com.google.gwt.dev.jjs.impl.MakeCallsStatic.CreateStaticImplsVisitor;
import java.util.HashMap;
import java.util.Map;
/**
* All call sites that might result in virtual dispatch to a JSO must be
* rewritten to force static dispatch. This transform may NOT be run multiple
* times; it will create ever-expanding replacement expressions.
*/
public class JsoDevirtualizer {
/**
* Rewrite any virtual dispatches to Object or JavaScriptObject such that
* dispatch occurs statically for JSOs.
*/
private class RewriteVirtualDispatches extends JModVisitor {
/**
* A method call at this point can be one of 5 things:
* <ol>
* <li>a dual dispatch interface</li>
* <li>a single dispatch trough single-jso interface</li>
* <li>a java.lang.Object override from JavaScriptObject</li>
* <li>a regular dispatch (no JSOs involved or static JSO call)</li>
* <li>in draftMode, a 'static' virtual JSO call that hasn't been made
* static yet.</li>
* </ol>
*/
@Override
public void endVisit(JMethodCall x, Context ctx) {
JMethod method = x.getTarget();
JMethod newMethod;
JDeclaredType targetType = method.getEnclosingType();
if (targetType == null) {
return;
}
if (!method.needsVtable()) {
return;
}
JType instanceType = x.getInstance().getType();
// if the instance can't possibly be a JSO, don't devirtualize
if (instanceType != program.getTypeJavaLangObject()
&& !program.typeOracle.canBeJavaScriptObject(instanceType)) {
return;
}
if (polyMethodToJsoMethod.containsKey(method)) {
// already did this one before
newMethod = polyMethodToJsoMethod.get(method);
} else if (program.typeOracle.isDualJsoInterface(targetType)) {
JMethod overridingMethod =
findOverridingMethod(method, program.typeOracle.getSingleJsoImpl(targetType));
assert overridingMethod != null;
JMethod jsoStaticImpl = getStaticImpl(overridingMethod);
newMethod = getOrCreateDevirtualMethod(x, jsoStaticImpl);
polyMethodToJsoMethod.put(method, newMethod);
} else if (program.isJavaScriptObject(targetType)) {
// It's a virtual JSO dispatch, usually occurs in draftCompile
newMethod = getStaticImpl(method);
polyMethodToJsoMethod.put(method, newMethod);
} else if (program.typeOracle.isSingleJsoImpl(targetType)) {
// interface dispatch with single implementing JSO concrete type
JMethod overridingMethod =
findOverridingMethod(method, program.typeOracle.getSingleJsoImpl(targetType));
assert overridingMethod != null;
newMethod = getStaticImpl(overridingMethod);
polyMethodToJsoMethod.put(method, newMethod);
} else if (targetType == program.getTypeJavaLangObject()) {
// it's a java.lang.Object overriden method in JSO
JMethod overridingMethod = findOverridingMethod(method, program.getJavaScriptObject());
if (overridingMethod != null) {
JMethod jsoStaticImpl = getStaticImpl(overridingMethod);
newMethod = getOrCreateDevirtualMethod(x, jsoStaticImpl);
polyMethodToJsoMethod.put(method, newMethod);
} else {
// else this method isn't overriden by JavaScriptObject
assert false : "Object method not overriden by JavaScriptObject";
return;
}
} else {
return;
}
assert (newMethod != null);
ctx.replaceMe(MakeCallsStatic.makeStaticCall(x, newMethod));
}
@Override
public boolean visit(JMethod x, Context ctx) {
// Don't rewrite the polymorphic call inside of the devirtualizing method!
if (polyMethodToDevirtualMethods.containsValue(x)) {
return false;
}
return true;
}
}
public static void exec(JProgram program) {
new JsoDevirtualizer(program).execImpl();
}
/**
* Maps each Object instance methods (ie, {@link Object#equals(Object)}) onto
* its corresponding devirtualizing method.
*/
protected Map<JMethod, JMethod> polyMethodToJsoMethod = new HashMap<JMethod, JMethod>();
/**
* Contains the Cast.isJavaObject method.
*/
private final JMethod isJavaObjectMethod;
/**
* Contains the set of devirtualizing methods that replace polymorphic calls
* to Object methods.
*/
private final Map<JMethod, JMethod> polyMethodToDevirtualMethods =
new HashMap<JMethod, JMethod>();
private final JProgram program;
private final CreateStaticImplsVisitor staticImplCreator;
private JsoDevirtualizer(JProgram program) {
this.program = program;
this.isJavaObjectMethod = program.getIndexedMethod("Cast.isJavaObject");
staticImplCreator = new CreateStaticImplsVisitor(program);
}
private void execImpl() {
JClassType jsoType = program.getJavaScriptObject();
if (jsoType == null) {
return;
}
RewriteVirtualDispatches rewriter = new RewriteVirtualDispatches();
rewriter.accept(program);
assert (rewriter.didChange());
}
/**
* Finds the method that overrides this method, starting with the target
* class.
*/
private JMethod findOverridingMethod(JMethod method, JClassType target) {
if (target == null) {
return null;
}
for (JMethod overridingMethod : target.getMethods()) {
if (JTypeOracle.methodsDoMatch(method, overridingMethod)) {
return overridingMethod;
}
}
return findOverridingMethod(method, target.getSuperClass());
}
/**
* Create a conditional method to discriminate between static and virtual
* dispatch.
*
* <pre>
* static boolean equals__devirtual$(Object this, Object other) {
* return Cast.isJavaObject(this) ? this.equals(other) : JavaScriptObject.equals$(this, other);
* }
* </pre>
*/
private JMethod getOrCreateDevirtualMethod(JMethodCall polyMethodCall, JMethod jsoImpl) {
JMethod polyMethod = polyMethodCall.getTarget();
/**
* TODO(cromwellian) generate a inlined expression instead of Method Because
* devirtualization happens after optimization, the devirtual methods don't
* optimize well in the JS pass. Consider "inlining" a hand optimized
* devirtual method at callsites instead of a JMethodCall. As a bonus, the
* inlined code can be specialized for each callsite, for example, if there
* are no side effects, then there's no need for a temporary. Or, if the
* instance can't possibly be java.lang.String, then the JSO check becomes a
* cheaper check for typeMarker.
*/
if (polyMethodToDevirtualMethods.containsKey(polyMethod)) {
return polyMethodToDevirtualMethods.get(polyMethod);
}
JClassType jsoType = program.getJavaScriptObject();
SourceInfo sourceInfo = jsoType.getSourceInfo().makeChild(SourceOrigin.UNKNOWN);
// Create the new method.
String name = polyMethod.getName() + "__devirtual$";
JMethod newMethod =
program.createMethod(sourceInfo, name, jsoType, polyMethod.getType(), false, true, true,
false, false);
newMethod.setSynthetic();
// Setup parameters.
JParameter thisParam =
JProgram.createParameter(sourceInfo, "this$static", program.getTypeJavaLangObject(), true,
true, newMethod);
for (JParameter oldParam : polyMethod.getParams()) {
JProgram.createParameter(sourceInfo, oldParam.getName(), oldParam.getType(), true, false,
newMethod);
}
newMethod.freezeParamTypes();
newMethod.addThrownExceptions(polyMethod.getThrownExceptions());
sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(newMethod));
// maybeJsoInvocation = this$static
JLocal temp =
JProgram.createLocal(sourceInfo, "maybeJsoInvocation", thisParam.getType(), true,
(JMethodBody) newMethod.getBody());
JMultiExpression multi = new JMultiExpression(sourceInfo);
// (maybeJsoInvocation = this$static, )
multi.exprs.add(JProgram.createAssignmentStmt(sourceInfo, new JLocalRef(sourceInfo, temp),
new JParameterRef(sourceInfo, thisParam)).getExpr());
// Build from bottom up.
// isJavaObject(temp)
JMethodCall condition = new JMethodCall(sourceInfo, null, isJavaObjectMethod);
condition.addArg(new JLocalRef(sourceInfo, temp));
// temp.method(args)
JMethodCall thenValue =
new JMethodCall(sourceInfo, new JLocalRef(sourceInfo, temp), polyMethod);
for (JParameter param : newMethod.getParams()) {
if (param != thisParam) {
thenValue.addArg(new JParameterRef(sourceInfo, param));
}
}
// jso$method(temp, args)
JMethodCall elseValue = new JMethodCall(sourceInfo, null, jsoImpl);
elseValue.addArg(new JLocalRef(sourceInfo, temp));
for (JParameter param : newMethod.getParams()) {
if (param != thisParam) {
elseValue.addArg(new JParameterRef(sourceInfo, param));
}
}
// isJavaObject(temp) ? temp.method(args) : jso$method(temp, args)
JConditional conditional =
new JConditional(sourceInfo, polyMethod.getType(), condition, thenValue, elseValue);
multi.exprs.add(conditional);
JReturnStatement returnStatement = new JReturnStatement(sourceInfo, multi);
((JMethodBody) newMethod.getBody()).getBlock().addStmt(returnStatement);
polyMethodToDevirtualMethods.put(polyMethod, newMethod);
return newMethod;
}
private JMethod getStaticImpl(JMethod method) {
assert !method.isStatic();
JMethod staticImpl = program.getStaticImpl(method);
if (staticImpl == null) {
staticImplCreator.accept(method);
staticImpl = program.getStaticImpl(method);
}
return staticImpl;
}
}