Cleanup GraphLens.lookupType()

This CL splits lookupType() into lookupType() and lookupClassType(). As the name suggest, the latter requires a DexType that satisfies isClassType().

This way the handling of array types can be dealt with in a single final method, such that each lens does not need to consider if the given type could be an array type.

Change-Id: I636e8e1adf38543a10623de890359df1c3977b4b
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index a24eb49..f4407b4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -214,7 +214,7 @@
   }
 
   public GraphLens clearCodeRewritings() {
-    return graphLens = graphLens.withCodeRewritingsApplied();
+    return graphLens = graphLens.withCodeRewritingsApplied(dexItemFactory());
   }
 
   public AppServices appServices() {
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index f3515e9..dac8f8b 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -5,12 +5,16 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * A graph lens that will not lead to any code rewritings in the {@link
@@ -21,9 +25,8 @@
  */
 public final class AppliedGraphLens extends NonIdentityGraphLens {
 
-  private final AppView<?> appView;
-
-  private final BiMap<DexType, DexType> originalTypeNames = HashBiMap.create();
+  private final BidirectionalManyToOneMap<DexType, DexType> renamedTypeNames =
+      new BidirectionalManyToOneMap<>();
   private final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
   private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
 
@@ -33,22 +36,10 @@
   private final Map<DexMethod, DexMethod> extraOriginalMethodSignatures = new IdentityHashMap<>();
 
   public AppliedGraphLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    super(GraphLens.getIdentityLens());
-    this.appView = appView;
-
+    super(appView.dexItemFactory(), GraphLens.getIdentityLens());
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       // Record original type names.
-      {
-        DexType type = clazz.type;
-        if (appView.verticallyMergedClasses() != null
-            && !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
-          DexType original = appView.graphLens().getOriginalType(type);
-          if (original != type) {
-            DexType existing = originalTypeNames.forcePut(type, original);
-            assert existing == null;
-          }
-        }
-      }
+      recordOriginalTypeNames(clazz, appView);
 
       // Record original field signatures.
       for (DexEncodedField encodedField : clazz.fields()) {
@@ -84,6 +75,29 @@
     MapUtils.removeIdentityMappings(extraOriginalMethodSignatures);
   }
 
+  private void recordOriginalTypeNames(
+      DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    DexType type = clazz.getType();
+    VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
+    if (verticallyMergedClasses != null && verticallyMergedClasses.hasBeenMergedIntoSubtype(type)) {
+      return;
+    }
+
+    DexType original = appView.graphLens().getOriginalType(type);
+    if (verticallyMergedClasses != null) {
+      List<DexType> sources = verticallyMergedClasses.getSourcesFor(type);
+      if (!sources.isEmpty()) {
+        renamedTypeNames.put(original, type);
+        sources.forEach(source -> renamedTypeNames.put(source, type));
+        return;
+      }
+    }
+
+    if (original != type) {
+      renamedTypeNames.put(original, type);
+    }
+  }
+
   @Override
   public boolean isAppliedLens() {
     return true;
@@ -91,7 +105,11 @@
 
   @Override
   public DexType getOriginalType(DexType type) {
-    return originalTypeNames.getOrDefault(type, type);
+    Set<DexType> originalTypeNames = renamedTypeNames.getKeys(type);
+    if (!originalTypeNames.isEmpty()) {
+      return originalTypeNames.iterator().next();
+    }
+    return type;
   }
 
   @Override
@@ -120,12 +138,8 @@
   }
 
   @Override
-  public DexType lookupType(DexType type) {
-    if (appView.verticallyMergedClasses() != null
-        && appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
-      return lookupType(appView.verticallyMergedClasses().getTargetFor(type));
-    }
-    return originalTypeNames.inverse().getOrDefault(type, type);
+  public DexType internalDescribeLookupClassType(DexType previous) {
+    return renamedTypeNames.getOrDefault(previous, previous);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 23fa997..c4e2701 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -252,6 +252,12 @@
     return descriptor.content[0] == 'D';
   }
 
+  public boolean isNullValueType() {
+    boolean isNullValueType = descriptor.content[0] == 'N';
+    assert !isNullValueType || this == DexItemFactory.nullValueType;
+    return isNullValueType;
+  }
+
   public boolean isArrayType() {
     char firstChar = (char) descriptor.content[0];
     return firstChar == '[';
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index ad7cd8b..b459207 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -22,6 +22,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * A GraphLens implements a virtual view on top of the graph, used to delay global rewrites until
@@ -262,6 +263,8 @@
     return holder.lookupProgramMethod(newMethod);
   }
 
+  public abstract DexType lookupClassType(DexType type);
+
   public abstract DexType lookupType(DexType type);
 
   // This overload can be used when the graph lens is known to be context insensitive.
@@ -354,9 +357,9 @@
     return null;
   }
 
-  public GraphLens withCodeRewritingsApplied() {
+  public GraphLens withCodeRewritingsApplied(DexItemFactory dexItemFactory) {
     if (hasCodeRewritings()) {
-      return new ClearCodeRewritingGraphLens(this);
+      return new ClearCodeRewritingGraphLens(dexItemFactory, this);
     }
     return this;
   }
@@ -514,9 +517,13 @@
 
   public abstract static class NonIdentityGraphLens extends GraphLens {
 
+    private final DexItemFactory dexItemFactory;
     private GraphLens previousLens;
 
-    public NonIdentityGraphLens(GraphLens previousLens) {
+    private final Map<DexType, DexType> arrayTypeCache = new ConcurrentHashMap<>();
+
+    public NonIdentityGraphLens(DexItemFactory dexItemFactory, GraphLens previousLens) {
+      this.dexItemFactory = dexItemFactory;
       this.previousLens = previousLens;
     }
 
@@ -532,6 +539,30 @@
     }
 
     @Override
+    public final DexType lookupType(DexType type) {
+      if (type.isPrimitiveType() || type.isVoidType() || type.isNullValueType()) {
+        return type;
+      }
+      if (type.isArrayType()) {
+        DexType result = arrayTypeCache.get(type);
+        if (result == null) {
+          DexType baseType = type.toBaseType(dexItemFactory);
+          DexType newType = lookupType(baseType);
+          result = baseType == newType ? type : type.replaceBaseType(newType, dexItemFactory);
+          arrayTypeCache.put(type, result);
+        }
+        return result;
+      }
+      return lookupClassType(type);
+    }
+
+    @Override
+    public final DexType lookupClassType(DexType type) {
+      assert type.isClassType() : "Expected class type, but was `" + type.toSourceString() + "`";
+      return internalDescribeLookupClassType(getPrevious().lookupClassType(type));
+    }
+
+    @Override
     protected final DexField internalLookupField(
         DexField reference, LookupFieldContinuation continuation) {
       return previousLens.internalLookupField(
@@ -540,6 +571,8 @@
 
     protected abstract FieldLookupResult internalDescribeLookupField(FieldLookupResult previous);
 
+    protected abstract DexType internalDescribeLookupClassType(DexType previous);
+
     @Override
     public final boolean isIdentityLens() {
       return false;
@@ -607,6 +640,12 @@
     }
 
     @Override
+    public DexType lookupClassType(DexType type) {
+      assert type.isClassType();
+      return type;
+    }
+
+    @Override
     public GraphLensLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
       return new GraphLensLookupResult(method, type, RewrittenPrototypeDescription.none());
     }
@@ -641,8 +680,8 @@
   // relies on the previous lens for names (getRenamed/Original methods).
   public static class ClearCodeRewritingGraphLens extends NonIdentityGraphLens {
 
-    public ClearCodeRewritingGraphLens(GraphLens previous) {
-      super(previous);
+    public ClearCodeRewritingGraphLens(DexItemFactory dexItemFactory, GraphLens previousLens) {
+      super(dexItemFactory, previousLens);
     }
 
     @Override
@@ -673,8 +712,8 @@
     }
 
     @Override
-    public DexType lookupType(DexType type) {
-      return getPrevious().lookupType(type);
+    public final DexType internalDescribeLookupClassType(DexType previous) {
+      return previous;
     }
 
     @Override
@@ -714,7 +753,6 @@
     protected final DexItemFactory dexItemFactory;
 
     protected final Map<DexType, DexType> typeMap;
-    private final Map<DexType, DexType> arrayTypeCache = new IdentityHashMap<>();
     protected final Map<DexMethod, DexMethod> methodMap;
     protected final Map<DexField, DexField> fieldMap;
 
@@ -737,7 +775,7 @@
         BiMap<DexMethod, DexMethod> originalMethodSignatures,
         GraphLens previousLens,
         DexItemFactory dexItemFactory) {
-      super(previousLens);
+      super(dexItemFactory, previousLens);
       assert !typeMap.isEmpty()
           || !methodMap.isEmpty()
           || !fieldMap.isEmpty()
@@ -797,28 +835,7 @@
     }
 
     @Override
-    public DexType lookupType(DexType type) {
-      if (type.isArrayType()) {
-        synchronized (this) {
-          // This block need to be synchronized due to arrayTypeCache.
-          DexType result = arrayTypeCache.get(type);
-          if (result == null) {
-            DexType baseType = type.toBaseType(dexItemFactory);
-            DexType newType = lookupType(baseType);
-            if (baseType == newType) {
-              result = type;
-            } else {
-              result = type.replaceBaseType(newType, dexItemFactory);
-            }
-            arrayTypeCache.put(type, result);
-          }
-          return result;
-        }
-      }
-      return internalDescribeLookupType(getPrevious().lookupType(type));
-    }
-
-    private DexType internalDescribeLookupType(DexType previous) {
+    protected DexType internalDescribeLookupClassType(DexType previous) {
       return typeMap != null ? typeMap.getOrDefault(previous, previous) : previous;
     }
 
@@ -921,7 +938,7 @@
             .setReboundReference(rewrittenReboundReference)
             .setReference(
                 rewrittenReboundReference.withHolder(
-                    internalDescribeLookupType(previous.getReference().getHolderType()),
+                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
                     dexItemFactory))
             .build();
       } else {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index 70b14cd..48c94cb 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.optimize;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
@@ -25,8 +26,10 @@
   private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap;
 
   private MemberRebindingIdentityLens(
-      Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap, GraphLens previousLens) {
-    super(previousLens);
+      Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap,
+      DexItemFactory dexItemFactory,
+      GraphLens previousLens) {
+    super(dexItemFactory, previousLens);
     assert !previousLens.hasCodeRewritings();
     this.nonReboundFieldReferenceToDefinitionMap = nonReboundFieldReferenceToDefinitionMap;
   }
@@ -74,8 +77,8 @@
   }
 
   @Override
-  public DexType lookupType(DexType type) {
-    return getPrevious().lookupType(type);
+  public final DexType internalDescribeLookupClassType(DexType previous) {
+    return previous;
   }
 
   @Override
@@ -109,11 +112,12 @@
       nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
     }
 
-    MemberRebindingIdentityLens build(GraphLens previousLens) {
+    MemberRebindingIdentityLens build(DexItemFactory dexItemFactory, GraphLens previousLens) {
       // This intentionally does not return null when the maps are empty. In this case there are no
       // non-rebound field or method references, but the member rebinding lens is still needed to
       // populate the rebound reference during field and method lookup.
-      return new MemberRebindingIdentityLens(nonReboundFieldReferenceToDefinitionMap, previousLens);
+      return new MemberRebindingIdentityLens(
+          nonReboundFieldReferenceToDefinitionMap, dexItemFactory, previousLens);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
index 48a01f8..0172114 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLensFactory.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
@@ -45,14 +46,16 @@
                 && testingOptions.alwaysUseExistingFieldAccessInfoCollectionInMemberRebinding
             ? appView.appInfo().withLiveness().getFieldAccessInfoCollection()
             : createFieldAccessInfoCollectionForMemberRebinding(appView, executorService);
-    return create(fieldAccessInfoCollection, appView.graphLens());
+    return create(fieldAccessInfoCollection, appView.dexItemFactory(), appView.graphLens());
   }
 
   public static MemberRebindingIdentityLens create(
-      FieldAccessInfoCollection<?> fieldAccessInfoCollection, GraphLens previousLens) {
+      FieldAccessInfoCollection<?> fieldAccessInfoCollection,
+      DexItemFactory dexItemFactory,
+      GraphLens previousLens) {
     MemberRebindingIdentityLens.Builder builder = MemberRebindingIdentityLens.builder();
     fieldAccessInfoCollection.forEach(builder::recordNonReboundFieldAccesses);
-    return builder.build(previousLens);
+    return builder.build(dexItemFactory, previousLens);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 729a1d8..774c954 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1700,7 +1700,7 @@
     private final DexProgramClass target;
 
     public SingleTypeMapperGraphLens(DexType source, DexProgramClass target) {
-      super(GraphLens.getIdentityLens());
+      super(appView.dexItemFactory(), GraphLens.getIdentityLens());
       this.source = source;
       this.target = target;
     }
@@ -1731,8 +1731,8 @@
     }
 
     @Override
-    public DexType lookupType(DexType type) {
-      return type == source ? target.type : mergedClasses.getOrDefault(type, type);
+    public final DexType internalDescribeLookupClassType(DexType previous) {
+      return previous == source ? target.type : mergedClasses.getOrDefault(previous, previous);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
new file mode 100644
index 0000000..ad65701
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneMap.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.collections;
+
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class BidirectionalManyToOneMap<K, V> {
+
+  private final Map<K, V> backing = new IdentityHashMap<>();
+  private final Map<V, Set<K>> inverse = new IdentityHashMap<>();
+
+  public V getOrDefault(K key, V value) {
+    return backing.getOrDefault(key, value);
+  }
+
+  public Set<K> getKeys(V value) {
+    return inverse.getOrDefault(value, Collections.emptySet());
+  }
+
+  public void put(K key, V value) {
+    backing.put(key, value);
+    inverse.computeIfAbsent(value, ignore -> new LinkedHashSet<>()).add(key);
+  }
+}