| /* |
| * 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; |
| } |
| } |