| /* |
| * 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.ast.AccessModifier; |
| 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.JExpression; |
| import com.google.gwt.dev.jjs.ast.JInterfaceType; |
| 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.JProgram.DispatchType; |
| import com.google.gwt.dev.jjs.ast.JReferenceType; |
| import com.google.gwt.dev.jjs.ast.JReturnStatement; |
| import com.google.gwt.dev.jjs.ast.JTypeOracle; |
| import com.google.gwt.dev.jjs.ast.JVariableRef; |
| import com.google.gwt.dev.jjs.ast.RuntimeConstants; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; |
| import com.google.gwt.dev.jjs.impl.MakeCallsStatic.CreateStaticImplsVisitor; |
| import com.google.gwt.dev.jjs.impl.MakeCallsStatic.StaticCallConverter; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsInvocation; |
| import com.google.gwt.dev.js.ast.JsModVisitor; |
| import com.google.gwt.dev.js.ast.JsNameRef; |
| import com.google.gwt.thirdparty.guava.common.collect.Iterables; |
| import com.google.gwt.thirdparty.guava.common.collect.Lists; |
| import com.google.gwt.thirdparty.guava.common.collect.Maps; |
| import com.google.gwt.thirdparty.guava.common.collect.Sets; |
| |
| import java.util.Collections; |
| import java.util.EnumMap; |
| import java.util.EnumSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Devirtualization is the process of converting virtual method calls on instances that might be |
| * a JSO, a string or and array (like "obj.doFoo();") to static calls (like |
| * "SomeClass.doFoo__devirtual$(obj)). |
| * |
| * This transformation is done on arrays, strings and JSOs virtual method calls; as this objects |
| * do not have the virtual methods in their prototypes. The static version is a trampoline that |
| * decides how to dispatch the method. |
| * |
| * See https://code.google.com/p/google-web-toolkit/wiki/OverlayTypes for why this is done for JSOs. |
| * <br /> |
| * |
| * To complete the transformation: |
| * <ul> |
| * <li> |
| * 1. methods that need to be devirtualized must be turned into static functions. |
| * </li> |
| * <li> |
| * 2. all method calls to the original functions must be rerouted either to the new static |
| * version or to a static dispatcher trampoline function that is created by this pass. |
| * </li> |
| * </ul> |
| * These trampolines are created whether a call to the function exists for separate compiled |
| * modules to work. In a globally optimized build unused ones are pruned away. <br /> |
| * |
| * This transform may NOT be run multiple times; it will create ever-expanding replacement |
| * expressions. |
| */ |
| public class Devirtualizer { |
| |
| /** |
| * Rewrite any virtual dispatches to Object, Strings or JavaScriptObject such that |
| * dispatch occurs statically for JSOs, strings and arrays. <br /> |
| * |
| * In the following cases JMethodCalls need to be rewritten: |
| * <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>methods defined at String</li> |
| * <li>in draftMode, a 'static' virtual JSO call that hasn't been made |
| * static yet.</li> |
| * </ol> |
| * |
| */ |
| private class RewriteVirtualDispatches extends JModVisitor { |
| |
| @Override |
| public void endVisit(JMethod x, Context ctx) { |
| if (!mightNeedDevirtualization(x)) { |
| return; |
| } |
| // The pruning pass will discard devirtualized methods that have not been called in |
| // whole program optimizing mode. |
| ensureDevirtualVersionExists(x); |
| } |
| |
| @Override |
| public void endVisit(JsniMethodRef x, Context ctx) { |
| JMethod method = x.getTarget(); |
| if (method == null || !mightNeedDevirtualization(method)) { |
| return; |
| } |
| ensureDevirtualVersionExists(method); |
| |
| // Replace the JMethod in jsni reference to a reference to the devirtualized method. |
| // Note that a JsniMethodRefs is a pair containing the Jsni reference as text (i.e. |
| // "@java.lang.Boolean::booleanValue") and a reference to the actual JMethod in the AST; |
| // in generation time the actual JMethod that is called is looked up from the reference text. |
| // |
| // Here we just replace the JMethod the reference is pointing to without updating the |
| // reference text. |
| // |
| // Keeping the "key" unchanged avoid the necessity to sync up with the modifications in the |
| // JS AST when the JsniMethodBody is processed. |
| JMethod devirtualMethod = devirtualMethodByMethod.get(method); |
| ctx.replaceMe(new JsniMethodRef( |
| x.getSourceInfo(), |
| x.getIdent(), |
| devirtualMethod, |
| program.getJavaScriptObject())); |
| } |
| |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| JMethod method = x.getTarget(); |
| if (!method.needsDynamicDispatch()) { |
| return; |
| } |
| |
| JReferenceType instanceType = (JReferenceType) x.getInstance().getType().getUnderlyingType(); |
| if (!mightNeedDevirtualization(method, instanceType)) { |
| return; |
| } |
| |
| // it is a super.m() call and the superclass is not a JSO. (this case is NOT reached if |
| // MakeCallsStatic was called). |
| if (x.isStaticDispatchOnly() && !method.isJsOverlay()) { |
| return; |
| } |
| |
| ensureDevirtualVersionExists(method); |
| |
| // Replaces this virtual method call with a static call to a devirtual version of the method. |
| JMethod devirtualMethod = devirtualMethodByMethod.get(method); |
| ctx.replaceMe(converter.convertCall(x, devirtualMethod)); |
| } |
| |
| @Override |
| public boolean visit(JMethod x, Context ctx) { |
| // Don't rewrite the polymorphic call inside of the devirtualizing method! |
| if (methodByDevirtualMethod.containsValue(x)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JsniMethodBody x, Context ctx) { |
| final Set<String> devirtualMethodJsniIdentifiers = Sets.newHashSet(); |
| for (JsniMethodRef jsniMethodRef : x.getJsniMethodRefs()) { |
| JMethod target = jsniMethodRef.getTarget(); |
| if (target != null && mightNeedDevirtualization(target)) { |
| devirtualMethodJsniIdentifiers.add(jsniMethodRef.getIdent()); |
| } |
| } |
| |
| // Devirtualize jsni method calls. |
| new JsModVisitor() { |
| @Override |
| public void endVisit(JsInvocation x, JsContext ctx) { |
| if (!(x.getQualifier() instanceof JsNameRef)) { |
| // If the invocation does not have a name as a qualifier then it is an expression and |
| // cannot be a jsni method reference. |
| return; |
| } |
| JsNameRef nameRef = (JsNameRef) x.getQualifier(); |
| if (!nameRef.isJsniReference()) { |
| // The invocation is not to a JSNI method. |
| return; |
| } |
| |
| // Retrieve the method referred by the JsniMethodRef and check whether it needs |
| // devirtualization. |
| if (!devirtualMethodJsniIdentifiers.contains(nameRef.getIdent())) { |
| return; |
| } |
| // Devirtualize method by rewriting |
| // a.@java.lang.Boolean::booleanValue() ==> @java.lang.Boolean::booleanValue(a). |
| // |
| // Not the the reference identifier is *NOT* changed and will act a the key in the lookup |
| // for the corresponding JMethod which is contained in the corresponding JsniMethodRef |
| // node. |
| ctx.replaceMe( |
| new JsInvocation( |
| x.getSourceInfo(), |
| new JsNameRef( |
| nameRef.getSourceInfo(), nameRef.getIdent()), |
| Iterables.concat( |
| Collections.singleton(nameRef.getQualifier()), x.getArguments()))); |
| return; |
| } |
| }.accept(x.getFunc()); |
| |
| // Now go ahead and fix the corresponding JSNI references. |
| return true; |
| } |
| |
| /** |
| * Constructs and caches a method that is a new static version of the given method or a |
| * trampoline function that wraps a new static version of the given method. It chooses which to |
| * construct based on how the given method's defining class relates to the JavascriptObject |
| * class. |
| */ |
| private void ensureDevirtualVersionExists(JMethod method) { |
| if (devirtualMethodByMethod.containsKey(method)) { |
| // already did this one before |
| return; |
| } |
| |
| JDeclaredType targetType = method.getEnclosingType(); |
| |
| // Separate compilation treats all JSOs as if they are "dualImpl", as the interface might |
| // be implemented by a regular Java object in a separate module. |
| |
| // TODO(rluble): (Separate compilation) Devirtualizer should be run before optimizations |
| // and optimizations need to be strong enough to perform the same kind of size reductions |
| // achieved by keeping track of singleImpls. |
| |
| if (method.getSignature().equals("toString()Ljava/lang/String;")) { |
| // Object.toString is special because: 1) every JS object has it and 2) GWT creates |
| // a bridge from toString to its implementation method. |
| devirtualMethodByMethod.put( |
| method, program.getIndexedMethod(RuntimeConstants.RUNTIME_TO_STRING)); |
| } else if (!program.typeOracle.isDualJsoInterface(targetType) && |
| program.typeOracle.isSingleJsoImpl(targetType)) { |
| // Optimize the trampoline away when there is ONLY JSO dispatch. |
| // TODO(rluble): verify that this case can not arise in optimized mode and if so |
| // remove as is an unnecessary optimization. |
| |
| assert targetType instanceof JInterfaceType; |
| assert !program.getTypeJavaLangString().getImplements().contains(targetType); |
| |
| JMethod overridingMethod = |
| findOverridingMethod(method, program.typeOracle.getSingleJsoImpl(targetType)); |
| assert overridingMethod != null; |
| |
| JMethod jsoStaticImpl = |
| staticImplCreator.getOrCreateStaticImpl(program, overridingMethod); |
| devirtualMethodByMethod.put(method, jsoStaticImpl); |
| } else if (isOverlayMethod(method)) { |
| // A virtual dispatch on a target that is already known to be an overlay method,. |
| JMethod devirtualMethod = staticImplCreator.getOrCreateStaticImpl(program, method); |
| devirtualMethodByMethod.put(method, devirtualMethod); |
| } else { |
| JMethod devirtualMethod = getOrCreateDevirtualMethod(method); |
| devirtualMethodByMethod.put(method, devirtualMethod); |
| } |
| } |
| |
| private boolean mightNeedDevirtualization(JMethod method) { |
| return mightNeedDevirtualization(method, method.getEnclosingType()); |
| } |
| |
| private boolean mightNeedDevirtualization(JMethod method, JReferenceType instanceType) { |
| if (instanceType == null || !method.needsDynamicDispatch()) { |
| return false; |
| } |
| if (devirtualMethodByMethod.containsKey(method)) { |
| return true; |
| } |
| if (isOverlayMethod(method)) { |
| return true; |
| } |
| if (method.getEnclosingType().isJsNative()) { |
| // Methods in a native JsType that are not JsOverlay should NOT be devirtualized. |
| return false; |
| } |
| if (instanceType.isNullType()) { |
| instanceType = method.getEnclosingType(); |
| } |
| EnumSet<DispatchType> dispatchType = program.getDispatchType(instanceType); |
| dispatchType.remove(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH); |
| return !dispatchType.isEmpty(); |
| } |
| } |
| |
| /** |
| * Returns true if {@code method} is an overlay method. Overlay methods include the ones that |
| * are marked as JsOverlay but also implicit overlays. |
| */ |
| private boolean isOverlayMethod(JMethod method) { |
| return method.isJsOverlay() |
| // Synthetic private methods on interfaces are the result of lambdas that capture the |
| // enclosing instance and are defined on default methods; these can appear in native |
| // interfaces and thus need to be treated as overlays. |
| || (method.getEnclosingType() instanceof JInterfaceType && method.isPrivate()) |
| // JsFunction implementation methods other than the other than the SAM implementation are |
| // also considered overalys to allow for lighter weight JsFuncitons. |
| // TODO(rluble): SAM implementation should also be devirtualized. |
| || (method.getEnclosingType().isJsFunctionImplementation() |
| && !method.isOrOverridesJsFunctionMethod()); |
| } |
| |
| public static void exec(JProgram program) { |
| new Devirtualizer(program).execImpl(); |
| } |
| |
| /** |
| * Maps each Object instance methods (ie, {@link Object#equals(Object)}) onto |
| * its corresponding devirtualizing method. |
| */ |
| private Map<JMethod, JMethod> devirtualMethodByMethod = Maps.newHashMap(); |
| |
| /** |
| * Contains the Cast.hasJavaObjectVirtualDispatch method. |
| */ |
| private final JMethod hasJavaObjectVirtualDispatch; |
| |
| /** |
| * Contains the Cast.isJavaArray method. |
| */ |
| private final JMethod isJavaArray; |
| |
| /** |
| * Contains the set of devirtualizing methods that replace polymorphic calls |
| * to Object methods. |
| */ |
| private final Map<JMethod, JMethod> methodByDevirtualMethod = Maps.newHashMap(); |
| |
| private final JProgram program; |
| |
| private final CreateStaticImplsVisitor staticImplCreator; |
| private final StaticCallConverter converter; |
| |
| /** |
| * Creates and empty devirtualized method for devirtualizing {@code method} in class |
| * {@code inclass}. |
| */ |
| private JMethod createDevirtualMethodFor(JMethod method, JDeclaredType inClass) { |
| SourceInfo sourceInfo = method.getSourceInfo().makeChild(); |
| |
| String prefix = computeEscapedSignature(method.getSignature()); |
| JMethod devirtualMethod = new JMethod(sourceInfo, prefix + "__devirtual$", |
| inClass, method.getType(), false, true, true, AccessModifier.PUBLIC); |
| // TODO(rluble): DoNotInline should be carried over if 'any' of the targets is marked so. |
| devirtualMethod.setInliningMode(method.getInliningMode()); |
| devirtualMethod.setBody(new JMethodBody(sourceInfo)); |
| devirtualMethod.setSynthetic(); |
| inClass.addMethod(devirtualMethod); |
| // Setup parameters. |
| devirtualMethod.createThisParameter(sourceInfo, method.getEnclosingType()); |
| for (JParameter oldParam : method.getParams()) { |
| devirtualMethod.createFinalParameter(sourceInfo, oldParam.getName(), oldParam.getType()); |
| } |
| |
| devirtualMethod.freezeParamTypes(); |
| devirtualMethod.addThrownExceptions(method.getThrownExceptions()); |
| sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(devirtualMethod)); |
| |
| return devirtualMethod; |
| } |
| |
| /** |
| * A normal method signature contains characters that are not valid in a method name. If you want |
| * to construct a method name based on an existing method signature then those characters need to |
| * be escaped. |
| */ |
| private static String computeEscapedSignature(String methodSignature) { |
| return methodSignature.replaceAll("[\\<\\>\\(\\)\\;\\/\\[]", "_"); |
| } |
| |
| private Devirtualizer(JProgram program) { |
| this.program = program; |
| |
| this.hasJavaObjectVirtualDispatch = |
| program.getIndexedMethod(RuntimeConstants.CAST_HAS_JAVA_OBJECT_VIRTUAL_DISPATCH); |
| this.isJavaArray = program.getIndexedMethod(RuntimeConstants.ARRAY_IS_JAVA_ARRAY); |
| // TODO: consider turning on null checks for "this"? |
| // However, for JSO's there is existing code that relies on nulls being okay. |
| this.converter = new StaticCallConverter(program, false); |
| staticImplCreator = new CreateStaticImplsVisitor(program); |
| } |
| |
| private void execImpl() { |
| JClassType jsoType = program.getJavaScriptObject(); |
| if (jsoType == null) { |
| return; |
| } |
| |
| new RewriteVirtualDispatches().accept(program); |
| } |
| |
| /** |
| * 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()); |
| } |
| |
| /** |
| * Construct conditional expression for dispatch. Handle the cases where a dispatch or check |
| * is null indicating impossibility of such operation. |
| */ |
| private static JExpression constructMinimalCondition(JMethod checkMethod, JVariableRef target, |
| JMethodCall trueDispatch, JExpression falseDispatch) { |
| // TODO(rluble): Maybe we should emit slightly different code in checked mode, so that if |
| // no condition is met an exception would be thrown rather than cascading. |
| if (falseDispatch == null && trueDispatch == null) { |
| return null; |
| } |
| if (falseDispatch == null) { |
| // No need for condition to be evaluated. |
| return trueDispatch; |
| } |
| if (trueDispatch == null || falseDispatch instanceof JMethodCall && |
| ((JMethodCall) falseDispatch).getTarget() == trueDispatch.getTarget()) { |
| // Both branches do the same dispatch (or no trueDispatch). |
| return falseDispatch; |
| } |
| JMethodCall condition = |
| new JMethodCall(trueDispatch.getSourceInfo(), null, checkMethod, target); |
| |
| return new JConditional(condition.getSourceInfo(), trueDispatch.getType(), condition, |
| trueDispatch, falseDispatch); |
| } |
| |
| /** |
| * Create a dispatch call taking the arguments from the devirtual method. |
| */ |
| private static JMethodCall maybeCreateDispatch(JMethod dispatchTo, JMethod devirtualMethod) { |
| if (dispatchTo == null) { |
| return null; |
| } |
| List<JParameter> parameters = Lists.newArrayList(devirtualMethod.getParams()); |
| SourceInfo sourceInfo = devirtualMethod.getSourceInfo(); |
| JParameterRef thisParamRef = null; |
| |
| if (!dispatchTo.isStatic()) { |
| // This is a virtual dispatch, take the first parameter as the receiver. |
| thisParamRef = parameters.remove(0).makeRef(sourceInfo); |
| } |
| |
| JMethodCall dispatchCall = new JMethodCall(sourceInfo, thisParamRef, dispatchTo); |
| for (JParameter param : parameters) { |
| dispatchCall.addArg(param.makeRef(sourceInfo)); |
| } |
| return dispatchCall; |
| } |
| |
| /** |
| * Create a conditional method to discriminate between static and virtual |
| * dispatch. |
| * |
| * <pre> |
| * static boolean equals__devirtual$(Object this, Object other) { |
| * return Cast.isJavaString() ? String.equals(other) : |
| * Cast.hasJavaObjectVirtualDispatch(this) ? |
| * this.equals(other) : JavaScriptObject.equals$(this, other); |
| * } |
| * </pre> |
| */ |
| private JMethod getOrCreateDevirtualMethod(JMethod method) { |
| |
| if (methodByDevirtualMethod.containsKey(method)) { |
| return methodByDevirtualMethod.get(method); |
| } |
| |
| ///////////////////////////////////////////////////////////////// |
| // 1. Determine which types of object are target of this dispatch |
| ///////////////////////////////////////////////////////////////// |
| JReferenceType enclosingType = method.getEnclosingType(); |
| EnumSet<DispatchType> possibleTargetTypes = program.getDispatchType( |
| enclosingType.getUnderlyingType()); |
| |
| ///////////////////////////////////////////////////////////////// |
| // 2. Compute the dispatch to method for each relevant case. |
| ///////////////////////////////////////////////////////////////// |
| EnumMap<DispatchType, JMethod> dispatchToMethodByTargetType = new EnumMap<>(DispatchType.class); |
| for (Map.Entry<JClassType, DispatchType> nativeRepresentedType : |
| program.getRepresentedAsNativeTypesDispatchMap().entrySet()) { |
| // skip non-instantiated boxed types, which have been pruned from the AST. |
| if (program.typeOracle.isInstantiatedType(nativeRepresentedType.getKey())) { |
| maybeCreateDispatchFor(method, nativeRepresentedType.getValue(), possibleTargetTypes, |
| dispatchToMethodByTargetType, nativeRepresentedType.getKey()); |
| } |
| } |
| |
| if (possibleTargetTypes.contains(DispatchType.JAVA_ARRAY)) { |
| maybeCreateDispatchFor(method, DispatchType.JAVA_ARRAY, possibleTargetTypes, |
| dispatchToMethodByTargetType, program.getTypeJavaLangObject()); |
| } |
| |
| if (possibleTargetTypes.contains(DispatchType.JSO)) { |
| JMethod overridingMethod = findOverridingMethod(method, |
| program.typeOracle.getSingleJsoImpl(enclosingType)); |
| if (overridingMethod == null && enclosingType == program.getTypeJavaLangObject()) { |
| overridingMethod = findOverridingMethod(method, program.getJavaScriptObject()); |
| } |
| assert overridingMethod != null : method.getEnclosingType().getName() + "::" + |
| method.getName() + " not overridden by JavaScriptObject"; |
| dispatchToMethodByTargetType.put(DispatchType.JSO, |
| staticImplCreator.getOrCreateStaticImpl(program, overridingMethod)); |
| } |
| |
| if (possibleTargetTypes.contains(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH)) { |
| dispatchToMethodByTargetType.put(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH, method); |
| } |
| |
| ///////////////////////////////////////////////////////////////// |
| // 3. Create a devirtualized method. |
| ///////////////////////////////////////////////////////////////// |
| |
| // Decide where to place the devirtual method. Ideally these methods should reside in the |
| // declaring type, but some of these will be interfaces and currently GWT does not emit |
| // any code for them. |
| // TODO(rluble): place interface methods in the corresponding interface once Java 8 defender |
| // method support is implemented. |
| JClassType devirtualMethodEnclosingClass = null; |
| if (method.getEnclosingType() instanceof JClassType) { |
| devirtualMethodEnclosingClass = (JClassType) method.getEnclosingType(); |
| } else { |
| for (Map.Entry<JClassType, DispatchType> nativeRepresentedType : |
| program.getRepresentedAsNativeTypesDispatchMap().entrySet()) { |
| if (dispatchToMethodByTargetType.containsKey(nativeRepresentedType.getValue())) { |
| devirtualMethodEnclosingClass = nativeRepresentedType.getKey(); |
| break; |
| } |
| } |
| } |
| |
| if (devirtualMethodEnclosingClass == null) { |
| if (dispatchToMethodByTargetType.get(DispatchType.JSO) != null) { |
| // This is an interface method implemented by a JSO, place in the JSO class. |
| devirtualMethodEnclosingClass = (JClassType) |
| dispatchToMethodByTargetType.get(DispatchType.JSO).getEnclosingType(); |
| } else { |
| // It is an interface implemented by devirtualized types, place it in Object. |
| devirtualMethodEnclosingClass = program.getTypeJavaLangObject(); |
| } |
| } |
| |
| // Devirtualization of external methods stays external and devirtualization of internal methods |
| // stays internal. |
| assert program.isReferenceOnly(devirtualMethodEnclosingClass) |
| == program.isReferenceOnly(method.getEnclosingType()); |
| // TODO(stalcup): devirtualization is modifying both internal and external types. Really |
| // external types should never be modified. Change the point at which types are saved into |
| // libraries to be after normalization has occurred, so that no further modification is |
| // necessary when loading external types. |
| JMethod devirtualMethod = createDevirtualMethodFor(method, devirtualMethodEnclosingClass); |
| |
| /** |
| * Encoding |
| */ |
| SourceInfo sourceInfo = method.getSourceInfo().makeChild(); |
| JParameter thisParam = devirtualMethod.getParams().get(0); |
| |
| // Synthesize the dispatch at a single conditional doing the checks in this order. |
| // isString(obj) ? dispatchToString : ( |
| // isRegularJavaObject(obj) ? obj.method : ( |
| // isJavaArray(obj) ? |
| // dispatchToArray : |
| // dispatchToJSO |
| // ) |
| // ) |
| |
| // Construct back to fort. Last is JSO. |
| JExpression dispatchExpression = |
| maybeCreateDispatch(dispatchToMethodByTargetType.get(DispatchType.JSO), devirtualMethod); |
| |
| // Dispatch to array |
| dispatchExpression = constructMinimalCondition( |
| isJavaArray, |
| thisParam.makeRef(thisParam.getSourceInfo()), |
| maybeCreateDispatch(dispatchToMethodByTargetType.get(DispatchType.JAVA_ARRAY), |
| devirtualMethod), |
| dispatchExpression); |
| |
| // Dispatch to regular object |
| dispatchExpression = constructMinimalCondition( |
| hasJavaObjectVirtualDispatch, |
| thisParam.makeRef(thisParam.getSourceInfo()), |
| maybeCreateDispatch( |
| dispatchToMethodByTargetType.get(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH), |
| devirtualMethod), |
| dispatchExpression); |
| |
| // Dispatch to regular string, double, boolean |
| for (Map.Entry<JClassType, DispatchType> nativeRepresentedType |
| : program.getRepresentedAsNativeTypesDispatchMap().entrySet()) { |
| DispatchType dispatchType = nativeRepresentedType.getValue(); |
| String castInstanceOfQualifier = dispatchType.getTypeCategory().castInstanceOfQualifier(); |
| dispatchExpression = constructMinimalCondition( |
| program.getIndexedMethod("Cast.instanceOf" + castInstanceOfQualifier), |
| thisParam.makeRef(thisParam.getSourceInfo()), |
| maybeCreateDispatch(dispatchToMethodByTargetType.get(dispatchType), devirtualMethod), |
| dispatchExpression); |
| } |
| |
| // return dispatchConditional; |
| JReturnStatement returnStatement = new JReturnStatement(sourceInfo, dispatchExpression); |
| |
| ((JMethodBody) devirtualMethod.getBody()).getBlock().addStmt(returnStatement); |
| methodByDevirtualMethod.put(method, devirtualMethod); |
| |
| return devirtualMethod; |
| } |
| |
| private void maybeCreateDispatchFor(JMethod method, DispatchType target, |
| EnumSet<DispatchType> possibleTargetTypes, |
| EnumMap<DispatchType, JMethod> dispatchToMethodByTargetType, JClassType targetDevirtualType) { |
| if (possibleTargetTypes.contains(target)) { |
| JMethod overridingMethod = findOverridingMethod(method, targetDevirtualType); |
| if (overridingMethod == null) { |
| throw new AssertionError(method.getEnclosingType().getName() + "::" + method.getName() |
| + " not overridden by " + targetDevirtualType.getSimpleName()); |
| } |
| dispatchToMethodByTargetType.put(target, |
| staticImplCreator.getOrCreateStaticImpl(program, overridingMethod)); |
| } |
| } |
| |
| private static String getJsniReferenceIdentifier(JMethod method) { |
| return "@" + method.getJsniSignature(true, false); |
| } |
| } |