Overhaul overlay types.

In general, JSOs now treated like other Java types, except for cast/instanceof
and instantiability.

1) Eliminate JavaScriptObjectNormalizer. JSO subtypes are no longer rewritten as
JSO, and trampoline functions not installed until after optimizations

2) No longer copy every Single JSO interface onto JSO itself

3) Teach TypeOracle about cross-cast JSO semantics

4) Each compiler pass recomputes whether a JSO interface is dually implemented

5) ControlFlowAnalyzer treats implicit casts to JSO as instantiations

6) JsoDevirtualizer now devirtualizes any JSO subtype method (not just
java.lang.Object)

7) CastNormalizer treats non-dual Single JSO Interfaces as casts/instanceof
against the concrete implementor.

This patch allows the compiler to effectively remove trampoline functions from
singlely implemented interface dispatches. It also rationalizes the way JSOs are
treated to bring them more in line with regular Java types by preserving their
type hierarchy in the AST.

Review by: scottb@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9853 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 19bbba7..cfc2998 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -82,7 +82,6 @@
 import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
 import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences;
 import com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields;
-import com.google.gwt.dev.jjs.impl.JavaScriptObjectNormalizer;
 import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
 import com.google.gwt.dev.jjs.impl.JsFunctionClusterer;
 import com.google.gwt.dev.jjs.impl.JsIEBlockTextTransformer;
@@ -620,9 +619,6 @@
       // Resolve entry points, rebinding non-static entry points.
       findEntryPoints(logger, rpo, declEntryPts, jprogram);
 
-      // Replace references to JSO subtypes with JSO itself.
-      JavaScriptObjectNormalizer.exec(jprogram);
-
       ImplementClassLiteralsAsFields.exec(jprogram);
       
       /*
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
index 09291fb..6be467b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -25,6 +25,7 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -280,6 +281,19 @@
     this.program = program;
   }
 
+  /**
+   * True if the type is a JSO or interface implemented by JSO..
+   * @param type
+   * @return
+   */
+  public boolean canBeJavaScriptObject(JType type) {
+    if (type instanceof JNonNullType) {
+      type = ((JNonNullType) type).getUnderlyingType();
+    }
+    return program.isJavaScriptObject(type)
+        || program.typeOracle.isSingleJsoImpl(type);
+  }
+
   public boolean canTheoreticallyCast(JReferenceType type, JReferenceType qType) {
     if (!type.canBeNull() && qType == program.getTypeNull()) {
       // Cannot cast non-nullable to null
@@ -295,6 +309,14 @@
       return true;
     }
 
+    /**
+     * Cross-cast allowed in theory, prevents TypeTightener from turning
+     * cross-casts into null-casts.
+     */
+    if (canBeJavaScriptObject(type) && canBeJavaScriptObject(qType)) {
+      return true;
+    }
+    
     if (canTriviallyCast(type, qType)) {
       return true;
     }
@@ -401,12 +423,6 @@
         if (isSuperClass(cType, qcType)) {
           return true;
         }
-        // All JavaScriptObject types can be freely cast to each other.
-        JClassType jsoType = program.getJavaScriptObject();
-        if (jsoType != null) {
-          return isSameOrSuper(cType, jsoType)
-              && isSameOrSuper(qcType, jsoType);
-        }
       } else if (qType instanceof JInterfaceType) {
         return implementsInterface(cType, (JInterfaceType) qType);
       }
@@ -458,20 +474,17 @@
       }
     }
 
-    /*
-     * Now that the basic type hierarchy is computed, move all interfaces that
-     * are implemented by overlay types onto JavaScriptObject itself before
-     * building the full maps.
+    /*                                                                                                 
+     * Now that the basic type hierarchy is computed, compute which
+     * JSOs implement interfaces singlely or dually.
      */
     JClassType jsoType = program.getJavaScriptObject();
     List<JClassType> jsoSubTypes = Lists.create();
     if (jsoType != null) {
-      assert jsoType.getImplements().size() == 0;
       jsoSubTypes = new ArrayList<JClassType>(get(subClassMap, jsoType));
       Collections.sort(jsoSubTypes, new HasNameSort());
       for (JClassType jsoSubType : jsoSubTypes) {
         for (JInterfaceType intf : jsoSubType.getImplements()) {
-          jsoType.addImplements(intf);
           jsoSingleImpls.put(intf, jsoSubType);
           for (JInterfaceType superIntf : get(superInterfaceMap, intf)) {
             if (!jsoSingleImpls.containsKey(superIntf)) {
@@ -499,14 +512,13 @@
     }
 
     // Create dual mappings for any jso interface with a Java implementor.
-    int totalJsoTypes = jsoSubTypes.size() + 1;
     for (JInterfaceType jsoIntf : jsoSingleImpls.keySet()) {
       Set<JClassType> implementors = get(isImplementedMap, jsoIntf);
-      if (implementors.size() == totalJsoTypes) {
-        assert implementors.contains(jsoType);
-      } else {
-        assert implementors.size() > totalJsoTypes;
-        dualImpls.add(jsoIntf);
+      for (JClassType implementor : implementors) {
+        if (!program.isJavaScriptObject(implementor)) {
+          dualImpls.add(jsoIntf);
+          break;
+        }
       }
     }
   }
@@ -538,6 +550,10 @@
     return results;
   }
 
+  public Set<JClassType> getAllSubClasses(JReferenceType type) {
+    return subClassMap.get(type);
+  }
+
   /**
    * Returns the set of methods the given method virtually overrides. A virtual
    * override is an association between a concrete method and an unrelated
@@ -574,10 +590,33 @@
     return dualImpls.contains(maybeDualImpl.getUnderlyingType());
   }
 
+  /**
+    * True if either a JSO, or is an interface that is ONLY
+    * implemented by a JSO.
+    */
+   public boolean isEffectivelyJavaScriptObject(JType type) {
+     if (type instanceof JReferenceType) {
+       JReferenceType refType = (JReferenceType) type;
+       return program.isJavaScriptObject(refType) ||
+         (isSingleJsoImpl(refType) && !isDualJsoInterface(refType));
+     } else {
+       return false;
+     }
+   }
+
   public boolean isInstantiatedType(JReferenceType type) {
     return isInstantiatedType(type, instantiatedTypes);
   }
 
+  public boolean isSameOrSuper(JClassType type, JClassType qType) {
+     return (type == qType || isSuperClass(type, qType));
+  }
+
+  public boolean isSingleJsoImpl(JType type) {
+    return type instanceof JReferenceType
+        && getSingleJsoImpl((JReferenceType) type) != null;
+  }
+
   /**
    * Returns true if qType is a subclass of type, directly or indirectly.
    */
@@ -598,9 +637,32 @@
    */
   public void recomputeAfterOptimizations() {
     Set<JDeclaredType> computed = new IdentityHashSet<JDeclaredType>();
+
     for (JDeclaredType type : program.getDeclaredTypes()) {
       computeClinitTarget(type, computed);
     }
+    nextDual: for (Iterator<JInterfaceType> it = dualImpls.iterator();
+        it.hasNext();) {
+      JInterfaceType dualIntf = it.next();
+      Set<JClassType> implementors = get(isImplementedMap, dualIntf);
+      for (JClassType implementor : implementors) {
+        if (isInstantiatedType(implementor)
+            && !program.isJavaScriptObject(implementor)) {
+          // This dual is still implemented by a Java class.
+          continue nextDual;
+        }
+      }
+      // No Java implementors.
+      it.remove();
+    }
+
+    // Prune jsoSingleImpls when implementor isn't live
+    Iterator<JClassType> jit = jsoSingleImpls.values().iterator();
+    while (jit.hasNext()) {
+      if (!isInstantiatedType(jit.next())) {
+        jit.remove();
+      }
+    }
   }
 
   public void setInstantiatedTypes(Set<JReferenceType> instantiatedTypes) {
@@ -912,10 +974,6 @@
     return instantiatedTypes.contains(type);
   }
 
-  private boolean isSameOrSuper(JClassType type, JClassType qType) {
-    return (type == qType || isSuperClass(type, qType));
-  }
-
   /**
    * Record the all of my super classes (and myself as a subclass of them).
    */
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
index 9cbbf1a..be297b4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CastNormalizer.java
@@ -191,6 +191,24 @@
       recordCast(x.getTestType(), x.getExpr());
     }
 
+    private boolean canTriviallyCastJsoSemantics(JReferenceType type,
+        JReferenceType qType) {
+      type = type.getUnderlyingType();
+      qType = qType.getUnderlyingType();
+
+      if (type instanceof JArrayType && qType instanceof JArrayType) {
+        JArrayType aType = (JArrayType) type;
+        JArrayType aqType = (JArrayType) qType;
+        return program.typeOracle.canTriviallyCast(type, qType)
+            || (program.isJavaScriptObject(aType.getLeafType())
+                && program.isJavaScriptObject(aqType.getLeafType()));
+      }
+
+      return program.typeOracle.canTriviallyCast(type, qType)
+            || (program.isJavaScriptObject(type)
+                 && program.isJavaScriptObject(qType));
+    }
+    
     /**
      * Create the data for JSON table to capture the mapping from a class to its
      * query types.
@@ -221,11 +239,16 @@
       for (JReferenceType qType : queriedTypes.keySet()) {
 
         Set<JReferenceType> querySet = queriedTypes.get(qType);
-        if (program.typeOracle.canTriviallyCast(type, qType)) {
+        /**
+         * Handles JSO[] -> JSO[] case now that canCastTrivially doesn't deal
+         * with JSO cross-casts anymore.
+         */
+        if (canTriviallyCastJsoSemantics(type, qType)) {
 
           for (JReferenceType argType : querySet) {
 
-            if (program.typeOracle.canTriviallyCast(type, argType)) {
+            if (canTriviallyCastJsoSemantics(type, argType) ||
+                program.isJavaScriptObject(qType)) {
               if (yesSet == null) {
                 yesSet = new HashSet<JReferenceType>();
               }
@@ -422,13 +445,16 @@
         JExpression curExpr = expr;
         JReferenceType refType = ((JReferenceType) toType).getUnderlyingType();
         JReferenceType argType = (JReferenceType) expr.getType();
-        if (program.typeOracle.canTriviallyCast(argType, refType)) {
+        if (program.typeOracle.canTriviallyCast(argType, refType)
+            || (program.typeOracle.isEffectivelyJavaScriptObject(argType)
+            &&  program.typeOracle.isEffectivelyJavaScriptObject(refType))) {
           // just remove the cast
           replaceExpr = curExpr;
         } else {
 
           JMethod method;
-          boolean isJsoCast = program.isJavaScriptObject(refType);
+          boolean isJsoCast =
+              program.typeOracle.isEffectivelyJavaScriptObject(refType);
           if (isJsoCast) {
             // A cast to a concrete JSO subtype
             method = program.getIndexedMethod("Cast.dynamicCastJso");
@@ -537,7 +563,10 @@
       JReferenceType toType = x.getTestType();
       // Only tests on run-time types are supported
       assert (toType == toType.getUnderlyingType());
-      if (program.typeOracle.canTriviallyCast(argType, toType)) {
+      if (program.typeOracle.canTriviallyCast(argType, toType)
+          // don't depend on type-tightener having run
+          || (program.typeOracle.isEffectivelyJavaScriptObject(argType)
+          && program.typeOracle.isEffectivelyJavaScriptObject(toType))) {
         // trivially true if non-null; replace with a null test
         JNullLiteral nullLit = program.getLiteralNull();
         JBinaryOperation eq = new JBinaryOperation(x.getSourceInfo(),
@@ -547,9 +576,9 @@
       } else {
         JMethod method;
         boolean isJsoCast = false;
-        if (program.typeOracle.getSingleJsoImpl(toType) != null) {
+        if (program.typeOracle.isDualJsoInterface(toType)) {
           method = program.getIndexedMethod("Cast.instanceOfOrJso");
-        } else if (program.isJavaScriptObject(toType)) {
+        } else if (program.typeOracle.isEffectivelyJavaScriptObject(toType)) {
           isJsoCast = true;
           method = program.getIndexedMethod("Cast.instanceOfJso");
         } else {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
index 1ebe42e..d66ca40 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -182,10 +182,25 @@
     public boolean visit(JCastOperation x, Context ctx) {
       // Rescue any JavaScriptObject type that is the target of a cast.
       JType targetType = x.getCastType();
-      if (program.isJavaScriptObject(targetType)) {
+      if (program.typeOracle.canBeJavaScriptObject(targetType)) {
         rescue((JReferenceType) targetType, true, true);
+        JType exprType = x.getExpr().getType();
+        if (program.typeOracle.isSingleJsoImpl(targetType)) {
+          /*
+            * It's a JSO interface, check if the source expr can be a live JSO
+            * 1) source is java.lang.Object (JSO could have been assigned to it)
+            * 2) source is JSO
+            * 3) source is SingleJSO interface whose implementor is live
+            */
+          if (program.getTypeJavaLangObject() == exprType
+              || program.typeOracle.canBeJavaScriptObject(exprType)) {
+            // source is JSO or SingleJso interface whose implementor is live
+            JClassType jsoImplementor =
+                program.typeOracle.getSingleJsoImpl((JReferenceType) targetType);
+            rescue(jsoImplementor, true, true);
+          }
+        }
       }
-
       return true;
     }
 
@@ -294,7 +309,6 @@
       }
 
       rescueMethodsIfInstantiable(type);
-
       return false;
     }
 
@@ -486,7 +500,8 @@
      */
     private void maybeRescueJavaScriptObjectPassingIntoJava(JType type) {
       boolean doIt = false;
-      if (program.isJavaScriptObject(type) || program.isJavaLangString(type)) {
+      if (program.typeOracle.canBeJavaScriptObject(type)
+          || program.isJavaLangString(type)) {
         doIt = true;
       } else if (type instanceof JArrayType) {
         /*
@@ -497,12 +512,17 @@
         JType elementType = arrayType.getElementType();
         if (elementType instanceof JPrimitiveType
             || program.isJavaLangString(elementType)
-            || program.isJavaScriptObject(elementType)) {
+            || program.typeOracle.canBeJavaScriptObject(elementType)) {
           doIt = true;
         }
       }
       if (doIt) {
         rescue((JReferenceType) type, true, true);
+        if (program.typeOracle.isSingleJsoImpl(type)) {
+          // Cast of JSO into SingleJso interface, rescue the implementor
+          rescue(program.typeOracle.getSingleJsoImpl((JReferenceType) type),
+              true, true);
+        }
       }
     }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
index 017e69b..a02032c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
@@ -273,6 +273,23 @@
     return typeName;
   }
 
+
+  private JType normalizeJsoType(JType type) {
+    if (program.isJavaScriptObject(type)) {
+      return program.getJavaScriptObject();
+    }
+
+    if (type instanceof JArrayType) {
+      JArrayType aType = (JArrayType) type;
+      if (program.isJavaScriptObject(aType.getLeafType())) {
+        return program.getTypeArray(program.getJavaScriptObject(),
+            aType.getDims());
+      }
+    }
+
+    return type;
+  }
+
   /**
    * Takes the form:
    * 
@@ -285,6 +302,7 @@
    */
   private JField resolveClassLiteralField(JClassLiteral classLiteral) {
     JType type = classLiteral.getRefType();
+    type = normalizeJsoType(type);
     JField field = classLiteralFields.get(type);
     if (field == null) {
       // Create the allocation expression FIRST since this may be recursive on
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java
deleted file mode 100644
index 743b6b9..0000000
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JavaScriptObjectNormalizer.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * 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(new JClassLiteral(x.getSourceInfo(), 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();
-
-        // 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();
-        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 : refType.getNonNull();
-    }
-  }
-
-  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);
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
index 84cc483..c399579 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsoDevirtualizer.java
@@ -20,6 +20,10 @@
 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.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;
@@ -28,12 +32,13 @@
 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.HashSet;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * All call sites that might result in virtual dispatch to a JSO must be
@@ -52,18 +57,66 @@
     public void endVisit(JMethodCall x, Context ctx) {
       JMethod method = x.getTarget();
       JMethod newMethod;
-      if (virtualJsoMethods.contains(method)) {
-        /*
-         * Force a JSO call to be static. Really, this should never be necessary
-         * as long as MakeCallsStatic runs. However, we would rather not insist
-         * that normalization depends on optimization having been done.
-         */
-        newMethod = program.getStaticImpl(method);
-      } else if (objectMethodToJsoMethod.keySet().contains(method)) {
-        /*
-         * Map the object call to its appropriate devirtualizing method.
-         */
-        newMethod = objectMethodToJsoMethod.get(method);
+      /**
+       * A method call at this point can be one of5 things:
+       * 1) a dual dispatch interface
+       * 2) a single dispatch trough single-jso interface
+       * 3) a java.lang.Object override from JavaScriptObject
+       * 4) a regular dispatch (no JSOs involved or static JSO call)
+       * 5) in draftMode, a 'static' virtual JSO call that hasn't been
+       * made static yet.
+       */
+      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;
       }
@@ -74,7 +127,7 @@
     @Override
     public boolean visit(JMethod x, Context ctx) {
       // Don't rewrite the polymorphic call inside of the devirtualizing method!
-      if (objectMethodToJsoMethod.values().contains(x)) {
+      if (polyMethodToDevirtualMethods.containsValue(x)) {
         return false;
       }
       return true;
@@ -89,19 +142,13 @@
    * Maps each Object instance methods (ie, {@link Object#equals(Object)}) onto
    * its corresponding devirtualizing method.
    */
-  protected Map<JMethod, JMethod> objectMethodToJsoMethod = new HashMap<JMethod, JMethod>();
-
-  /**
-   * Contains the set of live instance methods on JavaScriptObject; typically
-   * overrides of Object methods.
-   */
-  protected Set<JMethod> virtualJsoMethods = new HashSet<JMethod>();
+  protected Map<JMethod, JMethod> polyMethodToJsoMethod = new HashMap<JMethod, JMethod>();
 
   /**
    * Contains the set of devirtualizing methods that replace polymorphic calls
    * to Object methods.
    */
-  private Set<JMethod> devirtualMethods = new HashSet<JMethod>();
+  private Map<JMethod, JMethod> polyMethodToDevirtualMethods = new HashMap<JMethod, JMethod>();
 
   /**
    * Contains the Cast.isJavaObject method.
@@ -110,67 +157,12 @@
 
   private final JProgram program;
 
+  private final CreateStaticImplsVisitor staticImplCreator;
+
   private JsoDevirtualizer(JProgram program) {
     this.program = program;
     this.isJavaObjectMethod = program.getIndexedMethod("Cast.isJavaObject");
-  }
-
-  /**
-   * 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 createDevirtualMethod(JMethod objectMethod, JMethod jsoImpl) {
-    JClassType jsoType = program.getJavaScriptObject();
-    SourceInfo sourceInfo = jsoType.getSourceInfo().makeChild(SourceOrigin.UNKNOWN);
-
-    // Create the new method.
-    String name = objectMethod.getName() + "__devirtual$";
-    JMethod newMethod = program.createMethod(sourceInfo, name, jsoType,
-        objectMethod.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 : objectMethod.getParams()) {
-      JProgram.createParameter(sourceInfo, oldParam.getName(),
-          oldParam.getType(), true, false, newMethod);
-    }
-    newMethod.freezeParamTypes();
-    newMethod.addThrownExceptions(objectMethod.getThrownExceptions());
-    sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(newMethod));
-
-    // Build from bottom up.
-    JMethodCall condition = new JMethodCall(sourceInfo, null,
-        isJavaObjectMethod);
-    condition.addArg(new JParameterRef(sourceInfo, thisParam));
-
-    JMethodCall thenValue = new JMethodCall(sourceInfo, new JParameterRef(
-        sourceInfo, thisParam), objectMethod);
-    for (JParameter param : newMethod.getParams()) {
-      if (param != thisParam) {
-        thenValue.addArg(new JParameterRef(sourceInfo, param));
-      }
-    }
-
-    JMethodCall elseValue = new JMethodCall(sourceInfo, null, jsoImpl);
-    for (JParameter param : newMethod.getParams()) {
-      elseValue.addArg(new JParameterRef(sourceInfo, param));
-    }
-
-    JConditional conditional = new JConditional(sourceInfo, objectMethod.getType(),
-        condition, thenValue, elseValue);
-
-    JReturnStatement returnStatement = new JReturnStatement(sourceInfo,
-        conditional);
-    ((JMethodBody) newMethod.getBody()).getBlock().addStmt(returnStatement);
-    return newMethod;
+    staticImplCreator = new CreateStaticImplsVisitor(program);
   }
 
   private void execImpl() {
@@ -179,54 +171,136 @@
       return;
     }
 
-    for (JMethod method : jsoType.getMethods()) {
-      if (method.needsVtable()) {
-        virtualJsoMethods.add(method);
-      }
-    }
-
-    if (virtualJsoMethods.isEmpty()) {
-      return;
-    }
-
-    CreateStaticImplsVisitor creator = new CreateStaticImplsVisitor(program);
-    for (JMethod method : virtualJsoMethods) {
-      // Ensure staticImpls exist for any instance methods.
-      JMethod jsoStaticImpl = program.getStaticImpl(method);
-      if (jsoStaticImpl == null) {
-        creator.accept(method);
-        jsoStaticImpl = program.getStaticImpl(method);
-        assert (jsoStaticImpl != null);
-      }
-
-      // Find the object method this instance method overrides.
-      JMethod objectOverride = findObjectOverride(method);
-      if (objectOverride != null) {
-        JMethod devirtualizer = createDevirtualMethod(objectOverride,
-            jsoStaticImpl);
-        devirtualMethods.add(devirtualizer);
-        objectMethodToJsoMethod.put(objectOverride, devirtualizer);
-      }
-    }
-
     RewriteVirtualDispatches rewriter = new RewriteVirtualDispatches();
     rewriter.accept(program);
     assert (rewriter.didChange());
   }
 
   /**
-   * Finds the object method this method overrides.
+   * Finds the method that overrides this method, starting with the target
+   * class.
    */
-  private JMethod findObjectOverride(JMethod method) {
-    Set<JMethod> overrides = program.typeOracle.getAllRealOverrides(method);
-    JMethod objectOverride = null;
-    for (JMethod override : overrides) {
-      if (override.getEnclosingType() == program.getTypeJavaLangObject()) {
-        objectOverride = override;
-        break;
+  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 objectOverride;
+    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
+    JExpression instance = polyMethodCall.getInstance();
+    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;
+  }
 }
+
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
index f96623d..10db7b3 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -33,7 +33,6 @@
 import com.google.gwt.dev.jjs.impl.TypeLinker;
 import com.google.gwt.dev.jjs.impl.FixAssignmentToUnbox;
 import com.google.gwt.dev.jjs.impl.GenerateJavaAST;
-import com.google.gwt.dev.jjs.impl.JavaScriptObjectNormalizer;
 import com.google.gwt.dev.jjs.impl.TypeMap;
 import com.google.gwt.dev.js.ast.JsProgram;
 
@@ -273,8 +272,6 @@
         }
       }
     }
-    // Replace references to JSO subtypes with JSO itself.
-    JavaScriptObjectNormalizer.exec(jprogram);
 
     ImplementClassLiteralsAsFields.exec(jprogram);
 
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java b/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java
index 03d7460..21f979d 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JjsTypeTest.java
@@ -60,6 +60,7 @@
   private JInterfaceType intfI;
   private JInterfaceType intfIBase;
   private JInterfaceType intfJ;
+  private JInterfaceType intfK;
   private JProgram program;
   private SourceInfo synthSource;
   private JReferenceType typeNull;
@@ -136,11 +137,16 @@
     assertTrue(typeOracle.canTriviallyCast(arrayOfArrayOfB, arrayOfObject));
     assertFalse(typeOracle.canTriviallyCast(arrayOfObject, arrayOfArrayOfB));
 
-    assertTrue(typeOracle.canTriviallyCast(classJso1, classJso2));
-    assertTrue(typeOracle.canTriviallyCast(classJso2, classJso1));
+    assertTrue(typeOracle.canTheoreticallyCast(classJso1, classJso2));
+    assertTrue(typeOracle.canTheoreticallyCast(classJso2, classJso1));
 
-    assertTrue(typeOracle.canTriviallyCast(classJso, classJso1));
-    assertTrue(typeOracle.canTriviallyCast(classJso, classJso1));
+    assertTrue(typeOracle.canTheoreticallyCast(classJso1, intfK));
+    assertTrue(typeOracle.canTheoreticallyCast(intfK, classJso1));
+
+    assertTrue(typeOracle.canTheoreticallyCast(intfJ, intfK));
+    assertTrue(typeOracle.canTheoreticallyCast(intfK, intfJ));
+
+    assertTrue(typeOracle.canTriviallyCast(classJso1, classJso));
 
     assertTrue(typeOracle.canTriviallyCast(arrayOfA, intfSerializable));
     assertFalse(typeOracle.canTriviallyCast(intfSerializable, arrayOfA));
@@ -150,15 +156,13 @@
 
     /*
      * Test that two types cannot both be trivially castable to each other,
-     * unless they are the same type. Or, unless they are both JSOs.
+     * unless they are the same type.
      */
     for (JReferenceType type1 : severalTypes()) {
       for (JReferenceType type2 : severalTypes()) {
         if (type1 != type2) {
-          if (!isJso(type1) || !isJso(type2)) {
-            assertFalse(typeOracle.canTriviallyCast(type1, type2)
-                && typeOracle.canTriviallyCast(type2, type1));
-          }
+          assertFalse(typeOracle.canTriviallyCast(type1, type2)
+              && typeOracle.canTriviallyCast(type2, type1));
         }
       }
     }
@@ -246,6 +250,7 @@
     intfI.addImplements(intfIBase);
 
     intfJ = createInterface("J");
+    intfK = createInterface("K");
 
     classBase = createClass("Base", classObject, false, false);
 
@@ -260,8 +265,10 @@
     classBSub = createClass("BSub", classB, false, false);
 
     classJso1 = createClass("Jso1", classJso, false, false);
+    classJso1.addImplements(intfJ);
     classJso2 = createClass("Jso2", classJso, false, false);
-
+    classJso2.addImplements(intfK);
+    
     program.typeOracle.computeBeforeAST();
 
     // Save off some miscellaneous types to test against
@@ -289,10 +296,6 @@
     return program.generalizeTypes(types);
   }
 
-  private boolean isJso(JReferenceType type) {
-    return typeOracle.canTriviallyCast(type, classJso);
-  }
-
   /**
    * Return several types, for exhaustively testing basic properties.
    */
@@ -313,6 +316,7 @@
     types.add(classString);
     types.add(intfI);
     types.add(intfJ);
+    types.add(intfK);
     types.add(intfIBase);
     types.add(classJso1);
     types.add(classJso2);
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
index 15406c0..6291398 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
@@ -19,6 +19,8 @@
 import com.google.gwt.dev.javac.impl.MockJavaResource;
 import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JNode;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.util.Empty;
 
@@ -63,6 +65,21 @@
       assertEquals(expectedSet, cfa.getInstantiatedTypes());
     }
 
+    public void assertOnlyLiveFieldsAndMethods(String... expected) {
+      Set<JNode> expectedSet = new HashSet<JNode>();
+      for (String expectedRef : expected) {
+        JField field = findField(program, expectedRef);
+        if (field != null) {
+          expectedSet.add(field);
+        } else {
+          JMethod method = findQualifiedMethod(program, expectedRef);
+          assertNotNull(method);
+          expectedSet.add(method);
+        }
+      }
+      assertEquals(expectedSet, cfa.getLiveFieldsAndMethods());
+    }
+
     public void assertOnlyLiveStrings(String... expectedStrings) {
       Set<String> expectedSet = new HashSet<String>();
       Collections.addAll(expectedSet, expectedStrings);
@@ -86,6 +103,76 @@
    * circumstances where values can pass from JS into Java.
    */
   public void testRescueJavaScriptObjectFromJsni() throws Exception {
+      sourceOracle.addOrReplace(new MockJavaResource("test.JsoIntf") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("public interface JsoIntf {");
+        code.append("  public int getAny();");
+        code.append("}");
+        return code;
+      }
+    });
+
+      sourceOracle.addOrReplace(new MockJavaResource("test.UpRefIntf") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("public interface UpRefIntf {");
+        code.append("  public int getFoo();");
+        code.append("}");
+        return code;
+      }
+    });
+
+     sourceOracle.addOrReplace(new MockJavaResource("test.NonImplementor") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("public class NonImplementor extends JavaScriptObject {");
+        code.append("  protected NonImplementor() {}");
+        code.append("  final public native int getFoo() /*-{ return 0; }-*/;");
+        code.append("}");
+        return code;
+      }
+    });
+
+     sourceOracle.addOrReplace(new MockJavaResource("test.VirtualUpRef") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("final public class VirtualUpRef extends NonImplementor implements UpRefIntf {");
+        code.append("  protected VirtualUpRef() {}");
+        code.append("  public static native VirtualUpRef create() /*-{ return  {}; }-*/;");
+        code.append("}");
+        return code;
+      }
+    });
+
+    sourceOracle.addOrReplace(new MockJavaResource("test.SingleJso") {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package test;");
+        code.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+        code.append("final public class SingleJso extends JavaScriptObject implements JsoIntf {");
+        code.append("  protected SingleJso() {}");
+        code.append("  public native int getAny() /*-{ return 1; }-*/;");
+        code.append("  public static native JsoIntf returnsJsoIntf() /*-{ return {}; }-*/;");
+        code.append("  public static native SingleJso returnsJso() /*-{ return {}; }-*/;");
+        code.append("}");
+        return code;
+      }
+    });
+
     sourceOracle.addOrReplace(new MockJavaResource("test.Foo") {
       @Override
       protected CharSequence getContent() {
@@ -122,6 +209,28 @@
     // Passing a parameter from JS to Java rescues.
     analyzeSnippet("Foo.passesJsoParam();").assertOnlyInstantiatedTypes(
         "JavaScriptObject", "Object");
+
+    // Returning a JSO subType instantiates it
+    analyzeSnippet("SingleJso.returnsJso();").assertOnlyInstantiatedTypes(
+        "SingleJso", "JavaScriptObject", "Object", "JsoIntf");
+
+    // Returning a JSO SingleJsoImpl instantiates it and the implementor
+    analyzeSnippet("SingleJso.returnsJsoIntf();").assertOnlyInstantiatedTypes(
+        "SingleJso", "JavaScriptObject", "Object", "JsoIntf");
+
+    // A virtual upref should still be rescued
+    analyzeSnippet("VirtualUpRef.create().getFoo();").assertOnlyInstantiatedTypes(
+        "VirtualUpRef", "NonImplementor", "JavaScriptObject", "Object", "UpRefIntf");
+    
+    // and its methods
+    analyzeSnippet("VirtualUpRef.create().getFoo();").assertOnlyLiveFieldsAndMethods(
+        "VirtualUpRef.$clinit", "VirtualUpRef.create",
+        "NonImplementor.$clinit","NonImplementor.getFoo",
+        "UpRefIntf.$clinit",
+        "JavaScriptObject.$clinit",
+        "EntryPoint.$clinit",
+        "EntryPoint.onModuleLoad",
+        "Object.$clinit");
   }
 
   private Result analyzeSnippet(String codeSnippet)
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
index ce1328b..82f673f 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
@@ -109,6 +109,15 @@
     return findMethod(mainType, methodName);
   }
 
+  public static JMethod findQualifiedMethod(JProgram program, String methodName) {
+    int pos = methodName.lastIndexOf('.');
+    assertTrue(pos > 0);
+    String typeName = methodName.substring(0, pos);
+    String unqualMethodName = methodName.substring(pos + 1);
+    JDeclaredType type = findType(program, typeName);
+    return findMethod(type, unqualMethodName);
+  }
+
   /**
    * Finds a type by name. The type name may be short, e.g. <code>"Foo"</code>,
    * or fully-qualified, e.g. <code>"com.google.example.Foo"</code>. If a short