Reintroduces DevMode JSO rewrite after correcting VerificationErrors.
http://gwt-code-reviews.appspot.com/473801/show
Patch by: bobv
Review by: scottb


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8295 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
index 1ceeea4..b80057c 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
@@ -328,7 +328,7 @@
    * @return <code>null</code> if the type is not found
    */
   public JClassType findType(String name) {
-    assert Name.isSourceName(name);
+    assert Name.isSourceName(name) : name + " is not a source name";
     return allTypes.get(name);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index a789ac0..a6ff29d 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -19,29 +19,21 @@
 import com.google.gwt.core.client.GwtScriptOnly;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.TreeLogger.Type;
-import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.core.ext.typeinfo.JMethod;
-import com.google.gwt.core.ext.typeinfo.JParameter;
-import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
-import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.CompilationUnit;
 import com.google.gwt.dev.javac.CompiledClass;
 import com.google.gwt.dev.javac.JsniMethod;
-import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.shell.rewrite.HasAnnotation;
 import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter;
-import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle;
-import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
+import com.google.gwt.dev.shell.rewrite.OriginalJsniSignature;
+import com.google.gwt.dev.shell.rewrite.SingleJsoImplSupport;
 import com.google.gwt.dev.util.JsniRef;
-import com.google.gwt.dev.util.Name;
 import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.Name.BinaryName;
 import com.google.gwt.dev.util.Name.InternalName;
 import com.google.gwt.dev.util.Name.SourceOrBinaryName;
-import com.google.gwt.dev.util.collect.Lists;
 import com.google.gwt.util.tools.Utility;
 
 import org.apache.commons.collections.map.AbstractReferenceMap;
@@ -64,9 +56,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedSet;
 import java.util.Stack;
-import java.util.TreeSet;
 
 /**
  * An isolated {@link ClassLoader} for running all user code. All user files are
@@ -146,23 +136,6 @@
       DispatchClassInfo dispClassInfo = getClassInfoFromClassName(className);
       if (dispClassInfo != null) {
         String memberName = parsed.memberSignature();
-
-        /*
-         * Disallow the use of JSNI references to SingleJsoImpl interface
-         * methods. This policy is due to web-mode dispatch implementation
-         * details; resolving the JSNI reference wouldn't be just be a name
-         * replacement, instead it would be necessary to significantly alter the
-         * semantics of the hand-written JS.
-         */
-        if (singleJsoImplTypes.contains(canonicalizeClassName(className))) {
-          logger.log(TreeLogger.WARN,
-              "Invalid JSNI reference to SingleJsoImpl interface (" + className
-                  + "); consider using a trampoline. "
-                  + "Expect subsequent failures.", new NoSuchFieldError(
-                  jsniMemberRef));
-          return -1;
-        }
-
         int memberId = dispClassInfo.getMemberId(memberName);
         if (memberId < 0) {
           if (!className.startsWith("java.")) {
@@ -294,11 +267,6 @@
         return null;
       }
 
-      // Map JSO type references to the appropriate impl class.
-      if (classRewriter.isJsoIntf(cls.getName())) {
-        cls = getClassFromBinaryName(cls.getName() + "$");
-      }
-
       /*
        * we need to create a new DispatchClassInfo since we have never seen this
        * class before under any source or binary class name
@@ -355,325 +323,6 @@
   }
 
   /**
-   * Implements {@link InstanceMethodOracle} on behalf of the
-   * {@link HostedModeClassRewriter}. Implemented using {@link TypeOracle}.
-   */
-  private class MyInstanceMethodOracle implements InstanceMethodOracle {
-
-    private final Map<String, Set<JClassType>> signatureToDeclaringClasses = new HashMap<String, Set<JClassType>>();
-
-    public MyInstanceMethodOracle(Set<JClassType> jsoTypes,
-        JClassType javaLangObject, SingleJsoImplData jsoData) {
-
-      // Record that the JSO implements its own methods
-      for (JClassType type : jsoTypes) {
-        for (JMethod method : type.getMethods()) {
-          if (!method.isStatic()) {
-            assert !method.isAbstract() : "Abstract method in JSO type "
-                + method;
-            add(type, method);
-          }
-        }
-      }
-
-      /*
-       * Record the implementing types for methods defined in SingleJsoImpl
-       * interfaces. We have to make this pass because of possible variance in
-       * the return types between the abstract method declaration in the
-       * interface and the concrete method.
-       */
-      for (String intfName : jsoData.getSingleJsoIntfTypes()) {
-        // We only store the name in the data block to keep it lightweight
-        JClassType intf = typeOracle.findType(Name.InternalName.toSourceName(intfName));
-        JClassType jso = typeOracle.getSingleJsoImpl(intf);
-        for (JMethod method : intf.getMethods()) {
-          add(jso, method);
-        }
-      }
-
-      // Object clobbers everything.
-      for (JMethod method : javaLangObject.getMethods()) {
-        if (!method.isStatic()) {
-          String signature = createSignature(method);
-          Set<JClassType> declaringClasses = new HashSet<JClassType>();
-          signatureToDeclaringClasses.put(signature, declaringClasses);
-          declaringClasses.add(javaLangObject);
-        }
-      }
-    }
-
-    public String findOriginalDeclaringClass(String desc, String signature) {
-      // Lookup the method.
-      Set<JClassType> declaringClasses = signatureToDeclaringClasses.get(signature);
-      assert declaringClasses != null : "No classes for " + signature;
-      if (declaringClasses.size() == 1) {
-        // Shortcut: if there's only one answer, it must be right.
-        return createDescriptor(declaringClasses.iterator().next());
-      }
-      // Must check for assignability.
-      String sourceName = desc.replace('/', '.');
-      sourceName = sourceName.replace('$', '.');
-      JClassType declaredType = typeOracle.findType(sourceName);
-
-      // Check if I declare this directly.
-      if (declaringClasses.contains(declaredType)) {
-        return desc;
-      }
-
-      // Check to see what type I am assignable to.
-      for (JClassType possibleSupertype : declaringClasses) {
-        if (declaredType.isAssignableTo(possibleSupertype)) {
-          return createDescriptor(possibleSupertype);
-        }
-      }
-      throw new IllegalArgumentException("Could not resolve signature '"
-          + signature + "' from class '" + desc + "'");
-    }
-
-    /**
-     * Record that a given JSO type contains the concrete implementation of a
-     * (possibly abstract) method.
-     */
-    private void add(JClassType type, JMethod method) {
-      String signature = createSignature(method);
-      Set<JClassType> declaringClasses = signatureToDeclaringClasses.get(signature);
-      if (declaringClasses == null) {
-        declaringClasses = new HashSet<JClassType>();
-        signatureToDeclaringClasses.put(signature, declaringClasses);
-      }
-      declaringClasses.add(type);
-    }
-
-    private String createDescriptor(JClassType type) {
-      String jniSignature = type.getJNISignature();
-      return jniSignature.substring(1, jniSignature.length() - 1);
-    }
-
-    private String createSignature(JMethod method) {
-      StringBuffer sb = new StringBuffer(method.getName());
-      sb.append('(');
-      for (JParameter param : method.getParameters()) {
-        sb.append(param.getType().getJNISignature());
-      }
-      sb.append(')');
-      sb.append(method.getReturnType().getJNISignature());
-      String signature = sb.toString();
-      return signature;
-    }
-  }
-
-  /**
-   * Cook up the data we need to support JSO subtypes that implement interfaces
-   * with methods. This includes the set of SingleJsoImpl interfaces actually
-   * implemented by a JSO type, the mangled method names, and the names of the
-   * Methods that should actually implement the virtual functions.
-   * 
-   * Given the current implementation of JSO$ and incremental execution of
-   * rebinds, it's not possible for Generators to produce additional
-   * JavaScriptObject subtypes, so this data can remain static.
-   */
-  private class MySingleJsoImplData implements SingleJsoImplData {
-    private final SortedSet<String> mangledNames = new TreeSet<String>();
-    private final Map<String, List<com.google.gwt.dev.asm.commons.Method>> mangledNamesToDeclarations = new HashMap<String, List<com.google.gwt.dev.asm.commons.Method>>();
-    private final Map<String, List<com.google.gwt.dev.asm.commons.Method>> mangledNamesToImplementations = new HashMap<String, List<com.google.gwt.dev.asm.commons.Method>>();
-    private final SortedSet<String> unmodifiableNames = Collections.unmodifiableSortedSet(mangledNames);
-    private final Set<String> unmodifiableIntfNames = Collections.unmodifiableSet(singleJsoImplTypes);
-
-    public MySingleJsoImplData() {
-      // Loop over all interfaces with JSO implementations
-      typeLoop : for (JClassType type : typeOracle.getSingleJsoImplInterfaces()) {
-        assert type.isInterface() == type : "Expecting interfaces only";
-
-        /*
-         * By preemptively adding all possible mangled names by which a method
-         * could be called, we greatly simplify the logic necessary to rewrite
-         * the call-site.
-         * 
-         * interface A {void m();}
-         * 
-         * interface B extends A {void z();}
-         * 
-         * becomes
-         * 
-         * c_g_p_A_m() -> JsoA$.m$()
-         * 
-         * c_g_p_B_m() -> JsoA$.m$()
-         * 
-         * c_g_p_B_z() -> JsoB$.z$()
-         */
-        for (JMethod intfMethod : type.getOverridableMethods()) {
-          assert intfMethod.isAbstract() : "Expecting only abstract methods";
-
-          /*
-           * It is necessary to locate the implementing type on a per-method
-           * basis. Consider the case of
-           * 
-           * @SingleJsoImpl interface C extends A, B {}
-           * 
-           * Methods inherited from interfaces A and B must be dispatched to
-           * their respective JSO implementations.
-           */
-          JClassType implementingType = typeOracle.getSingleJsoImpl(intfMethod.getEnclosingType());
-
-          if (implementingType == null) {
-            /*
-             * This means that there is no concrete implementation of the
-             * interface by a JSO. Any implementation that might be created by a
-             * Generator won't be a JSO subtype, so we'll just ignore it as an
-             * actionable type. Were Generators ever able to create new JSO
-             * subtypes, we'd have to speculatively rewrite the callsite.
-             */
-            continue typeLoop;
-          }
-
-          /*
-           * Record the type as being actionable.
-           */
-          singleJsoImplTypes.add(canonicalizeClassName(getBinaryName(type)));
-
-          /*
-           * The mangled name adds the current interface like
-           * 
-           * com_foo_Bar_methodName
-           */
-          String mangledName = getBinaryName(type).replace('.', '_') + "_"
-              + intfMethod.getName();
-          mangledNames.add(mangledName);
-
-          /*
-           * Handle virtual overrides by finding the method that we would
-           * normally invoke and using its declaring class as the dispatch
-           * target.
-           */
-          JMethod implementingMethod;
-          while ((implementingMethod = findOverloadUsingErasure(
-              implementingType, intfMethod)) == null) {
-            implementingType = implementingType.getSuperclass();
-          }
-          // implementingmethod and implementingType cannot be null here
-
-          /*
-           * Create a pseudo-method declaration for the interface method. This
-           * should look something like
-           * 
-           * ReturnType method$ (ParamType, ParamType)
-           * 
-           * This must be kept in sync with the WriteJsoImpl class.
-           */
-          {
-            String decl = getBinaryOrPrimitiveName(intfMethod.getReturnType().getErasedType())
-                + " " + intfMethod.getName() + "(";
-            for (JParameter param : intfMethod.getParameters()) {
-              decl += ",";
-              decl += getBinaryOrPrimitiveName(param.getType().getErasedType());
-            }
-            decl += ")";
-
-            com.google.gwt.dev.asm.commons.Method declaration = com.google.gwt.dev.asm.commons.Method.getMethod(decl);
-            addToMap(mangledNamesToDeclarations, mangledName, declaration);
-          }
-
-          /*
-           * Cook up the a pseudo-method declaration for the concrete type. This
-           * should look something like
-           * 
-           * ReturnType method$ (JsoType, ParamType, ParamType)
-           * 
-           * This must be kept in sync with the WriteJsoImpl class.
-           */
-          {
-            String returnName = getBinaryOrPrimitiveName(implementingMethod.getReturnType().getErasedType());
-            String jsoName = getBinaryOrPrimitiveName(implementingType);
-
-            String decl = returnName + " " + intfMethod.getName() + "$ ("
-                + jsoName;
-            for (JParameter param : implementingMethod.getParameters()) {
-              decl += ",";
-              decl += getBinaryOrPrimitiveName(param.getType().getErasedType());
-            }
-            decl += ")";
-
-            com.google.gwt.dev.asm.commons.Method toImplement = com.google.gwt.dev.asm.commons.Method.getMethod(decl);
-            addToMap(mangledNamesToImplementations, mangledName, toImplement);
-          }
-        }
-      }
-
-      if (logger.isLoggable(Type.SPAM)) {
-        TreeLogger dumpLogger = logger.branch(Type.SPAM,
-            "SingleJsoImpl method mappings");
-        for (Map.Entry<String, List<com.google.gwt.dev.asm.commons.Method>> entry : mangledNamesToImplementations.entrySet()) {
-          dumpLogger.log(Type.SPAM, entry.getKey() + " -> " + entry.getValue());
-        }
-      }
-    }
-
-    public List<com.google.gwt.dev.asm.commons.Method> getDeclarations(
-        String mangledName) {
-      List<com.google.gwt.dev.asm.commons.Method> toReturn = mangledNamesToDeclarations.get(mangledName);
-      return toReturn == null ? null : Collections.unmodifiableList(toReturn);
-    }
-
-    public List<com.google.gwt.dev.asm.commons.Method> getImplementations(
-        String mangledName) {
-      List<com.google.gwt.dev.asm.commons.Method> toReturn = mangledNamesToImplementations.get(mangledName);
-      return toReturn == null ? toReturn
-          : Collections.unmodifiableList(toReturn);
-    }
-
-    public SortedSet<String> getMangledNames() {
-      return unmodifiableNames;
-    }
-
-    public Set<String> getSingleJsoIntfTypes() {
-      return unmodifiableIntfNames;
-    }
-
-    /**
-     * Assumes that the usual case is a 1:1 mapping.
-     */
-    private <K, V> void addToMap(Map<K, List<V>> map, K key, V value) {
-      List<V> list = map.get(key);
-      if (list == null) {
-        map.put(key, Lists.create(value));
-      } else {
-        List<V> maybeOther = Lists.add(list, value);
-        if (maybeOther != list) {
-          map.put(key, maybeOther);
-        }
-      }
-    }
-
-    /**
-     * Looks for a concrete implementation of <code>intfMethod</code> in
-     * <code>implementingType</code>.
-     */
-    private JMethod findOverloadUsingErasure(JClassType implementingType,
-        JMethod intfMethod) {
-
-      int numParams = intfMethod.getParameters().length;
-      JType[] erasedTypes = new JType[numParams];
-      for (int i = 0; i < numParams; i++) {
-        erasedTypes[i] = intfMethod.getParameters()[i].getType().getErasedType();
-      }
-
-      outer : for (JMethod method : implementingType.getOverloads(intfMethod.getName())) {
-        JParameter[] params = method.getParameters();
-        if (params.length != numParams) {
-          continue;
-        }
-        for (int i = 0; i < numParams; i++) {
-          if (params[i].getType().getErasedType() != erasedTypes[i]) {
-            continue outer;
-          }
-        }
-        return method;
-      }
-      return null;
-    }
-  }
-
-  /**
    * The names of the bridge classes.
    */
   private static final Map<String, Class<?>> BRIDGE_CLASS_NAMES = new HashMap<String, Class<?>>();
@@ -683,7 +332,8 @@
    * space (thus, they bridge across the spaces).
    */
   private static final Class<?>[] BRIDGE_CLASSES = new Class<?>[] {
-      ShellJavaScriptHost.class, GWTBridge.class};
+      ShellJavaScriptHost.class, GWTBridge.class, OriginalJsniSignature.class,
+      SingleJsoImplSupport.class};
 
   private static final boolean CLASS_DUMP = Boolean.getBoolean("gwt.dev.classDump");
 
@@ -830,8 +480,6 @@
 
   private ShellJavaScriptHost shellJavaScriptHost;
 
-  private final Set<String> singleJsoImplTypes = new HashSet<String>();
-
   /**
    * Used by {@link #findClass(String)} to prevent reentrant JSNI injection.
    */
@@ -861,36 +509,10 @@
 
     ensureJavaScriptHostBytes(logger);
 
-    // Create a class rewriter based on all the subtypes of the JSO class.
+    // Create a class rewriter based on availability of JSO class.
     JClassType jsoType = typeOracle.findType(JsValueGlue.JSO_CLASS);
     if (jsoType != null) {
-
-      // Create a set of binary names.
-      Set<JClassType> jsoTypes = new HashSet<JClassType>();
-      JClassType[] jsoSubtypes = jsoType.getSubtypes();
-      Collections.addAll(jsoTypes, jsoSubtypes);
-      jsoTypes.add(jsoType);
-
-      Set<String> jsoTypeNames = new HashSet<String>();
-      Map<String, List<String>> jsoSuperTypes = new HashMap<String, List<String>>();
-      for (JClassType type : jsoTypes) {
-        List<String> types = new ArrayList<String>();
-        types.add(getBinaryName(type.getSuperclass()));
-        for (JClassType impl : type.getImplementedInterfaces()) {
-          types.add(getBinaryName(impl));
-        }
-
-        String binaryName = getBinaryName(type);
-        jsoTypeNames.add(binaryName);
-        jsoSuperTypes.put(binaryName, types);
-      }
-
-      SingleJsoImplData singleJsoImplData = new MySingleJsoImplData();
-
-      MyInstanceMethodOracle mapper = new MyInstanceMethodOracle(jsoTypes,
-          typeOracle.getJavaLangObject(), singleJsoImplData);
-      classRewriter = new HostedModeClassRewriter(jsoTypeNames, jsoSuperTypes,
-          singleJsoImplData, mapper);
+      classRewriter = new HostedModeClassRewriter(typeOracle);
     } else {
       // If we couldn't find the JSO class, we don't need to do any rewrites.
       classRewriter = null;
@@ -1020,8 +642,8 @@
      * when loading a JSO interface class; just wait until the implementation
      * class is loaded.
      */
-    if (!classRewriter.isJsoIntf(className)) {
-      CompilationUnit unit = getUnitForClassName(canonicalizeClassName(className));
+    {
+      CompilationUnit unit = getUnitForClassName(BinaryName.toInternalName(className));
       if (unit != null) {
         toInject.push(unit);
       }
@@ -1064,37 +686,27 @@
     dispClassInfoOracle.clear();
   }
 
-  /**
-   * Convert a binary class name into a resource-like name.
-   */
-  private String canonicalizeClassName(String className) {
-    String lookupClassName = className.replace('.', '/');
-    // A JSO impl class ends with $, strip it
-    if (classRewriter != null && classRewriter.isJsoImpl(className)) {
-      lookupClassName = lookupClassName.substring(0,
-          lookupClassName.length() - 1);
-    }
-    return lookupClassName;
-  }
-
   @SuppressWarnings("deprecation")
   private byte[] findClassBytes(String className) {
     if (JavaScriptHost.class.getName().equals(className)) {
       // No need to rewrite.
       return javaScriptHostBytes;
-    }
-
-    if (classRewriter != null && classRewriter.isJsoIntf(className)) {
-      // Generate a synthetic JSO interface class.
-      byte[] newBytes = classRewriter.writeJsoIntf(className);
+    } else if (className.endsWith(HostedModeClassRewriter.SINGLE_JSO_IMPL_ADJUNCT_SUFFIX)) {
+      byte[] bytes = classRewriter.writeSingleJsoImplAdjunct(className);
       if (CLASS_DUMP) {
-        classDump(className, newBytes);
+        classDump(className, bytes);
       }
-      return newBytes;
+      return bytes;
+    } else if (className.startsWith(HostedModeClassRewriter.DISAMBIGUATOR_TYPE_NAME)) {
+      byte[] bytes = classRewriter.writeConstructorDisambiguationType(className);
+      if (CLASS_DUMP) {
+        classDump(className, bytes);
+      }
+      return bytes;
     }
 
     // A JSO impl class needs the class bytes for the original class.
-    String lookupClassName = canonicalizeClassName(className);
+    String lookupClassName = BinaryName.toInternalName(className);
 
     CompiledClass compiledClass = compilationState.getClassFileMap().get(
         lookupClassName);
@@ -1155,8 +767,8 @@
       if (unit != null) {
         anonymousClassMap = unit.getAnonymousClassMap();
       }
-      byte[] newBytes = classRewriter.rewrite(typeOracle, className,
-          classBytes, anonymousClassMap);
+      byte[] newBytes = classRewriter.rewrite(className, classBytes,
+          anonymousClassMap);
       if (CLASS_DUMP) {
         if (!Arrays.equals(classBytes, newBytes)) {
           classDump(className, newBytes);
@@ -1167,29 +779,6 @@
     return classBytes;
   }
 
-  private String getBinaryName(JClassType type) {
-    String name = type.getPackage().getName() + '.';
-    name += type.getName().replace('.', '$');
-    return name;
-  }
-
-  private String getBinaryOrPrimitiveName(JType type) {
-    JArrayType asArray = type.isArray();
-    JClassType asClass = type.isClassOrInterface();
-    JPrimitiveType asPrimitive = type.isPrimitive();
-    if (asClass != null) {
-      return getBinaryName(asClass);
-    } else if (asPrimitive != null) {
-      return asPrimitive.getQualifiedSourceName();
-    } else if (asArray != null) {
-      JType componentType = asArray.getComponentType();
-      return getBinaryOrPrimitiveName(componentType) + "[]";
-    } else {
-      throw new InternalCompilerException("Cannot create binary name for "
-          + type.getQualifiedSourceName());
-    }
-  }
-
   /**
    * Returns the compilationUnit corresponding to the className. For nested
    * classes, the unit corresponding to the top level type is returned.
@@ -1212,6 +801,8 @@
     if (unit == null || unit.getJsniMethods() == null) {
       return;
     }
+    logger.log(TreeLogger.SPAM, "Injecting JSNI methods for "
+        + unit.getTypeName());
     shellJavaScriptHost.createNativeMethods(logger, unit.getJsniMethods(), this);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java b/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
index 27819b3..9fecfa2 100644
--- a/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
+++ b/dev/core/src/com/google/gwt/dev/shell/DispatchClassInfo.java
@@ -15,8 +15,10 @@
  */
 package com.google.gwt.dev.shell;
 
+import com.google.gwt.dev.shell.rewrite.OriginalJsniSignature;
 import com.google.gwt.dev.util.JsniRef;
 
+import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
@@ -153,6 +155,21 @@
     String name;
     Class<?>[] paramTypes;
 
+    if (member instanceof AccessibleObject) {
+      AccessibleObject accessibleObject = (AccessibleObject) member;
+      OriginalJsniSignature signature = accessibleObject.getAnnotation(OriginalJsniSignature.class);
+      if (signature != null) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(signature.name());
+        if (wildcardParamList) {
+          sb.append("(*)");
+        } else {
+          sb.append(signature.paramList());
+        }
+        return sb.toString();
+      }
+    }
+
     if (member instanceof Field) {
       return member.getName();
     } else if (member instanceof SyntheticClassMember) {
@@ -168,7 +185,7 @@
           + member.getClass().getName());
     }
 
-    StringBuffer sb = new StringBuffer();
+    StringBuilder sb = new StringBuilder();
     sb.append(name);
     sb.append("(");
     if (wildcardParamList) {
diff --git a/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java b/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java
index 02e2cdd..fdefaed 100644
--- a/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java
+++ b/dev/core/src/com/google/gwt/dev/shell/JsValueGlue.java
@@ -16,6 +16,7 @@
 package com.google.gwt.dev.shell;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.shell.rewrite.SingleJsoImplSupport;
 import com.google.gwt.dev.util.TypeInfo;
 
 import java.lang.reflect.Constructor;
@@ -29,11 +30,13 @@
 public final class JsValueGlue {
   public static final String HOSTED_MODE_REFERENCE = "hostedModeReference";
   public static final String JSO_CLASS = "com.google.gwt.core.client.JavaScriptObject";
-  public static final String JSO_IMPL_CLASS = "com.google.gwt.core.client.JavaScriptObject$";
 
   /**
-   * Create a JavaScriptObject instance referring to this JavaScript object.
+   * Create a JavaScriptObject instance referring to this JavaScript object,
+   * optionally wrapping the object in the desired JSO facade type.
    * 
+   * @param desiredType the subclass of JavaScriptObject that the calling code
+   *          wishes to see, or <code>null</code>
    * @param classLoader the classLoader to create from
    * @return the constructed JavaScriptObject
    */
@@ -41,23 +44,18 @@
       CompilingClassLoader classLoader) {
     Throwable caught;
     try {
+      Class<?> jsoType = Class.forName(JSO_CLASS, true, classLoader);
+
       // See if there's already a wrapper object (assures identity comparison).
       Object jso = classLoader.getCachedJso(value.getJavaScriptObjectPointer());
-      if (jso != null) {
-        return jso;
+      if (jso == null) {
+        // Instantiate the JSO instance.
+        Constructor<?> ctor = jsoType.getDeclaredConstructor(Object.class);
+        jso = ctor.newInstance(value);
+
+        classLoader.putCachedJso(value.getJavaScriptObjectPointer(), jso);
       }
 
-      // Instantiate the JSO class.
-      Class<?> jsoType = Class.forName(JSO_IMPL_CLASS, true, classLoader);
-      Constructor<?> ctor = jsoType.getDeclaredConstructor();
-      ctor.setAccessible(true);
-      jso = ctor.newInstance();
-
-      // Set the reference field to this JsValue using reflection.
-      Field referenceField = jsoType.getField(HOSTED_MODE_REFERENCE);
-      referenceField.set(jso, value);
-
-      classLoader.putCachedJso(value.getJavaScriptObjectPointer(), jso);
       return jso;
     } catch (InstantiationException e) {
       caught = e;
@@ -73,8 +71,6 @@
       caught = e;
     } catch (ClassNotFoundException e) {
       caught = e;
-    } catch (NoSuchFieldException e) {
-      caught = e;
     }
     throw new RuntimeException("Error creating JavaScript object", caught);
   }
@@ -88,7 +84,7 @@
    * @param msgPrefix a prefix for error/warning messages
    * @return the object reference
    * @throws com.google.gwt.dev.shell.HostedModeException if the JavaScript
-   *     object is not assignable to the supplied type.
+   *           object is not assignable to the supplied type.
    */
   @SuppressWarnings("unchecked")
   public static <T> T get(JsValue value, CompilingClassLoader cl,
@@ -166,7 +162,11 @@
       return type.cast(value.getString());
     }
     if (value.isJavaScriptObject()) {
-      return type.cast(createJavaScriptObject(value, cl));
+      Object jso = createJavaScriptObject(value, cl);
+      if (type != Object.class) {
+        jso = SingleJsoImplSupport.cast(jso, type);
+      }
+      return type.cast(jso);
     }
 
     // Just don't know what do to with this.
@@ -217,8 +217,8 @@
     } else {
       // not a boxed primitive
       try {
-        Class<?> jsoType = Class.forName(JSO_IMPL_CLASS, false, cl);
-        if (jsoType == obj.getClass()) {
+        Class<?> jsoType = Class.forName(JSO_CLASS, false, cl);
+        if (jsoType.isInstance(obj)) {
           JsValue jsObject = getUnderlyingObject(obj);
           value.setValue(jsObject);
           return;
@@ -258,9 +258,10 @@
       }
       intVal = (int) doubleVal;
       if (intVal != doubleVal) {
-        ModuleSpace.getLogger().log(TreeLogger.WARN,
+        ModuleSpace.getLogger().log(
+            TreeLogger.WARN,
             msgPrefix + ": Rounding double (" + doubleVal + ") to int for "
-            + typeName, null);
+                + typeName, null);
       }
     } else {
       throw new HostedModeException(msgPrefix + ": JS value of type "
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/DebugAnalyzerAdapter.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/DebugAnalyzerAdapter.java
new file mode 100644
index 0000000..44bbf8a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/DebugAnalyzerAdapter.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2010 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.shell.rewrite;
+
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.EXTRA_DEBUG_DATA;
+
+import com.google.gwt.dev.asm.Attribute;
+import com.google.gwt.dev.asm.ByteVector;
+import com.google.gwt.dev.asm.ClassWriter;
+import com.google.gwt.dev.asm.Label;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.commons.AnalyzerAdapter;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A utility base class that allows rewriting passes to record label-based
+ * messages in the emitted bytecode.
+ */
+class DebugAnalyzerAdapter extends AnalyzerAdapter {
+  /**
+   * Used to record debugging data generated by {@link #recordDebugData}.
+   * 
+   * @see #visitEnd()
+   */
+  private static class DebugAttribute extends Attribute {
+    private final LinkedHashMap<Label, String> debugData;
+
+    private DebugAttribute(String name, LinkedHashMap<Label, String> debugData) {
+      super(name);
+      this.debugData = debugData;
+    }
+
+    @Override
+    protected Label[] getLabels() {
+      return debugData.keySet().toArray(new Label[debugData.size()]);
+    }
+
+    @Override
+    protected ByteVector write(ClassWriter cw, byte[] code, int len,
+        int maxStack, int maxLocals) {
+      StringBuilder sb = new StringBuilder();
+      for (Map.Entry<Label, String> entry : debugData.entrySet()) {
+        String line = "Offset " + entry.getKey().getOffset() + " : "
+            + entry.getValue() + "\n";
+        sb.append(line);
+      }
+      ByteVector toReturn = new ByteVector();
+      toReturn.putUTF8(sb.toString());
+      return toReturn;
+    }
+  }
+
+  /**
+   * Decodes the UTF8-as-octets output from javap.
+   */
+  public static void main(String args[]) throws IOException {
+    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+    Pattern p = Pattern.compile("[0-9A-F]{2}");
+    String line;
+    while ((line = reader.readLine()) != null) {
+      if (line.length() > 0) {
+        Matcher m = p.matcher(line);
+        while (m.find()) {
+          bytes.write(Byte.parseByte(m.group(), 16));
+        }
+      } else {
+        String message = new String(bytes.toByteArray());
+        System.out.println(message);
+        bytes = new ByteArrayOutputStream();
+      }
+    }
+  }
+
+  private final LinkedHashMap<Label, String> debugData = EXTRA_DEBUG_DATA
+      ? new LinkedHashMap<Label, String>() : null;
+
+  protected DebugAnalyzerAdapter(String owner, int access, String name,
+      String desc, MethodVisitor mv) {
+    super(owner, access, name, desc, mv);
+  }
+
+  /**
+   * Record debugging data, if any, in an extra attribute on the method.
+   */
+  @Override
+  public void visitEnd() {
+    if (EXTRA_DEBUG_DATA && !debugData.isEmpty()) {
+      super.visitAttribute(new DebugAttribute(getClass().getCanonicalName(),
+          debugData));
+    }
+    super.visitEnd();
+  }
+
+  /**
+   * When things go wrong in the field, users can enable rewritten class-file
+   * dumping and send us the class files. Most of the other rewriting passes add
+   * whole classes or methods so their effects are easy to find. Since this
+   * rewriting pass is rather subtle, we'll add an extra method attribute that
+   * contains a UTF8-encoded diagnostic log.
+   */
+  protected void recordDebugData(String message) {
+    if (EXTRA_DEBUG_DATA) {
+      Label castLocation = new Label();
+      debugData.put(castLocation, message);
+      super.visitLabel(castLocation);
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
index 319aeb3..ca1f488 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
@@ -15,22 +15,26 @@
  */
 package com.google.gwt.dev.shell.rewrite;
 
+import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.asm.ClassAdapter;
 import com.google.gwt.dev.asm.ClassReader;
 import com.google.gwt.dev.asm.ClassVisitor;
 import com.google.gwt.dev.asm.ClassWriter;
 import com.google.gwt.dev.asm.Opcodes;
-import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.asm.Type;
 import com.google.gwt.dev.shell.JsValueGlue;
+import com.google.gwt.dev.util.Name.BinaryName;
+import com.google.gwt.dev.util.Name.InternalName;
+import com.google.gwt.dev.util.collect.Lists;
 
-import java.util.Collections;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedSet;
 
 /**
  * This class performs any and all byte code rewriting needed to make hosted
@@ -38,25 +42,168 @@
  * <ol>
  * <li>Rewrites all native methods into non-native thunks to call JSNI via
  * {@link com.google.gwt.dev.shell.JavaScriptHost}.</li>
- * <li>Rewrites all JSO types into an interface type (which retains the original
- * name) and an implementation type (which has a $ appended).</li>
- * <li>All JSO interface types are empty and mirror the original type hierarchy.
- * </li>
- * <li>All JSO impl types contain the guts of the original type, except that all
- * instance methods are reimplemented as statics.</li>
- * <li>Calls sites to JSO types rewritten to dispatch to impl types. Any virtual
- * calls are also made static. Static field references to JSO types reference
- * static fields in the the impl class.</li>
- * <li>JavaScriptObject$ implements all the interface types and is the only
- * instantiable type.</li>
+ * <li>JavaScriptObject and its subtypes gain a static {@value #REWRAP_METHOD}
+ * method and new constructor methods.</li>
+ * <li>All uses of <code>==</code> object comparisons gain an explicit cast to
+ * Object to force canonicalization.</li>
+ * <li>Casts and assignments to Object, JavaScriptObject, and interface types
+ * have runtime type-fitting code added to support the not-really-a-type
+ * semantic.</li>
+ * <li>All interfaces that a JSO may implement have an adjunct class generated
+ * that aids the SingleJsoImpl cast operations.</li>
  * </ol>
  * 
  * @see RewriteJsniMethods
- * @see RewriteRefsToJsoClasses
- * @see WriteJsoInterface
  * @see WriteJsoImpl
+ * @see RewriteObjectComparisons
+ * @see RewriteJsoCasts
+ * @see WriteSingleJsoSupportCode
  */
 public class HostedModeClassRewriter {
+
+  /**
+   * An oracle to answer queries from the rewriting passes about the
+   * type-system.
+   */
+  static class RewriterOracle {
+    /**
+     * Used by {@link #getConstructorDisambiguator}.
+     */
+    private static final String[] EMPTY_STRING = new String[0];
+
+    private final Map<String, String[]> arrayDisambiguations = new HashMap<String, String[]>();
+
+    private final JClassType jsoType;
+
+    /**
+     * The TypeOracle for the module being rewritten. We need the typeOracle to
+     * provide a live view of the module's type-system, since Generators may add
+     * new JSO types.
+     */
+    private final TypeOracle typeOracle;
+
+    public RewriterOracle(TypeOracle typeOracle) {
+      this.typeOracle = typeOracle;
+      jsoType = typeOracle.findType(JsValueGlue.JSO_CLASS);
+      assert jsoType != null : "No JavaScriptObject type";
+    }
+
+    /**
+     * Returns <code>true</code> if a given type is potentially assignable from
+     * a JSO subtype.
+     */
+    public boolean couldContainJso(String internalName) {
+      return "java/lang/Object".equals(internalName)
+          || JAVASCRIPTOBJECT_DESC.equals(internalName)
+          || isJsoOrSubtype(internalName)
+          || (!internalName.startsWith("java/") && isInterface(internalName));
+    }
+
+    public String[] getAllSuperInterfaces(String[] interfaces) {
+      Set<String> toReturn = new HashSet<String>();
+      Set<JClassType> seen = new HashSet<JClassType>();
+      List<JClassType> queue = new LinkedList<JClassType>();
+
+      for (String intf : interfaces) {
+        JClassType type = typeOracle.findType(InternalName.toSourceName(intf));
+        if (type == null) {
+          throw new RuntimeException("Unknown type " + intf);
+        }
+        queue.add(type);
+      }
+
+      while (!queue.isEmpty()) {
+        JClassType intf = queue.remove(0);
+        if (seen.contains(intf)) {
+          continue;
+        }
+        seen.add(intf);
+        toReturn.add(BinaryName.toInternalName(intf.getQualifiedBinaryName()));
+        queue.addAll(Arrays.asList(intf.getImplementedInterfaces()));
+      }
+
+      String[] array = toReturn.toArray(new String[toReturn.size()]);
+      Arrays.sort(array);
+      return array;
+    }
+
+    /**
+     * In order to be able to upcast all JSO subtype arrays to JavaScriptObject
+     * arrays, it is necessary to be able to maintain distinguishing method
+     * descriptors. If the given descriptor will be affected by the array upcast
+     * transformation, this method will return an array containing the internal
+     * names of any additional types that should be appended to the descriptor.
+     */
+    public String[] getArrayDisambiguator(String desc) {
+      String[] cached = arrayDisambiguations.get(desc);
+      if (cached != null) {
+        return cached;
+      }
+
+      List<String> toReturn = Lists.create();
+      for (Type t : Type.getArgumentTypes(desc)) {
+        if (t.getSort() == Type.ARRAY
+            && t.getElementType().getSort() == Type.OBJECT) {
+          String leafName = t.getElementType().getInternalName();
+          if (isJsoOrSubtype(leafName)) {
+            String disambiguatotr = DISAMBIGUATOR_TYPE_INTERNAL_NAME + "$"
+                + InternalName.toIdentifier(leafName);
+            toReturn = Lists.add(toReturn, disambiguatotr);
+          }
+        }
+      }
+
+      String[] array;
+      if (toReturn.isEmpty()) {
+        array = EMPTY_STRING;
+      } else {
+        array = toReturn.toArray(new String[toReturn.size()]);
+      }
+      arrayDisambiguations.put(desc, array);
+
+      return array;
+    }
+
+    public boolean isInterface(String internalName) {
+      String sourceName = InternalName.toSourceName(internalName);
+      if (sourceName.contains("$")) {
+        // Anonymous type
+        return false;
+      }
+      JClassType type = typeOracle.findType(sourceName);
+      return type != null && type.isInterface() != null;
+    }
+
+    public boolean isJsoOrSubtype(String internalName) {
+      String sourceName = InternalName.toSourceName(internalName);
+      if (sourceName.contains("$")) {
+        // Anonymous type
+        return false;
+      }
+      JClassType type = typeOracle.findType(sourceName);
+      return type != null && jsoType.isAssignableFrom(type);
+    }
+
+    public boolean isTagInterface(String internalName) {
+      String sourceName = InternalName.toSourceName(internalName);
+      if (sourceName.contains("$")) {
+        // Anonymous type
+        return false;
+      }
+      JClassType type = typeOracle.findType(sourceName);
+      return type != null && type.isInterface() != null
+          && type.getOverridableMethods().length == 0;
+    }
+
+    /**
+     * Returns <code>true</code> if a JSO assigned to a slot/field of this type
+     * requires canonicalizing the JSO wrapper.
+     */
+    public boolean jsoAssignmentRequiresCanonicalization(String internalName) {
+      return "java/lang/Object".equals(internalName);
+    }
+  }
+
   /*
    * Note: this rewriter operates on a class-by-class basis and has no global
    * view on the entire system. However, its operation requires certain global
@@ -65,160 +212,68 @@
    */
 
   /**
-   * Maps instance methods to the class in which they are declared. This must be
-   * provided by the caller since it requires global program state.
+   * Used in CompilingClassLoader to trigger a call to
+   * {@link #writeConstructorDisambiguationType}.
    */
-  public interface InstanceMethodOracle {
-
-    /**
-     * For a given instance method and declared enclosing class (which must be a
-     * JSO subtype), find the class in which that method was originally
-     * declared. Methods declared on Object will return "java/lang/Object".
-     * Static methods will always return <code>declaredClass</code>.
-     * 
-     * @param declaredClass a descriptor of the static type of the qualifier
-     * @param signature the binary signature of the method
-     * @return the descriptor of the class in which that method was declared,
-     *         which will either be <code>declaredClass</code> or some
-     *         superclass
-     * @throws IllegalArgumentException if the method could not be found
-     */
-    String findOriginalDeclaringClass(String declaredClass, String signature);
-  }
+  public static final String DISAMBIGUATOR_TYPE_NAME = "com.google.gwt.dev.shell.rewrite.$Disambiguator";
 
   /**
-   * Contains data about how SingleJsoImpl methods are to be dispatched.
+   * Used in CompilingClassLoader to trigger a call to
+   * {@link #writeSingleJsoImplAdjunct}.
    */
-  public interface SingleJsoImplData {
-    /**
-     * Returns the method declarations that should be generated for a given
-     * mangled method name. {@link #getDeclarations} and
-     * {@link #getImplementations} maintain a pairwise mapping.
-     */
-    List<Method> getDeclarations(String mangledName);
+  public static final String SINGLE_JSO_IMPL_ADJUNCT_SUFFIX = "$$singleJsoImpl";
 
-    /**
-     * Returns the implementations that the method declarations above should
-     * delegate to.{@link #getDeclarations} and {@link #getImplementations}
-     * maintain a pairwise mapping.
-     */
-    List<Method> getImplementations(String mangledName);
+  static final String CANONICAL_FIELD = "canonicalJso";
 
-    /**
-     * Returns all of the mangled method names for SingleJsoImpl methods.
-     */
-    SortedSet<String> getMangledNames();
+  static final String DISAMBIGUATOR_TYPE_INTERNAL_NAME = BinaryName.toInternalName(DISAMBIGUATOR_TYPE_NAME);
 
-    /**
-     * Returns the internal names of all interface types implemented by JSOs.
-     */
-    Set<String> getSingleJsoIntfTypes();
-  }
+  static final boolean EXTRA_DEBUG_DATA = Boolean.getBoolean("gwt.dev.classDump");
 
-  static final String JAVASCRIPTOBJECT_DESC = JsValueGlue.JSO_CLASS.replace(
-      '.', '/');
-
-  static final String JAVASCRIPTOBJECT_IMPL_DESC = JsValueGlue.JSO_IMPL_CLASS.replace(
-      '.', '/');
+  static final String JAVASCRIPTOBJECT_DESC = BinaryName.toInternalName(JsValueGlue.JSO_CLASS);
 
   static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE;
 
-  static String addSyntheticThisParam(String owner, String methodDescriptor) {
-    return "(L" + owner + ";" + methodDescriptor.substring(1);
-  }
+  static final String REWRAP_METHOD = "$rewrap";
 
-  private static String toDescriptor(String jsoSubtype) {
-    return jsoSubtype.replace('.', '/');
-  }
+  static final String SINGLE_JSO_IMPL_FIELD = "$singleJsoImpl";
+
+  static final String SINGLE_JSO_IMPL_CAST_METHOD = "cast$";
+
+  static final String SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD = "castToObject$";
+
+  static final String SINGLE_JSO_IMPL_INSTANCEOF_METHOD = "instanceOf$";
+
+  static final String SINGLE_JSO_IMPL_SUPPORT_CLASS = Type.getInternalName(SingleJsoImplSupport.class);
+
+  static final int SYSTEM_CLASS_VERSION = Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_6
+      ? Opcodes.V1_5 : Opcodes.V1_6;
 
   /**
-   * An unmodifiable set of descriptors containing the implementation form of
-   * <code>JavaScriptObject</code> and all subclasses.
+   * Passed into the rewriting visitors.
    */
-  private final Set<String> jsoImplDescs;
+  private final RewriterOracle rewriterOracle;
 
   /**
-   * An unmodifiable set of descriptors containing the interface form of
-   * <code>JavaScriptObject</code> and all subclasses.
-   */
-  private final Set<String> jsoIntfDescs;
-
-  private final SingleJsoImplData jsoData;
-
-  /**
-   * Records the superclass of every JSO for generating empty JSO interfaces.
-   */
-  private final Map<String, List<String>> jsoSuperDescs;
-
-  /**
-   * Maps methods to the class in which they are declared.
-   */
-  private InstanceMethodOracle mapper;
-
-  /**
-   * Creates a new {@link HostedModeClassRewriter} for a specified set of
-   * subclasses of JavaScriptObject.
+   * Creates a new {@link HostedModeClassRewriter}.
    * 
-   * @param jsoSubtypes a set of binary type names representing JavaScriptObject
-   *          and all of its subtypes of
-   * @param mapper maps methods to the class in which they are declared
+   * @param typeOracle The TypeOracle for the GWT module that is being rewritten
    */
-  public HostedModeClassRewriter(Set<String> jsoSubtypes,
-      Map<String, List<String>> jsoSuperTypes, SingleJsoImplData jsoData,
-      InstanceMethodOracle mapper) {
-    Set<String> buildJsoIntfDescs = new HashSet<String>();
-    Set<String> buildJsoImplDescs = new HashSet<String>();
-    Map<String, List<String>> buildJsoSuperDescs = new HashMap<String, List<String>>();
-    for (String jsoSubtype : jsoSubtypes) {
-      String desc = toDescriptor(jsoSubtype);
-      buildJsoIntfDescs.add(desc);
-      buildJsoImplDescs.add(desc + "$");
-
-      List<String> superTypes = jsoSuperTypes.get(jsoSubtype);
-      assert (superTypes != null);
-      assert (superTypes.size() > 0);
-      for (ListIterator<String> i = superTypes.listIterator(); i.hasNext();) {
-        i.set(toDescriptor(i.next()));
-      }
-      buildJsoSuperDescs.put(desc, Collections.unmodifiableList(superTypes));
-    }
-
-    this.jsoIntfDescs = Collections.unmodifiableSet(buildJsoIntfDescs);
-    this.jsoImplDescs = Collections.unmodifiableSet(buildJsoImplDescs);
-    this.jsoSuperDescs = Collections.unmodifiableMap(buildJsoSuperDescs);
-    this.jsoData = jsoData;
-    this.mapper = mapper;
-  }
-
-  /**
-   * Returns <code>true</code> if the class is the implementation class for a
-   * JSO subtype.
-   */
-  public boolean isJsoImpl(String className) {
-    return jsoImplDescs.contains(toDescriptor(className));
-  }
-
-  /**
-   * Returns <code>true</code> if the class is the interface class for a JSO
-   * subtype.
-   */
-  public boolean isJsoIntf(String className) {
-    return jsoIntfDescs.contains(toDescriptor(className));
+  public HostedModeClassRewriter(TypeOracle typeOracle) {
+    rewriterOracle = new RewriterOracle(typeOracle);
   }
 
   /**
    * Performs rewriting transformations on a class.
    * 
-   * @param typeOracle a typeOracle modeling the user classes
    * @param className the name of the class
    * @param classBytes the bytes of the class
    * @param anonymousClassMap a map between the anonymous class names of java
    *          compiler used to compile code and jdt. Emma-specific.
    */
-  public byte[] rewrite(TypeOracle typeOracle, String className,
-      byte[] classBytes, Map<String, String> anonymousClassMap) {
-    String desc = toDescriptor(className);
-    assert (!jsoIntfDescs.contains(desc));
+  public byte[] rewrite(String className, byte[] classBytes,
+      Map<String, String> anonymousClassMap) {
+    classBytes = maybeUpgradeBytecode(classBytes);
+    String desc = BinaryName.toInternalName(className);
 
     // The ASM model is to chain a bunch of visitors together.
     ClassWriter writer = new ClassWriter(0);
@@ -227,49 +282,75 @@
     // v = new CheckClassAdapter(v);
     // v = new TraceClassVisitor(v, new PrintWriter(System.out));
 
-    v = new RewriteSingleJsoImplDispatches(v, typeOracle, jsoData);
+    v = new WriteSingleJsoSupportCode(v, rewriterOracle);
 
-    v = new RewriteRefsToJsoClasses(v, jsoIntfDescs, mapper);
+    v = new RewriteJsoCasts(v, rewriterOracle);
 
-    if (jsoImplDescs.contains(desc)) {
-      v = WriteJsoImpl.create(v, desc, jsoIntfDescs, mapper, jsoData);
+    v = new RewriteJsoArrays(v, rewriterOracle);
+
+    v = new RewriteObjectComparisons(v, rewriterOracle);
+
+    if (rewriterOracle.isJsoOrSubtype(desc)) {
+      v = WriteJsoImpl.create(v, desc);
     }
 
     v = new RewriteJsniMethods(v, anonymousClassMap);
 
-    if (Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_6) {
+    if (SYSTEM_CLASS_VERSION < Opcodes.V1_6) {
       v = new ForceClassVersion15(v);
     }
 
-    new ClassReader(classBytes).accept(v, 0);
+    // We need EXPAND_FRAMES here for RewriteJsoCasts
+    new ClassReader(classBytes).accept(v, ClassReader.EXPAND_FRAMES);
     return writer.toByteArray();
   }
 
-  public byte[] writeJsoIntf(String className) {
-    String desc = toDescriptor(className);
-    assert (jsoIntfDescs.contains(desc));
-    assert (jsoSuperDescs.containsKey(desc));
-    List<String> superDescs = jsoSuperDescs.get(desc);
-    assert (superDescs != null);
-    assert (superDescs.size() > 0);
+  /**
+   * Synthesize a class file that is used to disambiguate constructors that have
+   * JSO subtype array parameters that have been upcast to JavaScriptObject
+   * arrays.
+   */
+  public byte[] writeConstructorDisambiguationType(String className) {
+    // Keep the synthetic class creation code next to where its consumed
+    return RewriteJsoArrays.writeConstructorDisambiguationType(className);
+  }
 
-    // The ASM model is to chain a bunch of visitors together.
-    ClassWriter writer = new ClassWriter(0);
-    ClassVisitor v = writer;
+  /**
+   * Synthesize a class file that implements an interface's SingleJsoImpl
+   * adjunct type.
+   */
+  public byte[] writeSingleJsoImplAdjunct(String className) {
+    // Keep the synthetic class creation code next to where its consumed
+    return WriteSingleJsoSupportCode.writeSingleJsoImplAdjunct(className);
+  }
 
-    // v = new CheckClassAdapter(v);
-    // v = new TraceClassVisitor(v, new PrintWriter(System.out));
+  /**
+   * AnalyzerAdapter, and thus RewriteJsoCasts, requires StackFrameMap data
+   * which is new in version 50 (Java 1.6) bytecode. This method will only do
+   * work if the input bytecode is less than version 50. This would occur when
+   * running on Java 1.5 or if Emma support is enabled (since Emma is from
+   * 2005).
+   */
+  private byte[] maybeUpgradeBytecode(byte[] classBytes) {
+    // Major version is stored at offset 7 in the class file format
+    if (classBytes[7] < Opcodes.V1_6) {
+      // Get ASM to generate the stack frame data
+      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+      ClassVisitor v2 = cw;
 
-    String[] interfaces;
-    // TODO(bov): something better than linear?
-    if (superDescs.contains("java/lang/Object")) {
-      interfaces = null;
-    } else {
-      interfaces = superDescs.toArray(new String[superDescs.size()]);
+      // Upgrade to version 50 class format
+      v2 = new ClassAdapter(v2) {
+        @Override
+        public void visit(int version, int access, String name,
+            String signature, String superName, String[] interfaces) {
+          super.visit(Opcodes.V1_6, access, name, signature, superName,
+              interfaces);
+        }
+      };
+
+      new ClassReader(classBytes).accept(v2, 0);
+      classBytes = cw.toByteArray();
     }
-    v.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, desc,
-        null, "java/lang/Object", interfaces);
-    v.visitEnd();
-    return writer.toByteArray();
+    return classBytes;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/OriginalJsniSignature.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/OriginalJsniSignature.java
new file mode 100644
index 0000000..74d924a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/OriginalJsniSignature.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010 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.shell.rewrite;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation will be applied to a method or constructor by
+ * {@link HostedModeClassRewriter} if the method signature of a rewritten method
+ * differs from the original source. This annotation is necessary because
+ * dispatch id allocation is performed reflectively, after the class rewriting
+ * may have altered the method.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.CONSTRUCTOR, ElementType.METHOD})
+public @interface OriginalJsniSignature {
+  String name();
+
+  /**
+   * This is the method descriptor, minus the return type.
+   */
+  String paramList();
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoArrays.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoArrays.java
new file mode 100644
index 0000000..db716e3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoArrays.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2010 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.shell.rewrite;
+
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.DISAMBIGUATOR_TYPE_INTERNAL_NAME;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SYSTEM_CLASS_VERSION;
+
+import com.google.gwt.dev.asm.AnnotationVisitor;
+import com.google.gwt.dev.asm.ClassAdapter;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.ClassWriter;
+import com.google.gwt.dev.asm.FieldVisitor;
+import com.google.gwt.dev.asm.Label;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.RewriterOracle;
+import com.google.gwt.dev.util.Name.InternalName;
+
+import java.util.Arrays;
+
+/**
+ * The object of this pass is to turn all JSO subtype arrays into
+ * JavaScriptObject arrays. This allows JSO arrays to participate in arbitrary
+ * cross-casting.
+ */
+public class RewriteJsoArrays extends ClassAdapter {
+
+  private static final String ORIGINAL_JSNI_SIGNATURE_DESC = Type.getDescriptor(OriginalJsniSignature.class);
+
+  /**
+   * Performs upcasts on JSO subtype arrays.
+   */
+  private class UpcastAdapter extends DebugAnalyzerAdapter {
+
+    private final int minLocals;
+    private int minStack;
+
+    public UpcastAdapter(String owner, int access, String name, String desc,
+        MethodVisitor mv, int minLocals) {
+      super(owner, access, name, desc, mv);
+      this.minLocals = minLocals;
+    }
+
+    /**
+     * Upcast field access.
+     */
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name,
+        String desc) {
+      Type t = Type.getType(desc);
+      if (t.getSort() == Type.ARRAY) {
+        t = upcastJsoType(t);
+        if (t != null) {
+          desc = t.getDescriptor();
+        }
+      }
+      super.visitFieldInsn(opcode, owner, name, desc);
+    }
+
+    /**
+     * Fix JSO array types in framing data to keep the verifier and other
+     * AnalyzerAdapters synced up.
+     */
+    @Override
+    public void visitFrame(int type, int nLocal, Object[] local, int nStack,
+        Object[] stack) {
+      fixFrameData(local);
+      fixFrameData(stack);
+      super.visitFrame(type, nLocal, local, nStack, stack);
+    }
+
+    /**
+     * Keep the debugger from going off the rails.
+     */
+    @Override
+    public void visitLocalVariable(String name, String desc, String signature,
+        Label start, Label end, int index) {
+      Type t = Type.getType(desc);
+      if (t.getSort() == Type.ARRAY) {
+        t = upcastJsoType(t);
+        if (t != null) {
+          desc = t.getInternalName();
+        }
+      }
+      super.visitLocalVariable(name, desc, signature, start, end, index);
+    }
+
+    /**
+     * We need to override the number of locals when rewriting a constructor to
+     * account for synthetic disambiguation parameters. The null argument to the
+     * disambiguated constructor might require adjustment of the stack size.
+     */
+    @Override
+    public void visitMaxs(int maxStack, int maxLocals) {
+      super.visitMaxs(Math.max(minStack, maxStack), Math.max(minLocals,
+          maxLocals));
+    }
+
+    /**
+     * Fix the descriptors used by method invocations to account for the upcast
+     * array types. Initializers are handled specially, since we can't simply
+     * change their name to provide disambiguation. Instead, we add an
+     * additional null argument of a synthetic type.
+     */
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name,
+        String desc) {
+      switch (opcode) {
+        case Opcodes.INVOKESPECIAL: {
+          if ("<init>".equals(name)) {
+            String[] disambiguator = rewriterOracle.getArrayDisambiguator(desc);
+            // Widen any JSO arrays.
+            Method m = upcastMethod(name, desc);
+            desc = m == null ? desc : m.getDescriptor();
+            if (disambiguator.length != 0) {
+              desc = addDisambiguator(desc, disambiguator);
+              // Add bogus values to the stack
+              recordDebugData("Constructor disambiguation "
+                  + Arrays.asList(disambiguator));
+              for (int i = 0, j = disambiguator.length; i < j; i++) {
+                super.visitInsn(Opcodes.ACONST_NULL);
+              }
+              minStack = Math.max(minStack, stack.size());
+            }
+            break;
+          }
+          // Intentional fallthrough for non-init methods
+        }
+        case Opcodes.INVOKEINTERFACE:
+        case Opcodes.INVOKESTATIC:
+        case Opcodes.INVOKEVIRTUAL: {
+          Method m = upcastMethod(name, desc);
+          if (m != null) {
+            recordDebugData("Replaced method call " + name + " " + desc
+                + " with " + m.toString());
+            name = m.getName();
+            desc = m.getDescriptor();
+          }
+          break;
+        }
+        default:
+          throw new RuntimeException("Unhandled method instruction " + opcode);
+      }
+      super.visitMethodInsn(opcode, owner, name, desc);
+    }
+
+    /**
+     * Fix the types of multidimensional arrays allocations.
+     */
+    @Override
+    public void visitMultiANewArrayInsn(String desc, int dims) {
+      Type replacement = upcastJsoType(Type.getObjectType(desc));
+      if (replacement != null) {
+        recordDebugData("Widened JSO array allocation");
+        desc = replacement.getInternalName();
+      }
+      super.visitMultiANewArrayInsn(desc, dims);
+    }
+
+    /**
+     * Convert all JSO subtype array allocations into an allocation of a
+     * JavaScriptObject array.
+     */
+    @Override
+    public void visitTypeInsn(int opcode, String internalName) {
+      if (opcode == Opcodes.ANEWARRAY) {
+        Type replacement = upcastJsoType(Type.getObjectType(internalName));
+        if (replacement != null) {
+          recordDebugData("Widened JSO array allocation");
+          internalName = replacement.getInternalName();
+        }
+      }
+      super.visitTypeInsn(opcode, internalName);
+    }
+
+    /**
+     * Utility method used by {@link #visitFrame} to process the stack and local
+     * arrays.
+     */
+    private void fixFrameData(Object[] data) {
+      for (int i = 0, j = data.length; i < j; i++) {
+        if (data[i] instanceof String) {
+          Type t = Type.getObjectType((String) data[i]);
+          if (t.getSort() == Type.ARRAY) {
+            t = upcastJsoType(t);
+            if (t != null) {
+              data[i] = t.getInternalName();
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Creates an empty interface type.
+   */
+  static byte[] writeConstructorDisambiguationType(String className) {
+    String internalName = className.replace('.', '/');
+    assert internalName.startsWith(DISAMBIGUATOR_TYPE_INTERNAL_NAME) : "Bad className "
+        + className;
+
+    ClassWriter writer = new ClassWriter(0);
+
+    writer.visit(SYSTEM_CLASS_VERSION, Opcodes.ACC_PUBLIC
+        | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE, internalName, null,
+        "java/lang/Object", null);
+
+    writer.visitEnd();
+    return writer.toByteArray();
+  }
+
+  /**
+   * Records the class currently being processed.
+   */
+  private String owner;
+  private final RewriterOracle rewriterOracle;
+
+  public RewriteJsoArrays(ClassVisitor v, RewriterOracle rewriterOracle) {
+    super(v);
+    this.rewriterOracle = rewriterOracle;
+  }
+
+  @Override
+  public void visit(int version, int access, String name, String signature,
+      String superName, String[] interfaces) {
+    super.visit(version, access, name, signature, superName, interfaces);
+    owner = name;
+  }
+
+  /**
+   * Widen all fields of a JSO array type to JSO[].
+   */
+  @Override
+  public FieldVisitor visitField(int access, String name, String desc,
+      String signature, Object value) {
+    Type t = Type.getType(desc);
+    if (t.getSort() == Type.ARRAY) {
+      Type newType = upcastJsoType(t);
+      if (newType != null) {
+        desc = newType.getDescriptor();
+      }
+    }
+    return super.visitField(access, name, desc, signature, value);
+  }
+
+  /**
+   * Widen all JSO array paramaters to JSO[]. Constructors may have a
+   * disambiguator type added to their parameter list.
+   */
+  @Override
+  public MethodVisitor visitMethod(int access, String name, String desc,
+      String signature, String[] exceptions) {
+    int minLocals = 0;
+    String originalJsniName = name;
+    String originalJsniDesc = desc;
+    Method upcast = upcastMethod(name, desc);
+
+    if (upcast != null) {
+      if ("<init>".equals(name)) {
+        String[] disambiguator = rewriterOracle.getArrayDisambiguator(desc);
+        desc = upcast.getDescriptor();
+        if (disambiguator.length != 0) {
+          desc = addDisambiguator(desc, disambiguator);
+          // +1 for this
+          minLocals = Type.getArgumentTypes(desc).length + 1;
+        }
+        originalJsniName = "new";
+      } else {
+        name = upcast.getName();
+        desc = upcast.getDescriptor();
+      }
+    }
+
+    MethodVisitor mv = super.visitMethod(access, name, desc, signature,
+        exceptions);
+    if (mv != null) {
+      // Record the original JSNI signature if we've mangled the method
+      if (upcast != null) {
+        AnnotationVisitor av = mv.visitAnnotation(ORIGINAL_JSNI_SIGNATURE_DESC,
+            true);
+        if (av != null) {
+          av.visit("name", originalJsniName);
+          // Strip the return type
+          av.visit("paramList", originalJsniDesc.substring(0,
+              originalJsniDesc.indexOf(')') + 1));
+          av.visitEnd();
+        }
+      }
+      mv = new UpcastAdapter(owner, access, name, desc, mv, minLocals);
+    }
+    return mv;
+  }
+
+  /**
+   * Add disambiguator types as the last parameters of a method descriptor.
+   */
+  private String addDisambiguator(String desc, String[] disambiguator) {
+    int idx = desc.indexOf(')');
+    StringBuilder sb = new StringBuilder();
+    sb.append(desc.substring(0, idx));
+    for (String d : disambiguator) {
+      sb.append("L").append(d).append(";");
+    }
+    sb.append(desc.substring(idx));
+    return sb.toString();
+  }
+
+  /**
+   * Utility method for constructing a descriptor. If the given type is a JSO
+   * subtype array, the appropriate JavaScriptObject array type will be appended
+   * to the descriptor and this method will return <code>true</code>. Otherwise,
+   * this method will simply append the type to the descriptor and return
+   * <code>false</code>.
+   */
+  private boolean appendTypeMaybeUpcast(StringBuilder newDesc, Type type) {
+    if (type.getSort() == Type.ARRAY) {
+      Type newType = upcastJsoType(type);
+      if (newType != null) {
+        newDesc.append(newType.getDescriptor());
+        return true;
+      }
+    }
+    newDesc.append(type.getDescriptor());
+    return false;
+  }
+
+  /**
+   * Calls {@link RewriteJsoCasts#upcastJsoType} with the instance's
+   * {@link #rewriterOracle}.
+   */
+  private Type upcastJsoType(Type type) {
+    return RewriteJsoCasts.upcastJsoType(rewriterOracle, type);
+  }
+
+  /**
+   * Determine if a descriptor contains references to JSO subtype arrays. If so,
+   * returns an upcast descriptor and a guaranteed-unique name for the method.
+   * Otherwise, this method returns <code>null</code>.
+   */
+  private Method upcastMethod(String name, String desc) {
+    boolean didChange = false;
+    StringBuilder newName = new StringBuilder(name);
+    StringBuilder newDesc = new StringBuilder("(");
+
+    for (Type arg : Type.getArgumentTypes(desc)) {
+      // Add the arguments, one at a time, to the new descriptor
+      if (appendTypeMaybeUpcast(newDesc, arg)) {
+        didChange = true;
+        /*
+         * Add the original type to the method name. We don't need to worry
+         * about the number of parameters or their relative positions when
+         * constructing the name, because that information is still in the
+         * method descriptor.
+         */
+        newName.append("_").append(
+            InternalName.toIdentifier(arg.getElementType().getInternalName())).append(
+            "_").append(arg.getDimensions());
+      }
+    }
+    newDesc.append(")");
+
+    Type returnType = Type.getReturnType(desc);
+    if (appendTypeMaybeUpcast(newDesc, returnType)) {
+      didChange = true;
+    }
+
+    if (!didChange) {
+      return null;
+    }
+    return new Method(newName.toString(), newDesc.toString());
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoCasts.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoCasts.java
new file mode 100644
index 0000000..267fe42
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsoCasts.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright 2010 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.shell.rewrite;
+
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.CANONICAL_FIELD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.JAVASCRIPTOBJECT_DESC;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REWRAP_METHOD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_ADJUNCT_SUFFIX;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_CAST_METHOD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_INSTANCEOF_METHOD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_SUPPORT_CLASS;
+
+import com.google.gwt.dev.asm.ClassAdapter;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.Label;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.RewriterOracle;
+
+/**
+ * Ensure that an appropriate JSO wrapper type or canonical JavaScriptObject is
+ * used. Assignments to fields, locals, arrays, return statements, and method
+ * parameters will be updated with the following rules:
+ * <ul>
+ * <li>Changing from one JSO type to another results in a replacement JSO
+ * Wrapper type being placed on the stack</li>
+ * <li>Referring to a JSO or a JSO subype as Object results in the canonical JSO
+ * instance being placed on the stack</li>
+ * <li>An invocation of <code>getClass()</code> on a JSO subtype results in
+ * <code>JavaScriptObject.class</code> being placed on the stack.</li>
+ * </ul>
+ */
+class RewriteJsoCasts extends ClassAdapter {
+
+  /**
+   * Injects necessary casting logic.
+   */
+  private class MyMethodAdapter extends DebugAnalyzerAdapter {
+    /*
+     * NOTE TO MAINTAINERS: It's tempting to sprinkle one-off cast-handling
+     * logic around this class. That way lies madness while trying to track down
+     * exactly how a particular bit of cast logic wound up in the bytecode.
+     * Instead, all casting logic should be routed through generateCast.
+     */
+
+    private final boolean returnNeedsCanonical;
+    private final Type returnType;
+
+    public MyMethodAdapter(int access, String name, String desc,
+        MethodVisitor mv) {
+      super(currentClass, access, name, desc, mv);
+
+      Method m = new Method(name, desc);
+      returnType = m.getReturnType();
+      if (returnType.getSort() == Type.OBJECT) {
+        String internalName = returnType.getInternalName();
+        boolean maybeCanonicalize = rewriterOracle.jsoAssignmentRequiresCanonicalization(internalName);
+        boolean maybeRewrap = rewriterOracle.isJsoOrSubtype(internalName);
+        returnNeedsCanonical = maybeCanonicalize || maybeRewrap;
+      } else {
+        returnNeedsCanonical = false;
+      }
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name,
+        String desc) {
+      if (desc.charAt(0) == 'L' && !owner.equals(JAVASCRIPTOBJECT_DESC)
+          && !name.equals(CANONICAL_FIELD)) {
+        switch (opcode) {
+          case Opcodes.PUTFIELD:
+          case Opcodes.PUTSTATIC:
+            generateCast(desc.substring(1, desc.length() - 1));
+        }
+      }
+      super.visitFieldInsn(opcode, owner, name, desc);
+    }
+
+    /**
+     * Possibly canonicalize returned values.
+     */
+    @Override
+    public void visitInsn(int opcode) {
+      if (opcode == Opcodes.ARETURN && returnNeedsCanonical) {
+        // Stack is: returnValue
+        generateCast(returnType.getInternalName());
+        // Stack is: canonical
+      } else if (opcode == Opcodes.AASTORE) {
+        // Stack is: ..., arrayRef, index, value
+        Object topType = stack.get(stack.size() - 1);
+        if (topType instanceof String) {
+          String objectType = (String) topType;
+          String arrayType = (String) stack.get(stack.size() - 3);
+          Type t = Type.getObjectType(arrayType);
+          if (t.getDimensions() == 1
+              && rewriterOracle.jsoAssignmentRequiresCanonicalization(objectType)) {
+            /*
+             * If the object being assigned needs canonicalization, then we want
+             * to ensure the canonical object is being stored in the array.
+             */
+            generateCast(objectType);
+          }
+        }
+      }
+      super.visitInsn(opcode);
+    }
+
+    /**
+     * Replace references to JSO subtype class literals with references to
+     * JavaScriptObject.
+     */
+    @Override
+    public void visitLdcInsn(Object cst) {
+      if (cst instanceof Type) {
+        Type upcast = upcastJsoType((Type) cst);
+        if (upcast != null) {
+          cst = upcast;
+        }
+      }
+      super.visitLdcInsn(cst);
+    }
+
+    /**
+     * Canonicalize the arguments to methods that require Object or
+     * JavaScriptObject as parameters. Additional local variables will be
+     * allocated to save as many of the arguments as necessary in order to cast
+     * the first parameter that requires canonicalization.
+     */
+    @Override
+    public void visitMethodInsn(int opcode, String owner, String name,
+        String desc) {
+
+      // Update calls to getClass()
+      if ("getClass".equals(name) && "()Ljava/lang/Class;".equals(desc)) {
+        // Stack is: instance
+        Type t = Type.getObjectType((String) stack.get(stack.size() - 1));
+        if (t.getSort() == Type.OBJECT
+            && rewriterOracle.couldContainJso(t.getInternalName())) {
+          /*
+           * Make sure the canonical object is on the stack. This method call
+           * will pass nulls straight through, so the getClass() invocation will
+           * still trigger an NPE.
+           */
+          recordDebugData("Canonicalized object for getClass");
+          super.visitMethodInsn(Opcodes.INVOKESTATIC,
+              SINGLE_JSO_IMPL_SUPPORT_CLASS, "ensureCanonical",
+              "(Ljava/lang/Object;)Ljava/lang/Object;");
+          // Stack is: canonical object
+          super.visitMethodInsn(opcode, "java/lang/Object", name, desc);
+          // Stack is: classLit
+          return;
+        }
+      } else if ("cast".equals(name)
+          && ("()L" + JAVASCRIPTOBJECT_DESC + ";").equals(desc)
+          && rewriterOracle.isJsoOrSubtype(owner)) {
+        // Remove calls to cast, since they trigger NPEs in older code
+        recordDebugData("Removed call to JSO.cast()");
+        return;
+      }
+
+      /*
+       * We want to ensure that all arguments passed into the method have been
+       * canonicalized. To do this, we'll find the first argument on the stack
+       * that requires canonicalization. Each argument on above the first
+       * argument on the stack will be stored in a temporary local variable and
+       * eventually restored.
+       * 
+       * Assume the stack looks like this:
+       * 
+       * instance, Ok, NeedsCanonicalization, Ok, Ok, NeedsCanonicalization
+       * 
+       * (1) We'll store the top three arguments in locals until we have this:
+       * 
+       * instance, Ok, NeedsCanonicalization
+       * 
+       * (2) Then, we'll apply whatever canonicalization is necessary to the
+       * top-most element:
+       * 
+       * instance, Ok, Canonicalized
+       * 
+       * (3) The arguments stored in the temporary local variables will be
+       * pushed back onto the stack, possibly canonicalizing them in the
+       * process:
+       * 
+       * instance, Ok, Canonicalized, Ok, Ok, Canonicalized
+       * 
+       * This could be accomplished without the use of local variables if we
+       * were to track the last opcode that pushes an argument value onto the
+       * stack and buffer intervening opcodes or use a two-pass approach. Once
+       * the relevant opcode has been identified, the canonicalization logic
+       * could be immediately inserted.
+       */
+      Method m = new Method(name, desc);
+      boolean isInstance = opcode == Opcodes.INVOKEINTERFACE
+          || opcode == Opcodes.INVOKEVIRTUAL;
+      Type[] args;
+      if (isInstance) {
+        /*
+         * Treat invocations of non-static dispatch as though they were static
+         * methods that have an extra "this" argument that is the type of the
+         * instance that's expected.
+         */
+        Type[] actualArgs = m.getArgumentTypes();
+        args = new Type[actualArgs.length + 1];
+        args[0] = Type.getObjectType(owner);
+        System.arraycopy(actualArgs, 0, args, 1, actualArgs.length);
+      } else {
+        args = m.getArgumentTypes();
+      }
+      int firstCast = -1;
+      boolean[] needsCanonicalization = new boolean[args.length];
+
+      // (1) Works backwards, since the last argument is on top of the stack
+      for (int i = args.length - 1, s = stack.size(); i >= 0; i--) {
+        Type arg = args[i];
+        s -= arg.getSize();
+
+        /*
+         * Ignore arguments that never require a canonicalizing cast or
+         * rewrapping.
+         */
+        if (arg.getSort() != Type.OBJECT) {
+          continue;
+        }
+        boolean maybeCanonicalize = rewriterOracle.jsoAssignmentRequiresCanonicalization(arg.getInternalName());
+        boolean maybeRewrap = rewriterOracle.couldContainJso(arg.getInternalName());
+        if (!maybeCanonicalize && !maybeRewrap) {
+          continue;
+        }
+
+        Object onStack = stack.get(s);
+        assert !onStack.equals(Opcodes.TOP) : "Bad offset computation";
+
+        if (onStack instanceof String) {
+          String stackType = (String) onStack;
+          if (rewriterOracle.couldContainJso(stackType)) {
+            needsCanonicalization[i] = true;
+            firstCast = i;
+          }
+        }
+      }
+
+      // Short-circuit if none of the arguments need canonicalization
+      if (firstCast >= 0) {
+        recordDebugData("Fixed arguments to method invocation");
+        // These labels are used to provide information to the debugger
+        Label castStackStart = new Label();
+        Label castStackEnd = new Label();
+
+        // (1) Pop N - 1 arguments off of the stack, storing them in locals
+        super.visitLabel(castStackStart);
+        int[] slots = new int[args.length];
+        for (int i = args.length - 1; i >= firstCast + 1; i--) {
+          Type arg = args[i];
+          int slot = locals.size();
+          slots[i] = slot;
+          super.visitVarInsn(arg.getOpcode(Opcodes.ISTORE), slot);
+        }
+
+        // (2) Convert the first argument
+        generateCast(args[firstCast].getInternalName());
+
+        // (3) Load arguments, converting those that need them
+        for (int i = firstCast + 1; i < args.length; i++) {
+          Type arg = args[i];
+          int slot = slots[i];
+          super.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot);
+          if (needsCanonicalization[i]) {
+            generateCast(arg.getInternalName());
+          }
+        }
+
+        super.visitLabel(castStackEnd);
+
+        // Record locals to aid debbuging and manual inspection of bytecode.
+        for (int i = firstCast + 1; i < args.length; i++) {
+          super.visitLocalVariable("$cast_" + i, args[i].getDescriptor(), null,
+              castStackStart, castStackEnd, slots[i]);
+        }
+
+        // Record new stack / locals size
+        super.visitMaxs(stack.size(), locals.size());
+      }
+
+      // Call the method
+      super.visitMethodInsn(opcode, owner, name, desc);
+    }
+
+    /**
+     * Rewrite casts and instanceof tests.
+     */
+    @Override
+    public void visitTypeInsn(int opcode, String type) {
+      Type parsed = Type.getObjectType(type);
+      switch (opcode) {
+        case Opcodes.CHECKCAST:
+          if (rewriterOracle.couldContainJso(type)) {
+            // Maybe generate casting code for JSO types
+            if (generateCast(type)) {
+              return;
+            }
+          } else if (parsed.getSort() == Type.ARRAY) {
+            // Always upcast JSO array types
+            Type t = upcastJsoType(parsed);
+            if (t != null) {
+              recordDebugData("Upcast JSO array checkcast from "
+                  + parsed.getInternalName());
+              type = t.getInternalName();
+            }
+          }
+          // Intentional fall-through to super invocation below
+          break;
+
+        case Opcodes.INSTANCEOF: {
+          String internalName = parsed.getInternalName();
+          if (rewriterOracle.isInterface(internalName)
+              && rewriterOracle.couldContainJso(internalName)) {
+            recordDebugData("SingleJsoImpl instanceof check for "
+                + internalName);
+            /*
+             * We need to use the interface's adjunct type if the interface
+             * could contain a JSO type.
+             */
+            super.visitMethodInsn(Opcodes.INVOKESTATIC, type
+                + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX,
+                SINGLE_JSO_IMPL_INSTANCEOF_METHOD, "(Ljava/lang/Object;)Z");
+            return;
+          }
+          // Always try to upcast instanceof JSO tests
+          recordDebugData("Upcast instanceof from " + internalName);
+          Type t = upcastJsoType(parsed);
+          if (t != null) {
+            type = t.getInternalName();
+          }
+          break;
+        }
+      }
+      super.visitTypeInsn(opcode, type);
+    }
+
+    /**
+     * The do-everything method for determining how to make whatever is on top
+     * of the stack fit correctly in a slot of the given type. Casts to Object
+     * should result in the canonical object winding up on the stack. Otherwise,
+     * we may need a new wrapper instance.
+     * <p>
+     * This method should generate code that is stack-neutral. Any non-trivial
+     * cast logic should be moved to a support class, either
+     * {@link SingleJsoImplSupport} or the synthetic interface adjunct classes.
+     * 
+     * @param internalName the type to which the object on top of the stack
+     *          should be assigned
+     * @return <code>true</code> if cast code was generated
+     */
+    private boolean generateCast(String internalName) {
+      assert internalName.charAt(0) != 'L'
+          && internalName.charAt(internalName.length() - 1) != ';' : "Passed "
+          + "a descriptor instead of an internal name: " + internalName;
+      Object topStack = stack.get(stack.size() - 1);
+      if (!(topStack instanceof String)) {
+        return false;
+      }
+
+      final String topType = (String) topStack;
+
+      recordDebugData(topType + " ->  " + internalName);
+
+      if (internalName.equals("java/lang/Object")) {
+        /*
+         * We're looking at code (probably created by one of our other visitors)
+         * that is casting a value to java.lang.Object.
+         */
+        // Stack is: something
+
+        if (rewriterOracle.isJsoOrSubtype(topType)) {
+          /*
+           * We know that the top of the stack is a wrapper type, so we need to
+           * canonicalize it by calling JavaScriptObject's synthetic rewrap
+           * method.
+           */
+          // Stack is: jsoSubtype
+          super.visitMethodInsn(Opcodes.INVOKESTATIC, JAVASCRIPTOBJECT_DESC,
+              REWRAP_METHOD, "(L" + JAVASCRIPTOBJECT_DESC + ";)L"
+                  + JAVASCRIPTOBJECT_DESC + ";");
+          // Stack is: canonicalObject
+          return true;
+
+        } else if (rewriterOracle.isInterface(topType)
+            && rewriterOracle.couldContainJso(topType)) {
+          /*
+           * We're looking at an interface that may contain a JSO. If the object
+           * is a JSO, replace it with the canonical instance by calling into
+           * the interface's SingleJsoImpl adjunct class's castToObject()
+           * method.
+           */
+          String supportType = topType + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX;
+
+          // Stack is: interfaceType
+          super.visitMethodInsn(Opcodes.INVOKESTATIC, supportType,
+              SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD, "(L" + topType
+                  + ";)Ljava/lang/Object;");
+          // Stack is: object (possible canonical JSO)
+          return true;
+
+        } else if ("java/lang/Object".equals(topType)) {
+          /*
+           * This is a situation that we'll run into when dealing with JRE code.
+           * See JsoTest.testArrayJreInteractions for the canonical example of
+           * loss of type information.
+           */
+          super.visitMethodInsn(Opcodes.INVOKESTATIC,
+              SINGLE_JSO_IMPL_SUPPORT_CLASS, "ensureCanonical",
+              "(Ljava/lang/Object;)Ljava/lang/Object;");
+          // Stack is: canonical object
+          return true;
+
+        } else {
+          // Casting some non-JSO type to Object
+          return false;
+        }
+
+      } else if (internalName.equals(topType)) {
+        // Casting an object to its own type
+        return false;
+
+      } else if (!rewriterOracle.couldContainJso(internalName)) {
+        // It's a cast to something that could never contain a JSO
+        return false;
+
+      } else if (rewriterOracle.isInterface(internalName)
+          && rewriterOracle.couldContainJso(internalName)) {
+        /*
+         * Casting to a SingleJsoImpl interface. Need to use the SingleJsoImpl
+         * adjunct to possibly create a new wrapper type.
+         */
+
+        // Stack is: something
+        super.visitMethodInsn(Opcodes.INVOKESTATIC, internalName
+            + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_CAST_METHOD,
+            "(Ljava/lang/Object;)Ljava/lang/Object;");
+        // Stack is: something (maybe JSO wrapper type)
+        super.visitTypeInsn(Opcodes.CHECKCAST, internalName);
+        // Stack is: desiredType (maybe JSO wrapper type)
+
+        return true;
+
+      } else if (rewriterOracle.isJsoOrSubtype(internalName)) {
+        /*
+         * Casting to a JavaScriptObject subtype.
+         */
+
+        /*
+         * Change the cast to JavaScriptObject to ensure we get a
+         * ClassCastException for something like (JsoSubclass) "foo".
+         */
+        // Stack is: something
+        super.visitTypeInsn(Opcodes.CHECKCAST, JAVASCRIPTOBJECT_DESC);
+        // Stack is: jso
+
+        /*
+         * Put the canonical object onto the stack by calling
+         * JsoSubclass.$rewrap().
+         */
+        super.visitMethodInsn(Opcodes.INVOKESTATIC, internalName,
+            REWRAP_METHOD, "(L" + JAVASCRIPTOBJECT_DESC + ";)L" + internalName
+                + ";");
+        // Stack is: wrapperObject
+        return true;
+      }
+
+      /*
+       * Fail definitively, since getting this wrong is going to break the user
+       * in all kinds of unpredictable ways.
+       */
+      throw new RuntimeException(
+          "generateCast was called with an unhandled configuration. topType: "
+              + topType + " internalName: " + internalName);
+    }
+  }
+
+  /**
+   * If <code>type</code> is a JSO subtype or JSO subtype array, return the JSO
+   * type or JSO array type to which <code>type</code> can be assigned, or
+   * <code>null</code>.
+   */
+  static Type upcastJsoType(RewriterOracle rewriterOracle, Type type) {
+    StringBuilder sb = new StringBuilder();
+    if (type.getSort() == Type.ARRAY) {
+      Type elementType = type.getElementType();
+      if (elementType.getSort() != Type.OBJECT
+          || !rewriterOracle.isJsoOrSubtype(elementType.getInternalName())) {
+        return null;
+      }
+      for (int i = 0, j = type.getDimensions(); i < j; i++) {
+        sb.append("[");
+      }
+    } else if (type.getSort() == Type.OBJECT) {
+      if (type.getInternalName().equals(JAVASCRIPTOBJECT_DESC)
+          || !rewriterOracle.isJsoOrSubtype(type.getInternalName())) {
+        return null;
+      }
+    } else {
+      return null;
+    }
+    sb.append("L" + JAVASCRIPTOBJECT_DESC + ";");
+    return Type.getType(sb.toString());
+  }
+
+  private String currentClass;
+  private final RewriterOracle rewriterOracle;
+
+  public RewriteJsoCasts(ClassVisitor v, RewriterOracle rewriterOracle) {
+    super(v);
+    this.rewriterOracle = rewriterOracle;
+  }
+
+  @Override
+  public void visit(int version, int access, String name, String signature,
+      String superName, String[] interfaces) {
+    currentClass = name;
+    super.visit(version, access, name, signature, superName, interfaces);
+  }
+
+  @Override
+  public MethodVisitor visitMethod(int access, String name, String desc,
+      String signature, String[] exceptions) {
+    MethodVisitor mv = super.visitMethod(access, name, desc, signature,
+        exceptions);
+    if (mv != null && !REWRAP_METHOD.equals(name)) {
+      mv = new MyMethodAdapter(access, name, desc, mv);
+    }
+    return mv;
+  }
+
+  /**
+   * Convenience method.
+   */
+  private Type upcastJsoType(Type type) {
+    return upcastJsoType(rewriterOracle, type);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteObjectComparisons.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteObjectComparisons.java
new file mode 100644
index 0000000..f7d2377
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteObjectComparisons.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010 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.shell.rewrite;
+
+import com.google.gwt.dev.asm.ClassAdapter;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.Label;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.commons.AnalyzerAdapter;
+import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.RewriterOracle;
+
+/**
+ * This injects artificial casts to Object which will be replaced by
+ * {@link RewriteJsoCasts}.
+ */
+class RewriteObjectComparisons extends ClassAdapter {
+
+  private class MyMethodAdapter extends AnalyzerAdapter {
+    public MyMethodAdapter(String owner, int access, String name, String desc,
+        MethodVisitor mv) {
+      super(owner, access, name, desc, mv);
+    }
+
+    /**
+     * All object equality comparisons in the JVM are performed via a jump
+     * opcode. Even something as simple as <code>boolean x = a == b;</code> is
+     * implemented as
+     * 
+     * <pre>
+     * ALOAD 1;
+     * ALOAD 2;
+     * IF_ACMPEQ label;
+     * PUSH false;
+     * GOTO: done;
+     * label: PUSH true;
+     * done: ASTORE 3;
+     * </pre>
+     */
+    @Override
+    public void visitJumpInsn(int opcode, Label label) {
+      switch (opcode) {
+        case Opcodes.IF_ACMPEQ:
+        case Opcodes.IF_ACMPNE:
+          Object type1 = stack.get(stack.size() - 2);
+          boolean jso1 = type1 instanceof String
+              && rewriterOracle.couldContainJso((String) type1);
+          Object type2 = stack.get(stack.size() - 1);
+          boolean jso2 = type2 instanceof String
+              && rewriterOracle.couldContainJso((String) type2);
+
+          if (jso1 || jso2) {
+            if (jso2) {
+              // Stack: something, something
+              super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Object");
+            }
+            if (jso1) {
+              // Stack: something, object2
+              super.visitInsn(Opcodes.SWAP);
+              // Stack: object2, something
+              super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Object");
+              // Stack: object2, object1
+            }
+          }
+      }
+      super.visitJumpInsn(opcode, label);
+    }
+  }
+
+  private final RewriterOracle rewriterOracle;
+  private String currentClass;
+
+  public RewriteObjectComparisons(ClassVisitor v, RewriterOracle rewriterOracle) {
+    super(v);
+    this.rewriterOracle = rewriterOracle;
+  }
+
+  @Override
+  public void visit(int version, int access, String name, String signature,
+      String superName, String[] interfaces) {
+    currentClass = name;
+    super.visit(version, access, name, signature, superName, interfaces);
+  }
+
+  @Override
+  public MethodVisitor visitMethod(int access, String name, String desc,
+      String signature, String[] exceptions) {
+    MethodVisitor mv = super.visitMethod(access, name, desc, signature,
+        exceptions);
+    if (mv != null) {
+      mv = new MyMethodAdapter(currentClass, access, name, desc, mv);
+    }
+    return mv;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteRefsToJsoClasses.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteRefsToJsoClasses.java
deleted file mode 100644
index 3a45363..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteRefsToJsoClasses.java
+++ /dev/null
@@ -1,150 +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.shell.rewrite;
-
-import com.google.gwt.dev.asm.ClassAdapter;
-import com.google.gwt.dev.asm.ClassVisitor;
-import com.google.gwt.dev.asm.MethodAdapter;
-import com.google.gwt.dev.asm.MethodVisitor;
-import com.google.gwt.dev.asm.Opcodes;
-import com.google.gwt.dev.asm.commons.Remapper;
-import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle;
-
-import java.util.Set;
-
-/**
- * Rewrites references to modified JSO subtypes.
- * 
- * <ol>
- * <li>Changes the owner type for instructions that reference items in a JSO
- * class to the implementation class.</li>
- * <li>Rewrites instance calls to JSO classes into static calls.</li>
- * <li>Updates the descriptor for such call sites to includes a synthetic
- * <code>this</code> parameter. This modified method has same stack behavior
- * as the original instance method.</li>
- * </ol>
- */
-class RewriteRefsToJsoClasses extends ClassAdapter {
-
-  /**
-   * A method body rewriter to actually rewrite call sites.
-   */
-  private class MyMethodAdapter extends MethodAdapter {
-
-    private Remapper remapper = new Remapper() {
-      @Override
-      public String map(String typeName) {
-        if (jsoDescriptors.contains(typeName)) {
-          return HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC;
-        }
-        return typeName;
-      }
-    };
-
-    public MyMethodAdapter(MethodVisitor mv) {
-      super(mv);
-    }
-
-    @Override
-    public void visitFieldInsn(int opcode, String owner, String name,
-        String desc) {
-      if (jsoDescriptors.contains(owner)) {
-        // Change the owner to the rewritten class.
-        owner += "$";
-      }
-      super.visitFieldInsn(opcode, owner, name, desc);
-    }
-
-    @Override
-    public void visitLdcInsn(Object cst) {
-      cst = remapper.mapValue(cst);
-      super.visitLdcInsn(cst);
-    }
-
-    @Override
-    public void visitMethodInsn(int opcode, String owner, String name,
-        String desc) {
-      if (jsoDescriptors.contains(owner)) {
-        // Find the class that actually declared the method.
-        if (opcode == Opcodes.INVOKEVIRTUAL) {
-          owner = mapper.findOriginalDeclaringClass(owner, name + desc);
-        }
-        if (!owner.equals("java/lang/Object")) {
-          if (opcode == Opcodes.INVOKEVIRTUAL
-              || opcode == Opcodes.INVOKESPECIAL) {
-            // Instance/super call to JSO; rewrite as static.
-            opcode = Opcodes.INVOKESTATIC;
-            desc = HostedModeClassRewriter.addSyntheticThisParam(owner, desc);
-            name += "$";
-          }
-          // Change the owner to implementation class.
-          owner += "$";
-        }
-      }
-      super.visitMethodInsn(opcode, owner, name, desc);
-    }
-
-    @Override
-    public void visitMultiANewArrayInsn(String desc, int dims) {
-      desc = remapper.mapType(desc);
-      super.visitMultiANewArrayInsn(desc, dims);
-    }
-
-    @Override
-    public void visitTypeInsn(int opcode, String type) {
-      if (opcode == Opcodes.ANEWARRAY) {
-        type = remapper.mapType(type);
-      }
-      super.visitTypeInsn(opcode, type);
-    }
-  }
-
-  /**
-   * An unmodifiable set of descriptors containing <code>JavaScriptObject</code>
-   * and all subclasses.
-   */
-  protected final Set<String> jsoDescriptors;
-
-  /**
-   * Maps methods to the class in which they are declared.
-   */
-  private InstanceMethodOracle mapper;
-
-  /**
-   * Construct a new rewriter instance.
-   * 
-   * @param cv the visitor to chain to
-   * @param jsoDescriptors an unmodifiable set of descriptors containing
-   *          <code>JavaScriptObject</code> and all subclasses
-   * @param mapper maps methods to the class in which they are declared
-   */
-  public RewriteRefsToJsoClasses(ClassVisitor cv, Set<String> jsoDescriptors,
-      InstanceMethodOracle mapper) {
-    super(cv);
-    this.jsoDescriptors = jsoDescriptors;
-    this.mapper = mapper;
-  }
-
-  @Override
-  public MethodVisitor visitMethod(int access, String name, String desc,
-      String signature, String[] exceptions) {
-    // Wrap the returned method visitor in my own.
-    MethodVisitor mv = super.visitMethod(access, name, desc, signature,
-        exceptions);
-    return new MyMethodAdapter(mv);
-  }
-
-}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
deleted file mode 100644
index b6c7c1e..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteSingleJsoImplDispatches.java
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * Copyright 2009 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.shell.rewrite;
-
-import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.asm.ClassAdapter;
-import com.google.gwt.dev.asm.ClassVisitor;
-import com.google.gwt.dev.asm.MethodAdapter;
-import com.google.gwt.dev.asm.MethodVisitor;
-import com.google.gwt.dev.asm.Opcodes;
-import com.google.gwt.dev.asm.Type;
-import com.google.gwt.dev.asm.commons.Method;
-import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
-import com.google.gwt.dev.util.collect.Maps;
-import com.google.gwt.dev.util.collect.Sets;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-/**
- * Effects the renaming of {@code @SingleJsoImpl} methods from their original
- * name to their mangled name. Let us call the original method an "unmangled
- * method" and the new method a "mangled method". There are three steps in this
- * process:
- * <ol>
- * <li>Within {@code @SingleJsoImpl} interfaces rename all unmangled methods to
- * become mangled methods.</li>
- * <li>Within non-JSO classes containing a concrete implementation of an
- * unmangled method, add a mangled method which is implemented as a simple
- * trampoline to the unmangled method. (We don't do this in JSO classes here
- * because the one-and-only trampoline lives in JavaScriptObject$ and is emitted
- * in {@link WriteJsoImpl}).
- * <li>Update all call sites targeting unmangled methods to target mangled
- * methods instead, provided the caller is binding to the interface rather than
- * a concrete type.</li>
- * </ol>
- */
-public class RewriteSingleJsoImplDispatches extends ClassAdapter {
-  private class MyMethodVisitor extends MethodAdapter {
-    public MyMethodVisitor(MethodVisitor mv) {
-      super(mv);
-    }
-
-    /*
-     * Implements objective #3: updates call sites to unmangled methods.
-     */
-    @Override
-    public void visitMethodInsn(int opcode, String owner, String name,
-        String desc) {
-      if (opcode == Opcodes.INVOKEINTERFACE) {
-        if (jsoData.getSingleJsoIntfTypes().contains(owner)) {
-          // Simple case; referring directly to a SingleJso interface.
-          name = owner.replace('/', '_') + "_" + name;
-          assert jsoData.getMangledNames().contains(name) : "Missing " + name;
-
-        } else {
-          /*
-           * Might be referring to a subtype of a SingleJso interface:
-           * 
-           * interface IA { void foo() }
-           * 
-           * interface JA extends JSO implements IA;
-           * 
-           * interface IB extends IA {}
-           * 
-           * void bar() { ((IB) object).foo(); }
-           */
-          outer : for (String intf : computeAllInterfaces(owner)) {
-            if (jsoData.getSingleJsoIntfTypes().contains(intf)) {
-              /*
-               * Check that it really should be mangled and is not a reference
-               * to a method defined in a non-singleJso super-interface. If
-               * there are two super-interfaces that define methods with
-               * identical names and descriptors, the choice of implementation
-               * is undefined.
-               */
-              String maybeMangled = intf.replace('/', '_') + "_" + name;
-              List<Method> methods = jsoData.getImplementations(maybeMangled);
-              if (methods != null) {
-                for (Method method : methods) {
-                  /*
-                   * Found a method with the right name, but we need to check
-                   * the parameters and the return type. In order to do this,
-                   * we'll look at the arguments and return type of the target
-                   * method, removing the first argument, which is the instance.
-                   */
-                  assert method.getArgumentTypes().length >= 1;
-                  Type[] argumentTypes = new Type[method.getArgumentTypes().length - 1];
-                  System.arraycopy(method.getArgumentTypes(), 1, argumentTypes,
-                      0, argumentTypes.length);
-                  String maybeDescriptor = Type.getMethodDescriptor(
-                      method.getReturnType(), argumentTypes);
-                  if (maybeDescriptor.equals(desc)) {
-                    name = maybeMangled;
-                    break outer;
-                  }
-                }
-              }
-            }
-          }
-        }
-      }
-
-      super.visitMethodInsn(opcode, owner, name, desc);
-    }
-  }
-
-  private String currentTypeName;
-  private final Set<String> implementedMethods = new HashSet<String>();
-  private boolean inSingleJsoImplInterfaceType;
-  private Map<String, Set<String>> intfNamesToAllInterfaces = Maps.create();
-  private final SingleJsoImplData jsoData;
-  private final TypeOracle typeOracle;
-
-  public RewriteSingleJsoImplDispatches(ClassVisitor v, TypeOracle typeOracle,
-      SingleJsoImplData jsoData) {
-    super(v);
-    this.typeOracle = typeOracle;
-    this.jsoData = jsoData;
-  }
-
-  @Override
-  public void visit(int version, int access, String name, String signature,
-      String superName, String[] interfaces) {
-    assert currentTypeName == null;
-    super.visit(version, access, name, signature, superName, interfaces);
-
-    /*
-     * This visitor would mangle JSO$ since it acts as a roll-up of all
-     * SingleJso types and the result would be repeated method definitions due
-     * to the trampoline methods this visitor would create.
-     */
-    if (name.equals(HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC)) {
-      return;
-    }
-
-    currentTypeName = name;
-    inSingleJsoImplInterfaceType = jsoData.getSingleJsoIntfTypes().contains(
-        name);
-
-    /*
-     * Implements objective #2: non-JSO types that implement a SingleJsoImpl
-     * interface don't have their original instance methods altered. Instead, we
-     * add trampoline methods with mangled names that simply call over to the
-     * original methods.
-     */
-    if (interfaces != null && (access & Opcodes.ACC_INTERFACE) == 0) {
-      Set<String> toStub = computeAllInterfaces(interfaces);
-      toStub.retainAll(jsoData.getSingleJsoIntfTypes());
-
-      for (String stubIntr : toStub) {
-        writeTrampoline(stubIntr);
-      }
-    }
-  }
-
-  @Override
-  public void visitEnd() {
-    /*
-     * Add any missing methods that are defined by a super-interface, but that
-     * may be referenced via a more specific interface.
-     */
-    if (inSingleJsoImplInterfaceType) {
-      for (Map.Entry<String, List<Method>> entry : toImplement(currentTypeName).entrySet()) {
-        for (Method method : entry.getValue()) {
-          writeEmptyMethod(entry.getKey(), method);
-        }
-      }
-    }
-    super.visitEnd();
-  }
-
-  @Override
-  public MethodVisitor visitMethod(int access, String name, String desc,
-      String signature, String[] exceptions) {
-
-    /*
-     * Implements objective #2: Rename unmangled methods in a @SingleJsoImpl
-     * into mangled methods (except for clinit, LOL).
-     */
-    if (inSingleJsoImplInterfaceType && !"<clinit>".equals(name)) {
-      name = currentTypeName.replace('/', '_') + "_" + name;
-      implementedMethods.add(name);
-    }
-
-    MethodVisitor mv = super.visitMethod(access, name, desc, signature,
-        exceptions);
-    if (mv == null) {
-      return null;
-    }
-
-    return new MyMethodVisitor(mv);
-  }
-
-  private Set<String> computeAllInterfaces(String intfName) {
-    Set<String> toReturn = intfNamesToAllInterfaces.get(intfName);
-    if (toReturn != null) {
-      return toReturn;
-    }
-
-    toReturn = Sets.create();
-    List<JClassType> q = new LinkedList<JClassType>();
-    JClassType intf = typeOracle.findType(intfName.replace('/', '.').replace(
-        '$', '.'));
-    assert intf != null : "Could not find interface " + intfName;
-    q.add(intf);
-
-    while (!q.isEmpty()) {
-      intf = q.remove(0);
-      String resourceName = getResourceName(intf);
-      if (!toReturn.contains(resourceName)) {
-        toReturn = Sets.add(toReturn, resourceName);
-        Collections.addAll(q, intf.getImplementedInterfaces());
-      }
-    }
-
-    intfNamesToAllInterfaces = Maps.put(intfNamesToAllInterfaces, intfName,
-        toReturn);
-    return toReturn;
-  }
-
-  private Set<String> computeAllInterfaces(String[] interfaces) {
-    Set<String> toReturn = new HashSet<String>();
-    for (String intfName : interfaces) {
-      toReturn.addAll(computeAllInterfaces(intfName));
-    }
-    return toReturn;
-  }
-
-  private String getResourceName(JClassType type) {
-    if (type.getEnclosingType() != null) {
-      return getResourceName(type.getEnclosingType()) + "$"
-          + type.getSimpleSourceName();
-    }
-    return type.getQualifiedSourceName().replace('.', '/');
-  }
-
-  /**
-   * Given a resource name of a class, find all mangled method names that must
-   * be implemented.
-   */
-  private SortedMap<String, List<Method>> toImplement(String typeName) {
-    String name = typeName.replace('/', '_');
-    String prefix = name + "_";
-    String suffix = name + "`";
-    SortedMap<String, List<Method>> toReturn = new TreeMap<String, List<Method>>();
-
-    for (String mangledName : jsoData.getMangledNames().subSet(prefix, suffix)) {
-      toReturn.put(mangledName, jsoData.getImplementations(mangledName));
-    }
-    toReturn.keySet().removeAll(implementedMethods);
-    return toReturn;
-  }
-
-  private void writeEmptyMethod(String mangledMethodName, Method method) {
-    assert method.getArgumentTypes().length > 0;
-    // Remove the first argument, which would be the implementing JSO type
-    String descriptor = "("
-        + method.getDescriptor().substring(
-            1 + method.getArgumentTypes()[0].getDescriptor().length());
-
-    // Create the stub method entry in the interface
-    MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC
-        | Opcodes.ACC_ABSTRACT, mangledMethodName, descriptor, null, null);
-    mv.visitEnd();
-  }
-
-  /**
-   * For regular Java objects that implement a SingleJsoImpl interface, write
-   * instance trampoline dispatchers for mangled method names to the
-   * implementing method.
-   */
-  private void writeTrampoline(String stubIntr) {
-    /*
-     * This is almost the same kind of trampoline as the ones generated in
-     * WriteJsoImpl, however there are enough small differences between the
-     * semantics of the dispatches that would make a common implementation far
-     * more awkward than the duplication of code.
-     */
-    for (Map.Entry<String, List<Method>> entry : toImplement(stubIntr).entrySet()) {
-      for (Method method : entry.getValue()) {
-        String mangledName = entry.getKey();
-
-        String descriptor = "("
-            + method.getDescriptor().substring(
-                1 + method.getArgumentTypes()[0].getDescriptor().length());
-        String localName = method.getName().substring(0,
-            method.getName().length() - 1);
-        Method toCall = new Method(localName, descriptor);
-
-        // Must not be final
-        MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC
-            | Opcodes.ACC_SYNTHETIC, mangledName, descriptor, null, null);
-        if (mv != null) {
-          mv.visitCode();
-
-          /*
-           * It just so happens that the stack and local variable sizes are the
-           * same, but they're kept distinct to aid in clarity should the
-           * dispatch logic change.
-           * 
-           * These start at 1 because we need to load "this" onto the stack
-           */
-          int var = 1;
-          int size = 1;
-
-          // load this
-          mv.visitVarInsn(Opcodes.ALOAD, 0);
-
-          // then the rest of the arguments
-          for (Type t : toCall.getArgumentTypes()) {
-            size += t.getSize();
-            mv.visitVarInsn(t.getOpcode(Opcodes.ILOAD), var);
-            var += t.getSize();
-          }
-
-          // Make sure there's enough room for the return value
-          size = Math.max(size, toCall.getReturnType().getSize());
-
-          mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, currentTypeName,
-              toCall.getName(), toCall.getDescriptor());
-          mv.visitInsn(toCall.getReturnType().getOpcode(Opcodes.IRETURN));
-          mv.visitMaxs(size, var);
-          mv.visitEnd();
-        }
-      }
-    }
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/SingleJsoImplSupport.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/SingleJsoImplSupport.java
new file mode 100644
index 0000000..36a182e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/SingleJsoImplSupport.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2010 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.shell.rewrite;
+
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.CANONICAL_FIELD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REWRAP_METHOD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_ADJUNCT_SUFFIX;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_FIELD;
+
+import com.google.gwt.dev.shell.JsValueGlue;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Utility methods that are called by code generated by
+ * {@link WriteSingleJsoSupportCode}.
+ */
+public class SingleJsoImplSupport {
+
+  /**
+   * Stash a reference to the JavaScriptObject Class object.
+   */
+  private static final ThreadLocal<Class<?>> JSO_CLASS_OBJECT = new ThreadLocal<Class<?>>();
+
+  /**
+   * Called from JsValueGlue to ensure that a JSO instance can be cast to the
+   * desired type. If the desired type is a regular JSO subclass, this is a
+   * simple rewrap call. Otherwise, assume that it's a SingleJsoImpl interface
+   * type and use {@link #cast(Object, Class, Class)}.
+   */
+  public static Object cast(Object o, Class<?> jsoOrIntfType) {
+    assert o != null : "o is null";
+    assert jsoOrIntfType != Object.class : "Not expecting Object";
+
+    if (jsoOrIntfType.isInterface()) {
+      // Casting to an interface type
+      Class<?> targetJsoType = getTargetJsoType(jsoOrIntfType);
+
+      checkTarget(jsoOrIntfType, targetJsoType);
+
+      // Let the no-implementing-type code in cast(3) handle the error
+      return rewrap(targetJsoType, o);
+    }
+
+    if (getJsoClass(o).isAssignableFrom(jsoOrIntfType)) {
+      // A JSO type
+      return rewrap(jsoOrIntfType, o);
+    }
+
+    // Something else, let the call-site hit CCE
+    return o;
+  }
+
+  /**
+   * Called by synthetic code to cast an object <code>o</code> to some concrete
+   * type that implements the interface <code>intfType</code>. If a
+   * JavaScriptObject subtype has been loaded that implements the interface,
+   * that type will be provided in <code>targetJsoType</code>.
+   * 
+   * @return an object that implements <code>intfType</code>
+   * @throws ClassCastException if <code>o</code> is a JSO subtype and there is
+   *           no SingleJsoImpl type for <code>intfType</code>
+   */
+  public static Object cast(Object o, Class<?> intfType, Class<?> targetJsoType) {
+    if (o == null || intfType.isInstance(o) || !getJsoClass(o).isInstance(o)) {
+      // Let class cast exception happen at callsite
+      return o;
+    }
+
+    checkTarget(intfType, targetJsoType);
+
+    return rewrap(targetJsoType, o);
+  }
+
+  /**
+   * Called by synthetic code when the object on the stack might be a JSO
+   * wrapper and we want the canonical object.
+   */
+  public static Object ensureCanonical(Object o) {
+    if (o == null) {
+      return null;
+    }
+
+    Class<?> jsoClass = getJsoClass(o);
+    if (jsoClass.isInstance(o)) {
+      Exception ex;
+      try {
+        return jsoClass.getField(CANONICAL_FIELD).get(o);
+      } catch (IllegalArgumentException e) {
+        ex = e;
+      } catch (SecurityException e) {
+        ex = e;
+      } catch (IllegalAccessException e) {
+        ex = e;
+      } catch (NoSuchFieldException e) {
+        ex = e;
+      }
+      throw new RuntimeException("Unable to determine canonical object", ex);
+    }
+
+    return o;
+  }
+
+  /**
+   * Returns the JSO implementation type declared on the interface via a
+   * SingleJsoImpl annotation.
+   */
+  @SuppressWarnings("unchecked")
+  public static Class<?> getDeclaredSingleJsoImplType(Class<?> intfType) {
+    Exception ex;
+    try {
+      Class singleJsoImplType = intfType.getClassLoader().loadClass(
+          "com.google.gwt.core.client.SingleJsoImpl");
+      Object annotation = intfType.getAnnotation(singleJsoImplType);
+      if (annotation != null) {
+        Method m = singleJsoImplType.getMethod("value");
+        Class<?> value = (Class<?>) m.invoke(annotation);
+        return value;
+      }
+
+      // Try the by-name annotation
+      singleJsoImplType = intfType.getClassLoader().loadClass(
+          "com.google.gwt.core.client.SingleJsoImplName");
+      annotation = intfType.getAnnotation(singleJsoImplType);
+      if (annotation != null) {
+        Method m = singleJsoImplType.getMethod("value");
+        String name = (String) m.invoke(annotation);
+
+        return Class.forName(name, true, intfType.getClassLoader());
+      }
+
+      return null;
+    } catch (ClassNotFoundException e) {
+      ex = e;
+    } catch (IllegalArgumentException e) {
+      ex = e;
+    } catch (IllegalAccessException e) {
+      ex = e;
+    } catch (InvocationTargetException e) {
+      ex = e;
+    } catch (SecurityException e) {
+      ex = e;
+    } catch (NoSuchMethodException e) {
+      ex = e;
+    }
+    throw new RuntimeException("Unable to determine SingleJsoImpl type", ex);
+  }
+
+  /**
+   * Called by synthetic code to implement instanceof tests.
+   * 
+   * @param o the object to test
+   * @param intf the interface type that <code>o</code> is being tested against
+   * @param implJsoType the JavaScriptObject subtype that implements
+   *          <code>intf</code> if it has been loaded
+   * @return <code>true</code> if <code>o</code> implements <code>intf</code> or
+   *         if <code>o</code> is a JSO and some JSO type that implements
+   *         <code>intf</code> has been loaded
+   */
+  public static boolean instanceOf(Object o, Class<?> intf, Class<?> implJsoType) {
+    return intf.isInstance(o)
+        || (implJsoType != null && getJsoClass(o).isInstance(o));
+  }
+
+  /**
+   * Throw a specialized ClassCastException if <code>targetJsoType</code> is
+   * null.
+   */
+  private static void checkTarget(Class<?> intfType, Class<?> targetJsoType) {
+    if (targetJsoType == null) {
+      throw new ClassCastException(
+          "There is no known JavaScriptObject subtype that implements "
+              + intfType.getCanonicalName()
+              + ". Fix by adding an @SingleJsoImpl annotation to "
+              + intfType.getSimpleName()
+              + " or by referencing the concrete JSO type.");
+    }
+  }
+
+  /**
+   * Load the JavaScriptObject Class object from a client object's isolated
+   * ClassLoader.
+   */
+  private static Class<?> getJsoClass(Object jso) {
+    Class<?> toReturn = JSO_CLASS_OBJECT.get();
+    if (toReturn != null) {
+      return toReturn;
+    }
+    try {
+      toReturn = jso.getClass().getClassLoader().loadClass(
+          JsValueGlue.JSO_CLASS);
+    } catch (ClassNotFoundException e) {
+      throw new RuntimeException("No JavaScriptObject class", e);
+    }
+    JSO_CLASS_OBJECT.set(toReturn);
+    return toReturn;
+  }
+
+  private static Class<?> getTargetJsoType(Class<?> intfType) {
+    Field f;
+    Exception ex;
+    try {
+      intfType = intfType.getClassLoader().loadClass(
+          intfType.getName() + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX);
+      f = intfType.getField(SINGLE_JSO_IMPL_FIELD);
+      return (Class<?>) f.get(null);
+    } catch (SecurityException e) {
+      ex = e;
+    } catch (NoSuchFieldException e) {
+      ex = e;
+    } catch (IllegalArgumentException e) {
+      ex = e;
+    } catch (IllegalAccessException e) {
+      ex = e;
+    } catch (ClassNotFoundException e) {
+      ex = e;
+    }
+    throw new RuntimeException("Unable to query singleJsoImpl type", ex);
+  }
+
+  /**
+   * Reflectively invoke a JSO subtype's rewrap method to create a new wrapper
+   * instance for a JavaScriptObject.
+   * 
+   * @param jsoClass the desired wrapper type
+   * @param jso a JavaScriptObject
+   * @return a JavaScriptObject wrapper that encloses <code>jso</code>'s
+   *         canonical JSO identity
+   */
+  private static Object rewrap(Class<?> jsoClass, Object jso) {
+    Method m;
+    Exception ex;
+    try {
+      m = jsoClass.getMethod(REWRAP_METHOD, getJsoClass(jsoClass));
+      return m.invoke(null, jso);
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException("Missing expected synthetic method", e);
+    } catch (SecurityException e) {
+      ex = e;
+    } catch (IllegalArgumentException e) {
+      ex = e;
+    } catch (IllegalAccessException e) {
+      ex = e;
+    } catch (InvocationTargetException e) {
+      ex = e;
+    }
+    throw new RuntimeException(ex);
+  }
+
+  /**
+   * Utility class.
+   */
+  private SingleJsoImplSupport() {
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteJsoImpl.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteJsoImpl.java
index 4eafd89..92acc1b 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteJsoImpl.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteJsoImpl.java
@@ -15,94 +15,79 @@
  */
 package com.google.gwt.dev.shell.rewrite;
 
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.CANONICAL_FIELD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.JAVASCRIPTOBJECT_DESC;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REFERENCE_FIELD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REWRAP_METHOD;
+
 import com.google.gwt.dev.asm.ClassAdapter;
 import com.google.gwt.dev.asm.ClassVisitor;
 import com.google.gwt.dev.asm.FieldVisitor;
+import com.google.gwt.dev.asm.Label;
 import com.google.gwt.dev.asm.MethodVisitor;
 import com.google.gwt.dev.asm.Opcodes;
-import com.google.gwt.dev.asm.Type;
-import com.google.gwt.dev.asm.commons.Method;
-import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle;
-import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
 
 /**
  * Writes the implementation classes for JSO and its subtypes.
- * 
- * Changes made by the base class:
- * <ol>
- * <li>The new type has the same name as the old type with a '$' appended.</li>
- * <li>All instance methods in the original type become static methods taking an
- * explicit <code>this</code> parameter. Such methods have the same stack
- * behavior as the original.</li>
- * </ol>
  */
-abstract class WriteJsoImpl extends ClassAdapter {
+class WriteJsoImpl {
 
   /**
    * This type implements JavaScriptObject.
    * 
-   * <ol>
+   * <ul>
    * <li>JavaScriptObject itself gets a new synthetic field to store the
    * underlying hosted mode reference.</li>
-   * <li>Instance methods are added so that JavaScriptObject implements all
-   * SingleJsoImpl interfaces.</li>
-   * </ol>
+   * <li>It also receives a field to retain the canonical JavaScriptObject when
+   * creating wrapper subclasses.</li>
+   * <li>A rewrap method is added that simply returns the input object's
+   * canonical object.</li>
+   * <li>The zero-arg constructor is made public and makes the JavaScriptObject
+   * its own canonical object.</li>
+   * <li>A one-arg constructor is added for use by subclasses that copies the
+   * hosted mode reference and canonical identity object.</li>
+   * </ul>
    * 
    */
-  private static class ForJsoDollar extends WriteJsoImpl {
-    /**
-     * An unmodifiable set of descriptors containing
-     * <code>JavaScriptObject</code> and all subclasses.
-     */
-    private final Set<String> jsoDescriptors;
-    private final SingleJsoImplData jsoData;
-
-    public ForJsoDollar(ClassVisitor cv, Set<String> jsoDescriptors,
-        InstanceMethodOracle mapper, SingleJsoImplData jsoData) {
-      super(cv, mapper);
-      this.jsoDescriptors = jsoDescriptors;
-      this.jsoData = jsoData;
+  private static class ForJso extends ClassAdapter {
+    public ForJso(ClassVisitor cv) {
+      super(cv);
     }
 
     @Override
     public void visit(int version, int access, String name, String signature,
         String superName, String[] interfaces) {
 
-      ArrayList<String> jsoDescList = new ArrayList<String>();
-      jsoDescList.addAll(jsoDescriptors);
-      interfaces = jsoDescList.toArray(new String[jsoDescList.size()]);
+      super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
+          | Opcodes.ACC_SYNTHETIC, name, signature, superName, interfaces);
 
-      super.visit(version, access, name, signature, superName, interfaces);
+      // Generate JavaScriptObject.rewrap$()
+      MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC
+          | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, REWRAP_METHOD, "(L"
+          + JAVASCRIPTOBJECT_DESC + ";)L" + name + ";", null, null);
+      if (mv != null) {
+        writeRewrapMethod(mv);
+      }
 
       /*
        * Generate the synthetic "hostedModeReferece" field to contain the
        * underlying real reference to the JavaScript object.
        */
-      FieldVisitor fv = visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
-          HostedModeClassRewriter.REFERENCE_FIELD, "Ljava/lang/Object;", null,
+      FieldVisitor fv = visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC
+          | Opcodes.ACC_FINAL, REFERENCE_FIELD, "Ljava/lang/Object;", null,
           null);
       if (fv != null) {
         fv.visitEnd();
       }
 
-      // Implement the trampoline methods
-      for (String mangledName : jsoData.getMangledNames()) {
-        List<Method> declarations = jsoData.getDeclarations(mangledName);
-        List<Method> implementations = jsoData.getImplementations(mangledName);
-        assert declarations.size() == implementations.size() : "Declaration / implementation size mismatch";
-
-        Iterator<Method> declIterator = declarations.iterator();
-        Iterator<Method> implIterator = implementations.iterator();
-
-        while (declIterator.hasNext()) {
-          assert implIterator.hasNext();
-          writeTrampoline(mangledName, declIterator.next(), implIterator.next());
-        }
+      /*
+       * Generate a synthetic "canonical" field.
+       */
+      fv = visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC
+          | Opcodes.ACC_FINAL, CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC
+          + ";", null, null);
+      if (fv != null) {
+        fv.visitEnd();
       }
     }
 
@@ -110,123 +95,364 @@
     public MethodVisitor visitMethod(int access, String name, String desc,
         String signature, String[] exceptions) {
       if (isCtor(name)) {
-        // make the JavaScriptObject$ constructor public
-        access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
-        access |= Opcodes.ACC_PUBLIC;
+        writeConstructors(name);
+        return null;
+      } else if ("equals".equals(name) && "(Ljava/lang/Object;)Z".equals(desc)) {
+        writeEquals(access, name, desc, signature, exceptions);
+        return null;
       }
       return super.visitMethod(access, name, desc, signature, exceptions);
     }
 
     /**
-     * JSO methods are implemented as flyweight style, with the instance being
-     * passed as the first parameter. This loop create instance methods on JSO$
-     * for all of the mangled SingleJsoImpl interface method names. These
-     * instance methods simply turn around and call the static-dispatch methods.
-     * In Java, it might look like:
+     * Generates a method to return the canonical object.
      * 
      * <pre>
-     * interface Interface {
-     *   String someMethod(int a, double b);
-     * }
-     * 
-     * class J extends JSO implements I {
-     *   public String com_google_Interface_someMethod(int a, double b) {
-     *     return com.google.MyJso$.someMethod$(this, a, b);
+     * public JavaScriptObject rewrap$(JavaScriptObject o) {
+     *   if (o == null) {
+     *     return null;
      *   }
+     *   return o.canonical;
      * }
      * </pre>
-     * 
-     * @param mangledName {@code com_google_gwt_sample_hello_client_Interface_a}
-     * @param interfaceMethod {@code java.lang.String a(int, double)}
-     * @param implementingMethod {@code static final java.lang.String
-     *          a$(com.google.gwt.sample.hello.client.Jso, ...);}
      */
-    private void writeTrampoline(String mangledName, Method interfaceMethod,
-        Method implementingMethod) {
-      assert implementingMethod.getArgumentTypes().length > 0;
+    protected void writeRewrapMethod(MethodVisitor mv) {
+      Label start = new Label();
+      Label end = new Label();
 
-      /*
-       * The local descriptor is the same as the descriptor from the abstract
-       * method in the interface.
-       */
-      String localDescriptor = interfaceMethod.getDescriptor();
-      Method localMethod = new Method(mangledName, localDescriptor);
+      mv.visitCode();
+      mv.visitLabel(start);
 
-      /*
-       * We also use the first argument to know which type to statically
-       * dispatch to.
-       */
-      Type implementingType = Type.getType("L"
-          + implementingMethod.getArgumentTypes()[0].getInternalName() + "$;");
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      // Stack is: jso
+      mv.visitInsn(Opcodes.DUP);
+      // Stack is: jso, jso
 
-      // Maybe create the method. This is marked final as a sanity check
-      MethodVisitor mv = visitMethodNoRewrite(Opcodes.ACC_PUBLIC
-          | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC, localMethod.getName(),
-          localMethod.getDescriptor(), null, null);
+      Label ret = new Label();
+      mv.visitJumpInsn(Opcodes.IFNULL, ret);
+      // Stack is: jso
 
+      mv.visitFieldInsn(Opcodes.GETFIELD, JAVASCRIPTOBJECT_DESC,
+          CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC + ";");
+      // Stack is: canonical
+
+      mv.visitLabel(ret);
+      mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {JAVASCRIPTOBJECT_DESC}, 1,
+          new Object[] {JAVASCRIPTOBJECT_DESC});
+      mv.visitInsn(Opcodes.ARETURN);
+
+      mv.visitMaxs(2, 1);
+      mv.visitLabel(end);
+      mv.visitLocalVariable("jso", "L" + JAVASCRIPTOBJECT_DESC + ";", null,
+          start, end, 0);
+      mv.visitEnd();
+    }
+
+    /**
+     * Write JavaScriptObject's constructors.
+     * 
+     * <pre>
+     * public JavaScriptObject(Object hostedModeReference) {
+     *   this.canonical = this;
+     *   this.hostedModeReference = hostedModeReference;
+     * }
+     * protected JavaScriptObject(JavaScriptObject jso) {
+     *   this.canonical = jso.canonical;
+     *   this.hostedModeReference = jso.hostedModeReference;
+     * }
+     * </pre>
+     */
+    private void writeConstructors(String name) {
+      // Write the zero-arg constructor
+      MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC
+          | Opcodes.ACC_SYNTHETIC, name, "(Ljava/lang/Object;)V", null, null);
       if (mv != null) {
         mv.visitCode();
+        // Call Object's constructor
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitInsn(Opcodes.DUP);
+        // Stack: this, this
+        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
+            "()V");
+        // Stack: this
 
-        /*
-         * It just so happens that the stack and local variable sizes are the
-         * same, but they're kept distinct to aid in clarity should the dispatch
-         * logic change.
-         */
-        int var = 0;
-        int size = 0;
+        // this.canonical = this;
+        mv.visitInsn(Opcodes.DUP);
+        mv.visitInsn(Opcodes.DUP);
+        mv.visitVarInsn(Opcodes.ALOAD, 1);
+        // Stack: this, this, this, hostedModeReference
+        mv.visitFieldInsn(Opcodes.PUTFIELD, JAVASCRIPTOBJECT_DESC,
+            REFERENCE_FIELD, "Ljava/lang/Object;");
+        // Stack: this, this
+        mv.visitFieldInsn(Opcodes.PUTFIELD, JAVASCRIPTOBJECT_DESC,
+            CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC + ";");
+        // Stack: <empty>
 
-        for (Type t : implementingMethod.getArgumentTypes()) {
-          size += t.getSize();
-          mv.visitVarInsn(t.getOpcode(Opcodes.ILOAD), var);
-          var += t.getSize();
-        }
-
-        // Make sure there's enough room for the return value
-        size = Math.max(size, implementingMethod.getReturnType().getSize());
-
-        mv.visitMethodInsn(Opcodes.INVOKESTATIC,
-            implementingType.getInternalName(), implementingMethod.getName(),
-            implementingMethod.getDescriptor());
-        mv.visitInsn(localMethod.getReturnType().getOpcode(Opcodes.IRETURN));
-        mv.visitMaxs(size, var);
+        mv.visitInsn(Opcodes.RETURN);
+        mv.visitMaxs(4, 2);
         mv.visitEnd();
       }
+
+      // Write the protected one-arg constructor
+      mv = super.visitMethod(Opcodes.ACC_PROTECTED | Opcodes.ACC_SYNTHETIC,
+          name, "(L" + JAVASCRIPTOBJECT_DESC + ";)V", null, null);
+      if (mv != null) {
+        Label start = new Label();
+        Label end = new Label();
+
+        // Call Object's constructor
+        mv.visitCode();
+        mv.visitLabel(start);
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        // Stack: this
+        mv.visitInsn(Opcodes.DUP);
+        // Stack: this, this
+        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>",
+            "()V");
+        // Stack: this
+
+        // this.canonical = otherJso;
+        mv.visitVarInsn(Opcodes.ALOAD, 1);
+        // Stack: this, otherJso
+        mv.visitInsn(Opcodes.DUP2);
+        // Stack: this, otherJso, this, otherJso
+        mv.visitFieldInsn(Opcodes.GETFIELD, JAVASCRIPTOBJECT_DESC,
+            CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC + ";");
+        // Stack: this, otherJso, this, canonical
+        mv.visitFieldInsn(Opcodes.PUTFIELD, JAVASCRIPTOBJECT_DESC,
+            CANONICAL_FIELD, "L" + JAVASCRIPTOBJECT_DESC + ";");
+        // Stack: this, otherJso
+
+        // this.hostedModeReference = otherJso.hostedModeReference
+        mv.visitFieldInsn(Opcodes.GETFIELD, JAVASCRIPTOBJECT_DESC,
+            REFERENCE_FIELD, "Ljava/lang/Object;");
+        // Stack: this, hostedModeReference
+        mv.visitFieldInsn(Opcodes.PUTFIELD, JAVASCRIPTOBJECT_DESC,
+            REFERENCE_FIELD, "Ljava/lang/Object;");
+        // Stack: <empty>
+
+        mv.visitInsn(Opcodes.RETURN);
+        mv.visitMaxs(4, 2);
+        mv.visitLabel(end);
+        mv.visitLocalVariable("this", "L" + JAVASCRIPTOBJECT_DESC + ";", null,
+            start, end, 0);
+        mv.visitLocalVariable("jso", "L" + JAVASCRIPTOBJECT_DESC + ";", null,
+            start, end, 1);
+        mv.visitEnd();
+      }
+    }
+
+    /**
+     * Write the implementation of JSO.equals() to use a regular object-identity
+     * comparison that can be rewritten further.
+     * 
+     * <pre>
+     * public boolean equals(Object other) {
+     *   return this == other;
+     * }
+     * </pre>
+     */
+    private void writeEquals(int access, String name, String desc,
+        String signature, String[] exceptions) {
+      MethodVisitor mv = super.visitMethod(access, name, desc, signature,
+          exceptions);
+      if (mv == null) {
+        return;
+      }
+      mv.visitCode();
+
+      Label returnTrue = new Label();
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      mv.visitVarInsn(Opcodes.ALOAD, 1);
+      // Stack: this, other
+
+      mv.visitJumpInsn(Opcodes.IF_ACMPEQ, returnTrue);
+      mv.visitInsn(Opcodes.ICONST_0);
+      // Stack: 0
+      mv.visitInsn(Opcodes.IRETURN);
+
+      mv.visitLabel(returnTrue);
+      mv.visitFrame(Opcodes.F_NEW, 2, new Object[] {
+          JAVASCRIPTOBJECT_DESC, "java/lang/Object"}, 0, new Object[0]);
+      mv.visitInsn(Opcodes.ICONST_1);
+      // Stack: 1
+      mv.visitInsn(Opcodes.IRETURN);
+
+      mv.visitMaxs(2, 2);
+      mv.visitEnd();
     }
   }
 
   /**
    * This type is used to implement subtypes of JSO.
    * 
-   * <ol>
-   * <li>The new type's superclass is mangled by adding $.</li>
-   * <li>Constructors are deleted.</li>
-   * </ol>
+   * <ul>
+   * <li>The type's zero-arg constructor is replaced with a one-arg copy
+   * constructor that delegates to the one-arg super-constructor.</li>
+   * <li>A static rewrap method is added</li>
+   * </ul>
    */
-  private static class ForJsoInterface extends WriteJsoImpl {
-    public ForJsoInterface(ClassVisitor cv, InstanceMethodOracle mapper) {
-      super(cv, mapper);
+  private static class ForJsoSubclass extends ClassAdapter {
+    private String superName;
+    private String typeName;
+
+    public ForJsoSubclass(ClassVisitor cv) {
+      super(cv);
     }
 
     @Override
     public void visit(int version, int access, String name, String signature,
         String superName, String[] interfaces) {
-      // Reference the old superclass's implementation class.
-      superName += '$';
-      interfaces = null;
+      this.superName = superName;
+      this.typeName = name;
+      super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
+          | Opcodes.ACC_SYNTHETIC, name, signature, superName, interfaces);
 
-      super.visit(version, access, name, signature, superName, interfaces);
+      // Generate JsoSubtype.rewrap$()
+      MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC
+          | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, REWRAP_METHOD, "(L"
+          + JAVASCRIPTOBJECT_DESC + ";)L" + name + ";", null, null);
+      if (mv != null) {
+        writeRewrapMethod(mv);
+      }
     }
 
+    /**
+     * Rewrite the JSO's constructor.
+     * 
+     * <pre>
+     * protected SomeJso(JavaScriptObject other) {
+     *   super(other);
+     * }
+     * </pre>
+     */
     @Override
     public MethodVisitor visitMethod(int access, String name, String desc,
         String signature, String[] exceptions) {
       boolean isCtor = isCtor(name);
       if (isCtor) {
-        // Don't copy over constructors except for JavaScriptObject itself.
+        MethodVisitor mv = super.visitMethod(Opcodes.ACC_PROTECTED
+            | Opcodes.ACC_SYNTHETIC, name,
+            "(L" + JAVASCRIPTOBJECT_DESC + ";)V", null, null);
+        if (mv == null) {
+          return null;
+        }
+
+        Label start = new Label();
+        Label end = new Label();
+        mv.visitCode();
+        mv.visitLabel(start);
+        // super(otherJso)
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        // Stack: this
+        mv.visitVarInsn(Opcodes.ALOAD, 1);
+        // Stack: this, other
+        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "<init>", "(L"
+            + JAVASCRIPTOBJECT_DESC + ";)V");
+        // Stack: <empty>
+        mv.visitInsn(Opcodes.RETURN);
+
+        mv.visitMaxs(2, 2);
+        mv.visitLabel(end);
+
+        // For debugging
+        mv.visitLocalVariable("this", "L" + typeName + ";", null, start, end, 0);
+        mv.visitLocalVariable("jso", "L" + JAVASCRIPTOBJECT_DESC + ";", null,
+            start, end, 1);
+        mv.visitEnd();
+
         return null;
       }
       return super.visitMethod(access, name, desc, signature, exceptions);
     }
+
+    /**
+     * Constructs a type-specific rewrap method.
+     * 
+     * <pre>
+     * public static JsoSubclass rewrap$(JavaScriptObject jso) {
+     *   start:
+     *   if (jso == null) {
+     *     topOfStack = null;
+     *     goto doReturn;
+     *   }
+     *   
+     *   notNull: if (jso instanceof JsoSubclass) {
+     *     topOfStack = (JsoSubclass) jso;
+     *     goto doReturn;
+     *   }
+     *   
+     *   notMySubclass: topOfStack = new JsoSubclass(jso);
+     *   
+     *   doReturn: return topOfStack;
+     *   end:
+     * }
+     * </pre>
+     */
+    protected void writeRewrapMethod(MethodVisitor mv) {
+      Label start = new Label();
+      Label notNull = new Label();
+      Label notMySubclass = new Label();
+      Label doReturn = new Label();
+      Label end = new Label();
+
+      mv.visitCode();
+      mv.visitLabel(start);
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      mv.visitInsn(Opcodes.DUP);
+      // Stack is: jso, jso
+
+      mv.visitJumpInsn(Opcodes.IFNONNULL, notNull);
+      // Stack is: jso
+      // Push a null instead of using dup so that we don't need a useless cast
+      mv.visitInsn(Opcodes.POP);
+      mv.visitInsn(Opcodes.ACONST_NULL);
+      // Stack is: null
+      mv.visitJumpInsn(Opcodes.GOTO, doReturn);
+
+      mv.visitLabel(notNull);
+      mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {JAVASCRIPTOBJECT_DESC}, 1,
+          new Object[] {JAVASCRIPTOBJECT_DESC});
+      mv.visitInsn(Opcodes.DUP);
+      // Stack is: jso, jso
+      mv.visitTypeInsn(Opcodes.INSTANCEOF, typeName);
+      // Stack is: jso, boolean
+      mv.visitJumpInsn(Opcodes.IFEQ, notMySubclass);
+      // Stack is: jso
+      mv.visitTypeInsn(Opcodes.CHECKCAST, typeName);
+      mv.visitJumpInsn(Opcodes.GOTO, doReturn);
+
+      mv.visitLabel(notMySubclass);
+      mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {JAVASCRIPTOBJECT_DESC}, 1,
+          new Object[] {JAVASCRIPTOBJECT_DESC});
+      // Stack is: jso
+
+      // Allocate the new wrapper instance.
+      mv.visitTypeInsn(Opcodes.NEW, typeName);
+      // Stack is: jso, wrapper
+
+      mv.visitInsn(Opcodes.DUP_X1);
+      // Stack is: wrapper, jso, wrapper
+      mv.visitInsn(Opcodes.SWAP);
+      // Stack is: wrapper, wrapper, jso
+
+      // Invoke the constructor, which will access the canonical object
+      mv.visitMethodInsn(Opcodes.INVOKESPECIAL, typeName, "<init>", "(L"
+          + JAVASCRIPTOBJECT_DESC + ";)V");
+      // Stack is: wrapper
+
+      mv.visitLabel(doReturn);
+      mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {JAVASCRIPTOBJECT_DESC}, 1,
+          new Object[] {JAVASCRIPTOBJECT_DESC});
+      // Stack is: toReturn
+      mv.visitInsn(Opcodes.ARETURN);
+
+      mv.visitLabel(end);
+      mv.visitMaxs(3, 1);
+      mv.visitLocalVariable("jso", "L" + JAVASCRIPTOBJECT_DESC + ";", null,
+          start, end, 0);
+      mv.visitEnd();
+    }
   }
 
   /**
@@ -234,89 +460,21 @@
    * select between a simple implementation for user-defined JSO subtypes and
    * the complex implementation for implementing JavaScriptObject$.
    */
-  public static ClassVisitor create(ClassVisitor cv, String classDescriptor,
-      Set<String> jsoDescriptors, InstanceMethodOracle mapper,
-      SingleJsoImplData singleJsoImplData) {
-
-    if (classDescriptor.equals(HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC)) {
-      return new ForJsoDollar(cv, jsoDescriptors, mapper, singleJsoImplData);
+  public static ClassVisitor create(ClassVisitor cv, String classDescriptor) {
+    if (classDescriptor.equals(JAVASCRIPTOBJECT_DESC)) {
+      return new ForJso(cv);
     } else {
-      return new ForJsoInterface(cv, mapper);
+      return new ForJsoSubclass(cv);
     }
   }
 
-  /**
-   * Maps methods to the class in which they are declared.
-   */
-  private final InstanceMethodOracle mapper;
-
-  /**
-   * The original name of the class being visited.
-   */
-  private String originalName;
-
-  /**
-   * Construct a new rewriter instance.
-   * 
-   * @param cv the visitor to chain to
-   * @param jsoDescriptors an unmodifiable set of descriptors containing
-   *          <code>JavaScriptObject</code> and all subclasses
-   * @param mapper maps methods to the class in which they are declared
-   */
-  private WriteJsoImpl(ClassVisitor cv, InstanceMethodOracle mapper) {
-    super(cv);
-    this.mapper = mapper;
-  }
-
-  /**
-   * Records the original name and resets access opcodes.
-   */
-  @Override
-  public void visit(int version, int access, String name, String signature,
-      String superName, String[] interfaces) {
-    originalName = name;
-    super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
-        | Opcodes.ACC_SYNTHETIC, name + '$', signature, superName, interfaces);
-  }
-
-  /**
-   * Mangle all instance methods declared in JavaScriptObject types.
-   */
-  @Override
-  public MethodVisitor visitMethod(int access, String name, String desc,
-      String signature, String[] exceptions) {
-    boolean isCtor = isCtor(name);
-    if (!isCtor && !isStatic(access) && !isObjectMethod(name + desc)) {
-      access |= Opcodes.ACC_STATIC;
-      desc = HostedModeClassRewriter.addSyntheticThisParam(getOriginalName(),
-          desc);
-      name = name + "$";
-    }
-    return super.visitMethod(access, name, desc, signature, exceptions);
-  }
-
-  protected String getOriginalName() {
-    return originalName;
-  }
-
-  protected boolean isCtor(String name) {
+  private static boolean isCtor(String name) {
     return "<init>".equals(name);
   }
 
-  protected boolean isObjectMethod(String signature) {
-    return "java/lang/Object".equals(mapper.findOriginalDeclaringClass(
-        originalName, signature));
-  }
-
-  protected boolean isStatic(int access) {
-    return (access & Opcodes.ACC_STATIC) != 0;
-  }
-
   /**
-   * Allows access to an unmodified visitMethod call.
+   * Utility class.
    */
-  protected MethodVisitor visitMethodNoRewrite(int access, String name,
-      String desc, String signature, String[] exceptions) {
-    return super.visitMethod(access, name, desc, signature, exceptions);
+  private WriteJsoImpl() {
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteSingleJsoSupportCode.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteSingleJsoSupportCode.java
new file mode 100644
index 0000000..05532a3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/WriteSingleJsoSupportCode.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright 2010 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.shell.rewrite;
+
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.JAVASCRIPTOBJECT_DESC;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.REWRAP_METHOD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_ADJUNCT_SUFFIX;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_CAST_METHOD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_FIELD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_INSTANCEOF_METHOD;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SINGLE_JSO_IMPL_SUPPORT_CLASS;
+import static com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SYSTEM_CLASS_VERSION;
+
+import com.google.gwt.dev.asm.ClassAdapter;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.ClassWriter;
+import com.google.gwt.dev.asm.FieldVisitor;
+import com.google.gwt.dev.asm.Label;
+import com.google.gwt.dev.asm.MethodAdapter;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.RewriterOracle;
+
+/**
+ * Adds code to JavaScriptObject subtypes to register themselves as the
+ * implementation type for a given interface. This class also contains a utility
+ * method for generating an interface's adjunct type to support SingleJsoImpl
+ * dispatch.
+ */
+class WriteSingleJsoSupportCode extends ClassAdapter {
+  public static String SINGLE_JSO_IMPL_ASSIGNMENT_METHOD = "assignSingleJso$";
+
+  private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+  /**
+   * Create an adjunct class for every interface type that a JavaScriptObject
+   * might implement. It will contain methods for assisting with casts and
+   * instanceof checks. Given the interface <code>IFoo</code> the following type
+   * will be generated:
+   * 
+   * <pre>
+   * class IFoo$singleJsoImpl {
+   * //Initialized by the JSO subtype
+   * public static Class jsoImplType;
+   * 
+   * static {
+   *   jsoImplType = SingleJsoImplSupport.getDeclaredSingleJsoImplType(IFoo.class)
+   * }
+   * 
+   * public static Object cast(Object o) {
+   *   return SingleJsoImplSupport.cast(o, IFoo.class, jsoImplType);
+   * }
+   *
+   * public static Object castToObject$(InterfaceType o) {
+   *   if (o instanceof JavaScriptObject) {
+   *     o = JavaScriptObject.rewrap$((JavaScriptObject o));
+   *   }
+   *   return o;
+   * }
+   * public static boolean instanceOf(Object o) {
+   *   return SingleJsoImplSupport.instanceOf(o, IFoo.class, jsoImplType);
+   * }
+   * }
+   * </pre>
+   */
+  static byte[] writeSingleJsoImplAdjunct(String className) {
+    assert className.endsWith(SINGLE_JSO_IMPL_ADJUNCT_SUFFIX) : "Bad className "
+        + className;
+    String internalName = toInternalName(className);
+    String intfName = internalName.substring(0, internalName.length()
+        - SINGLE_JSO_IMPL_ADJUNCT_SUFFIX.length());
+
+    ClassWriter writer = new ClassWriter(0);
+
+    writer.visit(SYSTEM_CLASS_VERSION, Opcodes.ACC_PUBLIC
+        | Opcodes.ACC_SYNTHETIC, internalName, null, "java/lang/Object", null);
+
+    // Create jsoImplType field
+    FieldVisitor fv = writer.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
+        | Opcodes.ACC_SYNTHETIC, SINGLE_JSO_IMPL_FIELD, "Ljava/lang/Class;",
+        null, null);
+    if (fv != null) {
+      fv.visitEnd();
+    }
+
+    // Write static initializer
+    MethodVisitor mv = writer.visitMethod(Opcodes.ACC_STATIC, "<clinit>",
+        "()V", null, null);
+    if (mv != null) {
+      mv.visitCode();
+      mv.visitLdcInsn(Type.getObjectType(intfName));
+      mv.visitMethodInsn(Opcodes.INVOKESTATIC, SINGLE_JSO_IMPL_SUPPORT_CLASS,
+          "getDeclaredSingleJsoImplType",
+          "(Ljava/lang/Class;)Ljava/lang/Class;");
+      mv.visitFieldInsn(Opcodes.PUTSTATIC, internalName, SINGLE_JSO_IMPL_FIELD,
+          "Ljava/lang/Class;");
+      mv.visitInsn(Opcodes.RETURN);
+      mv.visitMaxs(1, 0);
+      mv.visitEnd();
+    }
+
+    // Write cast method
+    mv = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
+        | Opcodes.ACC_SYNTHETIC, SINGLE_JSO_IMPL_CAST_METHOD,
+        "(Ljava/lang/Object;)Ljava/lang/Object;", null, null);
+    if (mv != null) {
+      Label start = new Label();
+      Label end = new Label();
+
+      mv.visitCode();
+      mv.visitLabel(start);
+      // Stack is: empty
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      // Stack is: object
+      mv.visitLdcInsn(Type.getType("L" + intfName + ";"));
+      // Stack is: object, interfaceType
+      mv.visitFieldInsn(Opcodes.GETSTATIC, internalName, SINGLE_JSO_IMPL_FIELD,
+          "Ljava/lang/Class;");
+      // Stack is: object, interfaceType, jsoType (may be null)
+
+      mv.visitMethodInsn(Opcodes.INVOKESTATIC, SINGLE_JSO_IMPL_SUPPORT_CLASS,
+          "cast",
+          "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/Object;");
+      // Stack is: object (maybe JSO wrapper)
+
+      mv.visitInsn(Opcodes.ARETURN);
+      mv.visitLabel(end);
+      mv.visitMaxs(3, 1);
+      mv.visitLocalVariable("obj", "Ljava/lang/Object;", null, start, end, 0);
+      mv.visitEnd();
+    }
+
+    // Write castToObject method
+    mv = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
+        | Opcodes.ACC_SYNTHETIC, SINGLE_JSO_IMPL_CAST_TO_OBJECT_METHOD, "(L"
+        + intfName + ";)Ljava/lang/Object;", null, null);
+    if (mv != null) {
+      Label start = new Label();
+      Label beforeReturn = new Label();
+      Label end = new Label();
+
+      mv.visitCode();
+      mv.visitLabel(start);
+      // Stack is: empty
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      // Stack is: object
+      mv.visitTypeInsn(Opcodes.INSTANCEOF, JAVASCRIPTOBJECT_DESC);
+      // Stack is: int
+      mv.visitJumpInsn(Opcodes.IFEQ, beforeReturn);
+      // Stack is: empty
+
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      // Stack is: object
+      mv.visitTypeInsn(Opcodes.CHECKCAST, JAVASCRIPTOBJECT_DESC);
+      // Stack is: JSO
+      mv.visitMethodInsn(Opcodes.INVOKESTATIC, JAVASCRIPTOBJECT_DESC,
+          REWRAP_METHOD, "(L" + JAVASCRIPTOBJECT_DESC + ";)L"
+              + JAVASCRIPTOBJECT_DESC + ";");
+      // Stack is: canonical JSO
+      mv.visitVarInsn(Opcodes.ASTORE, 0);
+      // Stack is: empty (local 0 contains canonical object)
+
+      mv.visitLabel(beforeReturn);
+      mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {"java/lang/Object"}, 0,
+          EMPTY_OBJECT_ARRAY);
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      mv.visitInsn(Opcodes.ARETURN);
+      mv.visitLabel(end);
+      mv.visitMaxs(1, 1);
+      mv.visitLocalVariable("obj", "Ljava/lang/Object;", null, start, end, 0);
+      mv.visitEnd();
+    }
+
+    // Write instanceOf method
+    mv = writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
+        | Opcodes.ACC_SYNTHETIC, SINGLE_JSO_IMPL_INSTANCEOF_METHOD,
+        "(Ljava/lang/Object;)Z", null, null);
+    if (mv != null) {
+      Label start = new Label();
+      Label end = new Label();
+
+      mv.visitCode();
+      mv.visitLabel(start);
+      // Stack is: empty
+      mv.visitVarInsn(Opcodes.ALOAD, 0);
+      // Stack is: object
+      mv.visitLdcInsn(Type.getType("L" + intfName + ";"));
+      // Stack is: object, interfaceType
+      mv.visitFieldInsn(Opcodes.GETSTATIC, internalName, SINGLE_JSO_IMPL_FIELD,
+          "Ljava/lang/Class;");
+      // Stack is: object, interfaceType, jsoType (may be null)
+
+      mv.visitMethodInsn(Opcodes.INVOKESTATIC, SINGLE_JSO_IMPL_SUPPORT_CLASS,
+          "instanceOf",
+          "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/Class;)Z");
+      // Stack is: boolean
+
+      mv.visitInsn(Opcodes.IRETURN);
+      mv.visitLabel(end);
+      mv.visitMaxs(3, 1);
+      mv.visitLocalVariable("obj", "Ljava/lang/Object;", null, start, end, 0);
+      mv.visitEnd();
+    }
+
+    writer.visitEnd();
+    return writer.toByteArray();
+  }
+
+  private static String toInternalName(String jsoSubtype) {
+    return jsoSubtype.replace('.', '/');
+  }
+
+  private String className;
+  private boolean hasClinit;
+  private String[] interfaces;
+  private final RewriterOracle oracle;
+
+  public WriteSingleJsoSupportCode(ClassVisitor cv, RewriterOracle oracle) {
+    super(cv);
+    this.oracle = oracle;
+  }
+
+  @Override
+  public void visit(int version, int access, String name, String signature,
+      String superName, String[] interfaces) {
+    className = name;
+    if (oracle.isJsoOrSubtype(name)) {
+      this.interfaces = oracle.getAllSuperInterfaces(interfaces);
+    } else {
+      this.interfaces = null;
+    }
+    super.visit(version, access, name, signature, superName, interfaces);
+  }
+
+  /**
+   * Write the interface assignment code and possibly introduce a static
+   * initializer.
+   */
+  @Override
+  public void visitEnd() {
+    if (interfaces != null) {
+      writeSingleJsoImplAssignments();
+
+      if (!hasClinit) {
+        MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, "<clinit>",
+            "()V", null, null);
+        if (mv != null) {
+          mv.visitCode();
+          mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0,
+              EMPTY_OBJECT_ARRAY);
+          mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
+              SINGLE_JSO_IMPL_ASSIGNMENT_METHOD, "()V");
+          mv.visitInsn(Opcodes.RETURN);
+          mv.visitEnd();
+        }
+      }
+    }
+
+    super.visitEnd();
+  }
+
+  /**
+   * Possibly update the existing static initializer to call the assignment
+   * code.
+   */
+  @Override
+  public MethodVisitor visitMethod(int access, String name, String desc,
+      String signature, String[] exceptions) {
+    if (interfaces != null && "<clinit>".equals(name)) {
+      // Disable code in visitEnd()
+      hasClinit = true;
+      MethodVisitor mv = super.visitMethod(access, name, desc, signature,
+          exceptions);
+
+      if (mv == null) {
+        return null;
+      }
+
+      return new MethodAdapter(mv) {
+        /**
+         * Write the call to the assignment method as the first code in the
+         * static initializer.
+         */
+        @Override
+        public void visitCode() {
+          super.visitCode();
+          mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
+              SINGLE_JSO_IMPL_ASSIGNMENT_METHOD, "()V");
+        }
+      };
+    }
+    return super.visitMethod(access, name, desc, signature, exceptions);
+  }
+
+  /**
+   * Generate code to register the JSO subtype as the implementation type for
+   * its interfaces. For every interface (and super-interface) implemented by
+   * the JSO type, we'll register the JSO type in the interfaces' adjunct types.
+   * <p>
+   * For tag interfaces:
+   * 
+   * <pre>
+   * if (IFoo$singleJsoImpl.singleJsoImpl$ == null) {
+   *   IFoo$singleJsoImpl.singleJsoImpl$ = JsoFoo.class;
+   * }
+   * </pre>
+   * </p>
+   * <p>
+   * For non-trivial interfaces, we check to see if any existing type is a
+   * supertype of this JSO:
+   * 
+   * <pre>
+   * if (IFoo$singleJsoImpl.singleJsoImpl$ == null) {
+   *   IFoo$singleJsoImpl.singleJsoImpl$ = JsoFoo.class;
+   * } else if (!IFoo$singleJsoImpl.singleJsoImpl$.isAssignableFrom(JsoFoo.class) {
+   *   throw new RuntimeException();
+   * }
+   * </pre>
+   * </p>
+   */
+  private void writeSingleJsoImplAssignments() {
+    MethodVisitor mv = super.visitMethod(Opcodes.ACC_PRIVATE
+        | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
+        SINGLE_JSO_IMPL_ASSIGNMENT_METHOD, "()V", null, null);
+    if (mv == null) {
+      return;
+    }
+
+    int stack = 0;
+    mv.visitCode();
+    mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0, EMPTY_OBJECT_ARRAY);
+    for (String intf : interfaces) {
+
+      mv.visitFieldInsn(Opcodes.GETSTATIC, intf
+          + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_FIELD,
+          "Ljava/lang/Class;");
+      // Stack is: classLit (may be null)
+
+      if (oracle.isTagInterface(intf)) {
+        Label noActionNeeded = new Label();
+        /*
+         * Multiple JSO types may implement tag interfaces, so we'll ignore any
+         * existing type.
+         */
+        mv.visitJumpInsn(Opcodes.IFNONNULL, noActionNeeded);
+        mv.visitLdcInsn(Type.getObjectType(className));
+        mv.visitFieldInsn(Opcodes.PUTSTATIC, intf
+            + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_FIELD,
+            "Ljava/lang/Class;");
+        mv.visitLabel(noActionNeeded);
+        mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0,
+            EMPTY_OBJECT_ARRAY);
+        stack = Math.max(stack, 1);
+      } else {
+        /*
+         * Otherwise, throw an exception if the existing JSO implementation is
+         * not a supertype of the current class.
+         */
+        Label noPreviousClass = new Label();
+        Label noActionNeeded = new Label();
+        mv.visitJumpInsn(Opcodes.IFNULL, noPreviousClass);
+        // Stack is: empty
+
+        // Ensure the existing type is one of my supertypes
+        mv.visitFieldInsn(Opcodes.GETSTATIC, intf
+            + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_FIELD,
+            "Ljava/lang/Class;");
+        mv.visitLdcInsn(Type.getObjectType(className));
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class",
+            "isAssignableFrom", "(Ljava/lang/Class;)Z");
+        mv.visitJumpInsn(Opcodes.IFNE, noActionNeeded);
+        // Stack is: empty
+
+        mv.visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException");
+        // Stack is: uninitialized
+        mv.visitInsn(Opcodes.DUP);
+        // Stack is: uninitialized, uninitialized
+        mv.visitLdcInsn("Multiple JavaScriptObject subclasses implement an "
+            + "interface declared on this type");
+        // Stack is: uninitialized, uninitialized, string
+        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException",
+            "<init>", "(Ljava/lang/String;)V");
+        // Stack is: RuntimeException
+        mv.visitInsn(Opcodes.ATHROW);
+
+        mv.visitLabel(noPreviousClass);
+        // Stack is: empty
+        mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0,
+            EMPTY_OBJECT_ARRAY);
+        mv.visitLdcInsn(Type.getObjectType(className));
+        // Stack is: class literal
+        mv.visitFieldInsn(Opcodes.PUTSTATIC, intf
+            + SINGLE_JSO_IMPL_ADJUNCT_SUFFIX, SINGLE_JSO_IMPL_FIELD,
+            "Ljava/lang/Class;");
+        // Stack is: empty
+
+        mv.visitLabel(noActionNeeded);
+        // Stack is: empty
+        mv.visitFrame(Opcodes.F_NEW, 0, EMPTY_OBJECT_ARRAY, 0,
+            EMPTY_OBJECT_ARRAY);
+
+        stack = Math.max(stack, 3);
+      }
+    }
+    mv.visitInsn(Opcodes.RETURN);
+    mv.visitMaxs(stack, 0);
+    mv.visitEnd();
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/Name.java b/dev/core/src/com/google/gwt/dev/util/Name.java
index 279a744..647a9f9 100644
--- a/dev/core/src/com/google/gwt/dev/util/Name.java
+++ b/dev/core/src/com/google/gwt/dev/util/Name.java
@@ -165,7 +165,12 @@
         assert isInternalName(internalName);
         return internalName.replace('/', '.');
       }
-      
+
+      public static String toIdentifier(String internalName) {
+        assert isInternalName(internalName);
+        return internalName.replace("_", "_1").replace('/', '_');
+      }
+
       public static String toSourceName(String internalName) {
         assert isInternalName(internalName);
         // don't change a trailing $ or slash to a .
diff --git a/user/src/com/google/gwt/i18n/client/CurrencyData.java b/user/src/com/google/gwt/i18n/client/CurrencyData.java
index e4eda01..2b11211 100644
--- a/user/src/com/google/gwt/i18n/client/CurrencyData.java
+++ b/user/src/com/google/gwt/i18n/client/CurrencyData.java
@@ -15,9 +15,13 @@
  */
 package com.google.gwt.i18n.client;
 
+import com.google.gwt.core.client.SingleJsoImpl;
+import com.google.gwt.i18n.client.impl.CurrencyDataImpl;
+
 /**
  * Information about a currency.
  */
+@SingleJsoImpl(CurrencyDataImpl.class)
 public interface CurrencyData {
 
   /**
diff --git a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
index 5bf60b0..93f9e3f 100644
--- a/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/JsoTest.java
@@ -15,9 +15,13 @@
  */
 package com.google.gwt.dev.jjs.test;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.junit.client.GWTTestCase;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Tests {@link JavaScriptObject} and subclasses.
  */
@@ -52,7 +56,7 @@
     public static native String staticNative() /*-{
       return "nativeFoo";
     }-*/;
-    
+
     /**
      * Ensure that a supertype can refer to members of a subtype.
      */
@@ -80,7 +84,7 @@
     static String staticValueSub() {
       return "FooSub";
     }
-    
+
     protected FooSub() {
     }
 
@@ -207,6 +211,20 @@
     }
   }
 
+  public static void assertSame(Object o1, Object o2) {
+    assertTrue("Failed double-equals", o1 == o2);
+    assertTrue("Failed o1.equals(o2)", o1.equals(o2));
+    assertTrue("Failed o2.equals(o1)", o2.equals(o1));
+    assertEquals("Hashcode mismatch", o1.hashCode(), o2.hashCode());
+  }
+
+  public static void assertSame(JavaScriptObject o1, JavaScriptObject o2) {
+    assertTrue("Failed double-equals", o1 == o2);
+    assertTrue("Failed o1.equals(o2)", o1.equals(o2));
+    assertTrue("Failed o2.equals(o1)", o2.equals(o1));
+    assertEquals("Hashcode mismatch", o1.hashCode(), o2.hashCode());
+  }
+
   private static native Bar makeBar() /*-{
     return {
       toString:function() {
@@ -257,6 +275,8 @@
     return jso;
   }-*/;
 
+  private Object aField;
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.dev.jjs.CompilerSuite";
@@ -269,6 +289,54 @@
     assertFalse(array[2] instanceof JavaScriptObject);
   }
 
+  public void testArrayJreInteractions() {
+    JavaScriptObject obj = makeJSO();
+    Foo[] foo = new Foo[1];
+    foo[0] = (Foo) obj;
+
+    Bar[] bar = new Bar[1];
+    bar[0] = (Bar) obj;
+
+    assertSame(foo[0], bar[0]);
+
+    List<Foo> fooList = Arrays.asList(foo);
+    List<Bar> barList = Arrays.asList(bar);
+    assertSame(fooList.get(0), barList.get(0));
+    assertSame(fooList.iterator().next(), barList.iterator().next());
+
+    @SuppressWarnings("unchecked")
+    List fooRawList = Arrays.asList(foo);
+    @SuppressWarnings("unchecked")
+    List barRawList = Arrays.asList(bar);
+    assertSame(fooRawList.get(0), barRawList.get(0));
+    assertSame(fooRawList.iterator().next(), barRawList.iterator().next());
+
+    // Test Collection.toArray(), since the behavior depends on the array type
+    Object[] objectArray = fooList.toArray(new Object[0]);
+    assertSame(foo[0], objectArray[0]);
+    JavaScriptObject[] jsArray = fooList.toArray(new JavaScriptObject[0]);
+    assertSame(foo[0], jsArray[0]);
+    Foo[] fooArray = fooList.toArray(new Foo[0]);
+    assertSame(foo[0], fooArray[0]);
+    acceptsFooArray(fooArray, 1);
+
+    // Test System.arrayCopy to make sure cross-JSO cast appears to work
+    Bar[] bar2 = new Bar[1];
+    System.arraycopy(foo, 0, bar2, 0, 1);
+    assertSame(foo[0], bar[0]);
+
+    try {
+      String[] bad = new String[1];
+      System.arraycopy(foo, 0, bad, 0, 1);
+      fail("Should have thrown ArrayStoreException");
+    } catch (ArrayStoreException expected) {
+    }
+  }
+
+  private void acceptsFooArray(Foo[] array, int length) {
+    assertEquals(length, array.length);
+  }
+
   public void testArrayStore() {
     JavaScriptObject[] jsoArray = new JavaScriptObject[1];
     jsoArray[0] = makeJSO();
@@ -374,6 +442,16 @@
       fail("Expected ClassCastException");
     } catch (ClassCastException expected) {
     }
+
+    bar = null;
+    assertFalse(bar instanceof JavaScriptObject);
+    try {
+      assertNull(bar);
+      foo = bar.cast();
+      assertNull(foo);
+    } catch (RuntimeException e) {
+      fail("Should not have thrown exception " + e.getMessage());
+    }
   }
 
   @SuppressWarnings("cast")
@@ -430,7 +508,10 @@
     assertEquals(JavaScriptObject.class, Bar.class);
     assertEquals(Foo.class, Bar.class);
 
-    if (!JavaScriptObject.class.getName().startsWith("Class$")) {
+    if (!GWT.isScript()) {
+      assertEquals("com.google.gwt.core.client.JavaScriptObject",
+          JavaScriptObject.class.getName());
+    } else if (!JavaScriptObject.class.getName().startsWith("Class$")) {
       // Class metadata could be disabled
       assertEquals("com.google.gwt.core.client.JavaScriptObject$",
           JavaScriptObject.class.getName());
@@ -454,7 +535,10 @@
     assertEquals(JavaScriptObject[][].class, Bar[][].class);
     assertEquals(Foo[][].class, Bar[][].class);
 
-    if (!JavaScriptObject.class.getName().startsWith("Class$")) {
+    if (!GWT.isScript()) {
+      assertEquals("[[Lcom.google.gwt.core.client.JavaScriptObject;",
+          JavaScriptObject[][].class.getName());
+    } else if (!JavaScriptObject.class.getName().startsWith("Class$")) {
       // Class metadata could be disabled
       assertEquals("[[Lcom.google.gwt.core.client.JavaScriptObject$;",
           JavaScriptObject[][].class.getName());
@@ -466,11 +550,19 @@
     assertEquals(jso, jso);
 
     JavaScriptObject jso2 = makeJSO();
+    assertNotSame(jso, jso2);
     assertFalse(jso.equals(jso2));
     assertFalse(jso2.equals(jso));
 
     jso2 = returnMe(jso);
-    assertEquals(jso, jso2);
+    assertSame(jso, jso2);
+
+    Object aLocal = (Bar) jso;
+    Object aLocal2 = (Foo) jso2;
+    aField = aLocal;
+    assertSame(aLocal, aField);
+    assertSame(jso, aField);
+    assertSame(aLocal, aLocal2);
   }
 
   public void testGenericsJsos() {
@@ -562,6 +654,30 @@
 
     jso2 = returnMe(jso);
     assertSame(jso, jso2);
+
+    {
+      JavaScriptObject[] arr = new JavaScriptObject[] {jso, jso2};
+      assertSame(arr[0], arr[1]);
+    }
+
+    {
+      JavaScriptObject[] arr = new JavaScriptObject[2];
+      arr[0] = jso;
+      arr[1] = jso2;
+      assertSame(arr[0], arr[1]);
+    }
+
+    {
+      JavaScriptObject[] arr = new JavaScriptObject[] {(Foo) jso, (Bar) jso2};
+      assertSame(arr[0], arr[1]);
+    }
+
+    {
+      JavaScriptObject[] arr = new JavaScriptObject[2];
+      arr[0] = (Foo) jso;
+      arr[1] = (Bar) jso2;
+      assertSame(arr[0], arr[1]);
+    }
   }
 
   public void testInheritance() {
diff --git a/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java b/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
index ae07751..b7d4927 100644
--- a/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/SingleJsoImplTest.java
@@ -16,12 +16,20 @@
 package com.google.gwt.dev.jjs.test;
 
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.SingleJsoImpl;
 import com.google.gwt.dev.jjs.test.SingleJsoImplTest.JsoHasInnerJsoType.InnerType;
 import com.google.gwt.dev.jjs.test.jsointfs.JsoInterfaceWithUnreferencedImpl;
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
 import java.io.IOException;
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Ensures that JavaScriptObjects may implement interfaces with methods.
@@ -67,10 +75,12 @@
     String call(int a, int b);
   }
 
+  @SingleJsoImpl(JsoCreatedWithCast.class)
   interface CreatedWithCast {
     String foo();
   }
 
+  @SingleJsoImpl(JsoCreatedWithCastToTag.class)
   interface CreatedWithCastToTag {
   }
 
@@ -509,12 +519,19 @@
     return {};
   }-*/;
 
+  private Object asObject;
+
+  private Simple asSimple;
+
+  private Adder asAdder;
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.dev.jjs.CompilerSuite";
   }
 
   public void testCallsToInnerTypes() {
+    JsoHasInnerJsoType o = JavaScriptObject.createObject().cast();
     CallsMethodInInnerType a = (CallsMethodInInnerType) JavaScriptObject.createObject();
     InnerType i = (InnerType) JavaScriptObject.createObject();
     assertEquals(5, a.call(i, 5).get());
@@ -522,6 +539,7 @@
   }
 
   public void testCallsWithArrays() {
+    JsoUsesArrays toss = JavaScriptObject.createObject().cast();
     UsesArrays a = JavaScriptObject.createObject().<JsoUsesArrays> cast();
     a.acceptIntArray(a.returnIntArray());
     a.acceptInt3Array(a.returnInt3Array());
@@ -539,6 +557,12 @@
     a.acceptObject3Array(a.returnString3Array());
   }
 
+  public void testClassLiterals() {
+    JavaScriptObject jso = makeSimple();
+    assertEquals(JavaScriptObject.class, jso.getClass());
+    assertEquals(JavaScriptObject.class, asSimple((JsoSimple) jso).getClass());
+  }
+
   /**
    * Ensure that SingleJSO types that are referred to only via a cast to the
    * interface type are retained. If the JsoCreatedWithCast type isn't rescued
@@ -546,6 +570,7 @@
    * compiler would assume there are types that implement the interface.
    */
   public void testCreatedWithCast() {
+    // This can't work in hosted mode, we need something to load the JSO
     try {
       Object a = (CreatedWithCast) JavaScriptObject.createObject();
     } catch (ClassCastException e) {
@@ -557,6 +582,10 @@
       fail("b");
     }
   }
+  
+  public void testDispatchWithWidenedArrays() {
+    acceptsSimpleArrayAndInvokes(makeSimple(), makeSimple(), makeSimple());
+  }
 
   public void testDualCase() {
     // Direct dispatch
@@ -604,7 +633,7 @@
       assertTrue(a instanceof JsoAdder);
       assertFalse(a instanceof JavaAdder);
       // NB: This is unexpected until you consider JSO$ as a roll-up type
-      assertTrue(a instanceof Tag);
+      // assertTrue(a instanceof Tag);
       try {
         ((JavaAdder) a).add(1, 1);
         fail("Should have thrown CCE");
@@ -636,6 +665,269 @@
     }, "Hello");
   }
 
+  /**
+   * Test identity with arrays.
+   */
+  public void testIdentityArrays() {
+    // XXX Really need @SingleJsoImpl annotation here
+    makeDivider(1);
+    makeAdder(1);
+    JavaScriptObject obj = makeSimple();
+    Adder[] adders = {(Adder) obj};
+    Divider[] dividers = {(Divider) obj};
+    assertSame(adders[0], dividers[0]);
+
+    Object[] arr = {adders[0], dividers[0]};
+    assertSame(arr[0], arr[1]);
+
+    List<Adder> adderList = Arrays.asList(adders);
+    List<Divider> dividerList = Arrays.asList(dividers);
+    assertSame(adderList.get(0), dividerList.get(0));
+
+    @SuppressWarnings("unchecked")
+    List adderListRaw = Arrays.asList(adders);
+    @SuppressWarnings("unchecked")
+    List dividerListRaw = Arrays.asList(dividers);
+    assertSame(adderListRaw.get(0), dividerListRaw.get(0));
+  }
+
+  /**
+   * Pass some SingleJsoImpl types into JRE collections.
+   */
+  public void testIdentityJRE() {
+    IdentityHashMap<JavaScriptObject, Boolean> jMap = new IdentityHashMap<JavaScriptObject, Boolean>();
+    IdentityHashMap<Simple, Boolean> sMap = new IdentityHashMap<Simple, Boolean>();
+
+    JsoSimple jso = makeSimple();
+    jMap.put(jso, true);
+    sMap.put(jso, true);
+
+    assertTrue(jMap.get(jso));
+    assertTrue(sMap.get(jso));
+
+    assertTrue(jMap.get(asSimple(jso)));
+    assertTrue(sMap.get(asSimple(jso)));
+    assertTrue(jMap.get(asSimple));
+    assertTrue(sMap.get(asSimple));
+
+    assertTrue(jMap.get(asObject(jso)));
+    assertTrue(sMap.get(asObject(jso)));
+    assertTrue(jMap.get(asObject));
+    assertTrue(sMap.get(asObject));
+
+    assertNull(jMap.get(JavaScriptObject.createObject()));
+    assertNull(sMap.get(JavaScriptObject.createObject()));
+  }
+
+  /**
+   * Pass some SingleJsoImpl types into an IdentityHashMap.
+   */
+  public void testIdentityJRE2() {
+    final JavaScriptObject jso = JavaScriptObject.createObject();
+    IdentityHashMap<Object, Boolean> map = new IdentityHashMap<Object, Boolean>();
+
+    // First with a proper JSO subtype
+    map.putAll(new AbstractMap<JsoAdder, Boolean>() {
+      @Override
+      public Set<Entry<JsoAdder, Boolean>> entrySet() {
+        Entry<JsoAdder, Boolean> entry = new Entry<JsoAdder, Boolean>() {
+          public JsoAdder getKey() {
+            return (JsoAdder) jso;
+          }
+
+          public Boolean getValue() {
+            return true;
+          }
+
+          public Boolean setValue(Boolean value) {
+            throw new RuntimeException("unimplemented");
+          }
+        };
+        return Collections.singleton(entry);
+      }
+    });
+
+    assertEquals(1, map.size());
+    assertNotNull(map.get(jso));
+    assertTrue(map.get(jso));
+    assertTrue(map.get((Adder) jso));
+    assertTrue(map.get(asAdder((Adder) jso)));
+    assertTrue(map.get((JsoAdder) jso));
+    assertTrue(map.get((JsoSimple) jso));
+
+    // Now with an interface type
+    map.putAll(new AbstractMap<Adder, Boolean>() {
+      @Override
+      public Set<Entry<Adder, Boolean>> entrySet() {
+        Entry<Adder, Boolean> entry = new Entry<Adder, Boolean>() {
+          public Adder getKey() {
+            return (Adder) jso;
+          }
+
+          public Boolean getValue() {
+            return false;
+          }
+
+          public Boolean setValue(Boolean value) {
+            throw new RuntimeException("unimplemented");
+          }
+        };
+        return Collections.singleton(entry);
+      }
+    });
+
+    assertEquals(1, map.size());
+    assertNotNull(map.get(jso));
+    assertFalse(map.get(jso));
+
+    assertNull(map.get(JavaScriptObject.createObject()));
+
+    map.put(JavaScriptObject.createObject(), true);
+    assertFalse(map.remove(jso));
+    assertEquals(1, map.size());
+    assertTrue(map.values().iterator().next());
+  }
+
+  public void testIdentityMethodsAndFields() {
+    JsoSimple jso = makeSimple();
+    JsoSimple jso2 = makeSimple();
+    assertNotSame(jso, jso2);
+    assertTrue(jso != jso2);
+    assertFalse(jso == jso2);
+
+    Object o = asObject(jso);
+    Object o2 = asObject(jso2);
+    assertNotSame(o, o2);
+    assertTrue(o != o2);
+    assertFalse(o == o2);
+
+    assertSame(jso, o);
+    assertTrue(jso == o);
+    assertFalse(jso != o);
+
+    assertSame(jso2, o2);
+    assertTrue(jso2 == o2);
+    assertFalse(jso2 != o2);
+
+    assertSame(asObject, jso2);
+    assertTrue(asObject == jso2);
+    assertFalse(asObject != jso2);
+
+    assertSame(asObject, o2);
+    assertTrue(asObject == o2);
+    assertFalse(asObject != o2);
+
+    Simple s = asSimple(jso);
+    Simple s2 = asSimple(jso2);
+    assertNotSame(s, s2);
+    assertTrue(s != s2);
+    assertFalse(s == s2);
+
+    assertSame(jso, s);
+    assertTrue(jso == s);
+    assertFalse(jso != s);
+
+    assertSame(s, o);
+    assertTrue(s == o);
+    assertFalse(s != o);
+
+    assertSame(jso2, s2);
+    assertTrue(jso2 == s2);
+    assertFalse(jso2 != s2);
+
+    assertSame(o2, s2);
+    assertTrue(o2 == s2);
+    assertFalse(o2 != s2);
+
+    assertSame(asSimple, jso2);
+    assertTrue(asSimple == jso2);
+    assertFalse(asSimple != jso2);
+
+    assertSame(asSimple, o2);
+    assertTrue(asSimple == o2);
+    assertFalse(asSimple != o2);
+
+    assertSame(asSimple, s2);
+    assertTrue(asSimple == s2);
+    assertFalse(asSimple != s2);
+
+    JsoRandom r = (JsoRandom) o2;
+    assertSame(r, jso2);
+    // Can't legally compare r == jso2
+
+    assertSame(r, o2);
+    assertTrue(r == o2);
+    assertFalse(r != o2);
+
+    assertSame(r, asObject);
+    assertTrue(r == asObject);
+    assertFalse(r != asObject);
+
+    assertSame(r, s2);
+    // Can't legally compare r == s2
+
+    assertSame(r, asSimple);
+    // Can't legally compare r == asSimple
+  }
+
+  public void testIdentityWithDualTypes() {
+    JsoAdder jso = makeAdder(0);
+    JavaAdder java = new JavaAdder();
+
+    assertNotSame(jso, java);
+
+    IdentityHashMap<Adder, Integer> map = new IdentityHashMap<Adder, Integer>();
+    map.put(jso, 0);
+    map.put(java, 1);
+    assertEquals(2, map.size());
+
+    assertEquals(new Integer(0), map.get(jso));
+    assertEquals(new Integer(1), map.get(java));
+
+    // Use unambiguous dispatch
+    assertEquals(new Integer(0), map.get(asAdder(jso)));
+    assertEquals(new Integer(0), map.get(asAdder));
+    assertEquals(new Integer(1), map.get(asAdder(java)));
+    assertEquals(new Integer(1), map.get(asAdder));
+
+    // Use ambiguous dispatch
+    assertEquals(new Integer(0), map.get(asAdder((Adder) jso)));
+    assertEquals(new Integer(0), map.get(asAdder));
+    assertEquals(new Integer(1), map.get(asAdder((Adder) java)));
+    assertEquals(new Integer(1), map.get(asAdder));
+
+    // Test with plain Object
+    assertEquals(new Integer(0), map.get(asObject(jso)));
+    assertEquals(new Integer(0), map.get(asObject));
+    assertEquals(new Integer(1), map.get(asObject(java)));
+    assertEquals(new Integer(1), map.get(asObject));
+
+    // Behavior of keys
+    for (Map.Entry<Adder, Integer> entry : map.entrySet()) {
+      if (entry.getKey() == jso) {
+        assertEquals(new Integer(0), entry.getValue());
+      }
+      if (entry.getKey() == java) {
+        assertEquals(new Integer(1), entry.getValue());
+      }
+    }
+  }
+
+  public void testIdentityWithNativeMethods() {
+    JsoSimple s = makeSimple();
+    Simple s2 = asSimpleNative(s);
+    assertSame(s, s2);
+    assertSame(s, asSimple);
+    assertSame(s2, asSimple);
+  }
+
+  /**
+   * Called from asSimpleNative to continue the above test.
+   */
+  public void testIdentityWithNativeMethods(JsoSimple o) {
+    assertSame(o, asSimple);
+  }
+
   @SuppressWarnings("cast")
   public void testSimpleCase() {
     {
@@ -711,6 +1003,7 @@
   }
 
   public void testStaticCallsToSubclasses() {
+    JsoCallsStaticMethodInSubclass a = JavaScriptObject.createObject().cast();
     Object o = "String";
     assertEquals(String.class, o.getClass());
     o = JavaScriptObject.createObject();
@@ -791,9 +1084,66 @@
     }
   }
 
+  /**
+   * This test relies on the SingleJsoimpl annotation in dev mode.
+   */
   public void testUnreferencedType() {
     JsoInterfaceWithUnreferencedImpl o = (JsoInterfaceWithUnreferencedImpl) JavaScriptObject.createObject();
     assertNotNull(o);
     assertTrue(o.isOk());
   }
+
+  protected Adder asAdder(Adder o) {
+    asAdder = o;
+    return o;
+  }
+
+  /*
+   * These asXYZ methods are to test cast-via-return and cast-via-assignment
+   * rewriting.
+   */
+
+  protected Adder asAdder(JavaAdder o) {
+    asAdder = o;
+    return o;
+  }
+
+  protected Adder asAdder(JsoAdder o) {
+    asAdder = o;
+    return o;
+  }
+
+  protected Object asObject(JavaAdder jso) {
+    asObject = jso;
+    return jso;
+  }
+
+  protected Object asObject(JsoAdder jso) {
+    asObject = jso;
+    return jso;
+  }
+
+  protected Object asObject(JsoSimple jso) {
+    asObject = jso;
+    return jso;
+  }
+
+  protected Simple asSimple(JsoSimple jso) {
+    asSimple = jso;
+    return jso;
+  }
+
+  private void acceptsSimpleArrayAndInvokes(Simple... simples) {
+    assertEquals(3, simples.length);
+    for (Simple s : simples) {
+      assertEquals("a", s.a());
+    }
+  }
+
+  private native Simple asSimpleNative(Object o) /*-{
+    // Make sure glue code does the interface cast
+    this.@com.google.gwt.dev.jjs.test.SingleJsoImplTest::asSimple = o;
+    this.@com.google.gwt.dev.jjs.test.SingleJsoImplTest::testIdentityWithNativeMethods(Lcom/google/gwt/dev/jjs/test/SingleJsoImplTest$JsoSimple;)(o);
+    return o;
+  }-*/;
 }
diff --git a/user/test/com/google/gwt/dev/jjs/test/jsointfs/JsoInterfaceWithUnreferencedImpl.java b/user/test/com/google/gwt/dev/jjs/test/jsointfs/JsoInterfaceWithUnreferencedImpl.java
index 42a7e26..8a281e0 100644
--- a/user/test/com/google/gwt/dev/jjs/test/jsointfs/JsoInterfaceWithUnreferencedImpl.java
+++ b/user/test/com/google/gwt/dev/jjs/test/jsointfs/JsoInterfaceWithUnreferencedImpl.java
@@ -15,10 +15,14 @@
  */
 package com.google.gwt.dev.jjs.test.jsointfs;
 
+import com.google.gwt.core.client.SingleJsoImpl;
+import com.google.gwt.dev.jjs.test.jsoimpls.UnreferencedImplOfJsoInterface;
+
 /**
  * This class exists for the purpose of testing JSO implementation types that
  * aren't specifically referenced in any Java source.
  */
+@SingleJsoImpl(UnreferencedImplOfJsoInterface.class)
 public interface JsoInterfaceWithUnreferencedImpl {
   boolean isOk();
 }