| /* |
| * 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.Context; |
| import com.google.gwt.dev.jjs.ast.JArrayType; |
| import com.google.gwt.dev.jjs.ast.JCastOperation; |
| import com.google.gwt.dev.jjs.ast.JClassLiteral; |
| 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.JField; |
| import com.google.gwt.dev.jjs.ast.JInstanceOf; |
| 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.JNewArray; |
| import com.google.gwt.dev.jjs.ast.JNonNullType; |
| import com.google.gwt.dev.jjs.ast.JParameter; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReferenceType; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.js.JMultiExpression; |
| 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.Stack; |
| |
| /** |
| * Replace references to JSO subtypes with JSO itself. |
| */ |
| public class JavaScriptObjectNormalizer { |
| /** |
| * Map types from JSO subtypes to JSO itself. |
| */ |
| private class NormalizeVisitor extends JModVisitor { |
| |
| private final Stack<JMethodBody> currentMethodBody = new Stack<JMethodBody>(); |
| |
| @Override |
| public void endVisit(JCastOperation x, Context ctx) { |
| JType newType = translate(x.getCastType()); |
| if (newType != x.getCastType()) { |
| ctx.replaceMe(new JCastOperation(x.getSourceInfo(), newType, |
| x.getExpr())); |
| } |
| } |
| |
| @Override |
| public void endVisit(JClassLiteral x, Context ctx) { |
| JType newType = translate(x.getRefType()); |
| if (newType != x.getRefType()) { |
| ctx.replaceMe(program.getLiteralClass(newType)); |
| } |
| } |
| |
| @Override |
| public void endVisit(JField x, Context ctx) { |
| x.setType(translate(x.getType())); |
| } |
| |
| @Override |
| public void endVisit(JInstanceOf x, Context ctx) { |
| JReferenceType newType = (JReferenceType) translate(x.getTestType()); |
| if (newType != x.getTestType()) { |
| ctx.replaceMe(new JInstanceOf(x.getSourceInfo(), newType, x.getExpr())); |
| } |
| } |
| |
| @Override |
| public void endVisit(JLocal x, Context ctx) { |
| x.setType(translate(x.getType())); |
| } |
| |
| @Override |
| public void endVisit(JMethod x, Context ctx) { |
| x.setType(translate(x.getType())); |
| } |
| |
| @Override |
| public void endVisit(JMethodBody x, Context ctx) { |
| if (currentMethodBody.pop() != x) { |
| throw new RuntimeException("Unexpected JMethodBody popped"); |
| } |
| } |
| |
| /** |
| * Polymorphic dispatches to interfaces implemented by both a JSO and a |
| * regular type require special dispatch handling. If the instance is not a |
| * Java-derived object, the method from the single JSO implementation will |
| * be invoked. Otherwise, a polymorphic dispatch to the Java-derived object |
| * is made. |
| */ |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| JDeclaredType targetClass = x.getTarget().getEnclosingType(); |
| if (program.typeOracle.getSingleJsoImpl(targetClass) != null) { |
| |
| SourceInfo info = x.getSourceInfo().makeChild( |
| JavaScriptObjectNormalizer.class, |
| "Polymorphic invocation of SingleJsoImpl interface"); |
| |
| // Find the method in the JSO type |
| JMethod jsoMethod = findJsoMethod(x.getTarget()); |
| assert jsoMethod != null; |
| |
| if (program.typeOracle.isDualJsoInterface(targetClass)) { |
| /* |
| * This is the special-case code to handle interfaces. |
| */ |
| JMultiExpression multi = new JMultiExpression(info); |
| JExpression instance = maybeMakeTempAssignment(multi, x.getInstance()); |
| |
| // instance.method(arg, arg) |
| JMethodCall localCall = new JMethodCall(info, instance, x.getTarget()); |
| localCall.addArgs(x.getArgs()); |
| |
| // We need a second copy of the arguments for the else expression |
| CloneExpressionVisitor cloner = new CloneExpressionVisitor(); |
| |
| // instance.jsoMethod(arg, arg) |
| JMethodCall jsoCall = new JMethodCall(info, |
| cloner.cloneExpression(instance), jsoMethod); |
| jsoCall.addArgs(cloner.cloneExpressions(x.getArgs())); |
| |
| // Cast.isJavaScriptObject() ? instance.jsoMethod() : |
| // instance.method(); |
| JConditional newExpr = makeIsJsoConditional(info, |
| cloner.cloneExpression(instance), x.getType(), jsoCall, localCall); |
| |
| multi.exprs.add(newExpr); |
| // We may only have the ternary operation if there's no side-effect |
| ctx.replaceMe(multi.exprs.size() == 1 ? multi.exprs.get(0) : multi); |
| } else { |
| /* |
| * ... otherwise, if there's only a JSO implementation, we'll just |
| * call that directly. |
| */ |
| JMethodCall jsoCall = new JMethodCall(info, x.getInstance(), |
| jsoMethod); |
| jsoCall.addArgs(x.getArgs()); |
| ctx.replaceMe(jsoCall); |
| } |
| } |
| } |
| |
| @Override |
| public void endVisit(JNewArray x, Context ctx) { |
| x.setType((JNonNullType) translate(x.getType())); |
| } |
| |
| @Override |
| public void endVisit(JParameter x, Context ctx) { |
| x.setType(translate(x.getType())); |
| } |
| |
| @Override |
| public boolean visit(JMethodBody x, Context ctx) { |
| currentMethodBody.push(x); |
| return true; |
| } |
| |
| private JMethod findConcreteImplementation(JMethod method, |
| JClassType concreteType) { |
| /* |
| * Search supertypes for virtual overrides via subclass. See the javadoc |
| * on JTypeOracle.getAllVirtualOverrides for an example. |
| */ |
| while (concreteType != null) { |
| for (JMethod m : concreteType.getMethods()) { |
| if (program.typeOracle.getAllOverrides(m).contains(method)) { |
| if (!m.isAbstract()) { |
| return m; |
| } |
| } |
| } |
| concreteType = concreteType.getSuperClass(); |
| } |
| |
| return null; |
| } |
| |
| private JMethod findJsoMethod(JMethod interfaceMethod) { |
| JClassType jsoClass = program.typeOracle.getSingleJsoImpl(interfaceMethod.getEnclosingType()); |
| assert program.isJavaScriptObject(jsoClass); |
| assert jsoClass != null; |
| |
| JMethod toReturn = findConcreteImplementation(interfaceMethod, jsoClass); |
| assert toReturn != null; |
| assert !toReturn.isAbstract(); |
| assert jsoClass.isFinal() || toReturn.isFinal(); |
| |
| return toReturn; |
| } |
| |
| private JConditional makeIsJsoConditional(SourceInfo info, |
| JExpression instance, JType conditionalType, JExpression isJsoExpr, |
| JExpression notJsoExpr) { |
| // Cast.isJavaScriptObjectOrString(instance) |
| JMethod isJavaScriptObjectMethod = program.getIndexedMethod("Cast.isJavaScriptObjectOrString"); |
| JMethodCall isJavaScriptObjectExpr = new JMethodCall(info, null, |
| isJavaScriptObjectMethod); |
| isJavaScriptObjectExpr.addArg(instance); |
| return new JConditional(info, conditionalType, isJavaScriptObjectExpr, |
| isJsoExpr, notJsoExpr); |
| } |
| |
| private JExpression maybeMakeTempAssignment(JMultiExpression multi, |
| JExpression instance) { |
| if (instance.hasSideEffects()) { |
| /* |
| * It may be necessary to save off the instance expression into a local |
| * variable if its evaluation would produce side-effects. The |
| * multi-expression is used for this purpose. |
| */ |
| SourceInfo info = instance.getSourceInfo().makeChild( |
| JavaScriptObjectNormalizer.class, |
| "Temporary assignment for instance with side-effects"); |
| JLocal local = JProgram.createLocal(info, "maybeJsoInvocation", |
| instance.getType(), true, currentMethodBody.peek()); |
| multi.exprs.add(JProgram.createAssignmentStmt(info, |
| new JLocalRef(info, local), instance).getExpr()); |
| |
| instance = new JLocalRef(info, local); |
| } |
| return instance; |
| } |
| |
| private JType translate(JType type) { |
| if (!(type instanceof JReferenceType)) { |
| return type; |
| } |
| JReferenceType refType = (JReferenceType) type; |
| boolean canBeNull = refType.canBeNull(); |
| refType = refType.getUnderlyingType(); |
| |
| if (program.isJavaScriptObject(refType)) { |
| refType = program.getJavaScriptObject(); |
| } else if (program.typeOracle.getSingleJsoImpl(refType) != null |
| && !program.typeOracle.isDualJsoInterface(refType)) { |
| // Optimization: narrow to JSO if it's not a dual impl. |
| refType = program.getJavaScriptObject(); |
| } else if (refType instanceof JArrayType) { |
| JArrayType arrayType = (JArrayType) refType; |
| JType leafType = arrayType.getLeafType(); |
| JType replacement = translate(leafType); |
| if (leafType != replacement) { |
| refType = program.getTypeArray(replacement, arrayType.getDims()); |
| } |
| } |
| return canBeNull ? refType : program.getNonNullType(refType); |
| } |
| } |
| |
| public static void exec(JProgram program) { |
| Event normalizerEvent = SpeedTracerLogger.start(CompilerEventType.NORMALIZER); |
| new JavaScriptObjectNormalizer(program).execImpl(); |
| normalizerEvent.end(); |
| } |
| |
| private final JProgram program; |
| |
| private JavaScriptObjectNormalizer(JProgram program) { |
| this.program = program; |
| } |
| |
| private void execImpl() { |
| NormalizeVisitor visitor = new NormalizeVisitor(); |
| visitor.accept(program); |
| } |
| } |