Merge commit '003f5b447a7d850ac3bae1f499920fb5b63a476d' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 92f194a..9673bc2 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -68,6 +68,7 @@
     private boolean intermediate = false;
     private DesugarGraphConsumer desugarGraphConsumer = null;
     private StringConsumer desugaredLibraryKeepRuleConsumer = null;
+    private String synthesizedClassPrefix = "";
 
     private Builder() {
       this(new DefaultD8DiagnosticsHandler());
@@ -168,6 +169,11 @@
       return CompilationMode.DEBUG;
     }
 
+    Builder setSynthesizedClassesPrefix(String prefix) {
+      synthesizedClassPrefix = prefix;
+      return self();
+    }
+
     @Override
     void validate() {
       Reporter reporter = getReporter();
@@ -227,6 +233,7 @@
           libraryConfiguration,
           getAssertionsConfiguration(),
           getOutputInspections(),
+          synthesizedClassPrefix,
           getThreadCount(),
           factory);
     }
@@ -238,6 +245,7 @@
   private final DesugarGraphConsumer desugarGraphConsumer;
   private final StringConsumer desugaredLibraryKeepRuleConsumer;
   private final DesugaredLibraryConfiguration libraryConfiguration;
+  private final String synthesizedClassPrefix;
   private final DexItemFactory factory;
 
   public static Builder builder() {
@@ -297,6 +305,7 @@
       DesugaredLibraryConfiguration libraryConfiguration,
       List<AssertionsConfiguration> assertionsConfiguration,
       List<Consumer<Inspector>> outputInspections,
+      String synthesizedClassPrefix,
       int threadCount,
       DexItemFactory factory) {
     super(
@@ -317,6 +326,7 @@
     this.desugarGraphConsumer = desugarGraphConsumer;
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
     this.libraryConfiguration = libraryConfiguration;
+    this.synthesizedClassPrefix = synthesizedClassPrefix;
     this.factory = factory;
   }
 
@@ -326,6 +336,7 @@
     desugarGraphConsumer = null;
     desugaredLibraryKeepRuleConsumer = null;
     libraryConfiguration = null;
+    synthesizedClassPrefix = null;
     factory = null;
   }
 
@@ -372,8 +383,8 @@
     internal.dexClassChecksumFilter = getDexClassChecksumFilter();
     internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
 
-    // TODO(134732760): This is still work in progress.
     internal.desugaredLibraryConfiguration = libraryConfiguration;
+    internal.synthesizedClassPrefix = synthesizedClassPrefix;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
     // Default is to remove all javac generated assertion code when generating dex.
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 830f6b6..a6d704b 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -145,7 +145,7 @@
               ParameterAnnotationsList.empty(),
               code,
               50);
-      if (method.accessFlags.isStatic()) {
+      if (method.isStatic() || method.isDirectMethod()) {
         directMethods.add(throwingMethod);
       } else {
         virtualMethods.add(throwingMethod);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index c32fbb7..7741651 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -178,8 +178,10 @@
     assert internal.enableInheritanceClassInDexDistributor;
     internal.enableInheritanceClassInDexDistributor = false;
 
-    // TODO(134732760): This is still work in progress.
+    assert libraryConfiguration != null;
     internal.desugaredLibraryConfiguration = libraryConfiguration;
+    internal.synthesizedClassPrefix =
+        libraryConfiguration.getSynthesizedLibraryClassesPackagePrefix();
 
     // Default is to remove all javac generated assertion code when generating dex.
     assert internal.assertionsConfiguration == null;
@@ -289,6 +291,8 @@
         R8Command.Builder r8Builder =
             R8Command.builder(getReporter())
                 .addProgramResourceProvider(desugaredLibrary)
+                .setSynthesizedClassesPrefix(
+                    libraryConfiguration.getSynthesizedLibraryClassesPackagePrefix())
                 .setMinApiLevel(getMinApiLevel())
                 .setMode(getMode())
                 .setProgramConsumer(getProgramConsumer());
@@ -307,6 +311,8 @@
         D8Command.Builder d8Builder =
             D8Command.builder(getReporter())
                 .addProgramResourceProvider(desugaredLibrary)
+                .setSynthesizedClassesPrefix(
+                    libraryConfiguration.getSynthesizedLibraryClassesPackagePrefix())
                 .setMinApiLevel(getMinApiLevel())
                 .setMode(getMode())
                 .setProgramConsumer(getProgramConsumer());
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 57f8395..4b310aa 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
@@ -258,9 +257,9 @@
       }
       for (DexAnnotation annotation : method.annotations().annotations) {
         if (annotation.annotation.type == appInfo.dexItemFactory().annotationThrows) {
-          DexValueArray dexValues = (DexValueArray) annotation.annotation.elements[0].value;
+          DexValueArray dexValues = annotation.annotation.elements[0].value.asDexValueArray();
           for (DexValue dexValType : dexValues.getValues()) {
-            registerTypeReference(((DexValueType) dexValType).value);
+            registerTypeReference(dexValType.asDexValueType().value);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5223520..5bca79b1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -34,7 +34,7 @@
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
@@ -278,7 +278,7 @@
         MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
       }
       if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
-        BackportedMethodRewriter.checkForAssumedLibraryTypes(appView);
+        DesugaredLibraryRetargeter.checkForAssumedLibraryTypes(appView);
       }
 
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
@@ -411,7 +411,7 @@
       // The class type lattice elements include information about the interfaces that a class
       // implements. This information can change as a result of vertical class merging, so we need
       // to clear the cache, so that we will recompute the type lattice elements.
-      appView.dexItemFactory().clearTypeLatticeElementsCache();
+      appView.dexItemFactory().clearTypeElementsCache();
 
       if (options.getProguardConfiguration().isAccessModificationAllowed()) {
         GraphLense publicizedLense =
@@ -514,7 +514,7 @@
       }
 
       // None of the optimizations above should lead to the creation of type lattice elements.
-      assert appView.dexItemFactory().verifyNoCachedTypeLatticeElements();
+      assert appView.dexItemFactory().verifyNoCachedTypeElements();
 
       // Collect switch maps and ordinals maps.
       if (options.enableEnumSwitchMapRemoval) {
@@ -536,7 +536,7 @@
       }
 
       // Clear the reference type lattice element cache to reduce memory pressure.
-      appView.dexItemFactory().clearTypeLatticeElementsCache();
+      appView.dexItemFactory().clearTypeElementsCache();
 
       // At this point all code has been mapped according to the graph lens. We cannot remove the
       // graph lens entirely, though, since it is needed for mapping all field and method signatures
@@ -717,7 +717,7 @@
           // TODO(b/112437944): Avoid iterating the entire application to post-process every
           //  dynamicMethod() method.
           appView.withGeneratedMessageLiteShrinker(
-              shrinker -> shrinker.postOptimizeDynamicMethods(converter));
+              shrinker -> shrinker.postOptimizeDynamicMethods(converter, timing));
 
           // If proto shrinking is enabled, we need to post-process every
           // findLiteExtensionByNumber() method. This ensures that there are no references to dead
@@ -725,7 +725,7 @@
           // TODO(b/112437944): Avoid iterating the entire application to post-process every
           //  findLiteExtensionByNumber() method.
           appView.withGeneratedExtensionRegistryShrinker(
-              shrinker -> shrinker.postOptimizeGeneratedExtensionRegistry(converter));
+              shrinker -> shrinker.postOptimizeGeneratedExtensionRegistry(converter, timing));
         }
       }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 611bc7a..fea04c7 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -104,6 +104,7 @@
     private GraphConsumer mainDexKeptGraphConsumer = null;
     private BiFunction<String, Long, Boolean> dexClassChecksumFilter = (name, checksum) -> true;
     private final List<FeatureSplit> featureSplits = new ArrayList<>();
+    private String synthesizedClassPrefix = "";
 
     private boolean allowPartiallyImplementedProguardOptions = false;
     private boolean allowTestProguardOptions =
@@ -146,6 +147,11 @@
       return CompilationMode.RELEASE;
     }
 
+    Builder setSynthesizedClassesPrefix(String prefix) {
+      synthesizedClassPrefix = prefix;
+      return self();
+    }
+
     /**
      * Disable tree shaking.
      *
@@ -572,6 +578,7 @@
               featureSplitConfiguration,
               getAssertionsConfiguration(),
               getOutputInspections(),
+              synthesizedClassPrefix,
               getThreadCount());
 
       return command;
@@ -654,6 +661,7 @@
   private final StringConsumer desugaredLibraryKeepRuleConsumer;
   private final DesugaredLibraryConfiguration libraryConfiguration;
   private final FeatureSplitConfiguration featureSplitConfiguration;
+  private final String synthesizedClassPrefix;
 
   /** Get a new {@link R8Command.Builder}. */
   public static Builder builder() {
@@ -731,6 +739,7 @@
       FeatureSplitConfiguration featureSplitConfiguration,
       List<AssertionsConfiguration> assertionsConfiguration,
       List<Consumer<Inspector>> outputInspections,
+      String synthesizedClassPrefix,
       int threadCount) {
     super(
         inputApp,
@@ -764,6 +773,7 @@
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
     this.libraryConfiguration = libraryConfiguration;
     this.featureSplitConfiguration = featureSplitConfiguration;
+    this.synthesizedClassPrefix = synthesizedClassPrefix;
   }
 
   private R8Command(boolean printHelp, boolean printVersion) {
@@ -784,6 +794,7 @@
     desugaredLibraryKeepRuleConsumer = null;
     libraryConfiguration = null;
     featureSplitConfiguration = null;
+    synthesizedClassPrefix = null;
   }
 
   /** Get the enable-tree-shaking state. */
@@ -910,8 +921,8 @@
 
     internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
 
-    // TODO(134732760): This is still work in progress.
     internal.desugaredLibraryConfiguration = libraryConfiguration;
+    internal.synthesizedClassPrefix = synthesizedClassPrefix;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
     assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index efeccec..0affffd 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -174,19 +174,25 @@
     }
 
     private void processFieldValue(DexValue value) {
-      if (value instanceof DexValue.DexValueString) {
-        callback.referencedString(((DexValue.DexValueString) value).value.toString());
-      } else if (value instanceof DexValue.DexValueInt) {
-        int constantValue = ((DexValue.DexValueInt) value).getValue();
-        callback.referencedInt(constantValue);
-      } else if (value instanceof DexValue.DexValueArray) {
-        DexValue.DexValueArray arrayEncodedValue = (DexValue.DexValueArray) value;
-        for (DexValue encodedValue : arrayEncodedValue.getValues()) {
-          if (encodedValue instanceof DexValue.DexValueInt) {
-            int constantValue = ((DexValue.DexValueInt) encodedValue).getValue();
-            callback.referencedInt(constantValue);
+      switch (value.getValueKind()) {
+        case ARRAY:
+          for (DexValue elementValue : value.asDexValueArray().getValues()) {
+            if (elementValue.isDexValueInt()) {
+              callback.referencedInt(elementValue.asDexValueInt().getValue());
+            }
           }
-        }
+          break;
+
+        case INT:
+          callback.referencedInt(value.asDexValueInt().getValue());
+          break;
+
+        case STRING:
+          callback.referencedString(value.asDexValueString().value.toString());
+          break;
+
+        default:
+          // Intentionally empty.
       }
     }
 
@@ -286,22 +292,29 @@
     }
 
     private void processAnnotationValue(DexValue value) {
-      if (value instanceof DexValue.DexValueInt) {
-        DexValue.DexValueInt dexValueInt = (DexValue.DexValueInt) value;
-        callback.referencedInt(dexValueInt.value);
-      } else if (value instanceof DexValue.DexValueString) {
-        DexValue.DexValueString dexValueString = (DexValue.DexValueString) value;
-        callback.referencedString(dexValueString.value.toString());
-      } else if (value instanceof DexValue.DexValueArray) {
-        DexValue.DexValueArray dexValueArray = (DexValue.DexValueArray) value;
-        for (DexValue dexValue : dexValueArray.getValues()) {
-          processAnnotationValue(dexValue);
-        }
-      } else if (value instanceof DexValue.DexValueAnnotation) {
-        DexValue.DexValueAnnotation dexValueAnnotation = (DexValue.DexValueAnnotation) value;
-        for (DexAnnotationElement element : dexValueAnnotation.value.elements) {
-          processAnnotationValue(element.value);
-        }
+      switch (value.getValueKind()) {
+        case ANNOTATION:
+          for (DexAnnotationElement element : value.asDexValueAnnotation().value.elements) {
+            processAnnotationValue(element.value);
+          }
+          break;
+
+        case ARRAY:
+          for (DexValue elementValue : value.asDexValueArray().getValues()) {
+            processAnnotationValue(elementValue);
+          }
+          break;
+
+        case INT:
+          callback.referencedInt(value.asDexValueInt().value);
+          break;
+
+        case STRING:
+          callback.referencedString(value.asDexValueString().value.toString());
+          break;
+
+        default:
+          // Intentionally empty.
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java b/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java
index 1b68ef6..cc78305 100644
--- a/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java
+++ b/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java
@@ -18,7 +18,7 @@
   private final Phi phi;
 
   public FixedLocalValue(Phi phi) {
-    super(phi.getNumber(), phi.getTypeLattice(), phi.getLocalInfo());
+    super(phi.getNumber(), phi.getType(), phi.getLocalInfo());
     this.phi = phi;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index e77cba3..5b4d9f2 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -182,15 +182,15 @@
       return types.iterator().next();
     }
     Iterator<DexType> iterator = types.iterator();
-    TypeLatticeElement result = getLatticeElement(iterator.next());
+    TypeElement result = toTypeElement(iterator.next());
     while (iterator.hasNext()) {
-      result = result.join(getLatticeElement(iterator.next()), appView);
+      result = result.join(toTypeElement(iterator.next()), appView);
     }
     // All types are reference types so the join is either a class or an array.
     if (result.isClassType()) {
-      return result.asClassTypeLatticeElement().getClassType();
+      return result.asClassType().getClassType();
     } else if (result.isArrayType()) {
-      return result.asArrayTypeLatticeElement().getArrayType(appView.dexItemFactory());
+      return result.asArrayType().toDexType(appView.dexItemFactory());
     }
     throw new CompilationError("Unexpected join " + result + " of types: " +
         String.join(", ",
@@ -215,8 +215,8 @@
     return createInitializedType(join(ImmutableSet.of(type1, type2)));
   }
 
-  private TypeLatticeElement getLatticeElement(DexType type) {
-    return TypeLatticeElement.fromDexType(type, Nullability.maybeNull(), appView);
+  private TypeElement toTypeElement(DexType type) {
+    return TypeElement.fromDexType(type, Nullability.maybeNull(), appView);
   }
 
   public Map<Value, TypeInfo> computeVerificationTypes() {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 8977303..da5d12a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -128,7 +128,7 @@
 
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
-    builder.addConst(type.toPrimitiveTypeLattice(), state.push(type).register, value);
+    builder.addConst(type.toPrimitiveType(), state.push(type).register, value);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index ef0ec1b..8f01356 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -10,14 +10,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueDouble;
-import com.android.tools.r8.graph.DexValue.DexValueFloat;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueLong;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.ValueType;
@@ -55,26 +47,27 @@
         methodName.toString(), callSite.methodProto.toDescriptorString(lens), bsmHandle, bsmArgs);
   }
 
-  private Object decodeBootstrapArgument(DexValue dexValue, NamingLens lens) {
-    if (dexValue instanceof DexValueInt) {
-      return ((DexValueInt) dexValue).getValue();
-    } else if (dexValue instanceof DexValueLong) {
-      return ((DexValueLong) dexValue).getValue();
-    } else if (dexValue instanceof DexValueFloat) {
-      return ((DexValueFloat) dexValue).getValue();
-    } else if (dexValue instanceof DexValueDouble) {
-      return ((DexValueDouble) dexValue).getValue();
-    } else if (dexValue instanceof DexValueString) {
-      return ((DexValueString) dexValue).getValue();
-    } else if (dexValue instanceof DexValueType) {
-      return Type.getType(lens.lookupDescriptor(((DexValueType) dexValue).value).toString());
-    } else if (dexValue instanceof DexValueMethodType) {
-      return Type.getMethodType(((DexValueMethodType) dexValue).value.toDescriptorString(lens));
-    } else if (dexValue instanceof DexValueMethodHandle) {
-      return ((DexValueMethodHandle) dexValue).value.toAsmHandle(lens);
-    } else {
-      throw new Unreachable(
-          "Unsupported bootstrap argument of type " + dexValue.getClass().getSimpleName());
+  private Object decodeBootstrapArgument(DexValue value, NamingLens lens) {
+    switch (value.getValueKind()) {
+      case DOUBLE:
+        return value.asDexValueDouble().getValue();
+      case FLOAT:
+        return value.asDexValueFloat().getValue();
+      case INT:
+        return value.asDexValueInt().getValue();
+      case LONG:
+        return value.asDexValueLong().getValue();
+      case METHOD_HANDLE:
+        return value.asDexValueMethodHandle().getValue().toAsmHandle(lens);
+      case METHOD_TYPE:
+        return Type.getMethodType(value.asDexValueMethodType().getValue().toDescriptorString(lens));
+      case STRING:
+        return value.asDexValueString().getValue();
+      case TYPE:
+        return Type.getType(lens.lookupDescriptor(value.asDexValueType().value).toString());
+      default:
+        throw new Unreachable(
+            "Unsupported bootstrap argument of type " + value.getClass().getSimpleName());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Const.java b/src/main/java/com/android/tools/r8/code/Const.java
index 9026684..498f747 100644
--- a/src/main/java/com/android/tools/r8/code/Const.java
+++ b/src/main/java/com/android/tools/r8/code/Const.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -58,8 +58,7 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
-    builder.addConst(typeLattice, AA, value);
+    TypeElement type = value == 0 ? TypeElement.getTop() : TypeElement.getSingle();
+    builder.addConst(type, AA, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Const16.java b/src/main/java/com/android/tools/r8/code/Const16.java
index f859228..9d69f25 100644
--- a/src/main/java/com/android/tools/r8/code/Const16.java
+++ b/src/main/java/com/android/tools/r8/code/Const16.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -52,8 +52,7 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
-    builder.addConst(typeLattice, AA, value);
+    TypeElement type = value == 0 ? TypeElement.getTop() : TypeElement.getSingle();
+    builder.addConst(type, AA, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Const4.java b/src/main/java/com/android/tools/r8/code/Const4.java
index a036d57..b9fd59c 100644
--- a/src/main/java/com/android/tools/r8/code/Const4.java
+++ b/src/main/java/com/android/tools/r8/code/Const4.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -58,8 +58,7 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
-    builder.addConst(typeLattice, A, value);
+    TypeElement type = value == 0 ? TypeElement.getTop() : TypeElement.getSingle();
+    builder.addConst(type, A, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstHigh16.java b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
index f3821a6..b16e45d 100644
--- a/src/main/java/com/android/tools/r8/code/ConstHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -58,8 +58,7 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
-    builder.addConst(typeLattice, AA, value);
+    TypeElement type = value == 0 ? TypeElement.getTop() : TypeElement.getSingle();
+    builder.addConst(type, AA, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide.java b/src/main/java/com/android/tools/r8/code/ConstWide.java
index 31c1089..dd2e6e6 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
+    builder.addConst(TypeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide16.java b/src/main/java/com/android/tools/r8/code/ConstWide16.java
index 2f7634d..99c1810 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide16.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
+    builder.addConst(TypeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide32.java b/src/main/java/com/android/tools/r8/code/ConstWide32.java
index 51ef097..1e2c274 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide32.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide32.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
+    builder.addConst(TypeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
index 8437a35..26d5ea0 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
+    builder.addConst(TypeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index f15e96e..a6eb1c3 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -48,10 +48,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueKind;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.InnerClassAttribute;
@@ -1244,14 +1241,14 @@
         dexReader.getUint(dexSection.offset + (Constants.TYPE_CALL_SITE_ID_ITEM_SIZE * index));
     DexEncodedArray callSiteEncodedArray = encodedArrayAt(callSiteOffset);
     DexValue[] values = callSiteEncodedArray.values;
-    assert values[0] instanceof DexValueMethodHandle;
-    assert values[1] instanceof DexValueString;
-    assert values[2] instanceof DexValueMethodType;
+    assert values[0].isDexValueMethodHandle();
+    assert values[1].isDexValueString();
+    assert values[2].isDexValueMethodType();
 
     return dexItemFactory.createCallSite(
-        ((DexValueString) values[1]).value,
-        ((DexValueMethodType) values[2]).value,
-        ((DexValueMethodHandle) values[0]).value,
+        values[1].asDexValueString().value,
+        values[2].asDexValueMethodType().value,
+        values[0].asDexValueMethodHandle().value,
         // 3 means first extra args
         Arrays.asList(Arrays.copyOfRange(values, 3, values.length)));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 2ddba3f..a41e119 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -31,11 +31,15 @@
 
   private final DexApplication app;
   private final DexItemFactory dexItemFactory;
-  private final ConcurrentHashMap<DexType, Map<DexMember<?, ?>, DexEncodedMember<?, ?>>>
-      definitions = new ConcurrentHashMap<>();
+
+  // TODO(b/151804585): Remove this cache.
+  private final ConcurrentHashMap<DexType, Map<DexField, DexEncodedField>> fieldDefinitionsCache =
+      new ConcurrentHashMap<>();
+
   // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
   // class being optimized.
-  final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses =
+      new ConcurrentHashMap<>();
 
   // Set when a new AppInfo replaces a previous one. All public methods should verify that the
   // current instance is not obsolete, to ensure that we almost use the most recent AppInfo.
@@ -50,7 +54,7 @@
     assert !previous.isObsolete();
     this.app = previous.app;
     this.dexItemFactory = app.dexItemFactory;
-    this.definitions.putAll(previous.definitions);
+    this.fieldDefinitionsCache.putAll(previous.fieldDefinitionsCache);
     copyMetadataFromPrevious(previous);
   }
 
@@ -94,7 +98,7 @@
     assert checkIfObsolete();
     assert clazz.type.isD8R8SynthesizedClassType();
     DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
-    invalidateTypeCacheFor(clazz.type);
+    invalidateFieldCacheFor(clazz.type);
     assert previous == null || previous == clazz;
   }
 
@@ -103,12 +107,11 @@
     return Collections.unmodifiableCollection(synthesizedClasses.values());
   }
 
-  private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> computeDefinitions(DexType type) {
-    Builder<DexMember<?, ?>, DexEncodedMember<?, ?>> builder = ImmutableMap.builder();
+  private Map<DexField, DexEncodedField> computeFieldDefinitions(DexType type) {
+    Builder<DexField, DexEncodedField> builder = ImmutableMap.builder();
     DexClass clazz = definitionFor(type);
     if (clazz != null) {
-      clazz.forEachMethod(method -> builder.put(method.toReference(), method));
-      clazz.forEachField(field -> builder.put(field.toReference(), field));
+      clazz.forEachField(field -> builder.put(field.field, field));
     }
     return builder.build();
   }
@@ -173,35 +176,29 @@
   @Override
   public DexEncodedMethod definitionFor(DexMethod method) {
     assert checkIfObsolete();
-    DexType holderType = method.holder;
-    DexEncodedMethod cached = (DexEncodedMethod) getDefinitions(holderType).get(method);
-    if (cached != null && cached.isObsolete()) {
-      definitions.remove(holderType);
-      cached = (DexEncodedMethod) getDefinitions(holderType).get(method);
+    assert method.holder.isClassType();
+    if (!method.holder.isClassType()) {
+      return null;
     }
-    return cached;
+    DexClass clazz = definitionFor(method.holder);
+    if (clazz == null) {
+      return null;
+    }
+    return clazz.getMethodCollection().getMethod(method);
   }
 
   @Override
   public DexEncodedField definitionFor(DexField field) {
     assert checkIfObsolete();
-    return (DexEncodedField) getDefinitions(field.holder).get(field);
+    return getFieldDefinitions(field.holder).get(field);
   }
 
-  private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> getDefinitions(DexType type) {
-    Map<DexMember<?, ?>, DexEncodedMember<?, ?>> typeDefinitions = definitions.get(type);
-    if (typeDefinitions != null) {
-      return typeDefinitions;
-    }
-
-    typeDefinitions = computeDefinitions(type);
-    Map<DexMember<?, ?>, DexEncodedMember<?, ?>> existing =
-        definitions.putIfAbsent(type, typeDefinitions);
-    return existing != null ? existing : typeDefinitions;
+  private Map<DexField, DexEncodedField> getFieldDefinitions(DexType type) {
+    return fieldDefinitionsCache.computeIfAbsent(type, this::computeFieldDefinitions);
   }
 
-  public void invalidateTypeCacheFor(DexType type) {
-    definitions.remove(type);
+  public void invalidateFieldCacheFor(DexType type) {
+    fieldDefinitionsCache.remove(type);
   }
 
   // TODO(b/147578480): Temporary API since most of the code base use a type instead
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 5f2b826..58611d4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
 
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.WorkList;
@@ -480,7 +480,7 @@
     return !isSubtype(type1, type2) && !isSubtype(type2, type1);
   }
 
-  public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeLatticeElement type) {
+  public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeElement type) {
     if (type.getClassType() == dexItemFactory().objectType && !type.getInterfaces().isEmpty()) {
       for (DexType interfaceType : type.getInterfaces()) {
         if (computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(interfaceType, false)) {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 74735f4..4eb9a85 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -7,6 +7,9 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.kotlin.KotlinClassMetadataReader;
+import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -21,6 +24,7 @@
   private final boolean writeAnnotations;
   private final boolean writeIR;
   private final AppInfoWithSubtyping appInfo;
+  private final Kotlin kotlin;
   private final Timing timing = new Timing("AssemblyWriter");
 
   public AssemblyWriter(
@@ -41,6 +45,7 @@
     } else {
       this.appInfo = null;
     }
+    kotlin = new Kotlin(application.dexItemFactory);
   }
 
   @Override
@@ -59,7 +64,7 @@
     ps.println("# Bytecode for");
     ps.println("# Class: '" + clazzName + "'");
     if (writeAllClassInfo) {
-      writeAnnotations(clazz.annotations(), ps);
+      writeAnnotations(clazz, clazz.annotations(), ps);
       ps.println("# Flags: '" + clazz.accessFlags + "'");
       if (clazz.superType != application.dexItemFactory.objectType) {
         ps.println("# Extends: '" + clazz.superType.toSourceString() + "'");
@@ -87,7 +92,7 @@
       FieldSignature fieldSignature = naming != null
           ? naming.originalSignatureOf(field.field)
           : FieldSignature.fromDexField(field.field);
-      writeAnnotations(field.annotations(), ps);
+      writeAnnotations(null, field.annotations(), ps);
       ps.print(field.accessFlags + " ");
       ps.print(fieldSignature);
       if (field.isStatic() && field.hasExplicitStaticValue()) {
@@ -110,7 +115,7 @@
         : method.method.name.toString();
     ps.println("#");
     ps.println("# Method: '" + methodName + "':");
-    writeAnnotations(method.annotations(), ps);
+    writeAnnotations(null, method.annotations(), ps);
     ps.println("# " + method.accessFlags);
     ps.println("#");
     ps.println();
@@ -134,16 +139,17 @@
     ps.println(printer.toString());
   }
 
-  private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
+  private void writeAnnotations(
+      DexProgramClass clazz, DexAnnotationSet annotations, PrintStream ps) {
     if (writeAnnotations) {
       if (!annotations.isEmpty()) {
         ps.println("# Annotations:");
         for (DexAnnotation annotation : annotations.annotations) {
-          ps.print("#   ");
-          if (annotation.annotation.type
-              == application.dexItemFactory.createType("Lkotlin/Metadata;")) {
-            ps.println("<kotlin metadata>");
+          if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+            assert clazz != null : "Kotlin metadata is a class annotation";
+            writeKotlinMetadata(clazz, annotation, ps);
           } else {
+            ps.print("#   ");
             ps.println(annotation);
           }
         }
@@ -151,6 +157,14 @@
     }
   }
 
+  private void writeKotlinMetadata(
+      DexProgramClass clazz, DexAnnotation annotation, PrintStream ps) {
+    assert annotation.annotation.type == kotlin.metadata.kotlinMetadataType;
+    KotlinInfo kotlinInfo =
+        KotlinClassMetadataReader.createKotlinInfo(kotlin, clazz, annotation.annotation);
+    ps.println(kotlinInfo.toString("#  "));
+  }
+
   @Override
   void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 50b4b2f..a409737 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -99,9 +99,11 @@
 
   public static DexType getEnclosingClassFromAnnotation(
       DexAnnotation annotation, DexItemFactory factory) {
-    DexValueType typeValue =
-        (DexValueType) getSystemValueAnnotationValue(factory.annotationEnclosingClass, annotation);
-    return typeValue == null ? null : typeValue.value;
+    DexValue value = getSystemValueAnnotationValue(factory.annotationEnclosingClass, annotation);
+    if (value == null) {
+      return null;
+    }
+    return value.asDexValueType().value;
   }
 
   public static DexAnnotation createEnclosingMethodAnnotation(DexMethod enclosingMethod,
@@ -112,10 +114,11 @@
 
   public static DexMethod getEnclosingMethodFromAnnotation(
       DexAnnotation annotation, DexItemFactory factory) {
-    DexValueMethod methodValue =
-        (DexValueMethod)
-            getSystemValueAnnotationValue(factory.annotationEnclosingMethod, annotation);
-    return methodValue == null ? null : methodValue.value;
+    DexValue value = getSystemValueAnnotationValue(factory.annotationEnclosingMethod, annotation);
+    if (value == null) {
+      return null;
+    }
+    return value.asDexValueMethod().value;
   }
 
   public static boolean isEnclosingClassAnnotation(DexAnnotation annotation,
@@ -159,12 +162,12 @@
     Pair<DexString, Integer> result = new Pair<>();
     for (DexAnnotationElement element : elements) {
       if (element.name == factory.createString("name")) {
-        if (element.value instanceof DexValueString) {
-          result.setFirst(((DexValueString) element.value).getValue());
+        if (element.value.isDexValueString()) {
+          result.setFirst(element.value.asDexValueString().getValue());
         }
       } else {
         assert element.name == factory.createString("accessFlags");
-        result.setSecond(((DexValueInt) element.value).getValue());
+        result.setSecond(element.value.asDexValueInt().getValue());
       }
     }
     return result;
@@ -182,14 +185,14 @@
 
   public static List<DexType> getMemberClassesFromAnnotation(
       DexAnnotation annotation, DexItemFactory factory) {
-    DexValueArray membersArray =
-        (DexValueArray) getSystemValueAnnotationValue(factory.annotationMemberClasses, annotation);
-    if (membersArray == null) {
+    DexValue value = getSystemValueAnnotationValue(factory.annotationMemberClasses, annotation);
+    if (value == null) {
       return null;
     }
+    DexValueArray membersArray = value.asDexValueArray();
     List<DexType> types = new ArrayList<>(membersArray.getValues().length);
-    for (DexValue value : membersArray.getValues()) {
-      types.add(((DexValueType) value).value);
+    for (DexValue elementValue : membersArray.getValues()) {
+      types.add(elementValue.asDexValueType().value);
     }
     return types;
   }
@@ -233,10 +236,10 @@
   }
 
   public static String getSignature(DexAnnotation signatureAnnotation) {
-    DexValueArray elements = (DexValueArray) signatureAnnotation.annotation.elements[0].value;
+    DexValueArray elements = signatureAnnotation.annotation.elements[0].value.asDexValueArray();
     StringBuilder signature = new StringBuilder();
     for (DexValue element : elements.getValues()) {
-      signature.append(((DexValueString) element).value.toString());
+      signature.append(element.asDexValueString().value.toString());
     }
     return signature.toString();
   }
@@ -349,16 +352,16 @@
       if (!value.name.toSourceString().equals("value")) {
         throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
       }
-      if (!(value.value instanceof DexValueArray)) {
+      DexValueArray existingList = value.value.asDexValueArray();
+      if (existingList == null) {
         throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
       }
-      DexValueArray existingList = (DexValueArray) value.value;
       Collection<DexType> synthesized = new ArrayList<>(existingList.values.length);
       for (DexValue element : existingList.getValues()) {
-        if (!(element instanceof DexValueType)) {
+        if (!element.isDexValueType()) {
           throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
         }
-        synthesized.add(((DexValueType) element).value);
+        synthesized.add(element.asDexValueType().value);
       }
       return synthesized;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index 382f4bb..3249abf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -213,52 +213,35 @@
     private void write(List<DexValue> args) throws IOException {
       out.writeInt(args.size());
       for (DexValue arg : args) {
-        // String, Class, Integer, Long, Float, Double, MethodHandle, MethodType
-        if (arg instanceof DexValue.DexValueString) {
-          out.writeByte(0);
-          write(((DexValue.DexValueString) arg).value);
-          continue;
+        out.writeByte(arg.getValueKind().toByte());
+        switch (arg.getValueKind()) {
+          case DOUBLE:
+            out.writeDouble(arg.asDexValueDouble().value);
+            break;
+          case FLOAT:
+            out.writeFloat(arg.asDexValueFloat().value);
+            break;
+          case INT:
+            out.writeInt(arg.asDexValueInt().value);
+            break;
+          case LONG:
+            out.writeLong(arg.asDexValueLong().value);
+            break;
+          case METHOD_HANDLE:
+            write(arg.asDexValueMethodHandle().value);
+            break;
+          case METHOD_TYPE:
+            write(arg.asDexValueMethodType().value);
+            break;
+          case STRING:
+            write(arg.asDexValueString().value);
+            break;
+          case TYPE:
+            write(arg.asDexValueType().value);
+            break;
+          default:
+            assert false;
         }
-
-        if (arg instanceof DexValue.DexValueType) {
-          out.writeByte(1);
-          write(((DexValue.DexValueType) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueInt) {
-          out.writeByte(2);
-          out.writeInt(((DexValue.DexValueInt) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueLong) {
-          out.writeByte(3);
-          out.writeLong(((DexValue.DexValueLong) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueFloat) {
-          out.writeByte(4);
-          out.writeFloat(((DexValue.DexValueFloat) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueDouble) {
-          out.writeByte(5);
-          out.writeDouble(((DexValue.DexValueDouble) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueMethodHandle) {
-          out.writeByte(6);
-          write(((DexValue.DexValueMethodHandle) arg).value);
-          continue;
-        }
-
-        assert arg instanceof DexValue.DexValueMethodType;
-        out.writeByte(7);
-        write(((DexValue.DexValueMethodType) arg).value);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 773a71a..85810ed 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -85,9 +85,7 @@
     this.type = type;
     setStaticFields(staticFields);
     setInstanceFields(instanceFields);
-    this.methodCollection = new MethodCollection(this);
-    setDirectMethods(directMethods);
-    setVirtualMethods(virtualMethods);
+    this.methodCollection = new MethodCollection(this, directMethods, virtualMethods);
     this.nestHost = nestHost;
     this.nestMembers = nestMembers;
     assert nestMembers != null;
@@ -148,12 +146,8 @@
     return Iterables.filter(directMethods(), predicate::test);
   }
 
-  public void appendDirectMethod(DexEncodedMethod method) {
-    methodCollection.appendDirectMethod(method);
-  }
-
-  public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
-    methodCollection.appendDirectMethods(methods);
+  public void addDirectMethods(Collection<DexEncodedMethod> methods) {
+    methodCollection.addDirectMethods(methods);
   }
 
   public void removeDirectMethod(DexMethod method) {
@@ -172,12 +166,8 @@
     return Iterables.filter(virtualMethods(), predicate::test);
   }
 
-  public void appendVirtualMethod(DexEncodedMethod method) {
-    methodCollection.appendVirtualMethod(method);
-  }
-
-  public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
-    methodCollection.appendVirtualMethods(methods);
+  public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
+    methodCollection.addVirtualMethods(methods);
   }
 
   public void setVirtualMethods(DexEncodedMethod[] methods) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 8a21e7b..10427a3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -7,7 +7,7 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.IRCode;
@@ -207,7 +207,7 @@
         return null;
       }
       if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
-        TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
+        TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
         return singleValue.createMaterializingInstruction(
             appView, code, TypeAndLocalInfoSupplier.create(type, local));
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 342a3ed..01b0e84 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1108,6 +1108,7 @@
     MethodAccessFlags newFlags = target.accessFlags.copy();
     // Some debuggers (like IntelliJ) automatically skip synthetic methods on single step.
     newFlags.setSynthetic();
+    newFlags.unsetAbstract();
     ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
         ForwardMethodSourceCode.builder(newMethod);
     forwardSourceCodeBuilder
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index bfaf19c..30d9369 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import static com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement.computeLeastUpperBoundOfInterfaces;
+import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.dex.Constants;
@@ -18,11 +18,11 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.ReferenceTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
@@ -82,11 +82,11 @@
   private final Map<DexString, SetFile> setFiles = new HashMap<>();
   private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
 
-  // ReferenceTypeLattice canonicalization.
-  private final ConcurrentHashMap<DexType, ReferenceTypeLatticeElement>
-      referenceTypeLatticeElements = new ConcurrentHashMap<>();
-  private final ConcurrentHashMap<DexType, Set<DexType>>
-      classTypeLatticeInterfaces = new ConcurrentHashMap<>();
+  // ReferenceTypeElement canonicalization.
+  private final ConcurrentHashMap<DexType, ReferenceTypeElement> referenceTypes =
+      new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexType, Set<DexType>> classTypeInterfaces =
+      new ConcurrentHashMap<>();
   public final LRUCacheTable<Set<DexType>, Set<DexType>, Set<DexType>>
       leastUpperBoundOfInterfacesTable = LRUCacheTable.create(8, 8);
 
@@ -1201,7 +1201,7 @@
       if (invokedMethod == charSequenceConstructor) {
         // NullPointerException - if seq is null.
         Value seqValue = invoke.inValues().get(1);
-        return !seqValue.getTypeLattice().isNullable();
+        return !seqValue.getType().isNullable();
       }
 
       if (invokedMethod == defaultConstructor) {
@@ -1220,7 +1220,7 @@
       if (invokedMethod == stringConstructor) {
         // NullPointerException - if str is null.
         Value strValue = invoke.inValues().get(1);
-        return !strValue.getTypeLattice().isNullable();
+        return !strValue.getType().isNullable();
       }
 
       assert false : "Unexpected invoke targeting `" + invokedMethod.toSourceString() +  "`";
@@ -1644,20 +1644,20 @@
     return method.name == classConstructorMethodName;
   }
 
-  public void clearTypeLatticeElementsCache() {
-    referenceTypeLatticeElements.clear();
-    classTypeLatticeInterfaces.clear();
+  public void clearTypeElementsCache() {
+    referenceTypes.clear();
+    classTypeInterfaces.clear();
     leastUpperBoundOfInterfacesTable.clear();
   }
 
-  public boolean verifyNoCachedTypeLatticeElements() {
-    assert referenceTypeLatticeElements.isEmpty();
-    assert classTypeLatticeInterfaces.isEmpty();
+  public boolean verifyNoCachedTypeElements() {
+    assert referenceTypes.isEmpty();
+    assert classTypeInterfaces.isEmpty();
     assert leastUpperBoundOfInterfacesTable.isEmpty();
     return true;
   }
 
-  public ReferenceTypeLatticeElement createReferenceTypeLatticeElement(
+  public ReferenceTypeElement createReferenceTypeElement(
       DexType type, Nullability nullability, AppView<?> appView) {
     // Class case:
     // If two concurrent threads will try to create the same class-type the concurrent hash map will
@@ -1671,56 +1671,54 @@
     // (ii) If base is ArrayLattice case we can use our induction hypothesis to get that only one
     //      element is created for us up to this case. Threads will now race to return from the
     //      latest recursive call and fight to get access to .computeIfAbsent to add the
-    //      ArrayTypeLatticeElement but only one will enter. The property that only one
-    //      ArrayTypeLatticeElement is created per level therefore holds inductively.
-    TypeLatticeElement memberType = null;
+    //      ArrayTypeElement but only one will enter. The property that only one
+    //      ArrayTypeElement is created per level therefore holds inductively.
+    TypeElement memberType = null;
     if (type.isArrayType()) {
-      ReferenceTypeLatticeElement existing = referenceTypeLatticeElements.get(type);
+      ReferenceTypeElement existing = referenceTypes.get(type);
       if (existing != null) {
         return existing.getOrCreateVariant(nullability);
       }
       memberType =
-          TypeLatticeElement.fromDexType(
+          TypeElement.fromDexType(
               type.toArrayElementType(this), Nullability.maybeNull(), appView, true);
     }
-    TypeLatticeElement finalMemberType = memberType;
-    return referenceTypeLatticeElements
+    TypeElement finalMemberType = memberType;
+    return referenceTypes
         .computeIfAbsent(
             type,
             t -> {
               if (type.isClassType()) {
                 if (!appView.enableWholeProgramOptimizations()) {
                   // Don't reason at the level of interfaces in D8.
-                  return ClassTypeLatticeElement.create(type, nullability, Collections.emptySet());
+                  return ClassTypeElement.create(type, nullability, Collections.emptySet());
                 }
                 assert appView.appInfo().hasSubtyping();
                 if (appView.isInterface(type).isTrue()) {
-                  return ClassTypeLatticeElement.create(
+                  return ClassTypeElement.create(
                       objectType, nullability, Collections.singleton(type));
                 }
                 // In theory, `interfaces` is the least upper bound of implemented interfaces.
                 // It is expensive to walk through type hierarchy; collect implemented interfaces;
                 // and compute the least upper bound of two interface sets. Hence, lazy
                 // computations. Most likely during lattice join. See {@link
-                // ClassTypeLatticeElement#getInterfaces}.
-                return ClassTypeLatticeElement.create(type, nullability, appView.withSubtyping());
+                // ClassTypeElement#getInterfaces}.
+                return ClassTypeElement.create(type, nullability, appView.withSubtyping());
               }
               assert type.isArrayType();
-              return ArrayTypeLatticeElement.create(finalMemberType, nullability);
+              return ArrayTypeElement.create(finalMemberType, nullability);
             })
         .getOrCreateVariant(nullability);
   }
 
   public Set<DexType> getOrComputeLeastUpperBoundOfImplementedInterfaces(
       DexType type, AppView<? extends AppInfoWithSubtyping> appView) {
-    return classTypeLatticeInterfaces
-        .computeIfAbsent(
-            type,
-            t -> {
-              Set<DexType> itfs = appView.appInfo().implementedInterfaces(t);
-              return computeLeastUpperBoundOfInterfaces(appView, itfs, itfs);
-            }
-        );
+    return classTypeInterfaces.computeIfAbsent(
+        type,
+        t -> {
+          Set<DexType> itfs = appView.appInfo().implementedInterfaces(t);
+          return computeLeastUpperBoundOfInterfaces(appView, itfs, itfs);
+        });
   }
 
   synchronized public void forAllTypes(Consumer<DexType> f) {
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 4b8c6d4..01c5298 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
@@ -44,8 +45,7 @@
   private String toStringCache = null;
 
   DexType(DexString descriptor) {
-    assert !descriptor.toString().contains(".")
-        : "Malformed descriptor: " + descriptor.toString();
+    assert !descriptor.toString().contains(".") : "Malformed descriptor: " + descriptor.toString();
     this.descriptor = descriptor;
   }
 
@@ -145,11 +145,12 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection collection,
-      DexMethod method, int instructionOffset) {
+  public void collectIndexedItems(
+      IndexedItemCollection collection, DexMethod method, int instructionOffset) {
     if (collection.addType(this)) {
-      collection.getRenamedDescriptor(this).collectIndexedItems(collection, method,
-          instructionOffset);
+      collection
+          .getRenamedDescriptor(this)
+          .collectIndexedItems(collection, method, instructionOffset);
     }
   }
 
@@ -272,6 +273,7 @@
         || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME)
         || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME)
         || name.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX)
+        || name.contains(DesugaredLibraryRetargeter.DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX)
         || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME)
         || oldSynthesizedName(name);
   }
@@ -298,17 +300,17 @@
   public int elementSizeForPrimitiveArrayType() {
     assert isPrimitiveArrayType();
     switch (descriptor.content[1]) {
-      case 'Z':  // boolean
-      case 'B':  // byte
+      case 'Z': // boolean
+      case 'B': // byte
         return 1;
-      case 'S':  // short
-      case 'C':  // char
+      case 'S': // short
+      case 'C': // char
         return 2;
-      case 'I':  // int
-      case 'F':  // float
+      case 'I': // int
+      case 'F': // float
         return 4;
-      case 'J':  // long
-      case 'D':  // double
+      case 'J': // long
+      case 'D': // double
         return 8;
       default:
         throw new Unreachable("Not array of primitives '" + descriptor + "'");
@@ -328,8 +330,11 @@
     if (leadingSquareBrackets == 0) {
       return this;
     }
-    DexString newDesc = dexItemFactory.createString(descriptor.size - leadingSquareBrackets,
-        Arrays.copyOfRange(descriptor.content, leadingSquareBrackets, descriptor.content.length));
+    DexString newDesc =
+        dexItemFactory.createString(
+            descriptor.size - leadingSquareBrackets,
+            Arrays.copyOfRange(
+                descriptor.content, leadingSquareBrackets, descriptor.content.length));
     return dexItemFactory.createType(newDesc);
   }
 
@@ -364,8 +369,10 @@
 
   public DexType toArrayElementType(DexItemFactory dexItemFactory) {
     assert this.isArrayType();
-    DexString newDesc = dexItemFactory.createString(descriptor.size - 1,
-        Arrays.copyOfRange(descriptor.content, 1, descriptor.content.length));
+    DexString newDesc =
+        dexItemFactory.createString(
+            descriptor.size - 1,
+            Arrays.copyOfRange(descriptor.content, 1, descriptor.content.length));
     return dexItemFactory.createType(newDesc);
   }
 
@@ -376,7 +383,8 @@
     if (lastSeparator == -1) {
       return packagePart ? "" : descriptor.substring(1, descriptor.length() - 1);
     } else {
-      return packagePart ? descriptor.substring(1, lastSeparator)
+      return packagePart
+          ? descriptor.substring(1, lastSeparator)
           : descriptor.substring(lastSeparator + 1, descriptor.length() - 1);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index be0cf37..bab3c75 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstString;
@@ -102,6 +102,8 @@
 
   public static final DexValue[] EMPTY_ARRAY = {};
 
+  public abstract DexValueKind getValueKind();
+
   public boolean isDexItemBasedValueString() {
     return false;
   }
@@ -110,14 +112,30 @@
     return null;
   }
 
+  public boolean isDexValueMethodHandle() {
+    return false;
+  }
+
   public DexValueMethodHandle asDexValueMethodHandle() {
     return null;
   }
 
+  public boolean isDexValueMethodType() {
+    return false;
+  }
+
   public DexValueMethodType asDexValueMethodType() {
     return null;
   }
 
+  public boolean isDexValueAnnotation() {
+    return false;
+  }
+
+  public DexValueAnnotation asDexValueAnnotation() {
+    return null;
+  }
+
   public boolean isDexValueArray() {
     return false;
   }
@@ -158,6 +176,22 @@
     return null;
   }
 
+  public boolean isDexValueEnum() {
+    return false;
+  }
+
+  public DexValueEnum asDexValueEnum() {
+    return null;
+  }
+
+  public boolean isDexValueField() {
+    return false;
+  }
+
+  public DexValueField asDexValueField() {
+    return null;
+  }
+
   public boolean isDexValueFloat() {
     return false;
   }
@@ -182,6 +216,14 @@
     return null;
   }
 
+  public boolean isDexValueMethod() {
+    return false;
+  }
+
+  public DexValueMethod asDexValueMethod() {
+    return null;
+  }
+
   public boolean isDexValueNull() {
     return false;
   }
@@ -392,6 +434,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.BYTE;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.byteType;
     }
@@ -470,6 +517,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.SHORT;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.shortType;
     }
@@ -547,6 +599,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.CHAR;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.charType;
     }
@@ -628,6 +685,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.INT;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.intType;
     }
@@ -705,6 +767,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.LONG;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.longType;
     }
@@ -782,6 +849,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.FLOAT;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.floatType;
     }
@@ -836,8 +908,8 @@
       if (other == this) {
         return true;
       }
-      return (other instanceof DexValueFloat) &&
-          (Float.compare(value, ((DexValueFloat) other).value) == 0);
+      return other instanceof DexValueFloat
+          && Float.compare(value, ((DexValueFloat) other).value) == 0;
     }
 
     @Override
@@ -865,6 +937,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.DOUBLE;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.doubleType;
     }
@@ -937,8 +1014,6 @@
       this.value = value;
     }
 
-    protected abstract DexValueKind getValueKind();
-
     @Override
     public DexType getType(DexItemFactory factory) {
       throw new Unreachable();
@@ -1024,7 +1099,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.STRING;
     }
 
@@ -1036,7 +1111,7 @@
     @Override
     public ConstInstruction asConstInstruction(
         AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
-      TypeLatticeElement type = TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+      TypeElement type = TypeElement.stringClassType(appView, definitelyNotNull());
       Value outValue = code.createValue(type, local);
       ConstString instruction =
           new ConstString(outValue, value, ThrowingInfo.defaultForConstString(appView.options()));
@@ -1082,7 +1157,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.STRING;
     }
 
@@ -1094,7 +1169,7 @@
     @Override
     public ConstInstruction asConstInstruction(
         AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
-      TypeLatticeElement type = TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+      TypeElement type = TypeElement.stringClassType(appView, definitelyNotNull());
       Value outValue = code.createValue(type, local);
       DexItemBasedConstString instruction =
           new DexItemBasedConstString(
@@ -1121,7 +1196,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.TYPE;
     }
 
@@ -1149,7 +1224,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.FIELD;
     }
 
@@ -1158,6 +1233,16 @@
         IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
+
+    @Override
+    public boolean isDexValueField() {
+      return true;
+    }
+
+    @Override
+    public DexValueField asDexValueField() {
+      return this;
+    }
   }
 
   static public class DexValueMethod extends NestedDexValue<DexMethod> {
@@ -1167,7 +1252,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.METHOD;
     }
 
@@ -1176,6 +1261,16 @@
         IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
+
+    @Override
+    public boolean isDexValueMethod() {
+      return true;
+    }
+
+    @Override
+    public DexValueMethod asDexValueMethod() {
+      return this;
+    }
   }
 
   static public class DexValueEnum extends NestedDexValue<DexField> {
@@ -1185,7 +1280,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.ENUM;
     }
 
@@ -1194,6 +1289,16 @@
         IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
+
+    @Override
+    public boolean isDexValueEnum() {
+      return true;
+    }
+
+    @Override
+    public DexValueEnum asDexValueEnum() {
+      return this;
+    }
   }
 
   static public class DexValueMethodType extends NestedDexValue<DexProto> {
@@ -1203,12 +1308,17 @@
     }
 
     @Override
+    public boolean isDexValueMethodType() {
+      return true;
+    }
+
+    @Override
     public DexValueMethodType asDexValueMethodType() {
       return this;
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.METHOD_TYPE;
     }
 
@@ -1232,6 +1342,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.ARRAY;
+    }
+
+    @Override
     public void collectIndexedItems(IndexedItemCollection indexedItems,
         DexMethod method, int instructionOffset) {
       collectAll(indexedItems, values);
@@ -1310,6 +1425,21 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.ANNOTATION;
+    }
+
+    @Override
+    public boolean isDexValueAnnotation() {
+      return true;
+    }
+
+    @Override
+    public DexValueAnnotation asDexValueAnnotation() {
+      return this;
+    }
+
+    @Override
     public void collectIndexedItems(IndexedItemCollection indexedItems,
         DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
@@ -1377,6 +1507,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.NULL;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       throw new Unreachable();
     }
@@ -1421,7 +1556,7 @@
       if (other == this) {
         return true;
       }
-      return (other instanceof DexValueNull);
+      return other instanceof DexValueNull;
     }
 
     @Override
@@ -1458,6 +1593,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.BOOLEAN;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.booleanType;
     }
@@ -1502,7 +1642,7 @@
       if (other == this) {
         return true;
       }
-      return (other instanceof DexValueBoolean) && ((DexValueBoolean) other).value == value;
+      return other instanceof DexValueBoolean && ((DexValueBoolean) other).value == value;
     }
 
     @Override
@@ -1524,12 +1664,17 @@
     }
 
     @Override
+    public boolean isDexValueMethodHandle() {
+      return true;
+    }
+
+    @Override
     public DexValueMethodHandle asDexValueMethodHandle() {
       return this;
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.METHOD_HANDLE;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index b8210c8..03c329c 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -243,7 +243,7 @@
       return self();
     }
 
-    public Builder addClasspathClasses(List<DexClasspathClass> classes) {
+    public Builder addClasspathClasses(Collection<DexClasspathClass> classes) {
       classpathClasses =
           ImmutableList.<DexClasspathClass>builder()
               .addAll(classpathClasses)
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 7800d81a..4eda679 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.PredicateUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.MoreObjects;
@@ -11,7 +10,6 @@
 import com.google.common.collect.Sets;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
@@ -22,16 +20,23 @@
   private DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
   private DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
 
-  private boolean belongsToDirectPool(DexEncodedMethod method) {
-    return method.accessFlags.isStatic()
-        || method.accessFlags.isPrivate()
-        || method.accessFlags.isConstructor();
+  private boolean verifyNoDuplicateMethods() {
+    Set<DexMethod> unique = Sets.newIdentityHashSet();
+    forEachMethod(
+        method -> {
+          boolean changed = unique.add(method.method);
+          assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
+        });
+    return true;
   }
 
-  private boolean belongsToVirtualPool(DexEncodedMethod method) {
-    return !belongsToDirectPool(method);
+  @Override
+  boolean verify() {
+    assert verifyNoDuplicateMethods();
+    return true;
   }
 
+  @Override
   int size() {
     return directMethods.length + virtualMethods.length;
   }
@@ -53,27 +58,19 @@
     return TraversalContinuation.CONTINUE;
   }
 
+  @Override
   public Iterable<DexEncodedMethod> methods() {
     return Iterables.concat(Arrays.asList(directMethods), Arrays.asList(virtualMethods));
   }
 
+  @Override
   List<DexEncodedMethod> directMethods() {
     assert directMethods != null;
-    if (InternalOptions.assertionsEnabled()) {
-      return Collections.unmodifiableList(Arrays.asList(directMethods));
-    }
     return Arrays.asList(directMethods);
   }
 
-  void appendDirectMethod(DexEncodedMethod method) {
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1];
-    System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
-    newMethods[directMethods.length] = method;
-    directMethods = newMethods;
-    assert verifyNoDuplicateMethods();
-  }
-
-  void appendDirectMethods(Collection<DexEncodedMethod> methods) {
+  @Override
+  void addDirectMethods(Collection<DexEncodedMethod> methods) {
     DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + methods.size()];
     System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
     int i = directMethods.length;
@@ -92,6 +89,7 @@
     directMethods = newMethods;
   }
 
+  @Override
   void removeDirectMethod(DexMethod method) {
     int index = -1;
     for (int i = 0; i < directMethods.length; i++) {
@@ -105,33 +103,20 @@
     }
   }
 
-  void setDirectMethod(int index, DexEncodedMethod method) {
-    directMethods[index] = method;
-    assert verifyNoDuplicateMethods();
-  }
-
+  @Override
   void setDirectMethods(DexEncodedMethod[] methods) {
     directMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY);
     assert verifyNoDuplicateMethods();
   }
 
+  @Override
   List<DexEncodedMethod> virtualMethods() {
     assert virtualMethods != null;
-    if (InternalOptions.assertionsEnabled()) {
-      return Collections.unmodifiableList(Arrays.asList(virtualMethods));
-    }
     return Arrays.asList(virtualMethods);
   }
 
-  void appendVirtualMethod(DexEncodedMethod method) {
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
-    System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
-    newMethods[virtualMethods.length] = method;
-    virtualMethods = newMethods;
-    assert verifyNoDuplicateMethods();
-  }
-
-  void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
+  @Override
+  void addVirtualMethods(Collection<DexEncodedMethod> methods) {
     DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()];
     System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
     int i = virtualMethods.length;
@@ -143,11 +128,13 @@
     assert verifyNoDuplicateMethods();
   }
 
+  @Override
   void setVirtualMethods(DexEncodedMethod[] methods) {
     virtualMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY);
     assert verifyNoDuplicateMethods();
   }
 
+  @Override
   void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
     int vLen = virtualMethods.length;
     int dLen = directMethods.length;
@@ -174,6 +161,7 @@
     setVirtualMethods(newVirtualMethods);
   }
 
+  @Override
   DexEncodedMethod getDirectMethod(DexMethod method) {
     for (DexEncodedMethod directMethod : directMethods) {
       if (method.match(directMethod)) {
@@ -183,10 +171,12 @@
     return null;
   }
 
+  @Override
   DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) {
     return PredicateUtils.findFirst(directMethods, predicate);
   }
 
+  @Override
   DexEncodedMethod getVirtualMethod(DexMethod method) {
     for (DexEncodedMethod virtualMethod : virtualMethods) {
       if (method.match(virtualMethod)) {
@@ -196,51 +186,41 @@
     return null;
   }
 
+  @Override
   DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) {
     return PredicateUtils.findFirst(virtualMethods, predicate);
   }
 
+  @Override
   DexEncodedMethod getMethod(DexMethod method) {
     DexEncodedMethod result = getDirectMethod(method);
     return result == null ? getVirtualMethod(method) : result;
   }
 
+  @Override
   void addMethod(DexEncodedMethod method) {
-    if (method.accessFlags.isStatic()
-        || method.accessFlags.isPrivate()
-        || method.accessFlags.isConstructor()) {
+    if (belongsToDirectPool(method)) {
       addDirectMethod(method);
     } else {
       addVirtualMethod(method);
     }
   }
 
+  @Override
   void addVirtualMethod(DexEncodedMethod virtualMethod) {
-    assert !virtualMethod.accessFlags.isStatic();
-    assert !virtualMethod.accessFlags.isPrivate();
-    assert !virtualMethod.accessFlags.isConstructor();
+    assert belongsToVirtualPool(virtualMethod);
     virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1);
     virtualMethods[virtualMethods.length - 1] = virtualMethod;
   }
 
-  void addDirectMethod(DexEncodedMethod staticMethod) {
-    assert staticMethod.accessFlags.isStatic()
-        || staticMethod.accessFlags.isPrivate()
-        || staticMethod.accessFlags.isConstructor();
+  @Override
+  void addDirectMethod(DexEncodedMethod directMethod) {
+    assert belongsToDirectPool(directMethod);
     directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
-    directMethods[directMethods.length - 1] = staticMethod;
+    directMethods[directMethods.length - 1] = directMethod;
   }
 
-  boolean verifyNoDuplicateMethods() {
-    Set<DexMethod> unique = Sets.newIdentityHashSet();
-    forEachMethod(
-        method -> {
-          boolean changed = unique.add(method.method);
-          assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
-        });
-    return true;
-  }
-
+  @Override
   public DexEncodedMethod replaceDirectMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     for (int i = 0; i < directMethods.length; i++) {
@@ -255,6 +235,7 @@
     return null;
   }
 
+  @Override
   public DexEncodedMethod replaceDirectMethodWithVirtualMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     for (int i = 0; i < directMethods.length; i++) {
@@ -263,18 +244,20 @@
         DexEncodedMethod newMethod = replacement.apply(directMethod);
         assert belongsToVirtualPool(newMethod);
         removeDirectMethod(i);
-        appendVirtualMethod(newMethod);
+        addVirtualMethod(newMethod);
         return newMethod;
       }
     }
     return null;
   }
 
+  @Override
   public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     replaceDirectMethods(replacement);
     replaceVirtualMethods(replacement);
   }
 
+  @Override
   public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     for (int i = 0; i < directMethods.length; i++) {
       DexEncodedMethod method = directMethods[i];
@@ -287,6 +270,7 @@
     }
   }
 
+  @Override
   public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     for (int i = 0; i < virtualMethods.length; i++) {
       DexEncodedMethod method = virtualMethods[i];
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index a4f3d5a..269b36b 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -1,12 +1,13 @@
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -14,12 +15,37 @@
 
 public class MethodCollection {
 
-  private final DexClass holder;
-  private final MethodArrayBacking backing = new MethodArrayBacking();
-  private Optional<DexEncodedMethod> cachedClassInitializer = null;
+  // Threshold between using an array and a map for the backing store.
+  // Compiling R8 plus library shows classes with up to 30 methods account for about 95% of classes.
+  private static final int ARRAY_BACKING_THRESHOLD = 30;
 
-  public MethodCollection(DexClass holder) {
+  private final DexClass holder;
+  private final MethodCollectionBacking backing;
+  private DexEncodedMethod cachedClassInitializer = DexEncodedMethod.SENTINEL;
+
+  public MethodCollection(
+      DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
     this.holder = holder;
+    if (directMethods.length + virtualMethods.length > ARRAY_BACKING_THRESHOLD) {
+      backing = new MethodMapBacking();
+    } else {
+      backing = new MethodArrayBacking();
+    }
+    backing.setDirectMethods(directMethods);
+    backing.setVirtualMethods(virtualMethods);
+  }
+
+  private void resetCaches() {
+    resetDirectMethodCaches();
+    resetVirtualMethodCaches();
+  }
+
+  private void resetDirectMethodCaches() {
+    resetClassInitializerCache();
+  }
+
+  private void resetVirtualMethodCaches() {
+    // Nothing to do.
   }
 
   public int size() {
@@ -50,10 +76,16 @@
   }
 
   public List<DexEncodedMethod> directMethods() {
+    if (InternalOptions.assertionsEnabled()) {
+      return Collections.unmodifiableList(backing.directMethods());
+    }
     return backing.directMethods();
   }
 
   public List<DexEncodedMethod> virtualMethods() {
+    if (InternalOptions.assertionsEnabled()) {
+      return Collections.unmodifiableList(backing.virtualMethods());
+    }
     return backing.virtualMethods();
   }
 
@@ -77,48 +109,56 @@
     return backing.getVirtualMethod(predicate);
   }
 
+  private void resetClassInitializerCache() {
+    cachedClassInitializer = DexEncodedMethod.SENTINEL;
+  }
+
   public DexEncodedMethod getClassInitializer() {
-    if (cachedClassInitializer == null) {
-      cachedClassInitializer = Optional.empty();
+    if (cachedClassInitializer == DexEncodedMethod.SENTINEL) {
+      cachedClassInitializer = null;
       for (DexEncodedMethod directMethod : directMethods()) {
         if (directMethod.isClassInitializer()) {
-          cachedClassInitializer = Optional.of(directMethod);
+          cachedClassInitializer = directMethod;
           break;
         }
       }
     }
-    return cachedClassInitializer.orElse(null);
+    return cachedClassInitializer;
   }
 
   public void addMethod(DexEncodedMethod method) {
+    resetCaches();
     backing.addMethod(method);
   }
 
   public void addVirtualMethod(DexEncodedMethod virtualMethod) {
+    resetVirtualMethodCaches();
     backing.addVirtualMethod(virtualMethod);
   }
 
   public void addDirectMethod(DexEncodedMethod directMethod) {
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     backing.addDirectMethod(directMethod);
   }
 
   public DexEncodedMethod replaceDirectMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     return backing.replaceDirectMethod(method, replacement);
   }
 
   public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    resetCaches();
     backing.replaceMethods(replacement);
   }
 
   public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    resetVirtualMethodCaches();
     backing.replaceVirtualMethods(replacement);
   }
 
   public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     backing.replaceDirectMethods(replacement);
   }
 
@@ -131,49 +171,41 @@
    */
   public DexEncodedMethod replaceDirectMethodWithVirtualMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    // The class initializer can never by converted to a virtual.
+    resetCaches();
     return backing.replaceDirectMethodWithVirtualMethod(method, replacement);
   }
 
-  public void appendDirectMethod(DexEncodedMethod method) {
-    assert verifyCorrectnessOfMethodHolder(method);
-    cachedClassInitializer = null;
-    backing.appendDirectMethod(method);
-  }
-
-  public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
+  public void addDirectMethods(Collection<DexEncodedMethod> methods) {
     assert verifyCorrectnessOfMethodHolders(methods);
-    cachedClassInitializer = null;
-    backing.appendDirectMethods(methods);
+    resetDirectMethodCaches();
+    backing.addDirectMethods(methods);
   }
 
   public void removeDirectMethod(DexMethod method) {
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     backing.removeDirectMethod(method);
   }
 
   public void setDirectMethods(DexEncodedMethod[] methods) {
     assert verifyCorrectnessOfMethodHolders(methods);
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     backing.setDirectMethods(methods);
   }
 
-  public void appendVirtualMethod(DexEncodedMethod method) {
-    assert verifyCorrectnessOfMethodHolder(method);
-    backing.appendVirtualMethod(method);
-  }
-
-  public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
+  public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
     assert verifyCorrectnessOfMethodHolders(methods);
-    backing.appendVirtualMethods(methods);
+    resetVirtualMethodCaches();
+    backing.addVirtualMethods(methods);
   }
 
   public void setVirtualMethods(DexEncodedMethod[] methods) {
     assert verifyCorrectnessOfMethodHolders(methods);
+    resetVirtualMethodCaches();
     backing.setVirtualMethods(methods);
   }
 
   public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+    resetVirtualMethodCaches();
     backing.virtualizeMethods(privateInstanceMethods);
   }
 
@@ -191,7 +223,7 @@
         method -> {
           assert verifyCorrectnessOfMethodHolder(method);
         });
-    assert backing.verifyNoDuplicateMethods();
+    assert backing.verify();
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index b50adf6..427f022 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -4,11 +4,35 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 
 public abstract class MethodCollectionBacking {
 
+  // Internal consistency.
+
+  abstract boolean verify();
+
+  boolean belongsToDirectPool(DexEncodedMethod method) {
+    return method.accessFlags.isStatic()
+        || method.accessFlags.isPrivate()
+        || method.accessFlags.isConstructor();
+  }
+
+  boolean belongsToVirtualPool(DexEncodedMethod method) {
+    return !belongsToDirectPool(method);
+  }
+
+  // Collection methods.
+
+  abstract int size();
+
+  // Traversal methods.
+
   abstract TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn);
 
   void forEachMethod(Consumer<DexEncodedMethod> fn) {
@@ -18,4 +42,58 @@
           return TraversalContinuation.CONTINUE;
         });
   }
+
+  abstract Iterable<DexEncodedMethod> methods();
+
+  abstract List<DexEncodedMethod> directMethods();
+
+  abstract List<DexEncodedMethod> virtualMethods();
+
+  // Lookup methods.
+
+  abstract DexEncodedMethod getMethod(DexMethod method);
+
+  abstract DexEncodedMethod getDirectMethod(DexMethod method);
+
+  abstract DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate);
+
+  abstract DexEncodedMethod getVirtualMethod(DexMethod method);
+
+  abstract DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate);
+
+  // Amendment methods.
+
+  abstract void addMethod(DexEncodedMethod method);
+
+  abstract void addDirectMethod(DexEncodedMethod method);
+
+  abstract void addVirtualMethod(DexEncodedMethod method);
+
+  abstract void addDirectMethods(Collection<DexEncodedMethod> methods);
+
+  abstract void addVirtualMethods(Collection<DexEncodedMethod> methods);
+
+  // Removal methods.
+
+  abstract void removeDirectMethod(DexMethod method);
+
+  // Replacement/mutation methods.
+
+  abstract void setDirectMethods(DexEncodedMethod[] methods);
+
+  abstract void setVirtualMethods(DexEncodedMethod[] methods);
+
+  abstract void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract DexEncodedMethod replaceDirectMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract DexEncodedMethod replaceDirectMethodWithVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
new file mode 100644
index 0000000..8afffc6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -0,0 +1,306 @@
+// 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.graph;
+
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.base.Equivalence.Wrapper;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class MethodMapBacking extends MethodCollectionBacking {
+
+  private Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> methodMap;
+
+  public MethodMapBacking() {
+    this.methodMap = createMap();
+  }
+
+  private static Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> createMap() {
+    // Maintain a linked map so the output order remains a deterministic function of the input.
+    return new Object2ReferenceLinkedOpenHashMap<>();
+  }
+
+  private static Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> createMap(int capacity) {
+    // Maintain a linked map so the output order remains a deterministic function of the input.
+    return new Object2ReferenceLinkedOpenHashMap<>(capacity);
+  }
+
+  private Wrapper<DexMethod> wrap(DexMethod method) {
+    return MethodSignatureEquivalence.get().wrap(method);
+  }
+
+  private void replace(Wrapper<DexMethod> existingKey, DexEncodedMethod method) {
+    if (existingKey.get().match(method)) {
+      methodMap.put(existingKey, method);
+    } else {
+      methodMap.remove(existingKey);
+      methodMap.put(wrap(method.method), method);
+    }
+  }
+
+  private void rehash() {
+    Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> newMap = createMap(methodMap.size());
+    for (DexEncodedMethod method : methodMap.values()) {
+      newMap.put(wrap(method.method), method);
+    }
+    methodMap = newMap;
+  }
+
+  @Override
+  boolean verify() {
+    methodMap.forEach(
+        (key, method) -> {
+          assert key.get().match(method);
+        });
+    return true;
+  }
+
+  @Override
+  int size() {
+    return methodMap.size();
+  }
+
+  @Override
+  TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) {
+    for (Entry<Wrapper<DexMethod>, DexEncodedMethod> entry : methodMap.object2ReferenceEntrySet()) {
+      TraversalContinuation result = fn.apply(entry.getValue());
+      if (result.shouldBreak()) {
+        return result;
+      }
+    }
+    return TraversalContinuation.CONTINUE;
+  }
+
+  @Override
+  Iterable<DexEncodedMethod> methods() {
+    return methodMap.values();
+  }
+
+  @Override
+  List<DexEncodedMethod> directMethods() {
+    List<DexEncodedMethod> methods = new ArrayList<>(size());
+    forEachMethod(
+        method -> {
+          if (belongsToDirectPool(method)) {
+            methods.add(method);
+          }
+        });
+    return methods;
+  }
+
+  @Override
+  List<DexEncodedMethod> virtualMethods() {
+    List<DexEncodedMethod> methods = new ArrayList<>(size());
+    forEachMethod(
+        method -> {
+          if (belongsToVirtualPool(method)) {
+            methods.add(method);
+          }
+        });
+    return methods;
+  }
+
+  @Override
+  DexEncodedMethod getMethod(DexMethod method) {
+    return methodMap.get(wrap(method));
+  }
+
+  private DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) {
+    Box<DexEncodedMethod> found = new Box<>();
+    traverse(
+        method -> {
+          if (predicate.test(method)) {
+            found.set(method);
+            return TraversalContinuation.BREAK;
+          }
+          return TraversalContinuation.CONTINUE;
+        });
+    return found.get();
+  }
+
+  @Override
+  DexEncodedMethod getDirectMethod(DexMethod method) {
+    DexEncodedMethod definition = getMethod(method);
+    return definition != null && belongsToDirectPool(definition) ? definition : null;
+  }
+
+  @Override
+  DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) {
+    Predicate<DexEncodedMethod> isDirect = this::belongsToDirectPool;
+    return getMethod(isDirect.and(predicate));
+  }
+
+  @Override
+  DexEncodedMethod getVirtualMethod(DexMethod method) {
+    DexEncodedMethod definition = getMethod(method);
+    return definition != null && belongsToVirtualPool(definition) ? definition : null;
+  }
+
+  @Override
+  DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) {
+    Predicate<DexEncodedMethod> isVirtual = this::belongsToVirtualPool;
+    return getMethod(isVirtual.and(predicate));
+  }
+
+  @Override
+  void addMethod(DexEncodedMethod method) {
+    Wrapper<DexMethod> key = wrap(method.method);
+    DexEncodedMethod old = methodMap.put(key, method);
+    assert old == null;
+  }
+
+  @Override
+  void addDirectMethod(DexEncodedMethod method) {
+    assert belongsToDirectPool(method);
+    addMethod(method);
+  }
+
+  @Override
+  void addVirtualMethod(DexEncodedMethod method) {
+    assert belongsToVirtualPool(method);
+    addMethod(method);
+  }
+
+  @Override
+  void addDirectMethods(Collection<DexEncodedMethod> methods) {
+    for (DexEncodedMethod method : methods) {
+      addDirectMethod(method);
+    }
+  }
+
+  @Override
+  void addVirtualMethods(Collection<DexEncodedMethod> methods) {
+    for (DexEncodedMethod method : methods) {
+      addVirtualMethod(method);
+    }
+  }
+
+  @Override
+  void removeDirectMethod(DexMethod method) {
+    methodMap.remove(wrap(method));
+  }
+
+  @Override
+  void setDirectMethods(DexEncodedMethod[] methods) {
+    if ((methods == null || methods.length == 0) && methodMap.isEmpty()) {
+      return;
+    }
+    if (methods == null) {
+      methods = DexEncodedMethod.EMPTY_ARRAY;
+    }
+    Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> newMap =
+        createMap(size() + methods.length);
+    forEachMethod(
+        method -> {
+          if (belongsToVirtualPool(method)) {
+            newMap.put(wrap(method.method), method);
+          }
+        });
+    for (DexEncodedMethod method : methods) {
+      assert belongsToDirectPool(method);
+      newMap.put(wrap(method.method), method);
+    }
+    methodMap = newMap;
+  }
+
+  @Override
+  void setVirtualMethods(DexEncodedMethod[] methods) {
+    if ((methods == null || methods.length == 0) && methodMap.isEmpty()) {
+      return;
+    }
+    if (methods == null) {
+      methods = DexEncodedMethod.EMPTY_ARRAY;
+    }
+    Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> newMap =
+        createMap(size() + methods.length);
+    forEachMethod(
+        method -> {
+          if (belongsToDirectPool(method)) {
+            newMap.put(wrap(method.method), method);
+          }
+        });
+    for (DexEncodedMethod method : methods) {
+      assert belongsToVirtualPool(method);
+      newMap.put(wrap(method.method), method);
+    }
+    methodMap = newMap;
+  }
+
+  @Override
+  void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    boolean rehash = false;
+    for (Object2ReferenceMap.Entry<Wrapper<DexMethod>, DexEncodedMethod> entry :
+        methodMap.object2ReferenceEntrySet()) {
+      DexEncodedMethod newMethod = replacement.apply(entry.getValue());
+      if (newMethod != entry.getValue()) {
+        rehash = rehash || newMethod.method != entry.getKey().get();
+        entry.setValue(newMethod);
+      }
+    }
+    if (rehash) {
+      rehash();
+    }
+  }
+
+  @Override
+  void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    replaceMethods(method -> belongsToDirectPool(method) ? replacement.apply(method) : method);
+  }
+
+  @Override
+  void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    replaceMethods(method -> belongsToVirtualPool(method) ? replacement.apply(method) : method);
+  }
+
+  @Override
+  DexEncodedMethod replaceDirectMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    Wrapper<DexMethod> key = wrap(method);
+    DexEncodedMethod existing = methodMap.get(key);
+    if (existing == null || belongsToVirtualPool(existing)) {
+      return null;
+    }
+    DexEncodedMethod newMethod = replacement.apply(existing);
+    assert belongsToDirectPool(newMethod);
+    replace(key, newMethod);
+    return newMethod;
+  }
+
+  @Override
+  DexEncodedMethod replaceDirectMethodWithVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    Wrapper<DexMethod> key = wrap(method);
+    DexEncodedMethod existing = methodMap.get(key);
+    if (existing == null || belongsToVirtualPool(existing)) {
+      return null;
+    }
+    DexEncodedMethod newMethod = replacement.apply(existing);
+    assert belongsToVirtualPool(newMethod);
+    replace(key, newMethod);
+    return newMethod;
+  }
+
+  @Override
+  void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+    // This is a no-op as the virtualizer has modified the encoded method bits.
+    assert verifyVirtualizedMethods(privateInstanceMethods);
+  }
+
+  private boolean verifyVirtualizedMethods(Set<DexEncodedMethod> methods) {
+    for (DexEncodedMethod method : methods) {
+      assert belongsToVirtualPool(method);
+      assert methodMap.get(wrap(method.method)) == method;
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 5c1611d..e8cae8d 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -135,6 +135,10 @@
       return resolvedMethod;
     }
 
+    public DexClassAndMethod getResolutionPair() {
+      return DexClassAndMethod.create(resolvedHolder, resolvedMethod);
+    }
+
     @Override
     public boolean isSingleResolution() {
       return true;
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 2c27763..1e89e13 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -3,14 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.graph.DexValue.DexValueDouble;
-import com.android.tools.r8.graph.DexValue.DexValueFloat;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueLong;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 
 public abstract class UseRegistry {
 
@@ -128,22 +120,27 @@
     // Register bootstrap method arguments.
     // Only Type, MethodHandle, and MethodType need to be registered.
     for (DexValue arg : callSite.bootstrapArgs) {
-      if (arg instanceof DexValueType) {
-        registerTypeReference(((DexValueType) arg).value);
-      } else if (arg instanceof DexValueMethodHandle) {
-        DexMethodHandle handle = ((DexValueMethodHandle) arg).value;
-        MethodHandleUse use = isLambdaMetaFactory
-            ? MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY
-            : MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
-        registerMethodHandle(handle, use);
-      } else if (arg instanceof DexValueMethodType) {
-        registerProto(((DexValueMethodType) arg).value);
-      } else {
-        assert (arg instanceof DexValueInt)
-            || (arg instanceof DexValueLong)
-            || (arg instanceof DexValueFloat)
-            || (arg instanceof DexValueDouble)
-            || (arg instanceof DexValueString);
+      switch (arg.getValueKind()) {
+        case METHOD_HANDLE:
+          DexMethodHandle handle = arg.asDexValueMethodHandle().value;
+          MethodHandleUse use =
+              isLambdaMetaFactory
+                  ? MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY
+                  : MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+          registerMethodHandle(handle, use);
+          break;
+        case METHOD_TYPE:
+          registerProto(arg.asDexValueMethodType().value);
+          break;
+        case TYPE:
+          registerTypeReference(arg.asDexValueType().value);
+          break;
+        default:
+          assert arg.isDexValueInt()
+              || arg.isDexValueLong()
+              || arg.isDexValueFloat()
+              || arg.isDexValueDouble()
+              || arg.isDexValueString();
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index d8d48a2..8d61294 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -15,18 +15,16 @@
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
 import com.android.tools.r8.utils.OptionalBool;
 import java.util.ArrayList;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
 
 public class DesugaredLibraryConversionWrapperAnalysis extends EnqueuerAnalysis
     implements EnqueuerInvokeAnalysis {
 
   private final AppView<?> appView;
   private final DesugaredLibraryAPIConverter converter;
-  private boolean callbackGenerated = false;
-  private Map<DexProgramClass, DexProgramClass> wrappersToReverseMap = null;
+  private Map<DexType, DexProgramClass> synthesizedWrappers = new IdentityHashMap<>();
 
   public DesugaredLibraryConversionWrapperAnalysis(AppView<?> appView) {
     this.appView = appView;
@@ -69,22 +67,18 @@
   }
 
   public List<DexEncodedMethod> generateCallbackMethods() {
-    assert !callbackGenerated;
-    callbackGenerated = true;
     return converter.generateCallbackMethods();
   }
 
-  public Set<DexProgramClass> generateWrappers() {
-    assert wrappersToReverseMap == null;
-    wrappersToReverseMap = converter.synthesizeWrappersAndMapToReverse();
-    return wrappersToReverseMap.keySet();
+  public List<DexProgramClass> generateWrappers() {
+    return converter.synthesizeWrappers(synthesizedWrappers);
   }
 
   // Generate a mock classpath class for all vivified types.
   // Types will be available at runtime in the desugared library dex file.
-  public List<DexClasspathClass> generateWrappersSuperTypeMock() {
+  public List<DexClasspathClass> generateWrappersSuperTypeMock(List<DexProgramClass> wrappers) {
     List<DexClasspathClass> classpathClasses = new ArrayList<>();
-    for (DexProgramClass wrapper : wrappersToReverseMap.keySet()) {
+    for (DexProgramClass wrapper : wrappers) {
       boolean mockIsInterface = wrapper.interfaces.size() == 1;
       DexType mockType = mockIsInterface ? wrapper.interfaces.values[0] : wrapper.superType;
       if (appView.definitionFor(mockType) == null) {
@@ -106,37 +100,4 @@
     }
     return classpathClasses;
   }
-
-  public DesugaredLibraryConversionWrapperAnalysis registerWrite(
-      DexProgramClass wrapper, Consumer<DexEncodedMethod> registration) {
-    registration.accept(getInitializer(wrapper));
-    return this;
-  }
-
-  public DesugaredLibraryConversionWrapperAnalysis registerReads(
-      DexProgramClass wrapper, Consumer<DexEncodedMethod> registration) {
-    // The field of each wrapper is read exclusively in all virtual methods and the reverse wrapper
-    // convert method.
-    for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
-      registration.accept(virtualMethod);
-    }
-    DexProgramClass reverseWrapper = wrappersToReverseMap.get(wrapper);
-    assert reverseWrapper != null;
-    registration.accept(getConvertMethod(reverseWrapper));
-    return this;
-  }
-
-  private DexEncodedMethod getInitializer(DexProgramClass wrapper) {
-    DexEncodedMethod initializer =
-        wrapper.lookupDirectMethod(DexEncodedMethod::isInstanceInitializer);
-    assert initializer != null;
-    return initializer;
-  }
-
-  private DexEncodedMethod getConvertMethod(DexProgramClass wrapper) {
-    DexEncodedMethod convertMethod = wrapper.lookupDirectMethod(DexEncodedMethod::isStatic);
-    assert convertMethod != null;
-    assert convertMethod.method.name == appView.dexItemFactory().convertMethodName;
-    return convertMethod;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 8374410..29d7dcc 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.Timing;
 
 public abstract class EnqueuerAnalysis {
 
@@ -28,7 +29,7 @@
    * Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
    * analysis may enqueue items into the worklist upon the fixpoint using {@param worklist}.
    */
-  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist) {}
+  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {}
 
   /**
    * Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index 7df903b..f32143d 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
@@ -79,7 +79,7 @@
         mapping.getOrDefault(key, guaranteedToBeInitialized);
     mapping.put(
         key,
-        ClassTypeLatticeElement.computeLeastUpperBoundOfClasses(
+        ClassTypeElement.computeLeastUpperBoundOfClasses(
             appInfo, guaranteedToBeInitialized, existingGuaranteedToBeInitialized));
   }
 
diff --git a/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
index 1fba365..105f577 100644
--- a/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface BooleanValueInspector extends ValueInspector {
   boolean getBooleanValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
index 718183e..045476d 100644
--- a/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface ByteValueInspector extends ValueInspector {
   byte getByteValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
index d79ee9c..05d5c2b 100644
--- a/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface CharValueInspector extends ValueInspector {
   char getCharValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
index 334f7ac..e9ac7e1 100644
--- a/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface DoubleValueInspector extends ValueInspector {
   double getDoubleValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
index cb4c12d..a7be463 100644
--- a/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface FloatValueInspector extends ValueInspector {
   float getFloatValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
index 38376c2..6b43ac8 100644
--- a/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface IntValueInspector extends ValueInspector {
   int getIntValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
index 177a885..987721e 100644
--- a/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface LongValueInspector extends ValueInspector {
   long getLongValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
index fb1d823..6b01df3 100644
--- a/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface ShortValueInspector extends ValueInspector {
   short getShortValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
index c0b78c4..5336948 100644
--- a/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface StringValueInspector extends ValueInspector {
   String getStringValue();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 7270d42..7167e72 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.DominatorTree;
@@ -278,7 +278,7 @@
             instruction.isInstanceGet()
                 ? instruction.asInstanceGet().object()
                 : instruction.asInstancePut().object();
-        if (object.getTypeLattice().isNullable()) {
+        if (object.getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -294,7 +294,7 @@
         Query mode,
         AnalysisAssumption assumption) {
       if (assumption == AnalysisAssumption.NONE) {
-        if (instruction.getReceiver().getTypeLattice().isNullable()) {
+        if (instruction.getReceiver().getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -311,7 +311,7 @@
         Query mode,
         AnalysisAssumption assumption) {
       if (assumption == AnalysisAssumption.NONE) {
-        if (instruction.getReceiver().getTypeLattice().isNullable()) {
+        if (instruction.getReceiver().getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -362,7 +362,7 @@
         Query mode,
         AnalysisAssumption assumption) {
       if (assumption == AnalysisAssumption.NONE) {
-        if (instruction.getReceiver().getTypeLattice().isNullable()) {
+        if (instruction.getReceiver().getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -406,7 +406,7 @@
         Query mode,
         AnalysisAssumption assumption) {
       if (assumption == AnalysisAssumption.NONE) {
-        if (instruction.getReceiver().getTypeLattice().isNullable()) {
+        if (instruction.getReceiver().getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -554,9 +554,9 @@
       // implies that the type of the receiver must be initialized.
       if (!method.isStatic()) {
         assert arguments.size() > 0;
-        TypeLatticeElement type = arguments.get(0).getTypeLattice();
+        TypeElement type = arguments.get(0).getType();
         if (type.isClassType()) {
-          enqueue(type.asClassTypeLatticeElement().getClassType(), visited, worklist);
+          enqueue(type.asClassType().getClassType(), visited, worklist);
         }
       }
       // If an invoke to a method succeeds, and the method would have thrown and exception if the
@@ -566,9 +566,9 @@
       if (nonNullParamOrThrowFacts != null) {
         for (int i = 0; i < arguments.size(); i++) {
           if (nonNullParamOrThrowFacts.get(i)) {
-            TypeLatticeElement type = arguments.get(i).getTypeLattice();
+            TypeElement type = arguments.get(i).getType();
             if (type.isClassType()) {
-              enqueue(type.asClassTypeLatticeElement().getClassType(), visited, worklist);
+              enqueue(type.asClassType().getClassType(), visited, worklist);
             }
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index 01a7e3d..f8a621d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -67,15 +67,14 @@
     if (instruction.isReturnVoid()) {
       return true;
     }
-    TypeLatticeElement valueType = instruction.returnValue().getTypeLattice();
-    TypeLatticeElement returnType =
-        TypeLatticeElement.fromDexType(
-            method.method.proto.returnType, Nullability.maybeNull(), appView);
+    TypeElement valueType = instruction.returnValue().getType();
+    TypeElement returnType =
+        TypeElement.fromDexType(method.method.proto.returnType, Nullability.maybeNull(), appView);
     if (isSubtypeOf(valueType, returnType)) {
       return true;
     }
 
-    if (returnType.isClassType() && valueType.isReference()) {
+    if (returnType.isClassType() && valueType.isReferenceType()) {
       // Interface types are treated like Object according to the JVM spec.
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
       DexClass clazz = appView.definitionFor(method.method.proto.returnType);
@@ -91,15 +90,14 @@
 
   public boolean checkFieldPut(FieldInstruction instruction) {
     assert instruction.isFieldPut();
-    TypeLatticeElement valueType = instruction.value().getTypeLattice();
-    TypeLatticeElement fieldType =
-        TypeLatticeElement.fromDexType(
-            instruction.getField().type, valueType.nullability(), appView);
+    TypeElement valueType = instruction.value().getType();
+    TypeElement fieldType =
+        TypeElement.fromDexType(instruction.getField().type, valueType.nullability(), appView);
     if (isSubtypeOf(valueType, fieldType)) {
       return true;
     }
 
-    if (fieldType.isClassType() && valueType.isReference()) {
+    if (fieldType.isClassType() && valueType.isReferenceType()) {
       // Interface types are treated like Object according to the JVM spec.
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
       DexClass clazz = appView.definitionFor(instruction.getField().type);
@@ -110,16 +108,15 @@
   }
 
   public boolean check(Throw instruction) {
-    TypeLatticeElement valueType = instruction.exception().getTypeLattice();
-    TypeLatticeElement throwableType =
-        TypeLatticeElement.fromDexType(
+    TypeElement valueType = instruction.exception().getType();
+    TypeElement throwableType =
+        TypeElement.fromDexType(
             appView.dexItemFactory().throwableType, valueType.nullability(), appView);
     return isSubtypeOf(valueType, throwableType);
   }
 
-  private boolean isSubtypeOf(
-      TypeLatticeElement expectedSubtype, TypeLatticeElement expectedSupertype) {
-    return (expectedSubtype.isNullType() && expectedSupertype.isReference())
+  private boolean isSubtypeOf(TypeElement expectedSubtype, TypeElement expectedSupertype) {
+    return (expectedSubtype.isNullType() && expectedSupertype.isReferenceType())
         || expectedSubtype.lessThanOrEqual(expectedSupertype, appView)
         || expectedSubtype.isBasedOnMissingClass(appView);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
index 7422417..27c4e19 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
@@ -192,7 +192,7 @@
 
     Value outValue = instruction.outValue();
     Value otherOutValue = other.outValue();
-    if (!outValue.getTypeLattice().equals(otherOutValue.getTypeLattice())) {
+    if (!outValue.getType().equals(otherOutValue.getType())) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
index 466edeb..43e484d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
@@ -83,7 +83,7 @@
 
   // Returns the set of instructions where the value of interest can escape from the code.
   private void run(IRCode code, Value valueOfInterest, Predicate<Instruction> stoppingCriterion) {
-    assert valueOfInterest.getTypeLattice().isReference();
+    assert valueOfInterest.getType().isReferenceType();
     assert trackedValues.isEmpty();
     assert valuesToTrack.isEmpty();
 
@@ -153,13 +153,13 @@
       }
       if (couldIntroduceTrackedValueAlias) {
         Value outValue = user.outValue();
-        assert outValue != null && outValue.getTypeLattice().isReference();
+        assert outValue != null && outValue.getType().isReferenceType();
         addToWorklist(outValue);
       }
       // Track propagated values through which the value of interest can escape indirectly.
       Value propagatedValue = getPropagatedSubject(alias, user);
       if (propagatedValue != null && propagatedValue != alias) {
-        assert propagatedValue.getTypeLattice().isReference();
+        assert propagatedValue.getType().isReferenceType();
         addToWorklist(propagatedValue);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index e540a06..3aec803 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
 
 public class InstanceFieldValueAnalysis extends FieldValueAnalysis {
 
@@ -66,6 +67,20 @@
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
+      DexEncodedMethod method,
+      Timing timing) {
+    timing.begin("Analyze instance initializer");
+    InstanceFieldInitializationInfoCollection result =
+        run(appView, code, classInitializerDefaultsResult, feedback, method);
+    timing.end();
+    return result;
+  }
+
+  private static InstanceFieldInitializationInfoCollection run(
+      AppView<?> appView,
+      IRCode code,
+      ClassInitializerDefaultsResult classInitializerDefaultsResult,
+      OptimizationFeedback feedback,
       DexEncodedMethod method) {
     assert appView.appInfo().hasLiveness();
     assert appView.enableWholeProgramOptimizations();
@@ -153,10 +168,9 @@
 
     DexType fieldType = field.field.type;
     if (fieldType.isClassType()) {
-      ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
-      TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
-      TypeLatticeElement staticFieldType =
-          TypeLatticeElement.fromDexType(fieldType, maybeNull(), appView);
+      ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+      TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+      TypeElement staticFieldType = TypeElement.fromDexType(fieldType, maybeNull(), appView);
       if (dynamicLowerBoundType != null || !dynamicUpperBoundType.equals(staticFieldType)) {
         builder.recordInitializationInfo(
             field,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 10dd287..4d57a5e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,9 +12,9 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
 
@@ -46,13 +47,16 @@
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
-      DexEncodedMethod method) {
+      DexEncodedMethod method,
+      Timing timing) {
     assert appView.appInfo().hasLiveness();
     assert appView.enableWholeProgramOptimizations();
     assert method.isClassInitializer();
+    timing.begin("Analyze class initializer");
     DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
     new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
         .computeFieldOptimizationInfo(classInitializerDefaultsResult);
+    timing.end();
   }
 
   @Override
@@ -105,15 +109,15 @@
     }
 
     // Dynamic upper bound type.
-    TypeLatticeElement fieldType =
-        TypeLatticeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
-    TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+    TypeElement fieldType =
+        TypeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
+    TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
     if (dynamicUpperBoundType.strictlyLessThan(fieldType, appView)) {
       feedback.markFieldHasDynamicUpperBoundType(field, dynamicUpperBoundType);
     }
 
     // Dynamic lower bound type.
-    ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+    ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
     if (dynamicLowerBoundType != null) {
       assert dynamicLowerBoundType.lessThanOrEqual(dynamicUpperBoundType, appView);
       feedback.markFieldHasDynamicLowerBoundType(field, dynamicLowerBoundType);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 4de5c39..2a70440 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Sets;
 import java.io.IOException;
@@ -155,13 +156,15 @@
     return removedExtensionFields.contains(field);
   }
 
-  public void postOptimizeGeneratedExtensionRegistry(IRConverter converter) {
+  public void postOptimizeGeneratedExtensionRegistry(IRConverter converter, Timing timing) {
+    timing.begin("[Proto] Post optimize generated extension registry");
     forEachFindLiteExtensionByNumberMethod(
         method ->
             converter.processMethod(
                 method,
                 OptimizationFeedbackIgnore.getInstance(),
                 OneTimeMethodProcessor.getInstance()));
+    timing.end(); // [Proto] Post optimize generated extension registry
   }
 
   private void forEachFindLiteExtensionByNumberMethod(Consumer<DexEncodedMethod> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 8da13ed..feca06c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -13,9 +13,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -217,8 +217,8 @@
       if (!references.methodToInvokeMembers.isNewMutableInstanceEnum(methodToInvokeValue)) {
         continue;
       }
-      ClassTypeLatticeElement receiverType =
-          invoke.getReceiver().getDynamicUpperBoundType(appView).asClassTypeLatticeElement();
+      ClassTypeElement receiverType =
+          invoke.getReceiver().getDynamicUpperBoundType(appView).asClassType();
       if (receiverType != null) {
         AppInfoWithClassHierarchy appInfo = appView.appInfo();
         DexType rawReceiverType = receiverType.getClassType();
@@ -269,9 +269,9 @@
       if (definition.getInvokedMethod() != appView.dexItemFactory().enumMethods.ordinal) {
         return false;
       }
-      TypeLatticeElement enumType = definition.getReceiver().getTypeLattice();
+      TypeElement enumType = definition.getReceiver().getType();
       return enumType.isClassType()
-          && enumType.asClassTypeLatticeElement().getClassType() == references.methodToInvokeType;
+          && enumType.asClassType().getClassType() == references.methodToInvokeType;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index cc7307f..390f2b2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoMessageInfo;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoObject;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -43,8 +44,8 @@
   private final ProtoReferences references;
   private final ThrowingInfo throwingInfo;
 
-  private final TypeLatticeElement objectArrayType;
-  private final TypeLatticeElement stringType;
+  private final TypeElement objectArrayType;
+  private final TypeElement stringType;
 
   public GeneratedMessageLiteShrinker(
       AppView<AppInfoWithLiveness> appView,
@@ -58,9 +59,9 @@
 
     // Types.
     this.objectArrayType =
-        TypeLatticeElement.fromDexType(
+        TypeElement.fromDexType(
             appView.dexItemFactory().objectArrayType, Nullability.definitelyNotNull(), appView);
-    this.stringType = TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull());
+    this.stringType = TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
   }
 
   public void run(DexEncodedMethod method, IRCode code) {
@@ -69,13 +70,15 @@
     }
   }
 
-  public void postOptimizeDynamicMethods(IRConverter converter) {
+  public void postOptimizeDynamicMethods(IRConverter converter, Timing timing) {
+    timing.begin("[Proto] Post optimize dynamic methods");
     forEachDynamicMethod(
         method ->
             converter.processMethod(
                 method,
                 OptimizationFeedbackIgnore.getInstance(),
                 OneTimeMethodProcessor.getInstance()));
+    timing.end();
   }
 
   private void forEachDynamicMethod(Consumer<DexEncodedMethod> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
index 568b822..308ea07 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
@@ -31,8 +31,7 @@
   @Override
   public Instruction buildIR(AppView<?> appView, IRCode code) {
     Value value =
-        code.createValue(
-            TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull()));
+        code.createValue(TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
     ThrowingInfo throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
     if (appView.options().isMinifying()) {
       return new DexItemBasedConstString(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index bf6c8b1..e414f23 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
 import com.android.tools.r8.ir.analysis.proto.RawMessageInfoDecoder;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRCodeUtils;
@@ -37,6 +37,7 @@
 import com.android.tools.r8.shaking.KeepReason;
 import com.android.tools.r8.utils.BitUtils;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
@@ -171,7 +172,8 @@
   }
 
   @Override
-  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist) {
+  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {
+    timing.begin("[Proto] Extend fixpoint");
     populateExtensionGraph(enqueuer);
 
     markMapOrRequiredFieldsAsReachable(enqueuer, worklist);
@@ -187,6 +189,7 @@
         tracePendingInstructionsInDynamicMethods(enqueuer, worklist);
       }
     }
+    timing.end();
   }
 
   /**
@@ -317,17 +320,17 @@
       InvokeMethod invoke = extensionFactory.asInvokeMethod();
       DexMethod invokedMethod = invoke.getInvokedMethod();
 
-      TypeLatticeElement containerType, extensionType;
+      TypeElement containerType, extensionType;
       if (invokedMethod == references.generatedMessageLiteMethods.newRepeatedGeneratedExtension) {
-        containerType = invoke.arguments().get(0).getTypeLattice();
-        extensionType = invoke.arguments().get(1).getTypeLattice();
+        containerType = invoke.arguments().get(0).getType();
+        extensionType = invoke.arguments().get(1).getType();
       } else if (invokedMethod
           == references.generatedMessageLiteMethods.newSingularGeneratedExtension) {
-        containerType = invoke.arguments().get(0).getTypeLattice();
-        extensionType = invoke.arguments().get(2).getTypeLattice();
+        containerType = invoke.arguments().get(0).getType();
+        extensionType = invoke.arguments().get(2).getType();
       } else if (references.generatedExtensionMethods.isConstructor(invokedMethod)) {
-        containerType = invoke.arguments().get(1).getTypeLattice();
-        extensionType = invoke.arguments().get(3).getTypeLattice();
+        containerType = invoke.arguments().get(1).getType();
+        extensionType = invoke.arguments().get(3).getType();
       } else {
         return;
       }
@@ -343,9 +346,8 @@
 
       extensionGraph
           .computeIfAbsent(
-              containerType.asClassTypeLatticeElement().getClassType(),
-              ignore -> Sets.newIdentityHashSet())
-          .add(extensionType.asClassTypeLatticeElement().getClassType());
+              containerType.asClassType().getClassType(), ignore -> Sets.newIdentityHashSet())
+          .add(extensionType.asClassType().getClassType());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromInvokeStatic.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromInvokeStatic.java
index 0812098..8c9bc00 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromInvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromInvokeStatic.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeStatic;
@@ -26,8 +26,7 @@
   public Instruction buildIR(AppView<?> appView, IRCode code) {
     Value value =
         code.createValue(
-            TypeLatticeElement.fromDexType(
-                method.proto.returnType, Nullability.maybeNull(), appView));
+            TypeElement.fromDexType(method.proto.returnType, Nullability.maybeNull(), appView));
     return new InvokeStatic(method, value, ImmutableList.of());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromStaticGet.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromStaticGet.java
index 551466f..874ab9d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromStaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromStaticGet.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -28,8 +28,7 @@
   @Override
   public Instruction buildIR(AppView<?> appView, IRCode code) {
     Value value =
-        code.createValue(
-            TypeLatticeElement.fromDexType(field.type, Nullability.maybeNull(), appView));
+        code.createValue(TypeElement.fromDexType(field.type, Nullability.maybeNull(), appView));
     return new StaticGet(value, field);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
new file mode 100644
index 0000000..4af45c6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
@@ -0,0 +1,184 @@
+// Copyright (c) 2017, 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.ir.analysis.type;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import java.util.Objects;
+import java.util.function.Function;
+
+public class ArrayTypeElement extends ReferenceTypeElement {
+
+  private final TypeElement memberTypeLattice;
+
+  // On-demand link between other nullability-variants.
+  private final NullabilityVariants<ArrayTypeElement> variants;
+
+  public static ArrayTypeElement create(TypeElement memberTypeLattice, Nullability nullability) {
+    return NullabilityVariants.create(
+        nullability, (variants) -> new ArrayTypeElement(memberTypeLattice, nullability, variants));
+  }
+
+  private ArrayTypeElement(
+      TypeElement memberTypeLattice,
+      Nullability nullability,
+      NullabilityVariants<ArrayTypeElement> variants) {
+    super(nullability);
+    assert memberTypeLattice.isPrimitiveType() || memberTypeLattice.nullability().isMaybeNull();
+    this.memberTypeLattice = memberTypeLattice;
+    this.variants = variants;
+  }
+
+  public DexType toDexType(DexItemFactory factory) {
+    TypeElement baseTypeLattice = getBaseType();
+    DexType baseType;
+    if (baseTypeLattice.isPrimitiveType()) {
+      baseType = baseTypeLattice.asPrimitiveType().toDexType(factory);
+    } else {
+      assert baseTypeLattice.isClassType();
+      baseType = baseTypeLattice.asClassType().getClassType();
+    }
+    return factory.createArrayType(getNesting(), baseType);
+  }
+
+  public int getNesting() {
+    int nesting = 1;
+    TypeElement member = getMemberType();
+    while (member.isArrayType()) {
+      ++nesting;
+      member = member.asArrayType().getMemberType();
+    }
+    return nesting;
+  }
+
+  public TypeElement getMemberType() {
+    return memberTypeLattice;
+  }
+
+  public TypeElement getMemberTypeAsValueType() {
+    return memberTypeLattice.isFineGrainedType() ? getInt() : memberTypeLattice;
+  }
+
+  public TypeElement getBaseType() {
+    TypeElement base = getMemberType();
+    while (base.isArrayType()) {
+      base = base.asArrayType().getMemberType();
+    }
+    return base;
+  }
+
+  private ArrayTypeElement createVariant(
+      Nullability nullability, NullabilityVariants<ArrayTypeElement> variants) {
+    assert this.nullability != nullability;
+    return new ArrayTypeElement(memberTypeLattice, nullability, variants);
+  }
+
+  @Override
+  public ReferenceTypeElement getOrCreateVariant(Nullability nullability) {
+    ArrayTypeElement variant = variants.get(nullability);
+    if (variant != null) {
+      return variant;
+    }
+    return variants.getOrCreateElement(nullability, this::createVariant);
+  }
+
+  @Override
+  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
+    return memberTypeLattice.isBasedOnMissingClass(appView);
+  }
+
+  @Override
+  public boolean isArrayType() {
+    return true;
+  }
+
+  @Override
+  public ArrayTypeElement asArrayType() {
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return nullability.toString() + " (" + memberTypeLattice.toString() + "[])";
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof ArrayTypeElement)) {
+      return false;
+    }
+    ArrayTypeElement other = (ArrayTypeElement) o;
+    if (nullability() != other.nullability()) {
+      return false;
+    }
+    return memberTypeLattice.equals(other.memberTypeLattice);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(nullability, memberTypeLattice);
+  }
+
+  @Override
+  public ArrayTypeElement fixupClassTypeReferences(
+      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
+    if (memberTypeLattice.isReferenceType()) {
+      TypeElement substitutedMemberType =
+          memberTypeLattice.fixupClassTypeReferences(mapping, appView);
+      if (substitutedMemberType != memberTypeLattice) {
+        return ArrayTypeElement.create(substitutedMemberType, nullability);
+      }
+    }
+    return this;
+  }
+
+  ReferenceTypeElement join(ArrayTypeElement other, AppView<?> appView) {
+    Nullability nullability = nullability().join(other.nullability());
+    ReferenceTypeElement join =
+        joinMember(this.memberTypeLattice, other.memberTypeLattice, appView, nullability);
+    if (join == null) {
+      // Check if other has the right nullability before creating it.
+      if (other.nullability == nullability) {
+        return other;
+      } else {
+        return getOrCreateVariant(nullability);
+      }
+    } else {
+      assert join.nullability == nullability;
+      return join;
+    }
+  }
+
+  private static ReferenceTypeElement joinMember(
+      TypeElement aMember, TypeElement bMember, AppView<?> appView, Nullability nullability) {
+    if (aMember.equals(bMember)) {
+      // Return null indicating the join is the same as the member to avoid object allocation.
+      return null;
+    }
+    if (aMember.isArrayType() && bMember.isArrayType()) {
+      TypeElement join =
+          joinMember(
+              aMember.asArrayType().memberTypeLattice,
+              bMember.asArrayType().memberTypeLattice,
+              appView,
+              maybeNull());
+      return join == null ? null : ArrayTypeElement.create(join, nullability);
+    }
+    if (aMember.isClassType() && bMember.isClassType()) {
+      ReferenceTypeElement join = aMember.asClassType().join(bMember.asClassType(), appView);
+      return ArrayTypeElement.create(join, nullability);
+    }
+    if (aMember.isPrimitiveType() || bMember.isPrimitiveType()) {
+      return objectClassType(appView, nullability);
+    }
+    return objectArrayType(appView, nullability);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
deleted file mode 100644
index 39ebff8..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (c) 2017, 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.ir.analysis.type;
-
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import java.util.Objects;
-import java.util.function.Function;
-
-public class ArrayTypeLatticeElement extends ReferenceTypeLatticeElement {
-
-  private final TypeLatticeElement memberTypeLattice;
-
-  // On-demand link between other nullability-variants.
-  private final NullabilityVariants<ArrayTypeLatticeElement> variants;
-
-  public static ArrayTypeLatticeElement create(
-      TypeLatticeElement memberTypeLattice, Nullability nullability) {
-    return NullabilityVariants.create(
-        nullability,
-        (variants) -> new ArrayTypeLatticeElement(memberTypeLattice, nullability, variants));
-  }
-
-  private ArrayTypeLatticeElement(
-      TypeLatticeElement memberTypeLattice,
-      Nullability nullability,
-      NullabilityVariants<ArrayTypeLatticeElement> variants) {
-    super(nullability);
-    assert memberTypeLattice.isPrimitive() || memberTypeLattice.nullability().isMaybeNull();
-    this.memberTypeLattice = memberTypeLattice;
-    this.variants = variants;
-  }
-
-  public DexType getArrayType(DexItemFactory factory) {
-    TypeLatticeElement baseTypeLattice = getArrayBaseTypeLattice();
-    DexType baseType;
-    if (baseTypeLattice.isPrimitive()) {
-      baseType = baseTypeLattice.asPrimitiveTypeLatticeElement().toDexType(factory);
-    } else {
-      assert baseTypeLattice.isClassType();
-      baseType = baseTypeLattice.asClassTypeLatticeElement().getClassType();
-    }
-    return factory.createArrayType(getNesting(), baseType);
-  }
-
-  int getNesting() {
-    int nesting = 1;
-    TypeLatticeElement member = getArrayMemberTypeAsMemberType();
-    while (member.isArrayType()) {
-      ++nesting;
-      member = member.asArrayTypeLatticeElement().getArrayMemberTypeAsMemberType();
-    }
-    return nesting;
-  }
-
-  public TypeLatticeElement getArrayMemberTypeAsMemberType() {
-    return memberTypeLattice;
-  }
-
-  public TypeLatticeElement getArrayMemberTypeAsValueType() {
-    return memberTypeLattice.isFineGrainedType() ? getInt() : memberTypeLattice;
-  }
-
-  public TypeLatticeElement getArrayBaseTypeLattice() {
-    TypeLatticeElement base = getArrayMemberTypeAsMemberType();
-    while (base.isArrayType()) {
-      base = base.asArrayTypeLatticeElement().getArrayMemberTypeAsMemberType();
-    }
-    return base;
-  }
-
-  private ArrayTypeLatticeElement createVariant(
-      Nullability nullability, NullabilityVariants<ArrayTypeLatticeElement> variants) {
-    assert this.nullability != nullability;
-    return new ArrayTypeLatticeElement(memberTypeLattice, nullability, variants);
-  }
-
-  @Override
-  public ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability) {
-    ArrayTypeLatticeElement variant = variants.get(nullability);
-    if (variant != null) {
-      return variant;
-    }
-    return variants.getOrCreateElement(nullability, this::createVariant);
-  }
-
-  @Override
-  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
-    return memberTypeLattice.isBasedOnMissingClass(appView);
-  }
-
-  @Override
-  public boolean isArrayType() {
-    return true;
-  }
-
-  @Override
-  public ArrayTypeLatticeElement asArrayTypeLatticeElement() {
-    return this;
-  }
-
-  @Override
-  public String toString() {
-    return nullability.toString() + " (" + memberTypeLattice.toString() + "[])";
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (!(o instanceof ArrayTypeLatticeElement)) {
-      return false;
-    }
-    ArrayTypeLatticeElement other = (ArrayTypeLatticeElement) o;
-    if (nullability() != other.nullability()) {
-      return false;
-    }
-    return memberTypeLattice.equals(other.memberTypeLattice);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(nullability, memberTypeLattice);
-  }
-
-  @Override
-  public ArrayTypeLatticeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
-    if (memberTypeLattice.isReference()) {
-      TypeLatticeElement substitutedMemberType =
-          memberTypeLattice.fixupClassTypeReferences(mapping, appView);
-      if (substitutedMemberType != memberTypeLattice) {
-        return ArrayTypeLatticeElement.create(substitutedMemberType, nullability);
-      }
-    }
-    return this;
-  }
-
-  ReferenceTypeLatticeElement join(ArrayTypeLatticeElement other, AppView<?> appView) {
-    Nullability nullability = nullability().join(other.nullability());
-    ReferenceTypeLatticeElement join =
-        joinMember(this.memberTypeLattice, other.memberTypeLattice, appView, nullability);
-    if (join == null) {
-      // Check if other has the right nullability before creating it.
-      if (other.nullability == nullability) {
-        return other;
-      } else {
-        return getOrCreateVariant(nullability);
-      }
-    } else {
-      assert join.nullability == nullability;
-      return join;
-    }
-  }
-
-  private static ReferenceTypeLatticeElement joinMember(
-      TypeLatticeElement aMember,
-      TypeLatticeElement bMember,
-      AppView<?> appView,
-      Nullability nullability) {
-    if (aMember.equals(bMember)) {
-      // Return null indicating the join is the same as the member to avoid object allocation.
-      return null;
-    }
-    if (aMember.isArrayType() && bMember.isArrayType()) {
-      TypeLatticeElement join =
-          joinMember(
-              aMember.asArrayTypeLatticeElement().memberTypeLattice,
-              bMember.asArrayTypeLatticeElement().memberTypeLattice,
-              appView,
-              maybeNull());
-      return join == null ? null : ArrayTypeLatticeElement.create(join, nullability);
-    }
-    if (aMember.isClassType() && bMember.isClassType()) {
-      ReferenceTypeLatticeElement join =
-          aMember.asClassTypeLatticeElement().join(bMember.asClassTypeLatticeElement(), appView);
-      return ArrayTypeLatticeElement.create(join, nullability);
-    }
-    if (aMember.isPrimitive() || bMember.isPrimitive()) {
-      return objectClassType(appView, nullability);
-    }
-    return objectArrayType(appView, nullability);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java
similarity index 71%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java
index 07e3503..eea19ef 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class BooleanTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
+public class BooleanTypeElement extends SinglePrimitiveTypeElement {
 
-  private static final BooleanTypeLatticeElement INSTANCE = new BooleanTypeLatticeElement();
+  private static final BooleanTypeElement INSTANCE = new BooleanTypeElement();
 
-  static BooleanTypeLatticeElement getInstance() {
+  static BooleanTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeElement.java
similarity index 76%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeElement.java
index 1583714..5ded712 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeElement.java
@@ -3,15 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class BottomTypeLatticeElement extends TypeLatticeElement {
-  private static final BottomTypeLatticeElement INSTANCE = new BottomTypeLatticeElement();
+public class BottomTypeElement extends TypeElement {
+  private static final BottomTypeElement INSTANCE = new BottomTypeElement();
 
   @Override
   public Nullability nullability() {
     return Nullability.bottom();
   }
 
-  static BottomTypeLatticeElement getInstance() {
+  static BottomTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java
index b2df89c..c9e5e9f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class ByteTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
+public class ByteTypeElement extends SinglePrimitiveTypeElement {
 
-  private static final ByteTypeLatticeElement INSTANCE = new ByteTypeLatticeElement();
+  private static final ByteTypeElement INSTANCE = new ByteTypeElement();
 
-  static ByteTypeLatticeElement getInstance() {
+  static ByteTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java
index 3ff532f..0d33bfd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class CharTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
+public class CharTypeElement extends SinglePrimitiveTypeElement {
 
-  private static final CharTypeLatticeElement INSTANCE = new CharTypeLatticeElement();
+  private static final CharTypeElement INSTANCE = new CharTypeElement();
 
-  static CharTypeLatticeElement getInstance() {
+  static CharTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index 0499222..8a47047 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -23,38 +23,37 @@
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-public class ClassTypeLatticeElement extends ReferenceTypeLatticeElement {
+public class ClassTypeElement extends ReferenceTypeElement {
 
   // Least upper bound of interfaces that this class type is implementing.
   // Lazily computed on demand via DexItemFactory, where the canonicalized set will be maintained.
   private Set<DexType> lazyInterfaces;
   private AppView<? extends AppInfoWithSubtyping> appView;
   // On-demand link between other nullability-variants.
-  private final NullabilityVariants<ClassTypeLatticeElement> variants;
+  private final NullabilityVariants<ClassTypeElement> variants;
   private final DexType type;
 
-  public static ClassTypeLatticeElement create(
+  public static ClassTypeElement create(
       DexType classType, Nullability nullability, Set<DexType> interfaces) {
     assert interfaces != null;
     return NullabilityVariants.create(
         nullability,
-        (variants) ->
-            new ClassTypeLatticeElement(classType, nullability, interfaces, variants, null));
+        (variants) -> new ClassTypeElement(classType, nullability, interfaces, variants, null));
   }
 
-  public static ClassTypeLatticeElement create(
+  public static ClassTypeElement create(
       DexType classType, Nullability nullability, AppView<? extends AppInfoWithSubtyping> appView) {
     assert appView != null;
     return NullabilityVariants.create(
         nullability,
-        (variants) -> new ClassTypeLatticeElement(classType, nullability, null, variants, appView));
+        (variants) -> new ClassTypeElement(classType, nullability, null, variants, appView));
   }
 
-  private ClassTypeLatticeElement(
+  private ClassTypeElement(
       DexType classType,
       Nullability nullability,
       Set<DexType> interfaces,
-      NullabilityVariants<ClassTypeLatticeElement> variants,
+      NullabilityVariants<ClassTypeElement> variants,
       AppView<? extends AppInfoWithSubtyping> appView) {
     super(nullability);
     assert classType.isClassType();
@@ -80,20 +79,20 @@
     return lazyInterfaces;
   }
 
-  private ClassTypeLatticeElement createVariant(
-      Nullability nullability, NullabilityVariants<ClassTypeLatticeElement> variants) {
+  private ClassTypeElement createVariant(
+      Nullability nullability, NullabilityVariants<ClassTypeElement> variants) {
     assert this.nullability != nullability;
-    return new ClassTypeLatticeElement(type, nullability, lazyInterfaces, variants, appView);
+    return new ClassTypeElement(type, nullability, lazyInterfaces, variants, appView);
   }
 
-  public boolean isRelatedTo(ClassTypeLatticeElement other, AppView<?> appView) {
+  public boolean isRelatedTo(ClassTypeElement other, AppView<?> appView) {
     return lessThanOrEqualUpToNullability(other, appView)
         || other.lessThanOrEqualUpToNullability(this, appView);
   }
 
   @Override
-  public ClassTypeLatticeElement getOrCreateVariant(Nullability nullability) {
-    ClassTypeLatticeElement variant = variants.get(nullability);
+  public ClassTypeElement getOrCreateVariant(Nullability nullability) {
+    ClassTypeElement variant = variants.get(nullability);
     if (variant != null) {
       return variant;
     }
@@ -114,12 +113,12 @@
   }
 
   @Override
-  public ClassTypeLatticeElement asClassTypeLatticeElement() {
+  public ClassTypeElement asClassType() {
     return this;
   }
 
   @Override
-  public ClassTypeLatticeElement asMeetWithNotNull() {
+  public ClassTypeElement asMeetWithNotNull() {
     return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
   }
 
@@ -145,11 +144,11 @@
   }
 
   @Override
-  public TypeLatticeElement fixupClassTypeReferences(
+  public TypeElement fixupClassTypeReferences(
       Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
     DexType mappedType = mapping.apply(type);
     if (mappedType.isPrimitiveType()) {
-      return PrimitiveTypeLatticeElement.fromDexType(mappedType, false);
+      return PrimitiveTypeElement.fromDexType(mappedType, false);
     }
     if (mappedType != type) {
       return create(mappedType, nullability, appView);
@@ -196,12 +195,12 @@
     return this;
   }
 
-  ClassTypeLatticeElement join(ClassTypeLatticeElement other, AppView<?> appView) {
+  ClassTypeElement join(ClassTypeElement other, AppView<?> appView) {
     Nullability nullability = nullability().join(other.nullability());
     if (!appView.appInfo().hasSubtyping()) {
       assert lazyInterfaces != null && lazyInterfaces.isEmpty();
       assert other.lazyInterfaces != null && other.lazyInterfaces.isEmpty();
-      return ClassTypeLatticeElement.create(
+      return ClassTypeElement.create(
           getClassType() == other.getClassType()
               ? getClassType()
               : appView.dexItemFactory().objectType,
@@ -220,7 +219,7 @@
     if (lubItfs == null) {
       lubItfs = computeLeastUpperBoundOfInterfaces(appView.withSubtyping(), c1lubItfs, c2lubItfs);
     }
-    return ClassTypeLatticeElement.create(lubType, nullability, lubItfs);
+    return ClassTypeElement.create(lubType, nullability, lubItfs);
   }
 
   private enum InterfaceMarker {
@@ -371,10 +370,10 @@
     if (this == o) {
       return true;
     }
-    if (!(o instanceof ClassTypeLatticeElement)) {
+    if (!(o instanceof ClassTypeElement)) {
       return false;
     }
-    ClassTypeLatticeElement other = (ClassTypeLatticeElement) o;
+    ClassTypeElement other = (ClassTypeElement) o;
     if (nullability() != other.nullability()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
index 6449294..3e34256 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
@@ -40,7 +40,7 @@
     Deque<Phi> worklist = new ArrayDeque<>(affectedPhis);
     while (!worklist.isEmpty()) {
       Phi phi = worklist.poll();
-      phi.setTypeLattice(TypeLatticeElement.getBottom());
+      phi.setType(TypeElement.getBottom());
       for (Phi affectedPhi : phi.uniquePhiUsers()) {
         if (affectedPhis.add(affectedPhi)) {
           worklist.add(affectedPhi);
@@ -56,10 +56,10 @@
     worklist.addAll(affectedPhis);
     while (!worklist.isEmpty()) {
       Phi phi = worklist.poll();
-      TypeLatticeElement newType = phi.computePhiType(appView);
-      if (!phi.getTypeLattice().equals(newType)) {
+      TypeElement newType = phi.computePhiType(appView);
+      if (!phi.getType().equals(newType)) {
         assert !newType.isBottom();
-        phi.setTypeLattice(newType);
+        phi.setType(newType);
         worklist.addAll(phi.uniquePhiUsers());
         affectedValues.addAll(phi.affectedValues());
       }
@@ -80,12 +80,12 @@
       for (Value operand : phi.getOperands()) {
         if (operand.isPhi()) {
           Phi operandPhi = operand.asPhi();
-          TypeLatticeElement operandType = operandPhi.getTypeLattice();
+          TypeElement operandType = operandPhi.getType();
           assert !affectedPhis.contains(operandPhi) || operandType.isBottom();
           assert affectedPhis.contains(operandPhi)
-              || operandType.isPrimitive()
+              || operandType.isPrimitiveType()
               || operandType.isNullType()
-              || (operandType.isReference()
+              || (operandType.isReferenceType()
                   && operandType.fixupClassTypeReferences(mapping, appView) == operandType);
         }
       }
@@ -98,9 +98,9 @@
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
       for (Phi phi : block.getPhis()) {
-        TypeLatticeElement phiTypeLattice = phi.getTypeLattice();
-        TypeLatticeElement substituted = phiTypeLattice.fixupClassTypeReferences(mapping, appView);
-        assert substituted == phiTypeLattice || affectedPhis.contains(phi);
+        TypeElement phiType = phi.getType();
+        TypeElement substituted = phiType.fixupClassTypeReferences(mapping, appView);
+        assert substituted == phiType || affectedPhis.contains(phi);
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java
similarity index 69%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java
index 3f521c2..e9632a8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class DoubleTypeLatticeElement extends WidePrimitiveTypeLatticeElement {
+public class DoubleTypeElement extends WidePrimitiveTypeElement {
 
-  private static final DoubleTypeLatticeElement INSTANCE = new DoubleTypeLatticeElement();
+  private static final DoubleTypeElement INSTANCE = new DoubleTypeElement();
 
-  static DoubleTypeLatticeElement getInstance() {
+  static DoubleTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java
similarity index 69%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java
index 69933a0..386981d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class FloatTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
-  private static final FloatTypeLatticeElement INSTANCE = new FloatTypeLatticeElement();
+public class FloatTypeElement extends SinglePrimitiveTypeElement {
+  private static final FloatTypeElement INSTANCE = new FloatTypeElement();
 
-  static FloatTypeLatticeElement getInstance() {
+  static FloatTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java
similarity index 70%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java
index 2d7e5b3..5ecb417 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class IntTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
-  private static final IntTypeLatticeElement INSTANCE = new IntTypeLatticeElement();
+public class IntTypeElement extends SinglePrimitiveTypeElement {
+  private static final IntTypeElement INSTANCE = new IntTypeElement();
 
-  static IntTypeLatticeElement getInstance() {
+  static IntTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java
similarity index 70%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java
index db3ef45..caf2ed3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class LongTypeLatticeElement extends WidePrimitiveTypeLatticeElement {
+public class LongTypeElement extends WidePrimitiveTypeElement {
 
-  private static final LongTypeLatticeElement INSTANCE = new LongTypeLatticeElement();
+  private static final LongTypeElement INSTANCE = new LongTypeElement();
 
-  static LongTypeLatticeElement getInstance() {
+  static LongTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java
index 9a0f95a..2c76787 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java
@@ -7,14 +7,14 @@
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
-public class NullabilityVariants<T extends ReferenceTypeLatticeElement> {
+public class NullabilityVariants<T extends ReferenceTypeElement> {
 
   private T maybeNullVariant;
   private T definitelyNullVariant;
   private T definitelyNotNullVariant;
   private T bottomVariant;
 
-  public static <T extends ReferenceTypeLatticeElement> T create(
+  public static <T extends ReferenceTypeElement> T create(
       Nullability nullability, Function<NullabilityVariants<T>, T> callback) {
     NullabilityVariants<T> variants = new NullabilityVariants<>();
     T newElement = callback.apply(variants);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java
similarity index 68%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java
index 6a16fdc..4e9b5f1a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java
@@ -9,10 +9,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.NumericType;
 
-/**
- * A {@link TypeLatticeElement} that abstracts primitive types.
- */
-public abstract class PrimitiveTypeLatticeElement extends TypeLatticeElement {
+/** A {@link TypeElement} that abstracts primitive types. */
+public abstract class PrimitiveTypeElement extends TypeElement {
 
   @Override
   public Nullability nullability() {
@@ -20,16 +18,16 @@
   }
 
   @Override
-  public boolean isPrimitive() {
+  public boolean isPrimitiveType() {
     return true;
   }
 
   @Override
-  public PrimitiveTypeLatticeElement asPrimitiveTypeLatticeElement() {
+  public PrimitiveTypeElement asPrimitiveType() {
     return this;
   }
 
-  static PrimitiveTypeLatticeElement fromDexType(DexType type, boolean asArrayElementType) {
+  static PrimitiveTypeElement fromDexType(DexType type, boolean asArrayElementType) {
     assert type.isPrimitiveType();
     return fromTypeDescriptorChar((char) type.descriptor.content[0], asArrayElementType);
   }
@@ -73,37 +71,37 @@
         || isDouble();
   }
 
-  private static PrimitiveTypeLatticeElement fromTypeDescriptorChar(
+  private static PrimitiveTypeElement fromTypeDescriptorChar(
       char descriptor, boolean asArrayElementType) {
     switch (descriptor) {
       case 'Z':
         if (asArrayElementType) {
-          return TypeLatticeElement.getBoolean();
+          return TypeElement.getBoolean();
         }
         // fall through
       case 'B':
         if (asArrayElementType) {
-          return TypeLatticeElement.getByte();
+          return TypeElement.getByte();
         }
         // fall through
       case 'S':
         if (asArrayElementType) {
-          return TypeLatticeElement.getShort();
+          return TypeElement.getShort();
         }
         // fall through
       case 'C':
         if (asArrayElementType) {
-          return TypeLatticeElement.getChar();
+          return TypeElement.getChar();
         }
         // fall through
       case 'I':
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case 'F':
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case 'J':
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case 'D':
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       case 'V':
         throw new InternalCompilerError("No value type for void type.");
       default:
@@ -111,40 +109,40 @@
     }
   }
 
-  public static PrimitiveTypeLatticeElement fromNumericType(NumericType numericType) {
+  public static PrimitiveTypeElement fromNumericType(NumericType numericType) {
     switch(numericType) {
       case BYTE:
       case CHAR:
       case SHORT:
       case INT:
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case LONG:
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       default:
         throw new Unreachable("Invalid numeric type '" + numericType + "'");
     }
   }
 
-  TypeLatticeElement join(PrimitiveTypeLatticeElement other) {
+  TypeElement join(PrimitiveTypeElement other) {
     if (this == other) {
       return this;
     }
     if (isSinglePrimitive()) {
       if (other.isSinglePrimitive()) {
-        return TypeLatticeElement.getSingle();
+        return TypeElement.getSingle();
       }
       assert other.isWidePrimitive();
-      return TypeLatticeElement.getTop();
+      return TypeElement.getTop();
     }
     assert isWidePrimitive();
     if (other.isWidePrimitive()) {
-      return TypeLatticeElement.getWide();
+      return TypeElement.getWide();
     }
     assert other.isSinglePrimitive();
-    return TypeLatticeElement.getTop();
+    return TypeElement.getTop();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
new file mode 100644
index 0000000..c150cfd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, 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.ir.analysis.type;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+
+public abstract class ReferenceTypeElement extends TypeElement {
+
+  private static class NullElement extends ReferenceTypeElement {
+
+    NullElement(Nullability nullability) {
+      super(nullability);
+    }
+
+    @Override
+    public ReferenceTypeElement getOrCreateVariant(Nullability nullability) {
+      return nullability.isNullable() ? NULL_INSTANCE : NULL_BOTTOM_INSTANCE;
+    }
+
+    private static NullElement create() {
+      return new NullElement(Nullability.definitelyNull());
+    }
+
+    private static NullElement createBottom() {
+      return new NullElement(Nullability.bottom());
+    }
+
+    @Override
+    public boolean isNullType() {
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return nullability.toString() + " " + DexItemFactory.nullValueType.toString();
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof NullElement)) {
+        return false;
+      }
+      return true;
+    }
+  }
+
+  private static final ReferenceTypeElement NULL_INSTANCE = NullElement.create();
+  private static final ReferenceTypeElement NULL_BOTTOM_INSTANCE = NullElement.createBottom();
+
+  final Nullability nullability;
+
+  ReferenceTypeElement(Nullability nullability) {
+    this.nullability = nullability;
+  }
+
+  @Override
+  public Nullability nullability() {
+    return nullability;
+  }
+
+  static ReferenceTypeElement getNullType() {
+    return NULL_INSTANCE;
+  }
+
+  public abstract ReferenceTypeElement getOrCreateVariant(Nullability nullability);
+
+  public TypeElement asMeetWithNotNull() {
+    return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
+  }
+
+  public TypeElement asDefinitelyNotNull() {
+    return getOrCreateVariant(Nullability.definitelyNotNull());
+  }
+
+  public TypeElement asMaybeNull() {
+    return getOrCreateVariant(Nullability.maybeNull());
+  }
+
+  @Override
+  public boolean isReferenceType() {
+    return true;
+  }
+
+  @Override
+  public ReferenceTypeElement asReferenceType() {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    throw new Unreachable("Should be implemented on each sub type");
+  }
+
+  @Override
+  public int hashCode() {
+    throw new Unreachable("Should be implemented on each sub type");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
deleted file mode 100644
index b888ae5..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright (c) 2018, 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.ir.analysis.type;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexItemFactory;
-
-public abstract class ReferenceTypeLatticeElement extends TypeLatticeElement {
-
-  private static class NullLatticeElement extends ReferenceTypeLatticeElement {
-
-    NullLatticeElement(Nullability nullability) {
-      super(nullability);
-    }
-
-    @Override
-    public ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability) {
-      return nullability.isNullable() ? NULL_INSTANCE : NULL_BOTTOM_INSTANCE;
-    }
-
-    private static NullLatticeElement create() {
-      return new NullLatticeElement(Nullability.definitelyNull());
-    }
-
-    private static NullLatticeElement createBottom() {
-      return new NullLatticeElement(Nullability.bottom());
-    }
-
-    @Override
-    public boolean isNullType() {
-      return true;
-    }
-
-    @Override
-    public String toString() {
-      return nullability.toString() + " " + DexItemFactory.nullValueType.toString();
-    }
-
-    @Override
-    public int hashCode() {
-      return System.identityHashCode(this);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (!(o instanceof NullLatticeElement)) {
-        return false;
-      }
-      return true;
-    }
-  }
-
-  private static final ReferenceTypeLatticeElement NULL_INSTANCE = NullLatticeElement.create();
-  private static final ReferenceTypeLatticeElement NULL_BOTTOM_INSTANCE =
-      NullLatticeElement.createBottom();
-
-  final Nullability nullability;
-
-  ReferenceTypeLatticeElement(Nullability nullability) {
-    this.nullability = nullability;
-  }
-
-  @Override
-  public Nullability nullability() {
-    return nullability;
-  }
-
-  static ReferenceTypeLatticeElement getNullTypeLatticeElement() {
-    return NULL_INSTANCE;
-  }
-
-  public abstract ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability);
-
-  public TypeLatticeElement asMeetWithNotNull() {
-    return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
-  }
-
-  public TypeLatticeElement asDefinitelyNotNull() {
-    return getOrCreateVariant(Nullability.definitelyNotNull());
-  }
-
-  public TypeLatticeElement asMaybeNull() {
-    return getOrCreateVariant(Nullability.maybeNull());
-  }
-
-  @Override
-  public boolean isReference() {
-    return true;
-  }
-
-  @Override
-  public ReferenceTypeLatticeElement asReferenceTypeLatticeElement() {
-    return this;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    throw new Unreachable("Should be implemented on each sub type");
-  }
-
-  @Override
-  public int hashCode() {
-    throw new Unreachable("Should be implemented on each sub type");
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java
index 85bd6fc..61b8514 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class ShortTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
+public class ShortTypeElement extends SinglePrimitiveTypeElement {
 
-  private static final ShortTypeLatticeElement INSTANCE = new ShortTypeLatticeElement();
+  private static final ShortTypeElement INSTANCE = new ShortTypeElement();
 
-  static ShortTypeLatticeElement getInstance() {
+  static ShortTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java
similarity index 61%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java
index 2a94fb5..40436d1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java
@@ -3,17 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-/** A {@link TypeLatticeElement} that abstracts primitive types, which fit in 32 bits. */
-public class SinglePrimitiveTypeLatticeElement extends PrimitiveTypeLatticeElement {
+/** A {@link TypeElement} that abstracts primitive types, which fit in 32 bits. */
+public class SinglePrimitiveTypeElement extends PrimitiveTypeElement {
 
-  private static final SinglePrimitiveTypeLatticeElement INSTANCE =
-      new SinglePrimitiveTypeLatticeElement();
+  private static final SinglePrimitiveTypeElement INSTANCE = new SinglePrimitiveTypeElement();
 
-  SinglePrimitiveTypeLatticeElement() {
+  SinglePrimitiveTypeElement() {
     super();
   }
 
-  static SinglePrimitiveTypeLatticeElement getInstance() {
+  static SinglePrimitiveTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeElement.java
similarity index 77%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeElement.java
index 1d483e8..6c7fec2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeElement.java
@@ -3,15 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class TopTypeLatticeElement extends TypeLatticeElement {
-  private static final TopTypeLatticeElement INSTANCE = new TopTypeLatticeElement();
+public class TopTypeElement extends TypeElement {
+  private static final TopTypeElement INSTANCE = new TopTypeElement();
 
   @Override
   public Nullability nullability() {
     return Nullability.maybeNull();
   }
 
-  static TopTypeLatticeElement getInstance() {
+  static TopTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index 0596d7b..3d1c2b5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -101,7 +101,7 @@
         // The type for Argument, a quasi instruction is already set correctly during IR building.
         // Note that we don't need to enqueue the out value of arguments here because it's constant.
       } else if (instruction.hasInvariantOutType()) {
-        TypeLatticeElement derived = instruction.evaluate(appView);
+        TypeElement derived = instruction.evaluate(appView);
         updateTypeOfValue(outValue, derived);
       } else {
         enqueue(outValue);
@@ -113,18 +113,18 @@
   }
 
   private void analyzeValue(Value value) {
-    TypeLatticeElement previous = value.getTypeLattice();
-    TypeLatticeElement derived =
+    TypeElement previous = value.getType();
+    TypeElement derived =
         value.isPhi() ? value.asPhi().computePhiType(appView) : value.definition.evaluate(appView);
     assert mayHaveImpreciseTypes || derived.isPreciseType();
     assert !previous.isPreciseType() || derived.isPreciseType();
     updateTypeOfValue(value, derived);
   }
 
-  private void updateTypeOfValue(Value value, TypeLatticeElement type) {
+  private void updateTypeOfValue(Value value, TypeElement type) {
     assert mode != Mode.UNSET;
 
-    TypeLatticeElement current = value.getTypeLattice();
+    TypeElement current = value.getType();
     if (current.equals(type)) {
       return;
     }
@@ -158,10 +158,10 @@
   public static DexType getRefinedReceiverType(
       AppView<? extends AppInfoWithSubtyping> appView, InvokeMethodWithReceiver invoke) {
     Value receiver = invoke.getReceiver();
-    TypeLatticeElement lattice = receiver.getDynamicUpperBoundType(appView);
+    TypeElement lattice = receiver.getDynamicUpperBoundType(appView);
     DexType staticReceiverType = invoke.getInvokedMethod().holder;
     if (lattice.isClassType()) {
-      ClassTypeLatticeElement classType = lattice.asClassTypeLatticeElement();
+      ClassTypeElement classType = lattice.asClassType();
       DexType refinedType = classType.getClassType();
       if (refinedType == appView.dexItemFactory().objectType) {
         Set<DexType> interfaces = classType.getInterfaces();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
new file mode 100644
index 0000000..ecc7340
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -0,0 +1,422 @@
+// Copyright (c) 2017, 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.ir.analysis.type;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Value;
+import java.util.function.Function;
+
+/** The base abstraction of lattice elements for local type analysis. */
+public abstract class TypeElement {
+
+  public static BottomTypeElement getBottom() {
+    return BottomTypeElement.getInstance();
+  }
+
+  public static TopTypeElement getTop() {
+    return TopTypeElement.getInstance();
+  }
+
+  public static BooleanTypeElement getBoolean() {
+    return BooleanTypeElement.getInstance();
+  }
+
+  public static ByteTypeElement getByte() {
+    return ByteTypeElement.getInstance();
+  }
+
+  public static ShortTypeElement getShort() {
+    return ShortTypeElement.getInstance();
+  }
+
+  public static CharTypeElement getChar() {
+    return CharTypeElement.getInstance();
+  }
+
+  public static IntTypeElement getInt() {
+    return IntTypeElement.getInstance();
+  }
+
+  public static FloatTypeElement getFloat() {
+    return FloatTypeElement.getInstance();
+  }
+
+  public static SinglePrimitiveTypeElement getSingle() {
+    return SinglePrimitiveTypeElement.getInstance();
+  }
+
+  public static LongTypeElement getLong() {
+    return LongTypeElement.getInstance();
+  }
+
+  public static DoubleTypeElement getDouble() {
+    return DoubleTypeElement.getInstance();
+  }
+
+  public static WidePrimitiveTypeElement getWide() {
+    return WidePrimitiveTypeElement.getInstance();
+  }
+
+  public static ReferenceTypeElement getNull() {
+    return ReferenceTypeElement.getNullType();
+  }
+
+  public TypeElement fixupClassTypeReferences(
+      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
+    return this;
+  }
+
+  public boolean isNullable() {
+    return nullability().isNullable();
+  }
+
+  public abstract Nullability nullability();
+
+  /**
+   * Computes the least upper bound of the current and the other elements.
+   *
+   * @param other {@link TypeElement} to join.
+   * @param appView {@link DexDefinitionSupplier}.
+   * @return {@link TypeElement}, a least upper bound of {@param this} and {@param other}.
+   */
+  public TypeElement join(TypeElement other, AppView<?> appView) {
+    if (this == other) {
+      return this;
+    }
+    if (isBottom()) {
+      return other;
+    }
+    if (other.isBottom()) {
+      return this;
+    }
+    if (isTop() || other.isTop()) {
+      return getTop();
+    }
+    if (isPrimitiveType()) {
+      return other.isPrimitiveType() ? asPrimitiveType().join(other.asPrimitiveType()) : getTop();
+    }
+    if (other.isPrimitiveType()) {
+      // By the above case, !(isPrimitive())
+      return getTop();
+    }
+    // From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
+    assert isReferenceType() && other.isReferenceType();
+    assert isPreciseType() && other.isPreciseType();
+    Nullability nullabilityJoin = nullability().join(other.nullability());
+    if (isNullType()) {
+      return other.asReferenceType().getOrCreateVariant(nullabilityJoin);
+    }
+    if (other.isNullType()) {
+      return this.asReferenceType().getOrCreateVariant(nullabilityJoin);
+    }
+    if (getClass() != other.getClass()) {
+      return objectClassType(appView, nullabilityJoin);
+    }
+    // From now on, getClass() == other.getClass()
+    if (isArrayType()) {
+      assert other.isArrayType();
+      return asArrayType().join(other.asArrayType(), appView);
+    }
+    if (isClassType()) {
+      assert other.isClassType();
+      return asClassType().join(other.asClassType(), appView);
+    }
+    throw new Unreachable("unless a new type lattice is introduced.");
+  }
+
+  public static TypeElement join(Iterable<TypeElement> typeLattices, AppView<?> appView) {
+    TypeElement result = getBottom();
+    for (TypeElement other : typeLattices) {
+      result = result.join(other, appView);
+    }
+    return result;
+  }
+
+  /**
+   * Determines the strict partial order of the given {@link TypeElement}s.
+   *
+   * @param other expected to be *strictly* bigger than {@param this}
+   * @param appView {@link DexDefinitionSupplier} to compute the least upper bound of {@link
+   *     TypeElement}
+   * @return {@code true} if {@param this} is strictly less than {@param other}.
+   */
+  public boolean strictlyLessThan(TypeElement other, AppView<?> appView) {
+    if (equals(other)) {
+      return false;
+    }
+    TypeElement lub = join(other, appView);
+    return !equals(lub) && other.equals(lub);
+  }
+
+  /**
+   * Determines the partial order of the given {@link TypeElement}s.
+   *
+   * @param other expected to be bigger than or equal to {@param this}
+   * @param appView {@link DexDefinitionSupplier} to compute the least upper bound of {@link
+   *     TypeElement}
+   * @return {@code true} if {@param this} is less than or equal to {@param other}.
+   */
+  public boolean lessThanOrEqual(TypeElement other, AppView<?> appView) {
+    return equals(other) || strictlyLessThan(other, appView);
+  }
+
+  /**
+   * Determines if this {@link TypeElement} is less than or equal to the given {@link TypeElement}
+   * up to nullability.
+   *
+   * @param other to check for equality with this
+   * @return {@code true} if {@param this} is equal up to nullability with {@param other}.
+   */
+  public boolean lessThanOrEqualUpToNullability(TypeElement other, AppView<?> appView) {
+    if (this == other) {
+      return true;
+    }
+    if (this.isTop()) {
+      return other.isTop();
+    }
+    if (other.isTop()) {
+      return true;
+    }
+    if (this.isBottom()) {
+      return true;
+    }
+    if (other.isBottom()) {
+      return false;
+    }
+    if (isPrimitiveType()) {
+      // Primitives cannot be nullable.
+      return lessThanOrEqual(other, appView);
+    }
+    assert isReferenceType() && other.isReferenceType();
+    ReferenceTypeElement otherAsNullable =
+        other.isNullable()
+            ? other.asReferenceType()
+            : other.asReferenceType().getOrCreateVariant(Nullability.maybeNull());
+    return lessThanOrEqual(otherAsNullable, appView);
+  }
+
+  /**
+   * Determines if the {@link TypeElement}s are equal up to nullability.
+   *
+   * @param other to check for equality with this
+   * @return {@code true} if {@param this} is equal up to nullability with {@param other}.
+   */
+  public boolean equalUpToNullability(TypeElement other) {
+    if (this == other) {
+      return true;
+    }
+    if (isPrimitiveType() || other.isPrimitiveType()) {
+      return false;
+    }
+    assert isReferenceType() && other.isReferenceType();
+    ReferenceTypeElement thisAsMaybeNull =
+        this.asReferenceType().getOrCreateVariant(Nullability.maybeNull());
+    ReferenceTypeElement otherAsMaybeNull =
+        other.asReferenceType().getOrCreateVariant(Nullability.maybeNull());
+    return thisAsMaybeNull.equals(otherAsMaybeNull);
+  }
+
+  /**
+   * Determines if this type is based on a missing class, directly or indirectly.
+   *
+   * @return {@code} true if this type is based on a missing class.
+   * @param appView
+   */
+  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
+    return false;
+  }
+
+  /**
+   * Represents a type that can be everything.
+   *
+   * @return {@code true} if the corresponding {@link Value} could be any kinds.
+   */
+  public boolean isTop() {
+    return false;
+  }
+
+  /**
+   * Represents an empty type.
+   *
+   * @return {@code true} if the type of corresponding {@link Value} is not determined yet.
+   */
+  public boolean isBottom() {
+    return false;
+  }
+
+  public boolean isReferenceType() {
+    return false;
+  }
+
+  public ReferenceTypeElement asReferenceType() {
+    return null;
+  }
+
+  public boolean isArrayType() {
+    return false;
+  }
+
+  public ArrayTypeElement asArrayType() {
+    return null;
+  }
+
+  public boolean isClassType() {
+    return false;
+  }
+
+  public ClassTypeElement asClassType() {
+    return null;
+  }
+
+  public boolean isPrimitiveType() {
+    return false;
+  }
+
+  public PrimitiveTypeElement asPrimitiveType() {
+    return null;
+  }
+
+  public boolean isSinglePrimitive() {
+    return false;
+  }
+
+  public boolean isWidePrimitive() {
+    return false;
+  }
+
+  boolean isBoolean() {
+    return false;
+  }
+
+  boolean isByte() {
+    return false;
+  }
+
+  boolean isShort() {
+    return false;
+  }
+
+  boolean isChar() {
+    return false;
+  }
+
+  public boolean isInt() {
+    return false;
+  }
+
+  public boolean isFloat() {
+    return false;
+  }
+
+  public boolean isLong() {
+    return false;
+  }
+
+  public boolean isDouble() {
+    return false;
+  }
+
+  public boolean isPreciseType() {
+    return isArrayType()
+        || isClassType()
+        || isNullType()
+        || isInt()
+        || isFloat()
+        || isLong()
+        || isDouble()
+        || isBottom();
+  }
+
+  public boolean isFineGrainedType() {
+    return isBoolean()
+        || isByte()
+        || isShort()
+        || isChar();
+  }
+
+  /**
+   * Determines if this type only includes null values that are defined by a const-number
+   * instruction in the same enclosing method.
+   *
+   * These null values can be assigned to any type.
+   */
+  public boolean isNullType() {
+    return false;
+  }
+
+  /**
+   * Determines if this type only includes null values.
+   *
+   * These null values cannot be assigned to any type. For example, it is a type error to "throw v"
+   * where the value `v` satisfies isDefinitelyNull(), because the static type of `v` may not be a
+   * subtype of Throwable.
+   */
+  public boolean isDefinitelyNull() {
+    return nullability().isDefinitelyNull();
+  }
+
+  public boolean isDefinitelyNotNull() {
+    return nullability().isDefinitelyNotNull();
+  }
+
+  public int requiredRegisters() {
+    assert !isBottom() && !isTop();
+    return 1;
+  }
+
+  public static ClassTypeElement objectClassType(AppView<?> appView, Nullability nullability) {
+    return fromDexType(appView.dexItemFactory().objectType, nullability, appView).asClassType();
+  }
+
+  static ArrayTypeElement objectArrayType(AppView<?> appView, Nullability nullability) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return fromDexType(
+            dexItemFactory.createArrayType(1, dexItemFactory.objectType), nullability, appView)
+        .asArrayType();
+  }
+
+  public static ClassTypeElement classClassType(AppView<?> appView, Nullability nullability) {
+    return fromDexType(appView.dexItemFactory().classType, nullability, appView).asClassType();
+  }
+
+  public static ClassTypeElement stringClassType(AppView<?> appView, Nullability nullability) {
+    return fromDexType(appView.dexItemFactory().stringType, nullability, appView).asClassType();
+  }
+
+  public static TypeElement fromDexType(DexType type, Nullability nullability, AppView<?> appView) {
+    return fromDexType(type, nullability, appView, false);
+  }
+
+  public static TypeElement fromDexType(
+      DexType type, Nullability nullability, AppView<?> appView, boolean asArrayElementType) {
+    if (type == DexItemFactory.nullValueType) {
+      assert !nullability.isDefinitelyNotNull();
+      return getNull();
+    }
+    if (type.isPrimitiveType()) {
+      return PrimitiveTypeElement.fromDexType(type, asArrayElementType);
+    }
+    return appView.dexItemFactory().createReferenceTypeElement(type, nullability, appView);
+  }
+
+  public boolean isValueTypeCompatible(TypeElement other) {
+    return (isReferenceType() && other.isReferenceType())
+        || (isSinglePrimitive() && other.isSinglePrimitive())
+        || (isWidePrimitive() && other.isWidePrimitive());
+  }
+
+  @Override
+  public abstract String toString();
+
+  @Override
+  public abstract boolean equals(Object o);
+
+  @Override
+  public abstract int hashCode();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
deleted file mode 100644
index de7b894..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ /dev/null
@@ -1,434 +0,0 @@
-// Copyright (c) 2017, 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.ir.analysis.type;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Value;
-import java.util.function.Function;
-
-/**
- * The base abstraction of lattice elements for local type analysis.
- */
-public abstract class TypeLatticeElement {
-
-  public static BottomTypeLatticeElement getBottom() {
-    return BottomTypeLatticeElement.getInstance();
-  }
-
-  public static TopTypeLatticeElement getTop() {
-    return TopTypeLatticeElement.getInstance();
-  }
-
-  public static BooleanTypeLatticeElement getBoolean() {
-    return BooleanTypeLatticeElement.getInstance();
-  }
-
-  public static ByteTypeLatticeElement getByte() {
-    return ByteTypeLatticeElement.getInstance();
-  }
-
-  public static ShortTypeLatticeElement getShort() {
-    return ShortTypeLatticeElement.getInstance();
-  }
-
-  public static CharTypeLatticeElement getChar() {
-    return CharTypeLatticeElement.getInstance();
-  }
-
-  public static IntTypeLatticeElement getInt() {
-    return IntTypeLatticeElement.getInstance();
-  }
-
-  public static FloatTypeLatticeElement getFloat() {
-    return FloatTypeLatticeElement.getInstance();
-  }
-
-  public static SinglePrimitiveTypeLatticeElement getSingle() {
-    return SinglePrimitiveTypeLatticeElement.getInstance();
-  }
-
-  public static LongTypeLatticeElement getLong() {
-    return LongTypeLatticeElement.getInstance();
-  }
-
-  public static DoubleTypeLatticeElement getDouble() {
-    return DoubleTypeLatticeElement.getInstance();
-  }
-
-  public static WidePrimitiveTypeLatticeElement getWide() {
-    return WidePrimitiveTypeLatticeElement.getInstance();
-  }
-
-  public static ReferenceTypeLatticeElement getNull() {
-    return ReferenceTypeLatticeElement.getNullTypeLatticeElement();
-  }
-
-  public TypeLatticeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
-    return this;
-  }
-
-  public boolean isNullable() {
-    return nullability().isNullable();
-  }
-
-  public abstract Nullability nullability();
-
-  /**
-   * Computes the least upper bound of the current and the other elements.
-   *
-   * @param other {@link TypeLatticeElement} to join.
-   * @param appView {@link DexDefinitionSupplier}.
-   * @return {@link TypeLatticeElement}, a least upper bound of {@param this} and {@param other}.
-   */
-  public TypeLatticeElement join(TypeLatticeElement other, AppView<?> appView) {
-    if (this == other) {
-      return this;
-    }
-    if (isBottom()) {
-      return other;
-    }
-    if (other.isBottom()) {
-      return this;
-    }
-    if (isTop() || other.isTop()) {
-      return getTop();
-    }
-    if (isPrimitive()) {
-      return other.isPrimitive()
-          ? asPrimitiveTypeLatticeElement().join(other.asPrimitiveTypeLatticeElement())
-          : getTop();
-    }
-    if (other.isPrimitive()) {
-      // By the above case, !(isPrimitive())
-      return getTop();
-    }
-    // From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
-    assert isReference() && other.isReference();
-    assert isPreciseType() && other.isPreciseType();
-    Nullability nullabilityJoin = nullability().join(other.nullability());
-    if (isNullType()) {
-      return other.asReferenceTypeLatticeElement().getOrCreateVariant(nullabilityJoin);
-    }
-    if (other.isNullType()) {
-      return this.asReferenceTypeLatticeElement().getOrCreateVariant(nullabilityJoin);
-    }
-    if (getClass() != other.getClass()) {
-      return objectClassType(appView, nullabilityJoin);
-    }
-    // From now on, getClass() == other.getClass()
-    if (isArrayType()) {
-      assert other.isArrayType();
-      return asArrayTypeLatticeElement().join(other.asArrayTypeLatticeElement(), appView);
-    }
-    if (isClassType()) {
-      assert other.isClassType();
-      return asClassTypeLatticeElement().join(other.asClassTypeLatticeElement(), appView);
-    }
-    throw new Unreachable("unless a new type lattice is introduced.");
-  }
-
-  public static TypeLatticeElement join(
-      Iterable<TypeLatticeElement> typeLattices, AppView<?> appView) {
-    TypeLatticeElement result = getBottom();
-    for (TypeLatticeElement other : typeLattices) {
-      result = result.join(other, appView);
-    }
-    return result;
-  }
-
-  /**
-   * Determines the strict partial order of the given {@link TypeLatticeElement}s.
-   *
-   * @param other expected to be *strictly* bigger than {@param this}
-   * @param appView {@link DexDefinitionSupplier} to compute the least upper bound of {@link
-   *     TypeLatticeElement}
-   * @return {@code true} if {@param this} is strictly less than {@param other}.
-   */
-  public boolean strictlyLessThan(TypeLatticeElement other, AppView<?> appView) {
-    if (equals(other)) {
-      return false;
-    }
-    TypeLatticeElement lub = join(other, appView);
-    return !equals(lub) && other.equals(lub);
-  }
-
-  /**
-   * Determines the partial order of the given {@link TypeLatticeElement}s.
-   *
-   * @param other expected to be bigger than or equal to {@param this}
-   * @param appView {@link DexDefinitionSupplier} to compute the least upper bound of {@link
-   *     TypeLatticeElement}
-   * @return {@code true} if {@param this} is less than or equal to {@param other}.
-   */
-  public boolean lessThanOrEqual(TypeLatticeElement other, AppView<?> appView) {
-    return equals(other) || strictlyLessThan(other, appView);
-  }
-
-  /**
-   * Determines if this {@link TypeLatticeElement} is less than or equal to the given {@link
-   * TypeLatticeElement} up to nullability.
-   *
-   * @param other to check for equality with this
-   * @return {@code true} if {@param this} is equal up to nullability with {@param other}.
-   */
-  public boolean lessThanOrEqualUpToNullability(TypeLatticeElement other, AppView<?> appView) {
-    if (this == other) {
-      return true;
-    }
-    if (this.isTop()) {
-      return other.isTop();
-    }
-    if (other.isTop()) {
-      return true;
-    }
-    if (this.isBottom()) {
-      return true;
-    }
-    if (other.isBottom()) {
-      return false;
-    }
-    if (isPrimitive()) {
-      // Primitives cannot be nullable.
-      return lessThanOrEqual(other, appView);
-    }
-    assert isReference() && other.isReference();
-    ReferenceTypeLatticeElement otherAsNullable =
-        other.isNullable()
-            ? other.asReferenceTypeLatticeElement()
-            : other.asReferenceTypeLatticeElement().getOrCreateVariant(Nullability.maybeNull());
-    return lessThanOrEqual(otherAsNullable, appView);
-  }
-
-  /**
-   * Determines if the {@link TypeLatticeElement}s are equal up to nullability.
-   *
-   * @param other to check for equality with this
-   * @return {@code true} if {@param this} is equal up to nullability with {@param other}.
-   */
-  public boolean equalUpToNullability(TypeLatticeElement other) {
-    if (this == other) {
-      return true;
-    }
-    if (isPrimitive() || other.isPrimitive()) {
-      return false;
-    }
-    assert isReference() && other.isReference();
-    ReferenceTypeLatticeElement thisAsMaybeNull =
-        this.asReferenceTypeLatticeElement().getOrCreateVariant(Nullability.maybeNull());
-    ReferenceTypeLatticeElement otherAsMaybeNull =
-        other.asReferenceTypeLatticeElement().getOrCreateVariant(Nullability.maybeNull());
-    return thisAsMaybeNull.equals(otherAsMaybeNull);
-  }
-
-  /**
-   * Determines if this type is based on a missing class, directly or indirectly.
-   *
-   * @return {@code} true if this type is based on a missing class.
-   * @param appView
-   */
-  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
-    return false;
-  }
-
-  /**
-   * Represents a type that can be everything.
-   *
-   * @return {@code true} if the corresponding {@link Value} could be any kinds.
-   */
-  public boolean isTop() {
-    return false;
-  }
-
-  /**
-   * Represents an empty type.
-   *
-   * @return {@code true} if the type of corresponding {@link Value} is not determined yet.
-   */
-  public boolean isBottom() {
-    return false;
-  }
-
-  public boolean isReference() {
-    return false;
-  }
-
-  public ReferenceTypeLatticeElement asReferenceTypeLatticeElement() {
-    return null;
-  }
-
-  public boolean isArrayType() {
-    return false;
-  }
-
-  public ArrayTypeLatticeElement asArrayTypeLatticeElement() {
-    return null;
-  }
-
-  public boolean isClassType() {
-    return false;
-  }
-
-  public ClassTypeLatticeElement asClassTypeLatticeElement() {
-    return null;
-  }
-
-  public boolean isPrimitive() {
-    return false;
-  }
-
-  public PrimitiveTypeLatticeElement asPrimitiveTypeLatticeElement() {
-    return null;
-  }
-
-  public boolean isSinglePrimitive() {
-    return false;
-  }
-
-  public boolean isWidePrimitive() {
-    return false;
-  }
-
-  boolean isBoolean() {
-    return false;
-  }
-
-  boolean isByte() {
-    return false;
-  }
-
-  boolean isShort() {
-    return false;
-  }
-
-  boolean isChar() {
-    return false;
-  }
-
-  public boolean isInt() {
-    return false;
-  }
-
-  public boolean isFloat() {
-    return false;
-  }
-
-  public boolean isLong() {
-    return false;
-  }
-
-  public boolean isDouble() {
-    return false;
-  }
-
-  public boolean isPreciseType() {
-    return isArrayType()
-        || isClassType()
-        || isNullType()
-        || isInt()
-        || isFloat()
-        || isLong()
-        || isDouble()
-        || isBottom();
-  }
-
-  public boolean isFineGrainedType() {
-    return isBoolean()
-        || isByte()
-        || isShort()
-        || isChar();
-  }
-
-  /**
-   * Determines if this type only includes null values that are defined by a const-number
-   * instruction in the same enclosing method.
-   *
-   * These null values can be assigned to any type.
-   */
-  public boolean isNullType() {
-    return false;
-  }
-
-  /**
-   * Determines if this type only includes null values.
-   *
-   * These null values cannot be assigned to any type. For example, it is a type error to "throw v"
-   * where the value `v` satisfies isDefinitelyNull(), because the static type of `v` may not be a
-   * subtype of Throwable.
-   */
-  public boolean isDefinitelyNull() {
-    return nullability().isDefinitelyNull();
-  }
-
-  public boolean isDefinitelyNotNull() {
-    return nullability().isDefinitelyNotNull();
-  }
-
-  public int requiredRegisters() {
-    assert !isBottom() && !isTop();
-    return 1;
-  }
-
-  public static ClassTypeLatticeElement objectClassType(
-      AppView<?> appView, Nullability nullability) {
-    return fromDexType(appView.dexItemFactory().objectType, nullability, appView)
-        .asClassTypeLatticeElement();
-  }
-
-  static ArrayTypeLatticeElement objectArrayType(AppView<?> appView, Nullability nullability) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    return fromDexType(
-            dexItemFactory.createArrayType(1, dexItemFactory.objectType), nullability, appView)
-        .asArrayTypeLatticeElement();
-  }
-
-  public static ClassTypeLatticeElement classClassType(
-      AppView<?> appView, Nullability nullability) {
-    return fromDexType(appView.dexItemFactory().classType, nullability, appView)
-        .asClassTypeLatticeElement();
-  }
-
-  public static ClassTypeLatticeElement stringClassType(
-      AppView<?> appView, Nullability nullability) {
-    return fromDexType(appView.dexItemFactory().stringType, nullability, appView)
-        .asClassTypeLatticeElement();
-  }
-
-  public static TypeLatticeElement fromDexType(
-      DexType type, Nullability nullability, AppView<?> appView) {
-    return fromDexType(type, nullability, appView, false);
-  }
-
-  public static TypeLatticeElement fromDexType(
-      DexType type, Nullability nullability, AppView<?> appView, boolean asArrayElementType) {
-    if (type == DexItemFactory.nullValueType) {
-      assert !nullability.isDefinitelyNotNull();
-      return getNull();
-    }
-    if (type.isPrimitiveType()) {
-      return PrimitiveTypeLatticeElement.fromDexType(type, asArrayElementType);
-    }
-    return appView.dexItemFactory().createReferenceTypeLatticeElement(type, nullability, appView);
-  }
-
-  public boolean isValueTypeCompatible(TypeLatticeElement other) {
-    return (isReference() && other.isReference())
-        || (isSinglePrimitive() && other.isSinglePrimitive())
-        || (isWidePrimitive() && other.isWidePrimitive());
-  }
-
-  @Override
-  public abstract String toString();
-
-  @Override
-  public abstract boolean equals(Object o);
-
-  @Override
-  public abstract int hashCode();
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java
index 912b379..02459a7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java
@@ -8,8 +8,8 @@
 
 public class TypeUtils {
 
-  public static boolean isNullPointerException(TypeLatticeElement type, AppView<?> appView) {
+  public static boolean isNullPointerException(TypeElement type, AppView<?> appView) {
     return type.isClassType()
-        && type.asClassTypeLatticeElement().getClassType() == appView.dexItemFactory().npeType;
+        && type.asClassType().getClassType() == appView.dexItemFactory().npeType;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java
similarity index 64%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java
index 3e07e54..0ae7426 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java
@@ -3,17 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-/** A {@link TypeLatticeElement} that abstracts primitive types, which fit in 64 bits. */
-public class WidePrimitiveTypeLatticeElement extends PrimitiveTypeLatticeElement {
+/** A {@link TypeElement} that abstracts primitive types, which fit in 64 bits. */
+public class WidePrimitiveTypeElement extends PrimitiveTypeElement {
 
-  private static final WidePrimitiveTypeLatticeElement INSTANCE =
-      new WidePrimitiveTypeLatticeElement();
+  private static final WidePrimitiveTypeElement INSTANCE = new WidePrimitiveTypeElement();
 
-  WidePrimitiveTypeLatticeElement() {
+  WidePrimitiveTypeElement() {
     super();
   }
 
-  static WidePrimitiveTypeLatticeElement getInstance() {
+  static WidePrimitiveTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index 897b31e..34a83d1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.classClassType;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.classClassType;
 import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -63,13 +63,11 @@
   @Override
   public Instruction createMaterializingInstruction(
       AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
-    TypeLatticeElement typeLattice = info.getTypeLattice();
+    TypeElement typeLattice = info.getOutType();
     DebugLocalInfo debugLocalInfo = info.getLocalInfo();
     assert typeLattice.isClassType();
     assert appView
-        .isSubtype(
-            appView.dexItemFactory().classType,
-            typeLattice.asClassTypeLatticeElement().getClassType())
+        .isSubtype(appView.dexItemFactory().classType, typeLattice.asClassType().getClassType())
         .isTrue();
     Value returnedValue =
         code.createValue(classClassType(appView, definitelyNotNull()), debugLocalInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 5479737..ef38879 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -14,8 +14,8 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -39,9 +39,8 @@
   public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(AppView<AppInfoWithLiveness> appView) {
     DexType fieldType = field.type;
     if (fieldType.isClassType()) {
-      ClassTypeLatticeElement fieldClassType =
-          TypeLatticeElement.fromDexType(fieldType, maybeNull(), appView)
-              .asClassTypeLatticeElement();
+      ClassTypeElement fieldClassType =
+          TypeElement.fromDexType(fieldType, maybeNull(), appView).asClassType();
       return appView.appInfo().mayHaveFinalizeMethodDirectlyOrIndirectly(fieldClassType);
     }
     assert fieldType.isArrayType() || fieldType.isPrimitiveType();
@@ -76,8 +75,8 @@
   @Override
   public Instruction createMaterializingInstruction(
       AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
-    TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
-    assert type.lessThanOrEqual(info.getTypeLattice(), appView);
+    TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
+    assert type.lessThanOrEqual(info.getOutType(), appView);
     Value outValue = code.createValue(type, info.getLocalInfo());
     return new StaticGet(outValue, field);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index b408c90..e2f12fe 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -68,12 +68,12 @@
   @Override
   public Instruction createMaterializingInstruction(
       AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
-    TypeLatticeElement typeLattice = info.getTypeLattice();
+    TypeElement typeLattice = info.getOutType();
     DebugLocalInfo debugLocalInfo = info.getLocalInfo();
-    assert !typeLattice.isReference() || value == 0;
+    assert !typeLattice.isReferenceType() || value == 0;
     Value returnedValue =
         code.createValue(
-            typeLattice.isReference() ? TypeLatticeElement.getNull() : typeLattice, debugLocalInfo);
+            typeLattice.isReferenceType() ? TypeElement.getNull() : typeLattice, debugLocalInfo);
     return new ConstNumber(returnedValue, value);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index a7439c0..1280264 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.stringClassType;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.stringClassType;
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
@@ -65,13 +65,11 @@
       AppView<? extends AppInfoWithSubtyping> appView,
       IRCode code,
       TypeAndLocalInfoSupplier info) {
-    TypeLatticeElement typeLattice = info.getTypeLattice();
+    TypeElement typeLattice = info.getOutType();
     DebugLocalInfo debugLocalInfo = info.getLocalInfo();
     assert typeLattice.isClassType();
     assert appView
-        .isSubtype(
-            appView.dexItemFactory().stringType,
-            typeLattice.asClassTypeLatticeElement().getClassType())
+        .isSubtype(appView.dexItemFactory().stringType, typeLattice.asClassType().getClassType())
         .isTrue();
     Value returnedValue =
         code.createValue(stringClassType(appView, definitelyNotNull()), debugLocalInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 1db2ba6..2ace686 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -121,8 +121,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return outValue.getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return outValue.getType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index 5d97e99..bf545a0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -140,20 +140,20 @@
       ConstNumber newConst;
       if (type == NumericType.INT) {
         int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
-        Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getInt(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else if (type == NumericType.LONG) {
         long result = foldLongs(leftConst.getLongValue(), rightConst.getLongValue());
-        Value value = code.createValue(TypeLatticeElement.getLong(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getLong(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else if (type == NumericType.FLOAT) {
         float result = foldFloat(leftConst.getFloatValue(), rightConst.getFloatValue());
-        Value value = code.createValue(TypeLatticeElement.getFloat(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getFloat(), getLocalInfo());
         newConst = new ConstNumber(value, Float.floatToIntBits(result));
       } else {
         assert type == NumericType.DOUBLE;
         double result = foldDouble(leftConst.getDoubleValue(), rightConst.getDoubleValue());
-        Value value = code.createValue(TypeLatticeElement.getDouble(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getDouble(), getLocalInfo());
         newConst = new ConstNumber(value, Double.doubleToLongBits(result));
       }
       return new ConstLatticeElement(newConst);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java b/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
new file mode 100644
index 0000000..14ac116
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
@@ -0,0 +1,38 @@
+// 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.ir.code;
+
+import java.util.List;
+
+public abstract class ArrayAccess extends Instruction implements ImpreciseMemberTypeInstruction {
+
+  // Input values are ordered according to the stack order of the Java bytecodes.
+  private static final int ARRAY_INDEX = 0;
+  private static final int INDEX_INDEX = 1;
+
+  ArrayAccess(Value outValue, List<? extends Value> inValues) {
+    super(outValue, inValues);
+  }
+
+  public Value array() {
+    return inValues.get(ARRAY_INDEX);
+  }
+
+  public Value index() {
+    return inValues.get(INDEX_INDEX);
+  }
+
+  @Override
+  public boolean isArrayAccess() {
+    return true;
+  }
+
+  @Override
+  public ArrayAccess asArrayAccess() {
+    return this;
+  }
+
+  public abstract ArrayAccess withMemberType(MemberType newMemberType);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 17dcc5e..432cf11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -19,8 +19,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
@@ -30,7 +30,7 @@
 import java.util.Arrays;
 import java.util.Set;
 
-public class ArrayGet extends Instruction implements ImpreciseMemberTypeInstruction {
+public class ArrayGet extends ArrayAccess {
 
   private MemberType type;
 
@@ -53,14 +53,6 @@
     return outValue;
   }
 
-  public Value array() {
-    return inValues.get(0);
-  }
-
-  public Value index() {
-    return inValues.get(1);
-  }
-
   @Override
   public MemberType getMemberType() {
     return type;
@@ -68,9 +60,9 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     assert outValue != null;
-    return outValue.getTypeLattice().isReference();
+    return outValue.getType().isReferenceType();
   }
 
   @Override
@@ -94,13 +86,12 @@
         instruction = new AgetObject(dest, array, index);
         break;
       case BOOLEAN_OR_BYTE:
-        ArrayTypeLatticeElement arrayType = array().getTypeLattice().asArrayTypeLatticeElement();
-        if (arrayType != null
-            && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getBoolean()) {
+        ArrayTypeElement arrayType = array().getType().asArrayType();
+        if (arrayType != null && arrayType.getMemberType() == TypeElement.getBoolean()) {
           instruction = new AgetBoolean(dest, array, index);
         } else {
-          assert array().getTypeLattice().isDefinitelyNull()
-              || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getByte();
+          assert array().getType().isDefinitelyNull()
+              || arrayType.getMemberType() == TypeElement.getByte();
           instruction = new AgetByte(dest, array, index);
         }
         break;
@@ -172,7 +163,7 @@
   @Override
   public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
     // This method is not called for ArrayGet on primitive array.
-    assert this.outValue.getTypeLattice().isReference();
+    assert this.outValue.getType().isReferenceType();
     DexType arrayType = helper.getDexType(array());
     if (arrayType == DexItemFactory.nullValueType) {
       // JVM 8 §4.10.1.9.aaload: Array component type of null is null.
@@ -193,56 +184,51 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    ArrayTypeLatticeElement arrayTypeLattice = array().getTypeLattice().isArrayType()
-        ? array().getTypeLattice().asArrayTypeLatticeElement()
-        : null;
+  public TypeElement evaluate(AppView<?> appView) {
+    ArrayTypeElement arrayTypeLattice =
+        array().getType().isArrayType() ? array().getType().asArrayType() : null;
     switch (getMemberType()) {
       case OBJECT:
         // If the out-type of the array is bottom (the input array must be definitely null), then
         // the instruction cannot return. For now we return NULL as the type to ensure we have a
         // type consistent witness for the out-value type. We could consider returning bottom in
         // this case as the value is indeed empty, i.e., the instruction will always fail.
-        TypeLatticeElement valueType =
+        TypeElement valueType =
             arrayTypeLattice == null
-                ? TypeLatticeElement.getNull()
-                : arrayTypeLattice.getArrayMemberTypeAsValueType();
-        assert valueType.isReference();
+                ? TypeElement.getNull()
+                : arrayTypeLattice.getMemberTypeAsValueType();
+        assert valueType.isReferenceType();
         return valueType;
       case BOOLEAN_OR_BYTE:
       case CHAR:
       case SHORT:
       case INT:
-        assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isInt();
-        return TypeLatticeElement.getInt();
+        assert arrayTypeLattice == null || arrayTypeLattice.getMemberTypeAsValueType().isInt();
+        return TypeElement.getInt();
       case FLOAT:
-        assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isFloat();
-        return TypeLatticeElement.getFloat();
+        assert arrayTypeLattice == null || arrayTypeLattice.getMemberTypeAsValueType().isFloat();
+        return TypeElement.getFloat();
       case LONG:
-        assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isLong();
-        return TypeLatticeElement.getLong();
+        assert arrayTypeLattice == null || arrayTypeLattice.getMemberTypeAsValueType().isLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isDouble();
-        return TypeLatticeElement.getDouble();
+        assert arrayTypeLattice == null || arrayTypeLattice.getMemberTypeAsValueType().isDouble();
+        return TypeElement.getDouble();
       case INT_OR_FLOAT:
         assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isSinglePrimitive();
+            || arrayTypeLattice.getMemberTypeAsValueType().isSinglePrimitive();
         return checkConstraint(dest(), ValueTypeConstraint.INT_OR_FLOAT);
       case LONG_OR_DOUBLE:
         assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isWidePrimitive();
+            || arrayTypeLattice.getMemberTypeAsValueType().isWidePrimitive();
         return checkConstraint(dest(), ValueTypeConstraint.LONG_OR_DOUBLE);
       default:
         throw new Unreachable("Unexpected member type: " + getMemberType());
     }
   }
 
-  private static TypeLatticeElement checkConstraint(Value value, ValueTypeConstraint constraint) {
-    TypeLatticeElement latticeElement = value.constrainedType(constraint);
+  private static TypeElement checkConstraint(Value value, ValueTypeConstraint constraint) {
+    TypeElement latticeElement = value.constrainedType(constraint);
     if (latticeElement != null) {
       return latticeElement;
     }
@@ -267,9 +253,8 @@
 
   @Override
   public boolean outTypeKnownToBeBoolean(Set<Phi> seen) {
-    return array().getTypeLattice().isArrayType()
-        && array().getTypeLattice().asArrayTypeLatticeElement().getArrayMemberTypeAsMemberType()
-            == TypeLatticeElement.getBoolean();
+    return array().getType().isArrayType()
+        && array().getType().asArrayType().getMemberType() == TypeElement.getBoolean();
   }
 
   @Override
@@ -281,4 +266,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) {
     return false;
   }
+
+  @Override
+  public ArrayAccess withMemberType(MemberType newMemberType) {
+    return new ArrayGet(newMemberType, outValue(), array(), index());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index da92bf3..dc0b133 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -75,7 +75,7 @@
 
   @Override
   public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) {
-    if (array().typeLattice.isNullable()) {
+    if (array().type.isNullable()) {
       return AbstractError.specific(appView.dexItemFactory().npeType);
     }
 
@@ -130,8 +130,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.getInt();
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.getInt();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 589710f..f4a8a14 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -16,8 +16,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
@@ -26,11 +26,9 @@
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.Arrays;
 
-public class ArrayPut extends Instruction implements ImpreciseMemberTypeInstruction {
+public class ArrayPut extends ArrayAccess {
 
   // Input values are ordered according to the stack order of the Java bytecode astore.
-  private static final int ARRAY_INDEX = 0;
-  private static final int INDEX_INDEX = 1;
   private static final int VALUE_INDEX = 2;
 
   private MemberType type;
@@ -53,14 +51,6 @@
     return visitor.visit(this);
   }
 
-  public Value array() {
-    return inValues.get(ARRAY_INDEX);
-  }
-
-  public Value index() {
-    return inValues.get(INDEX_INDEX);
-  }
-
   public Value value() {
     return inValues.get(VALUE_INDEX);
   }
@@ -89,13 +79,12 @@
         instruction = new AputObject(value, array, index);
         break;
       case BOOLEAN_OR_BYTE:
-        ArrayTypeLatticeElement arrayType = array().getTypeLattice().asArrayTypeLatticeElement();
-        if (arrayType != null
-            && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getBoolean()) {
+        ArrayTypeElement arrayType = array().getType().asArrayType();
+        if (arrayType != null && arrayType.getMemberType() == TypeElement.getBoolean()) {
           instruction = new AputBoolean(value, array, index);
         } else {
-          assert array().getTypeLattice().isDefinitelyNull()
-              || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getByte();
+          assert array().getType().isDefinitelyNull()
+              || arrayType.getMemberType() == TypeElement.getByte();
           instruction = new AputByte(value, array, index);
         }
         break;
@@ -176,13 +165,12 @@
     }
 
     // Check for type errors.
-    TypeLatticeElement arrayType = array.getTypeLattice();
-    TypeLatticeElement valueType = value().getTypeLattice();
+    TypeElement arrayType = array.getType();
+    TypeElement valueType = value().getType();
     if (!arrayType.isArrayType()) {
       return true;
     }
-    TypeLatticeElement memberType =
-        arrayType.asArrayTypeLatticeElement().getArrayMemberTypeAsValueType();
+    TypeElement memberType = arrayType.asArrayType().getMemberTypeAsValueType();
     if (!valueType.lessThanOrEqualUpToNullability(memberType, appView)) {
       return true;
     }
@@ -275,4 +263,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) {
     return false;
   }
+
+  @Override
+  public ArrayAccess withMemberType(MemberType newMemberType) {
+    return new ArrayPut(newMemberType, array(), index(), value());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 37de422..6611bc7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -9,8 +9,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume.Assumption;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -47,8 +47,8 @@
   }
 
   public static Assume<DynamicTypeAssumption> createAssumeDynamicTypeInstruction(
-      TypeLatticeElement dynamicUpperBoundType,
-      ClassTypeLatticeElement dynamicLowerBoundType,
+      TypeElement dynamicUpperBoundType,
+      ClassTypeElement dynamicLowerBoundType,
       Value dest,
       Value src,
       Instruction origin,
@@ -164,10 +164,10 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     assert outValue != null;
-    TypeLatticeElement outType = outValue.getTypeLattice();
-    if (outType.isPrimitive()) {
+    TypeElement outType = outValue.getType();
+    if (outType.isPrimitiveType()) {
       return false;
     }
     if (assumption.isAssumeNone()) {
@@ -179,14 +179,17 @@
     }
     if (appView.appInfo().hasSubtyping()) {
       if (outType.isClassType()
-          && root.getTypeLattice().isClassType()
-          && appView.appInfo().withSubtyping().inDifferentHierarchy(
-              outType.asClassTypeLatticeElement().getClassType(),
-              root.getTypeLattice().asClassTypeLatticeElement().getClassType())) {
+          && root.getType().isClassType()
+          && appView
+              .appInfo()
+              .withSubtyping()
+              .inDifferentHierarchy(
+                  outType.asClassType().getClassType(),
+                  root.getType().asClassType().getClassType())) {
         return false;
       }
     }
-    return outType.isReference();
+    return outType.isReferenceType();
   }
 
   @Override
@@ -240,13 +243,13 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
+  public TypeElement evaluate(AppView<?> appView) {
     if (assumption.isAssumeNone() || assumption.isAssumeDynamicType()) {
-      return src().getTypeLattice();
+      return src().getType();
     }
     if (assumption.isAssumeNonNull()) {
-      assert src().getTypeLattice().isReference();
-      return src().getTypeLattice().asReferenceTypeLatticeElement().asMeetWithNotNull();
+      assert src().getType().isReferenceType();
+      return src().getType().asReferenceType().asMeetWithNotNull();
     }
     throw new Unimplemented();
   }
@@ -275,18 +278,17 @@
   public boolean verifyTypes(AppView<?> appView) {
     assert super.verifyTypes(appView);
 
-    TypeLatticeElement inType = src().getTypeLattice();
-    TypeLatticeElement outType = outValue().getTypeLattice();
+    TypeElement inType = src().getType();
+    TypeElement outType = outValue().getType();
     if (isAssumeNone() || isAssumeDynamicType()) {
-      assert inType.isReference() : inType;
+      assert inType.isReferenceType() : inType;
       assert outType.equals(inType)
           : "At " + this + System.lineSeparator() + outType + " != " + inType;
     } else {
       assert isAssumeNonNull() : this;
-      assert inType.isReference() : inType;
-      assert inType.isNullType()
-          || outType.equals(inType.asReferenceTypeLatticeElement().asMeetWithNotNull())
-              : "At " + this + System.lineSeparator() + outType + " != " + inType;
+      assert inType.isReferenceType() : inType;
+      assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
+          : "At " + this + System.lineSeparator() + outType + " != " + inType;
     }
     return true;
   }
@@ -351,27 +353,27 @@
 
     @Override
     public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
-      assert dest.getTypeLattice() == src.getTypeLattice();
+      assert dest.getType() == src.getType();
       return true;
     }
   }
 
   public static class DynamicTypeAssumption extends Assumption {
 
-    private final TypeLatticeElement dynamicUpperBoundType;
-    private final ClassTypeLatticeElement dynamicLowerBoundType;
+    private final TypeElement dynamicUpperBoundType;
+    private final ClassTypeElement dynamicLowerBoundType;
 
     private DynamicTypeAssumption(
-        TypeLatticeElement dynamicUpperBoundType, ClassTypeLatticeElement dynamicLowerBoundType) {
+        TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
       this.dynamicUpperBoundType = dynamicUpperBoundType;
       this.dynamicLowerBoundType = dynamicLowerBoundType;
     }
 
-    public TypeLatticeElement getDynamicUpperBoundType() {
+    public TypeElement getDynamicUpperBoundType() {
       return dynamicUpperBoundType;
     }
 
-    public ClassTypeLatticeElement getDynamicLowerBoundType() {
+    public ClassTypeElement getDynamicLowerBoundType() {
       return dynamicLowerBoundType;
     }
 
@@ -382,7 +384,7 @@
 
     @Override
     public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
-      assert dynamicUpperBoundType.lessThanOrEqualUpToNullability(src.getTypeLattice(), appView);
+      assert dynamicUpperBoundType.lessThanOrEqualUpToNullability(src.getType(), appView);
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 584ea2c..37d9b9e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -1445,8 +1445,8 @@
 
   public static BasicBlock createRethrowBlock(
       IRCode code, Position position, DexType guard, AppView<?> appView) {
-    TypeLatticeElement guardTypeLattice =
-        TypeLatticeElement.fromDexType(guard, Nullability.definitelyNotNull(), appView);
+    TypeElement guardTypeLattice =
+        TypeElement.fromDexType(guard, Nullability.definitelyNotNull(), appView);
     BasicBlock block = new BasicBlock();
     MoveException moveException =
         new MoveException(
@@ -1743,14 +1743,14 @@
     int nextBlockNumber = code.getHighestBlockNumber() + 1;
     List<BasicBlock> predecessors = getMutablePredecessors();
     boolean hasMoveException = entry().isMoveException();
-    TypeLatticeElement exceptionTypeLattice = null;
+    TypeElement exceptionTypeLattice = null;
     DexType exceptionType = null;
     MoveException move = null;
     Position position = entry().getPosition();
     if (hasMoveException) {
       // Remove the move-exception instruction.
       move = entry().asMoveException();
-      exceptionTypeLattice = move.outValue().getTypeLattice();
+      exceptionTypeLattice = move.outValue().getType();
       exceptionType = move.getExceptionType();
       assert move.getDebugValues().isEmpty();
       getInstructions().remove(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 98f4c97..4e5c923 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -228,7 +228,7 @@
       throw new IllegalStateException();
     }
 
-    assert !current.hasOutValue() || current.outValue().getTypeLattice().isInt();
+    assert !current.hasOutValue() || current.outValue().getType().isInt();
 
     // Replace the instruction by const-number.
     ConstNumber constNumber = code.createIntConstant(value, current.getLocalInfo());
@@ -249,8 +249,8 @@
     }
 
     // Replace the instruction by static-get.
-    TypeLatticeElement newType = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
-    TypeLatticeElement oldType = current.hasOutValue() ? current.outValue().getTypeLattice() : null;
+    TypeElement newType = TypeElement.fromDexType(field.type, maybeNull(), appView);
+    TypeElement oldType = current.hasOutValue() ? current.outValue().getType() : null;
     Value value = code.createValue(newType, current.getLocalInfo());
     StaticGet staticGet = new StaticGet(value, field);
     for (Value inValue : current.inValues()) {
@@ -534,9 +534,8 @@
       // TODO(b/120257211): Even if the receiver is used, we can avoid inserting a check-cast
       //  instruction if the program still type checks without the cast.
       Value receiver = invoke.inValues().get(0);
-      TypeLatticeElement castTypeLattice =
-          TypeLatticeElement.fromDexType(
-              downcast, receiver.getTypeLattice().nullability(), appView);
+      TypeElement castTypeLattice =
+          TypeElement.fromDexType(downcast, receiver.getType().nullability(), appView);
       CheckCast castInstruction =
           new CheckCast(code.createValue(castTypeLattice), receiver, downcast);
       castInstruction.setPosition(invoke.getPosition());
@@ -715,7 +714,7 @@
             new Phi(
                 code.valueNumberGenerator.next(),
                 newExitBlock,
-                TypeLatticeElement.getBottom(),
+                TypeElement.getBottom(),
                 null,
                 RegisterReadType.NORMAL);
         phi.addOperands(operands);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index 70187db..f0d4eff 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -134,8 +134,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return PrimitiveTypeLatticeElement.fromNumericType(type);
+  public TypeElement evaluate(AppView<?> appView) {
+    return PrimitiveTypeElement.fromNumericType(type);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index a084a0f..2d7e409 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -121,8 +121,7 @@
       }
     }
     AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-    TypeLatticeElement castType =
-        TypeLatticeElement.fromDexType(type, definitelyNotNull(), appView);
+    TypeElement castType = TypeElement.fromDexType(type, definitelyNotNull(), appView);
     if (object()
         .getDynamicUpperBoundType(appViewWithLiveness)
         .lessThanOrEqualUpToNullability(castType, appView)) {
@@ -160,21 +159,20 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(type, object().getTypeLattice().nullability(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(type, object().getType().nullability(), appView);
   }
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     assert super.verifyTypes(appView);
 
-    TypeLatticeElement inType = object().getTypeLattice();
+    TypeElement inType = object().getType();
 
     assert inType.isPreciseType();
 
-    TypeLatticeElement outType = outValue().getTypeLattice();
-    TypeLatticeElement castType =
-        TypeLatticeElement.fromDexType(getType(), inType.nullability(), appView);
+    TypeElement outType = outValue().getType();
+    TypeElement castType = TypeElement.fromDexType(getType(), inType.nullability(), appView);
 
     if (inType.lessThanOrEqual(castType, appView)) {
       // Cast can be removed. Check that it is sound to replace all users of the out-value by the
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index acccf31..2b44f93 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.LongInterval;
@@ -186,7 +186,7 @@
           result = (int) Math.signum(left - right);
         }
       }
-      Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
+      Value value = code.createValue(TypeElement.getInt(), getLocalInfo());
       ConstNumber newConst = new ConstNumber(value, result);
       return new ConstLatticeElement(newConst);
     } else if (leftLattice.isValueRange() && rightLattice.isConst()) {
@@ -214,7 +214,7 @@
       return Bottom.getInstance();
     }
     int result = Integer.signum(Long.compare(leftRange.getMin(), rightRange.getMin()));
-    Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
+    Value value = code.createValue(TypeElement.getInt(), getLocalInfo());
     ConstNumber newConst = new ConstNumber(value, result);
     return new ConstLatticeElement(newConst);
   }
@@ -235,8 +235,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.getInt();
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.getInt();
   }
 
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index a9089f8..e77f315 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -46,7 +46,7 @@
     Value newValue =
         new Value(
             code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -172,8 +172,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.classClassType(appView, Nullability.definitelyNotNull());
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.classClassType(appView, Nullability.definitelyNotNull());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 446601c..996bf38 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -40,7 +40,7 @@
     Value newValue =
         new Value(
             code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -116,8 +116,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(
         appView.dexItemFactory().methodHandleType, Nullability.definitelyNotNull(), appView);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index bb3f574..6cd4d0b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -40,7 +40,7 @@
     Value newValue =
         new Value(
             code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -110,8 +110,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(
         appView.dexItemFactory().methodTypeType, Nullability.definitelyNotNull(), appView);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 84f33f3..2683b66 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -54,10 +54,11 @@
   }
 
   public static ConstNumber copyOf(IRCode code, ConstNumber original) {
-    Value newValue = new Value(
-        code.valueNumberGenerator.next(),
-        original.outValue().getTypeLattice(),
-        original.getLocalInfo());
+    Value newValue =
+        new Value(
+            code.valueNumberGenerator.next(),
+            original.outValue().getType(),
+            original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
@@ -250,7 +251,7 @@
   @Override
   public String toString() {
     if (outValue != null) {
-      return super.toString() + " " + value + " (" + outValue().getTypeLattice() + ")";
+      return super.toString() + " " + value + " (" + outValue().getType() + ")";
     } else {
       return super.toString() + " " + value + " (dead)";
     }
@@ -314,16 +315,14 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return outValue().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return outValue().getType();
   }
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     assert super.verifyTypes(appView);
-    assert !isZero()
-        || outValue().getTypeLattice().isPrimitive()
-        || outValue().getTypeLattice().isNullType();
+    assert !isZero() || outValue().getType().isPrimitiveType() || outValue().getType().isNullType();
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index a80bc66..c98d8b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -42,8 +42,9 @@
 
   public static ConstString copyOf(IRCode code, ConstString original) {
     Value newValue =
-        new Value(code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+        new Value(
+            code.valueNumberGenerator.next(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -153,8 +154,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull());
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 453a564..5884660 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -79,7 +79,7 @@
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     super.verifyTypes(appView);
-    assert src().getTypeLattice().lessThanOrEqual(outValue().getTypeLattice(), appView);
+    assert src().getType().lessThanOrEqual(outValue().getType(), appView);
     return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 979f172..70ebd80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -48,8 +48,9 @@
 
   public static DexItemBasedConstString copyOf(IRCode code, DexItemBasedConstString original) {
     Value newValue =
-        new Value(code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+        new Value(
+            code.valueNumberGenerator.next(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -153,8 +154,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull());
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 79fe507..cddc41e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -70,7 +70,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    if (this.inValues.get(0).getTypeLattice().isWidePrimitive()) {
+    if (this.inValues.get(0).getType().isWidePrimitive()) {
       builder.add(new CfStackInstruction(Opcode.Dup2));
     } else {
       builder.add(new CfStackInstruction(Opcode.Dup));
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index 2c24e04..465c408 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -33,8 +33,8 @@
 
   private Dup2(StackValues dest, Value srcBottom, Value srcTop) {
     super(dest, ImmutableList.of(srcBottom, srcTop));
-    assert !srcBottom.getTypeLattice().isWidePrimitive();
-    assert !srcTop.getTypeLattice().isWidePrimitive();
+    assert !srcBottom.getType().isWidePrimitive();
+    assert !srcTop.getType().isWidePrimitive();
     assert dest.getStackValues().length == 4;
     assert srcBottom.isValueOnStack() && !(srcBottom instanceof StackValues);
     assert srcTop.isValueOnStack() && !(srcTop instanceof StackValues);
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 507ae25..d7e0067 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -114,7 +114,7 @@
     if (isInstanceGet() || isInstancePut()) {
       if (!assumption.canAssumeReceiverIsNotNull()) {
         Value receiver = inValues.get(0);
-        if (receiver.isAlwaysNull(appView) || receiver.typeLattice.isNullable()) {
+        if (receiver.isAlwaysNull(appView) || receiver.type.isNullable()) {
           return AbstractError.specific(appView.dexItemFactory().npeType);
         }
       }
@@ -189,9 +189,8 @@
       AppView<AppInfoWithLiveness> appView, DexEncodedField field) {
     assert isFieldPut();
 
-    TypeLatticeElement type = value().getTypeLattice();
-    TypeLatticeElement baseType =
-        type.isArrayType() ? type.asArrayTypeLatticeElement().getArrayBaseTypeLattice() : type;
+    TypeElement type = value().getType();
+    TypeElement baseType = type.isArrayType() ? type.asArrayType().getBaseType() : type;
     if (!baseType.isClassType()) {
       return false;
     }
@@ -225,7 +224,7 @@
       return resolutionResult != null && resolutionResult.isProgramMethod(appView);
     }
 
-    return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(baseType.asClassTypeLatticeElement());
+    return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(baseType.asClassType());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
index b074e44..c1424b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import java.util.function.Predicate;
 
 // Value that has a fixed register allocated. These are used for inserting spill, restore, and phi
@@ -12,7 +12,7 @@
 public class FixedRegisterValue extends Value {
   private final int register;
 
-  public FixedRegisterValue(TypeLatticeElement moveType, int register) {
+  public FixedRegisterValue(TypeElement moveType, int register) {
     // Set local info to null since these values are never representatives of live-ranges.
     super(-1, moveType, null);
     setNeedsRegister(true);
@@ -21,8 +21,8 @@
 
   @Override
   public ValueType outType() {
-    TypeLatticeElement type = getTypeLattice();
-    if (type.isPrimitive()) {
+    TypeElement type = getType();
+    if (type.isPrimitiveType()) {
       if (type.isSinglePrimitive()) {
         if (type.isInt()) {
           return ValueType.INT;
@@ -40,7 +40,7 @@
         }
       }
     } else {
-      assert type.isReference();
+      assert type.isReferenceType();
       return ValueType.OBJECT;
     }
     throw new Unreachable("Unexpected imprecise type: " + type);
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 883d49f..53573ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -12,9 +12,9 @@
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.origin.Origin;
@@ -565,9 +565,8 @@
       return true;
     }
     for (Instruction instruction : instructions()) {
-      if (instruction.outValue != null && instruction.outValue.getTypeLattice().isClassType()) {
-        ClassTypeLatticeElement classTypeLattice =
-            instruction.outValue.getTypeLattice().asClassTypeLatticeElement();
+      if (instruction.outValue != null && instruction.outValue.getType().isClassType()) {
+        ClassTypeElement classTypeLattice = instruction.outValue.getType().asClassType();
         assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(classTypeLattice.getClassType());
         for (DexType itf : classTypeLattice.getInterfaces()) {
           assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(itf);
@@ -893,11 +892,11 @@
   public boolean verifyNoImpreciseOrBottomTypes() {
     Predicate<Value> verifyValue =
         v -> {
-          assert v.getTypeLattice().isPreciseType();
-          assert !v.getTypeLattice().isFineGrainedType();
+          assert v.getType().isPreciseType();
+          assert !v.getType().isFineGrainedType();
           // For now we assume no bottom types on IR values. We may want to reconsider this for
           // representing unreachable code.
-          assert !v.getTypeLattice().isBottom();
+          assert !v.getType().isBottom();
           assert !(v.definition instanceof ImpreciseMemberTypeInstruction)
               || ((ImpreciseMemberTypeInstruction) v.definition).getMemberType().isPrecise();
           return true;
@@ -908,9 +907,8 @@
   public boolean verifyNoNullabilityBottomTypes() {
     Predicate<Value> verifyValue =
         v -> {
-          assert v.getTypeLattice().isPrimitive()
-              || v.getTypeLattice().asReferenceTypeLatticeElement().nullability()
-                  != Nullability.bottom();
+          assert v.getType().isPrimitiveType()
+              || v.getType().asReferenceType().nullability() != Nullability.bottom();
           return true;
         };
     return verifySSATypeLattice(wrapSSAVerifierWithStackValueHandling(verifyValue));
@@ -1049,21 +1047,21 @@
     return thisValue;
   }
 
-  public Value createValue(TypeLatticeElement typeLattice, DebugLocalInfo local) {
+  public Value createValue(TypeElement typeLattice, DebugLocalInfo local) {
     return new Value(valueNumberGenerator.next(), typeLattice, local);
   }
 
-  public Value createValue(TypeLatticeElement typeLattice) {
+  public Value createValue(TypeElement typeLattice) {
     return createValue(typeLattice, null);
   }
 
   public ConstNumber createDoubleConstant(double value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getDouble(), local);
+    Value out = createValue(TypeElement.getDouble(), local);
     return new ConstNumber(out, Double.doubleToLongBits(value));
   }
 
   public ConstNumber createFloatConstant(float value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getFloat(), local);
+    Value out = createValue(TypeElement.getFloat(), local);
     return new ConstNumber(out, Float.floatToIntBits(value));
   }
 
@@ -1072,16 +1070,16 @@
   }
 
   public ConstNumber createIntConstant(int value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getInt(), local);
+    Value out = createValue(TypeElement.getInt(), local);
     return new ConstNumber(out, value);
   }
 
   public ConstNumber createLongConstant(long value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getLong(), local);
+    Value out = createValue(TypeElement.getLong(), local);
     return new ConstNumber(out, value);
   }
 
-  public Phi createPhi(BasicBlock block, TypeLatticeElement type) {
+  public Phi createPhi(BasicBlock block, TypeElement type) {
     return new Phi(valueNumberGenerator.next(), block, type, null, RegisterReadType.NORMAL);
   }
 
@@ -1091,17 +1089,17 @@
 
   public ConstClass createConstClass(AppView<?> appView, DexType type) {
     Value out =
-        createValue(TypeLatticeElement.fromDexType(type, Nullability.definitelyNotNull(), appView));
+        createValue(TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView));
     return new ConstClass(out, type);
   }
 
   public ConstNumber createConstNull() {
-    Value out = createValue(TypeLatticeElement.getNull());
+    Value out = createValue(TypeElement.getNull());
     return new ConstNumber(out, 0);
   }
 
   public ConstNumber createConstNull(DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getNull(), local);
+    Value out = createValue(TypeElement.getNull(), local);
     return new ConstNumber(out, 0);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 611ebcc..da0592d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -61,10 +61,10 @@
     }
   }
 
-  private static boolean verifyTypeCompatible(TypeLatticeElement valueType, If.Type ifType) {
+  private static boolean verifyTypeCompatible(TypeElement valueType, If.Type ifType) {
     return valueType.isInt()
         || (valueType.isFloat() && (ifType == Type.EQ || ifType == Type.NE))
-        || (valueType.isReference() && (ifType == Type.EQ || ifType == Type.NE));
+        || (valueType.isReferenceType() && (ifType == Type.EQ || ifType == Type.NE));
   }
 
   private Type type;
@@ -199,14 +199,14 @@
 
   public BasicBlock targetFromCondition(ConstNumber value) {
     assert isZeroTest();
-    assert verifyTypeCompatible(value.outValue().getTypeLattice(), type);
+    assert verifyTypeCompatible(value.outValue().getType(), type);
     return targetFromCondition(Long.signum(value.getRawValue()));
   }
 
   public BasicBlock targetFromCondition(ConstNumber left, ConstNumber right) {
     assert !isZeroTest();
     assert left.outType() == right.outType();
-    assert verifyTypeCompatible(left.outValue().getTypeLattice(), type);
+    assert verifyTypeCompatible(left.outValue().getType(), type);
     return targetFromCondition(Long.signum(left.getRawValue() - right.getRawValue()));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java b/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java
index 2b5bc02..ff21fbd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
 
 public interface ImpreciseMemberTypeInstruction {
+
   MemberType getMemberType();
 
   void constrainType(TypeConstraintResolver constraintResolver);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 8dd260c..02ee0eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -30,7 +30,7 @@
   public InitClass(Value outValue, DexType clazz) {
     super(outValue);
     assert hasOutValue();
-    assert outValue.getTypeLattice().isInt();
+    assert outValue.getType().isInt();
     assert clazz.isClassType();
     this.clazz = clazz;
   }
@@ -50,8 +50,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.getInt();
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.getInt();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 6d9b8e9..6edc6da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -68,9 +68,9 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     assert outValue != null;
-    return outValue.getTypeLattice().isReference();
+    return outValue.getType().isReferenceType();
   }
 
   @Override
@@ -181,8 +181,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(getField().type, Nullability.maybeNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(getField().type, Nullability.maybeNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index de4fa66..c18e3c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -91,8 +91,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.getInt();
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.getInt();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 5680260..116a3e6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -160,7 +160,7 @@
       // If the value being written by this instruction is an array, then make sure that the value
       // being written by the other instruction is the exact same value. Otherwise, the verifier
       // may incorrectly join the types of these arrays to Object[].
-      if (value().getTypeLattice().isArrayType() && value() != instancePut.value()) {
+      if (value().getType().isArrayType() && value() != instancePut.value()) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index a56f0a2..654c2e8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
@@ -145,9 +145,9 @@
   }
 
   @Override
-  public TypeLatticeElement getTypeLattice() {
+  public TypeElement getOutType() {
     if (hasOutValue()) {
-      return outValue().getTypeLattice();
+      return outValue().getType();
     }
     return null;
   }
@@ -661,6 +661,14 @@
     return this;
   }
 
+  public boolean isArrayAccess() {
+    return false;
+  }
+
+  public ArrayAccess asArrayAccess() {
+    return null;
+  }
+
   public boolean isArrayGet() {
     return false;
   }
@@ -1375,7 +1383,7 @@
   public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
 
   public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
-    assert outValue == null || !outValue.getTypeLattice().isReference();
+    assert outValue == null || !outValue.getType().isReferenceType();
     throw new Unreachable("Instruction without object outValue cannot compute verification type");
   }
 
@@ -1389,7 +1397,7 @@
   }
 
   // TODO(b/72693244): maybe rename to computeOutType once TypeVerificationHelper is gone?
-  public TypeLatticeElement evaluate(AppView<?> appView) {
+  public TypeElement evaluate(AppView<?> appView) {
     assert outValue == null;
     throw new Unimplemented(
         "Implement type lattice evaluation for: " + getInstructionName());
@@ -1398,16 +1406,16 @@
   public boolean verifyTypes(AppView<?> appView) {
     // TODO(b/72693244): for instructions with invariant out type, we can verify type directly here.
     if (outValue != null) {
-      TypeLatticeElement outTypeLatticeElement = outValue.getTypeLattice();
-      if (outTypeLatticeElement.isArrayType()) {
+      TypeElement outTypeElement = outValue.getType();
+      if (outTypeElement.isArrayType()) {
         DexType outBaseType =
-            outTypeLatticeElement
-                .asArrayTypeLatticeElement()
-                .getArrayType(appView.dexItemFactory())
+            outTypeElement
+                .asArrayType()
+                .toDexType(appView.dexItemFactory())
                 .toBaseType(appView.dexItemFactory());
         assert appView.graphLense().lookupType(outBaseType) == outBaseType;
-      } else if (outTypeLatticeElement.isClassType()) {
-        DexType outType = outTypeLatticeElement.asClassTypeLatticeElement().getClassType();
+      } else if (outTypeElement.isClassType()) {
+        DexType outType = outTypeElement.asClassType().getClassType();
         assert appView.graphLense().lookupType(outType) == outType;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 15834d9..05e80d1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.List;
 import java.util.Set;
@@ -167,14 +167,14 @@
   protected void addInvokeAndMoveResult(
       com.android.tools.r8.code.Instruction instruction, DexBuilder builder) {
     if (outValue != null && outValue.needsRegister()) {
-      TypeLatticeElement moveType = outValue.getTypeLattice();
+      TypeElement moveType = outValue.getType();
       int register = builder.allocatedRegister(outValue, getNumber());
       com.android.tools.r8.code.Instruction moveResult;
       if (moveType.isSinglePrimitive()) {
         moveResult = new MoveResult(register);
       } else if (moveType.isWidePrimitive()) {
         moveResult = new MoveResultWide(register);
-      } else if (moveType.isReference()) {
+      } else if (moveType.isReferenceType()) {
         moveResult = new MoveResultObject(register);
       } else {
         throw new Unreachable("Unexpected result type " + outType());
@@ -187,24 +187,27 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     if (outValue == null) {
       return false;
     }
-    TypeLatticeElement outType = outValue.getTypeLattice();
-    if (outType.isPrimitive()) {
+    TypeElement outType = outValue.getType();
+    if (outType.isPrimitiveType()) {
       return false;
     }
     if (appView.appInfo().hasSubtyping()) {
       if (outType.isClassType()
-          && root.getTypeLattice().isClassType()
-          && appView.appInfo().withSubtyping().inDifferentHierarchy(
-              outType.asClassTypeLatticeElement().getClassType(),
-              root.getTypeLattice().asClassTypeLatticeElement().getClassType())) {
+          && root.getType().isClassType()
+          && appView
+              .appInfo()
+              .withSubtyping()
+              .inDifferentHierarchy(
+                  outType.asClassType().getClassType(),
+                  root.getType().asClassType().getClassType())) {
         return false;
       }
     }
-    return outType.isReference();
+    return outType.isReferenceType();
   }
 
   @Override
@@ -284,12 +287,12 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
+  public TypeElement evaluate(AppView<?> appView) {
     DexType returnType = getReturnType();
     if (returnType.isVoidType()) {
       throw new Unreachable("void methods have no type.");
     }
-    return TypeLatticeElement.fromDexType(returnType, Nullability.maybeNull(), appView);
+    return TypeElement.fromDexType(returnType, Nullability.maybeNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 2c7dcfb..9ee1988 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -10,9 +10,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -43,9 +43,9 @@
   }
 
   private static boolean verifyLambdaInterfaces(
-      TypeLatticeElement returnTypeLattice, Set<DexType> lambdaInterfaceSet, DexType objectType) {
-    Set<DexType> primaryInterfaces = returnTypeLattice.asClassTypeLatticeElement().getInterfaces();
-    if (returnTypeLattice.asClassTypeLatticeElement().getClassType() == objectType) {
+      TypeElement returnType, Set<DexType> lambdaInterfaceSet, DexType objectType) {
+    Set<DexType> primaryInterfaces = returnType.asClassType().getInterfaces();
+    if (returnType.asClassType().getClassType() == objectType) {
       assert primaryInterfaces.size() == 1;
       // The interfaces returned by the LambdaDescriptor assumed to already contain the primary
       // interface. If they're both singleton lists they must be identical and we can return the
@@ -53,47 +53,46 @@
       assert lambdaInterfaceSet.contains(primaryInterfaces.iterator().next());
     } else {
       // We arrive here if the primary interface is a missing class. In that case the
-      // returnTypeLattice will be the missing type as the class type.
+      // returnType will be the missing type as the class type.
       assert primaryInterfaces.isEmpty();
-      assert lambdaInterfaceSet.contains(
-          returnTypeLattice.asClassTypeLatticeElement().getClassType());
+      assert lambdaInterfaceSet.contains(returnType.asClassType().getClassType());
     }
     return true;
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    TypeLatticeElement returnTypeLattice = super.evaluate(appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    TypeElement returnType = super.evaluate(appView);
     if (!appView.appInfo().hasSubtyping()) {
-      return returnTypeLattice;
+      return returnType;
     }
     List<DexType> lambdaInterfaces =
         LambdaDescriptor.getInterfaces(callSite, appView.appInfo().withSubtyping());
     if (lambdaInterfaces == null || lambdaInterfaces.isEmpty()) {
-      return returnTypeLattice;
+      return returnType;
     }
 
     // The primary return type is either an interface or a missing type.
-    assert returnTypeLattice instanceof ClassTypeLatticeElement;
+    assert returnType instanceof ClassTypeElement;
 
-    Set<DexType> primaryInterfaces = returnTypeLattice.asClassTypeLatticeElement().getInterfaces();
+    Set<DexType> primaryInterfaces = returnType.asClassType().getInterfaces();
     DexType objectType = appView.dexItemFactory().objectType;
 
-    if (returnTypeLattice.asClassTypeLatticeElement().getClassType() == objectType) {
+    if (returnType.asClassType().getClassType() == objectType) {
       assert primaryInterfaces.size() == 1;
       // Shortcut for the common case: single interface. Save creating a new lattice type.
       if (lambdaInterfaces.size() == 1) {
         assert lambdaInterfaces.get(0) == primaryInterfaces.iterator().next();
-        return returnTypeLattice;
+        return returnType;
       }
     }
 
     Set<DexType> lambdaInterfaceSet =
         ImmutableSet.<DexType>builder().addAll(lambdaInterfaces).build();
 
-    assert verifyLambdaInterfaces(returnTypeLattice, lambdaInterfaceSet, objectType);
+    assert verifyLambdaInterfaces(returnType, lambdaInterfaceSet, objectType);
 
-    return ClassTypeLatticeElement.create(objectType, Nullability.maybeNull(), lambdaInterfaceSet);
+    return ClassTypeElement.create(objectType, Nullability.maybeNull(), lambdaInterfaceSet);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index b4531c3..ddc8952 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -166,7 +166,7 @@
 
     // Check if it could throw a NullPointerException as a result of the receiver being null.
     Value receiver = getReceiver();
-    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) {
+    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getType().isNullable()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 401b4df..9b708bc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -161,7 +161,7 @@
       // incorrectly join the types of these arrays to Object[].
       for (int i = 0; i < arguments().size(); ++i) {
         Value argument = arguments().get(i);
-        if (argument.getTypeLattice().isArrayType() && argument != invoke.arguments().get(i)) {
+        if (argument.getType().isArrayType() && argument != invoke.arguments().get(i)) {
           return false;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index e15a387..689bf8f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -8,9 +8,9 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -70,12 +70,12 @@
     assert super.verifyTypes(appView);
 
     Value receiver = getReceiver();
-    TypeLatticeElement receiverType = receiver.getTypeLattice();
+    TypeElement receiverType = receiver.getType();
     assert receiverType.isPreciseType();
 
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      ClassTypeLatticeElement receiverLowerBoundType =
+      ClassTypeElement receiverLowerBoundType =
           receiver.getDynamicLowerBoundType(appViewWithLiveness);
       if (receiverLowerBoundType != null) {
         DexType refinedReceiverType =
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 02e45ef..a71d185 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -82,8 +82,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index ce75c7c..7992a1c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -111,8 +111,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 1218f08..711c911 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -154,7 +154,7 @@
 
     // Check if it could throw a NullPointerException as a result of the receiver being null.
     Value receiver = getReceiver();
-    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) {
+    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getType().isNullable()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index e1885b0..9886690 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -89,8 +89,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return src().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return src().getType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index 7f87253..267db9b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -117,7 +117,7 @@
       ConstNumber newConst;
       if (type == NumericType.INT) {
         int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
-        Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getInt(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else {
         assert type == NumericType.LONG;
@@ -129,7 +129,7 @@
           right = rightConst.getLongValue();
         }
         long result = foldLongs(leftConst.getLongValue(), right);
-        Value value = code.createValue(TypeLatticeElement.getLong(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getLong(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       }
       return new ConstLatticeElement(newConst);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 3478645..adcc848 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -73,7 +73,7 @@
 
   @Override
   public String toString() {
-    return super.toString() + " (" + outValue().getTypeLattice() + ")";
+    return super.toString() + " (" + outValue().getType() + ")";
   }
 
   @Override
@@ -104,8 +104,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return src().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return src().getType();
   }
 
   @Override
@@ -128,7 +128,7 @@
     super.verifyTypes(appView);
     // DebugLocalWrite defines it's own verification of types but should be allowed to call super.
     if (!this.isDebugLocalWrite()) {
-      assert src().getTypeLattice().equals(outValue().getTypeLattice());
+      assert src().getType().equals(outValue().getType());
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 143227a..e9cd95f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -111,8 +111,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(exceptionType, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(exceptionType, Nullability.definitelyNotNull(), appView);
   }
 
   public DexType getExceptionType() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index 41ec4f1..ad0886a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -88,7 +88,7 @@
     LatticeElement sourceLattice = getLatticeElement.apply(source());
     if (sourceLattice.isConst()) {
       ConstNumber sourceConst = sourceLattice.asConst().getConstNumber();
-      TypeLatticeElement typeLattice = PrimitiveTypeLatticeElement.fromNumericType(type);
+      TypeElement typeLattice = PrimitiveTypeElement.fromNumericType(type);
       Value value = code.createValue(typeLattice, getLocalInfo());
       ConstNumber newConst;
       if (type == NumericType.INT) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 6d2d5df..bd5c34f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -134,8 +134,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index afd87ff..2211a59 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -121,7 +121,7 @@
       return AbstractError.top();
     }
 
-    if (src().getTypeLattice().isNullable()) {
+    if (src().getType().isNullable()) {
       return AbstractError.top();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 384a27a..d8acd0b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -125,8 +125,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(clazz, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(clazz, Nullability.definitelyNotNull(), appView);
   }
 
   @Override
@@ -226,10 +226,9 @@
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
-    TypeLatticeElement type = outValue().getTypeLattice();
+    TypeElement type = outValue().getType();
     assert type.isClassType();
-    assert type.asClassTypeLatticeElement().getClassType() == clazz
-        || appView.options().testing.allowTypeErrors;
+    assert type.asClassType().getClassType() == clazz || appView.options().testing.allowTypeErrors;
     assert type.isDefinitelyNotNull();
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index 0ab5c78..d000623 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -45,13 +45,13 @@
     LatticeElement sourceLattice = getLatticeElement.apply(source());
     if (sourceLattice.isConst()) {
       ConstNumber sourceConst = sourceLattice.asConst().getConstNumber();
-      TypeLatticeElement typeLattice  = PrimitiveTypeLatticeElement.fromNumericType(type);
-      Value value = code.createValue(typeLattice, getLocalInfo());
+      TypeElement type = PrimitiveTypeElement.fromNumericType(this.type);
+      Value value = code.createValue(type, getLocalInfo());
       ConstNumber newConst;
-      if (type == NumericType.INT) {
+      if (this.type == NumericType.INT) {
         newConst = new ConstNumber(value, ~sourceConst.getIntValue());
       } else {
-        assert type == NumericType.LONG;
+        assert this.type == NumericType.LONG;
         newConst = new ConstNumber(value, ~sourceConst.getLongValue());
       }
       return new ConstLatticeElement(newConst);
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
index 99adf0c..e6b6f8d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
@@ -21,8 +21,8 @@
 import com.android.tools.r8.code.LongToInt;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.Set;
@@ -153,8 +153,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return PrimitiveTypeLatticeElement.fromNumericType(to);
+  public TypeElement evaluate(AppView<?> appView) {
+    return PrimitiveTypeElement.fromNumericType(to);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 95c2608..49779aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.EdgeType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
@@ -53,10 +53,10 @@
   public Phi(
       int number,
       BasicBlock block,
-      TypeLatticeElement typeLattice,
+      TypeElement type,
       DebugLocalInfo local,
       RegisterReadType readType) {
-    super(number, typeLattice, local);
+    super(number, type, local);
     this.block = block;
     this.readType = readType;
     block.addPhi(this);
@@ -97,7 +97,7 @@
       throw new InvalidDebugInfoException(
           "Type information in locals-table is inconsistent."
               + " Cannot constrain type: "
-              + typeLattice
+              + type
               + " for value: "
               + this
               + " by constraint "
@@ -115,7 +115,7 @@
       throwUndefinedValueError();
     }
 
-    ValueTypeConstraint readConstraint = TypeConstraintResolver.constraintForType(typeLattice);
+    ValueTypeConstraint readConstraint = TypeConstraintResolver.constraintForType(type);
     List<Value> operands = new ArrayList<>(block.getPredecessors().size());
     for (BasicBlock pred : block.getPredecessors()) {
       EdgeType edgeType = pred.getEdgeType(block);
@@ -125,7 +125,7 @@
 
     if (readType != RegisterReadType.NORMAL) {
       for (Value operand : operands) {
-        TypeLatticeElement type = operand.getTypeLattice();
+        TypeElement type = operand.getType();
         ValueTypeConstraint constraint = TypeConstraintResolver.constraintForType(type);
         abortOnInvalidDebugInfo(constraint);
       }
@@ -261,8 +261,8 @@
       return false;
     }
     // Ensure that the value that replaces this phi is constrained to the type of this phi.
-    if (builder != null && typeLattice.isPreciseType() && !typeLattice.isBottom()) {
-      builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(typeLattice));
+    if (builder != null && type.isPreciseType() && !type.isBottom()) {
+      builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(type));
     }
     if (affectedValues != null) {
       affectedValues.addAll(this.affectedValues());
@@ -320,7 +320,7 @@
     }
     builder.append(" <- phi");
     StringUtils.append(builder, ListUtils.map(operands, Value::toString));
-    builder.append(" : ").append(getTypeLattice());
+    builder.append(" : ").append(getType());
     return builder.toString();
   }
 
@@ -398,17 +398,16 @@
   }
 
   // Type of phi(v1, v2, ..., vn) is the least upper bound of all those n operands.
-  public TypeLatticeElement computePhiType(AppView<?> appView) {
-    TypeLatticeElement result = TypeLatticeElement.getBottom();
+  public TypeElement computePhiType(AppView<?> appView) {
+    TypeElement result = TypeElement.getBottom();
     for (Value operand : getOperands()) {
-      result = result.join(operand.getTypeLattice(), appView);
+      result = result.join(operand.getType(), appView);
     }
     return result;
   }
 
   @Override
-  public TypeLatticeElement getDynamicUpperBoundType(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+  public TypeElement getDynamicUpperBoundType(AppView<? extends AppInfoWithSubtyping> appView) {
     Set<Phi> reachablePhis = SetUtils.newIdentityHashSet(this);
     Deque<Phi> worklist = DequeUtils.newArrayDeque(this);
     while (!worklist.isEmpty()) {
@@ -422,7 +421,7 @@
       }
     }
     Set<Value> visitedOperands = Sets.newIdentityHashSet();
-    TypeLatticeElement result = TypeLatticeElement.getBottom();
+    TypeElement result = TypeElement.getBottom();
     for (Phi phi : reachablePhis) {
       for (Value operand : phi.getOperands()) {
         if (!operand.getAliasedValue().isPhi() && visitedOperands.add(operand)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 037f856..0adf292 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -42,9 +42,9 @@
     return inValues.size() == 0;
   }
 
-  public TypeLatticeElement getReturnType() {
+  public TypeElement getReturnType() {
     assert !isReturnVoid();
-    return returnValue().getTypeLattice();
+    return returnValue().getType();
   }
 
   public Value returnValue() {
@@ -57,8 +57,8 @@
       return new ReturnVoid();
     }
     int register = builder.allocatedRegister(returnValue(), getNumber());
-    TypeLatticeElement returnType = getReturnType();
-    if (returnType.isReference()) {
+    TypeElement returnType = getReturnType();
+    if (returnType.isReferenceType()) {
       return new ReturnObject(register);
     }
     if (returnType.isSinglePrimitive()) {
@@ -124,8 +124,6 @@
   @Override
   public void buildCf(CfBuilder builder) {
     builder.add(
-        isReturnVoid()
-            ? new CfReturnVoid()
-            : new CfReturn(ValueType.fromTypeLattice(getReturnType())));
+        isReturnVoid() ? new CfReturnVoid() : new CfReturn(ValueType.fromType(getReturnType())));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValue.java b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
index 430f290..0d13811 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
@@ -6,15 +6,15 @@
 import com.android.tools.r8.cf.TypeVerificationHelper.TypeInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 public class StackValue extends Value {
 
   private final int height;
   private final TypeInfo typeInfo;
 
-  private StackValue(TypeInfo typeInfo, TypeLatticeElement typeLattice, int height) {
-    super(Value.UNDEFINED_NUMBER, typeLattice, null);
+  private StackValue(TypeInfo typeInfo, TypeElement type, int height) {
+    super(Value.UNDEFINED_NUMBER, type, null);
     this.height = height;
     this.typeInfo = typeInfo;
     assert height >= 0;
@@ -23,7 +23,7 @@
   public static StackValue create(TypeInfo typeInfo, int height, AppView<?> appView) {
     return new StackValue(
         typeInfo,
-        TypeLatticeElement.fromDexType(typeInfo.getDexType(), Nullability.maybeNull(), appView),
+        TypeElement.fromDexType(typeInfo.getDexType(), Nullability.maybeNull(), appView),
         height);
   }
 
@@ -36,7 +36,7 @@
   }
 
   public StackValue duplicate(int height) {
-    return new StackValue(typeInfo, getTypeLattice(), height);
+    return new StackValue(typeInfo, getType(), height);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValues.java b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
index 528cddd..d0c56eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValues.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 /**
  * {@link StackValues} allow us to represent stack operations that produces two or more elements on
@@ -16,7 +16,7 @@
   private final StackValue[] stackValues;
 
   public StackValues(StackValue... stackValues) {
-    super(Value.UNDEFINED_NUMBER, TypeLatticeElement.getBottom(), null);
+    super(Value.UNDEFINED_NUMBER, TypeElement.getBottom(), null);
     this.stackValues = stackValues;
     assert stackValues.length >= 2;
   }
@@ -55,7 +55,7 @@
   }
 
   @Override
-  public TypeLatticeElement getTypeLattice() {
+  public TypeElement getType() {
     throw new Unreachable();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index a3c202b..2c246f92 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -40,7 +40,7 @@
     Value newValue =
         new Value(
             code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -71,22 +71,25 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     assert outValue != null;
-    TypeLatticeElement outType = outValue.getTypeLattice();
-    if (outType.isPrimitive()) {
+    TypeElement outType = outValue.getType();
+    if (outType.isPrimitiveType()) {
       return false;
     }
     if (appView.appInfo().hasSubtyping()) {
       if (outType.isClassType()
-          && root.getTypeLattice().isClassType()
-          && appView.appInfo().withSubtyping().inDifferentHierarchy(
-              outType.asClassTypeLatticeElement().getClassType(),
-              root.getTypeLattice().asClassTypeLatticeElement().getClassType())) {
+          && root.getType().isClassType()
+          && appView
+              .appInfo()
+              .withSubtyping()
+              .inDifferentHierarchy(
+                  outType.asClassType().getClassType(),
+                  root.getType().asClassType().getClassType())) {
         return false;
       }
     }
-    return outType.isReference();
+    return outType.isReferenceType();
   }
 
   @Override
@@ -219,8 +222,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(getField().type, Nullability.maybeNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(getField().type, Nullability.maybeNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index e7d2bdc..16c102b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -168,7 +168,7 @@
       // If the value being written by this instruction is an array, then make sure that the value
       // being written by the other instruction is the exact same value. Otherwise, the verifier
       // may incorrectly join the types of these arrays to Object[].
-      if (value().getTypeLattice().isArrayType() && value() != staticPut.value()) {
+      if (value().getType().isArrayType() && value() != staticPut.value()) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 72eb683..4fe63c0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -89,8 +89,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return src().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return src().getType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 8cdaa77..2515934 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -26,7 +26,7 @@
     super(dest, ImmutableList.of(src1, src2));
     assert src1.isValueOnStack() && !(src1 instanceof StackValues);
     assert src2.isValueOnStack() && !(src2 instanceof StackValues);
-    assert !src1.getTypeLattice().isWidePrimitive() && !src2.getTypeLattice().isWidePrimitive();
+    assert !src1.getType().isWidePrimitive() && !src2.getType().isWidePrimitive();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index 115e9fd..2b6b80d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -91,7 +91,7 @@
     if (exception() == value) {
       return true;
     }
-    TypeLatticeElement exceptionType = exception().getTypeLattice();
+    TypeElement exceptionType = exception().getType();
     if (exceptionType.isNullType()) {
       // throw null
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
index 0d5cfd6..d28407f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
+++ b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
@@ -5,13 +5,14 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 public interface TypeAndLocalInfoSupplier {
   DebugLocalInfo getLocalInfo();
-  TypeLatticeElement getTypeLattice();
 
-  static TypeAndLocalInfoSupplier create(TypeLatticeElement type, DebugLocalInfo local) {
+  TypeElement getOutType();
+
+  static TypeAndLocalInfoSupplier create(TypeElement type, DebugLocalInfo local) {
     return new TypeAndLocalInfoSupplier() {
 
       @Override
@@ -20,7 +21,7 @@
       }
 
       @Override
-      public TypeLatticeElement getTypeLattice() {
+      public TypeElement getOutType() {
         return type;
       }
     };
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index 57fad2d..1765626 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
@@ -58,8 +58,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return source().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return source().getType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 25ed39f..e84f64a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -20,8 +20,8 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
@@ -52,81 +52,80 @@
 
   public void constrainType(
       ValueTypeConstraint constraint, DexMethod method, Origin origin, Reporter reporter) {
-    TypeLatticeElement constrainedType = constrainedType(constraint);
+    TypeElement constrainedType = constrainedType(constraint);
     if (constrainedType == null) {
       throw reporter.fatalError(
           new StringDiagnostic(
               "Cannot constrain type: "
-                  + typeLattice
+                  + type
                   + " for value: "
                   + this
                   + " by constraint: "
                   + constraint,
               origin,
               new MethodPosition(method)));
-    } else if (constrainedType != typeLattice) {
-      setTypeLattice(constrainedType);
+    } else if (constrainedType != type) {
+      setType(constrainedType);
     }
   }
 
-  public TypeLatticeElement constrainedType(ValueTypeConstraint constraint) {
-    if (constraint == ValueTypeConstraint.INT_OR_FLOAT_OR_OBJECT
-        && !typeLattice.isWidePrimitive()) {
-      return typeLattice;
+  public TypeElement constrainedType(ValueTypeConstraint constraint) {
+    if (constraint == ValueTypeConstraint.INT_OR_FLOAT_OR_OBJECT && !type.isWidePrimitive()) {
+      return type;
     }
     switch (constraint) {
       case OBJECT:
-        if (typeLattice.isTop()) {
+        if (type.isTop()) {
           if (definition != null && definition.isConstNumber()) {
             assert definition.asConstNumber().isZero();
-            return TypeLatticeElement.getNull();
+            return TypeElement.getNull();
           } else {
-            return TypeLatticeElement.getBottom();
+            return TypeElement.getBottom();
           }
         }
-        if (typeLattice.isReference()) {
-          return typeLattice;
+        if (type.isReferenceType()) {
+          return type;
         }
-        if (typeLattice.isBottom()) {
+        if (type.isBottom()) {
           // Only a few instructions may propagate a bottom input to a bottom output.
           assert isPhi()
               || definition.isDebugLocalWrite()
               || (definition.isArrayGet()
                   && definition.asArrayGet().getMemberType() == MemberType.OBJECT);
-          return typeLattice;
+          return type;
         }
         break;
       case INT:
-        if (typeLattice.isTop() || (typeLattice.isSinglePrimitive() && !typeLattice.isFloat())) {
-          return TypeLatticeElement.getInt();
+        if (type.isTop() || (type.isSinglePrimitive() && !type.isFloat())) {
+          return TypeElement.getInt();
         }
         break;
       case FLOAT:
-        if (typeLattice.isTop() || (typeLattice.isSinglePrimitive() && !typeLattice.isInt())) {
-          return TypeLatticeElement.getFloat();
+        if (type.isTop() || (type.isSinglePrimitive() && !type.isInt())) {
+          return TypeElement.getFloat();
         }
         break;
       case INT_OR_FLOAT:
-        if (typeLattice.isTop()) {
-          return TypeLatticeElement.getSingle();
+        if (type.isTop()) {
+          return TypeElement.getSingle();
         }
-        if (typeLattice.isSinglePrimitive()) {
-          return typeLattice;
+        if (type.isSinglePrimitive()) {
+          return type;
         }
         break;
       case LONG:
-        if (typeLattice.isWidePrimitive() && !typeLattice.isDouble()) {
-          return TypeLatticeElement.getLong();
+        if (type.isWidePrimitive() && !type.isDouble()) {
+          return TypeElement.getLong();
         }
         break;
       case DOUBLE:
-        if (typeLattice.isWidePrimitive() && !typeLattice.isLong()) {
-          return TypeLatticeElement.getDouble();
+        if (type.isWidePrimitive() && !type.isLong()) {
+          return TypeElement.getDouble();
         }
         break;
       case LONG_OR_DOUBLE:
-        if (typeLattice.isWidePrimitive()) {
-          return typeLattice;
+        if (type.isWidePrimitive()) {
+          return type;
         }
         break;
       default:
@@ -212,8 +211,7 @@
 
   public static final int UNDEFINED_NUMBER = -1;
 
-  public static final Value UNDEFINED =
-      new Value(UNDEFINED_NUMBER, TypeLatticeElement.getBottom(), null);
+  public static final Value UNDEFINED = new Value(UNDEFINED_NUMBER, TypeElement.getBottom(), null);
 
   protected final int number;
   public Instruction definition = null;
@@ -228,13 +226,13 @@
   private boolean isThis = false;
   private LongInterval valueRange;
   private DebugData debugData;
-  protected TypeLatticeElement typeLattice;
+  protected TypeElement type;
 
-  public Value(int number, TypeLatticeElement typeLattice, DebugLocalInfo local) {
-    assert typeLattice != null;
+  public Value(int number, TypeElement type, DebugLocalInfo local) {
+    assert type != null;
     this.number = number;
     this.debugData = local == null ? null : new DebugData(local);
-    this.typeLattice = typeLattice;
+    this.type = type;
   }
 
   public boolean isFixedRegisterValue() {
@@ -310,7 +308,7 @@
   }
 
   public int requiredRegisters() {
-    return typeLattice.requiredRegisters();
+    return type.requiredRegisters();
   }
 
   public DebugLocalInfo getLocalInfo() {
@@ -553,14 +551,11 @@
       // Not always null as the value can be changed via the debugger.
       return false;
     }
-    if (typeLattice.isDefinitelyNull()) {
+    if (type.isDefinitelyNull()) {
       return true;
     }
-    if (typeLattice.isClassType() && appView.appInfo().hasLiveness()) {
-      return typeLattice
-          .asClassTypeLatticeElement()
-          .getClassType()
-          .isAlwaysNull(appView.withLiveness());
+    if (type.isClassType() && appView.appInfo().hasLiveness()) {
+      return type.asClassType().getClassType().isAlwaysNull(appView.withLiveness());
     }
     return false;
   }
@@ -831,7 +826,7 @@
       builder.append("(");
       if (isConstant && definition.asConstNumber().outValue != null) {
         ConstNumber constNumber = definition.asConstNumber();
-        if (constNumber.outValue().getTypeLattice().isSinglePrimitive()) {
+        if (constNumber.outValue().getType().isSinglePrimitive()) {
           builder.append((int) constNumber.getRawValue());
         } else {
           builder.append(constNumber.getRawValue());
@@ -852,7 +847,7 @@
   }
 
   public ValueType outType() {
-    return ValueType.fromTypeLattice(typeLattice);
+    return ValueType.fromType(type);
   }
 
   public ConstInstruction getConstInstruction() {
@@ -921,9 +916,9 @@
    * Returns whether this value is known to never be <code>null</code>.
    */
   public boolean isNeverNull() {
-    assert typeLattice.isReference();
+    assert type.isReferenceType();
     return (definition != null && definition.isAssumeNonNull())
-        || typeLattice.nullability().isDefinitelyNotNull();
+        || type.nullability().isDefinitelyNotNull();
   }
 
   public boolean isArgument() {
@@ -956,7 +951,7 @@
   }
 
   public boolean knownToBeBoolean(Set<Phi> seen) {
-    if (!getTypeLattice().isInt()) {
+    if (!getType().isInt()) {
       return false;
     }
 
@@ -1014,11 +1009,11 @@
 
   public LongInterval getValueRange() {
     if (isConstNumber()) {
-      if (typeLattice.isSinglePrimitive()) {
+      if (type.isSinglePrimitive()) {
         int value = getConstInstruction().asConstNumber().getIntValue();
         return new LongInterval(value, value);
       } else {
-        assert typeLattice.isWidePrimitive();
+        assert type.isWidePrimitive();
         long value = getConstInstruction().asConstNumber().getLongValue();
         return new LongInterval(value, value);
       }
@@ -1096,45 +1091,44 @@
    *
    * @param newType The new type lattice element
    */
-  public void setTypeLattice(TypeLatticeElement newType) {
+  public void setType(TypeElement newType) {
     assert newType != null;
-    typeLattice = newType;
+    type = newType;
   }
 
-  public void widening(AppView<?> appView, TypeLatticeElement newType) {
+  public void widening(AppView<?> appView, TypeElement newType) {
     // During WIDENING (due to fix-point iteration), type update is monotonically upwards,
     //   i.e., towards something wider.
-    assert this.typeLattice.lessThanOrEqual(newType, appView)
+    assert this.type.lessThanOrEqual(newType, appView)
         : "During WIDENING, "
             + newType
             + " < "
-            + typeLattice
+            + type
             + " at "
             + (isPhi() ? asPhi().printPhi() : definition.toString());
-    setTypeLattice(newType);
+    setType(newType);
   }
 
-  public void narrowing(AppView<?> appView, TypeLatticeElement newType) {
+  public void narrowing(AppView<?> appView, TypeElement newType) {
     // During NARROWING (e.g., after inlining), type update is monotonically downwards,
     //   i.e., towards something narrower, with more specific type info.
     assert (!appView.options().testing.enableNarrowingChecksInD8
                 && !appView.enableWholeProgramOptimizations())
-            || !this.typeLattice.strictlyLessThan(newType, appView)
+            || !this.type.strictlyLessThan(newType, appView)
         : "During NARROWING, "
-            + typeLattice
+            + type
             + " < "
             + newType
             + " at "
             + (isPhi() ? asPhi().printPhi() : definition.toString());
-    setTypeLattice(newType);
+    setType(newType);
   }
 
-  public TypeLatticeElement getTypeLattice() {
-    return typeLattice;
+  public TypeElement getType() {
+    return type;
   }
 
-  public TypeLatticeElement getDynamicUpperBoundType(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+  public TypeElement getDynamicUpperBoundType(AppView<? extends AppInfoWithSubtyping> appView) {
     Value root = getAliasedValue();
     if (root.isPhi()) {
       assert getSpecificAliasedValue(
@@ -1147,7 +1141,7 @@
     // Assume<DynamicTypeAssumption>.
     Value aliasedValue =
         getSpecificAliasedValue(value -> !value.isPhi() && value.definition.isAssumeDynamicType());
-    TypeLatticeElement lattice;
+    TypeElement lattice;
     if (aliasedValue != null) {
       // If there is an alias of the receiver, which is defined by an Assume<DynamicTypeAssumption>
       // instruction, then use the dynamic type as the refined receiver type.
@@ -1155,25 +1149,24 @@
           aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicUpperBoundType();
 
       // For precision, verify that the dynamic type is at least as precise as the static type.
-      assert lattice.lessThanOrEqualUpToNullability(typeLattice, appView)
-          : typeLattice + " < " + lattice;
+      assert lattice.lessThanOrEqualUpToNullability(type, appView) : type + " < " + lattice;
     } else {
       // Otherwise, simply use the static type.
-      lattice = typeLattice;
+      lattice = type;
     }
 
     // Account for nullability, which could be flown from non-null assumption in between dynamic
     // type assumption or simply from array/object creation.
-    if (typeLattice.isDefinitelyNotNull() && lattice.isNullable()) {
+    if (type.isDefinitelyNotNull() && lattice.isNullable()) {
       // Having non-null assumption means it is a reference type.
-      assert lattice.isReference();
+      assert lattice.isReferenceType();
       // Then, we can return the non-null variant of dynamic type if both assumptions are aliased.
-      return lattice.asReferenceTypeLatticeElement().asMeetWithNotNull();
+      return lattice.asReferenceType().asMeetWithNotNull();
     }
     return lattice;
   }
 
-  public ClassTypeLatticeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
+  public ClassTypeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
     Value root = getAliasedValue();
     if (root.isPhi()) {
       return null;
@@ -1184,7 +1177,7 @@
       DexType type = definition.asNewInstance().clazz;
       DexClass clazz = appView.definitionFor(type);
       if (clazz != null && !clazz.isInterface()) {
-        return ClassTypeLatticeElement.create(type, definitelyNotNull(), appView);
+        return ClassTypeElement.create(type, definitelyNotNull(), appView);
       }
       return null;
     }
@@ -1193,16 +1186,16 @@
     // Assume<DynamicTypeAssumption>.
     Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
     if (aliasedValue != null) {
-      ClassTypeLatticeElement lattice =
+      ClassTypeElement lattice =
           aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicLowerBoundType();
-      return lattice != null && typeLattice.isDefinitelyNotNull() && lattice.isNullable()
+      return lattice != null && type.isDefinitelyNotNull() && lattice.isNullable()
           ? lattice.asMeetWithNotNull()
           : lattice;
     }
 
     // If it is a final or effectively-final class type, then we know the lower bound.
-    if (getTypeLattice().isClassType()) {
-      ClassTypeLatticeElement classType = getTypeLattice().asClassTypeLatticeElement();
+    if (getType().isClassType()) {
+      ClassTypeElement classType = getType().asClassType();
       DexType type = classType.getClassType();
       DexClass clazz = appView.definitionFor(type);
       if (clazz != null && clazz.isEffectivelyFinal(appView)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index 865c7d4..729cdcf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 public enum ValueType {
   OBJECT,
@@ -102,35 +102,35 @@
     }
   }
 
-  public static ValueType fromTypeLattice(TypeLatticeElement typeLatticeElement) {
-    if (typeLatticeElement.isReference()) {
+  public static ValueType fromType(TypeElement type) {
+    if (type.isReferenceType()) {
       return OBJECT;
     }
-    if (typeLatticeElement.isInt()) {
+    if (type.isInt()) {
       return INT;
     }
-    if (typeLatticeElement.isFloat()) {
+    if (type.isFloat()) {
       return FLOAT;
     }
-    if (typeLatticeElement.isLong()) {
+    if (type.isLong()) {
       return LONG;
     }
-    if (typeLatticeElement.isDouble()) {
+    if (type.isDouble()) {
       return DOUBLE;
     }
-    throw new Unreachable("Unexpected conversion of imprecise type: " + typeLatticeElement);
+    throw new Unreachable("Unexpected conversion of imprecise type: " + type);
   }
 
-  public PrimitiveTypeLatticeElement toPrimitiveTypeLattice() {
+  public PrimitiveTypeElement toPrimitiveType() {
     switch (this) {
       case INT:
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case LONG:
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       default:
         throw new Unreachable("Unexpected type in conversion to primitive: " + this);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java b/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
index 1f7f97b..13cd1ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 public enum ValueTypeConstraint {
   OBJECT,
@@ -129,44 +129,44 @@
     }
   }
 
-  public static ValueTypeConstraint fromTypeLattice(TypeLatticeElement typeLatticeElement) {
-    if (typeLatticeElement.isReference()) {
+  public static ValueTypeConstraint fromTypeLattice(TypeElement typeElement) {
+    if (typeElement.isReferenceType()) {
       return OBJECT;
     }
-    if (typeLatticeElement.isFineGrainedType() || typeLatticeElement.isInt()) {
+    if (typeElement.isFineGrainedType() || typeElement.isInt()) {
       return INT;
     }
-    if (typeLatticeElement.isFloat()) {
+    if (typeElement.isFloat()) {
       return FLOAT;
     }
-    if (typeLatticeElement.isLong()) {
+    if (typeElement.isLong()) {
       return LONG;
     }
-    if (typeLatticeElement.isDouble()) {
+    if (typeElement.isDouble()) {
       return DOUBLE;
     }
-    if (typeLatticeElement.isSinglePrimitive()) {
+    if (typeElement.isSinglePrimitive()) {
       return INT_OR_FLOAT;
     }
-    if (typeLatticeElement.isWidePrimitive()) {
+    if (typeElement.isWidePrimitive()) {
       return LONG_OR_DOUBLE;
     }
-    if (typeLatticeElement.isTop()) {
+    if (typeElement.isTop()) {
       return INT_OR_FLOAT_OR_OBJECT;
     }
-    throw new Unreachable("Unexpected conversion of type: " + typeLatticeElement);
+    throw new Unreachable("Unexpected conversion of type: " + typeElement);
   }
 
-  public PrimitiveTypeLatticeElement toPrimitiveTypeLattice() {
+  public PrimitiveTypeElement toPrimitiveType() {
     switch (this) {
       case INT:
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case LONG:
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       default:
         throw new Unreachable("Unexpected type in conversion to primitive: " + this);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index a16d39d..ae3e7cd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -27,7 +27,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -234,7 +234,7 @@
 
         // Insert ConstNumber(v, -1) before Not.
         it.previous();
-        Value constValue = code.createValue(inValue.getTypeLattice());
+        Value constValue = code.createValue(inValue.getType());
         Instruction newInstruction = new ConstNumber(constValue, -1);
         newInstruction.setBlock(block);
         newInstruction.setPosition(current.getPosition());
@@ -386,7 +386,7 @@
             || constNumber == null
             || add == null
             || store == null
-            || constNumber.outValue().getTypeLattice() != TypeLatticeElement.getInt()) {
+            || constNumber.outValue().getType() != TypeElement.getInt()) {
           it.next();
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 8af9977..a4ca85a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -42,7 +42,7 @@
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEventBuilder;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -1404,7 +1404,7 @@
     @Override
     public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
       Move move = getMove();
-      TypeLatticeElement moveType = move.outValue().getTypeLattice();
+      TypeElement moveType = move.outValue().getType();
       int src = srcRegister(builder);
       int dest = destRegister(builder);
       Instruction instruction;
@@ -1418,7 +1418,7 @@
             instruction = new com.android.tools.r8.code.Move(dest, src);
           } else if (moveType.isWidePrimitive()) {
             instruction = new MoveWide(dest, src);
-          } else if (moveType.isReference()) {
+          } else if (moveType.isReferenceType()) {
             instruction = new MoveObject(dest, src);
           } else {
             throw new Unreachable("Unexpected type: " + move.outType());
@@ -1429,7 +1429,7 @@
             instruction = new MoveFrom16(dest, src);
           } else if (moveType.isWidePrimitive()) {
             instruction = new MoveWideFrom16(dest, src);
-          } else if (moveType.isReference()) {
+          } else if (moveType.isReferenceType()) {
             instruction = new MoveObjectFrom16(dest, src);
           } else {
             throw new Unreachable("Unexpected type: " + move.outType());
@@ -1440,7 +1440,7 @@
             instruction = new Move16(dest, src);
           } else if (moveType.isWidePrimitive()) {
             instruction = new MoveWide16(dest, src);
-          } else if (moveType.isReference()) {
+          } else if (moveType.isReferenceType()) {
             instruction = new MoveObject16(dest, src);
           } else {
             throw new Unreachable("Unexpected type: " + move.outType());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index a6d6267..c6070c5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -6,8 +6,8 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -19,9 +19,9 @@
 
   void markFieldAsPropagated(DexEncodedField field);
 
-  void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeLatticeElement type);
+  void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeElement type);
 
-  void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type);
+  void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeElement type);
 
   void markFieldBitsRead(DexEncodedField field, int bitsRead);
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index e87f619..993a69c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -6,14 +6,14 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getBottom;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getDouble;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getLong;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getNull;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getSingle;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getWide;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getBottom;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getDouble;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getInt;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getLong;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getNull;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getSingle;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getWide;
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.CompilationError;
@@ -39,9 +39,9 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.And;
 import com.android.tools.r8.ir.code.Argument;
@@ -151,7 +151,7 @@
 
   public static final int INITIAL_BLOCK_OFFSET = -1;
 
-  private static TypeLatticeElement fromMemberType(MemberType type) {
+  private static TypeElement fromMemberType(MemberType type) {
     switch (type) {
       case BOOLEAN_OR_BYTE:
       case CHAR:
@@ -176,8 +176,8 @@
     }
   }
 
-  public TypeLatticeElement getTypeLattice(DexType type, Nullability nullability) {
-    return TypeLatticeElement.fromDexType(type, nullability, appView);
+  public TypeElement getTypeLattice(DexType type, Nullability nullability) {
+    return TypeElement.fromDexType(type, nullability, appView);
   }
 
   // SSA construction uses a worklist of basic blocks reachable from the entry and their
@@ -543,13 +543,13 @@
 
     int usedArgumentIndex = 0;
     while (argumentIndex < numberOfArguments) {
-      TypeLatticeElement type;
+      TypeElement type;
       ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(argumentIndex);
       if (argumentInfo.isRemovedArgumentInfo()) {
         RemovedArgumentInfo removedArgumentInfo = argumentInfo.asRemovedArgumentInfo();
         writeCallback.accept(register, removedArgumentInfo.getType());
         type =
-            TypeLatticeElement.fromDexType(
+            TypeElement.fromDexType(
                 removedArgumentInfo.getType(), Nullability.maybeNull(), appView);
         addConstantOrUnusedArgument(register, removedArgumentInfo);
       } else {
@@ -567,7 +567,7 @@
         }
         usedArgumentIndex++;
         writeCallback.accept(register, argType);
-        type = TypeLatticeElement.fromDexType(argType, Nullability.maybeNull(), appView);
+        type = TypeElement.fromDexType(argType, Nullability.maybeNull(), appView);
         if (argType.isBooleanType()) {
           addBooleanNonThisArgument(register);
         } else {
@@ -882,8 +882,8 @@
     int moveExceptionDest = source.getMoveExceptionRegister(targetIndex);
     Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
     if (moveExceptionDest >= 0) {
-      TypeLatticeElement typeLattice =
-          TypeLatticeElement.fromDexType(moveExceptionItem.guard, definitelyNotNull(), appView);
+      TypeElement typeLattice =
+          TypeElement.fromDexType(moveExceptionItem.guard, definitelyNotNull(), appView);
       Value out = writeRegister(moveExceptionDest, typeLattice, ThrowingInfo.NO_THROW, null);
       MoveException moveException =
           new MoveException(out, moveExceptionItem.guard, appView.options());
@@ -932,12 +932,11 @@
   void addThisArgument(int register) {
     boolean receiverCouldBeNull = context != null && context != method;
     Nullability nullability = receiverCouldBeNull ? maybeNull() : definitelyNotNull();
-    TypeLatticeElement receiverType =
-        TypeLatticeElement.fromDexType(method.method.holder, nullability, appView);
+    TypeElement receiverType = TypeElement.fromDexType(method.method.holder, nullability, appView);
     addThisArgument(register, receiverType);
   }
 
-  public void addThisArgument(int register, TypeLatticeElement receiverType) {
+  public void addThisArgument(int register, TypeElement receiverType) {
     DebugLocalInfo local = getOutgoingLocal(register);
     Value value = writeRegister(register, receiverType, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value, currentBlock.size(), false));
@@ -945,7 +944,7 @@
     value.markAsThis();
   }
 
-  public void addNonThisArgument(int register, TypeLatticeElement typeLattice) {
+  public void addNonThisArgument(int register, TypeElement typeLattice) {
     DebugLocalInfo local = getOutgoingLocal(register);
     Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
     addNonThisArgument(new Argument(value, currentBlock.size(), false));
@@ -1010,8 +1009,7 @@
       // Note that the write register must not lookup outgoing local information and the local is
       // never considered clobbered by a start (if the in value has local info it must have been
       // marked ended elsewhere).
-      Value out = writeRegister(
-          register, incomingValue.getTypeLattice(), ThrowingInfo.NO_THROW, local);
+      Value out = writeRegister(register, incomingValue.getType(), ThrowingInfo.NO_THROW, local);
       DebugLocalWrite write = new DebugLocalWrite(out, incomingValue);
       addInstruction(write);
     }
@@ -1092,7 +1090,7 @@
   public void addArrayGet(MemberType type, int dest, int array, int index) {
     Value in1 = readRegister(array, ValueTypeConstraint.OBJECT);
     Value in2 = readRegister(index, ValueTypeConstraint.INT);
-    TypeLatticeElement typeLattice = fromMemberType(type);
+    TypeElement typeLattice = fromMemberType(type);
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ArrayGet instruction = new ArrayGet(type, out, in1, in2);
     assert instruction.instructionTypeCanThrow();
@@ -1123,8 +1121,8 @@
 
   public void addCheckCast(int value, DexType type) {
     Value in = readRegister(value, ValueTypeConstraint.OBJECT);
-    TypeLatticeElement castTypeLattice =
-        TypeLatticeElement.fromDexType(type, in.getTypeLattice().nullability(), appView);
+    TypeElement castTypeLattice =
+        TypeElement.fromDexType(type, in.getType().nullability(), appView);
     Value out = writeRegister(value, castTypeLattice, ThrowingInfo.CAN_THROW);
     CheckCast instruction = new CheckCast(out, in, type);
     assert instruction.instructionTypeCanThrow();
@@ -1140,7 +1138,7 @@
     add(instruction);
   }
 
-  public void addConst(TypeLatticeElement typeLattice, int dest, long value) {
+  public void addConst(TypeElement typeLattice, int dest, long value) {
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.NO_THROW);
     ConstNumber instruction = new ConstNumber(out, value);
     assert !instruction.instructionTypeCanThrow();
@@ -1168,8 +1166,7 @@
   }
 
   public void addConstClass(int dest, DexType type) {
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.classClassType(appView, definitelyNotNull());
+    TypeElement typeLattice = TypeElement.classClassType(appView, definitelyNotNull());
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstClass instruction = new ConstClass(out, type);
     assert instruction.instructionTypeCanThrow();
@@ -1183,8 +1180,8 @@
           "Const-method-handle",
           null /* sourceString */);
     }
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.fromDexType(
+    TypeElement typeLattice =
+        TypeElement.fromDexType(
             appView.dexItemFactory().methodHandleType, definitelyNotNull(), appView);
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstMethodHandle instruction = new ConstMethodHandle(out, methodHandle);
@@ -1198,8 +1195,8 @@
           "Const-method-type",
           null /* sourceString */);
     }
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.fromDexType(
+    TypeElement typeLattice =
+        TypeElement.fromDexType(
             appView.dexItemFactory().methodTypeType, definitelyNotNull(), appView);
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstMethodType instruction = new ConstMethodType(out, methodType);
@@ -1213,16 +1210,14 @@
   }
 
   public void addConstString(int dest, DexString string) {
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+    TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
     ThrowingInfo throwingInfo = throwingInfoForConstStrings();
     add(new ConstString(writeRegister(dest, typeLattice, throwingInfo), string, throwingInfo));
   }
 
   public void addDexItemBasedConstString(
       int dest, DexReference item, NameComputationInfo<?> nameComputationInfo) {
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+    TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
     ThrowingInfo throwingInfo = throwingInfoForConstStrings();
     Value out = writeRegister(dest, typeLattice, throwingInfo);
     add(new DexItemBasedConstString(out, item, nameComputationInfo, throwingInfo));
@@ -1268,7 +1263,7 @@
       // If the move is writing to a different local we must construct a new value.
       DebugLocalInfo destLocal = getOutgoingLocal(dest);
       if (destLocal != null && destLocal != in.getLocalInfo()) {
-        Value out = writeRegister(dest, in.getTypeLattice(), ThrowingInfo.NO_THROW);
+        Value out = writeRegister(dest, in.getType(), ThrowingInfo.NO_THROW);
         addInstruction(new DebugLocalWrite(out, in));
         return;
       }
@@ -1423,7 +1418,7 @@
     Value out =
         writeRegister(
             dest,
-            TypeLatticeElement.fromDexType(field.type, maybeNull(), appView),
+            TypeElement.fromDexType(field.type, maybeNull(), appView),
             ThrowingInfo.CAN_THROW);
     InstanceGet instruction = new InstanceGet(out, in, field);
     assert instruction.instructionTypeCanThrow();
@@ -1712,11 +1707,11 @@
             : maybeNull();
     // InvokeCustom.evaluate will look into the metadata of the callsite which will provide more
     // information than just looking at the type.
-    TypeLatticeElement typeLatticeElement =
+    TypeElement typeElement =
         invoke.isInvokeCustom()
             ? invoke.evaluate(appView)
-            : TypeLatticeElement.fromDexType(outType, nullability, appView);
-    Value outValue = writeRegister(dest, typeLatticeElement, ThrowingInfo.CAN_THROW);
+            : TypeElement.fromDexType(outType, nullability, appView);
+    Value outValue = writeRegister(dest, typeElement, ThrowingInfo.CAN_THROW);
     invoke.setOutValue(outValue);
   }
 
@@ -1745,8 +1740,7 @@
   public void addNewArrayEmpty(int dest, int size, DexType type) {
     assert type.isArrayType();
     Value in = readRegister(size, ValueTypeConstraint.INT);
-    TypeLatticeElement arrayTypeLattice =
-        TypeLatticeElement.fromDexType(type, definitelyNotNull(), appView);
+    TypeElement arrayTypeLattice = TypeElement.fromDexType(type, definitelyNotNull(), appView);
     Value out = writeRegister(dest, arrayTypeLattice, ThrowingInfo.CAN_THROW);
     NewArrayEmpty instruction = new NewArrayEmpty(out, in, type);
     assert instruction.instructionTypeCanThrow();
@@ -1762,8 +1756,7 @@
   }
 
   public void addNewInstance(int dest, DexType type) {
-    TypeLatticeElement instanceType =
-        TypeLatticeElement.fromDexType(type, definitelyNotNull(), appView);
+    TypeElement instanceType = TypeElement.fromDexType(type, definitelyNotNull(), appView);
     Value out = writeRegister(dest, instanceType, ThrowingInfo.CAN_THROW);
     NewInstance instruction = new NewInstance(type, out);
     assert instruction.instructionTypeCanThrow();
@@ -1797,7 +1790,7 @@
   }
 
   public void addInitClass(int dest, DexType clazz) {
-    Value out = writeRegister(dest, TypeLatticeElement.getInt(), ThrowingInfo.CAN_THROW);
+    Value out = writeRegister(dest, TypeElement.getInt(), ThrowingInfo.CAN_THROW);
     InitClass instruction = new InitClass(out, clazz);
     assert instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -1807,7 +1800,7 @@
     Value out =
         writeRegister(
             dest,
-            TypeLatticeElement.fromDexType(field.type, maybeNull(), appView),
+            TypeElement.fromDexType(field.type, maybeNull(), appView),
             ThrowingInfo.CAN_THROW);
     StaticGet instruction = new StaticGet(out, field);
     assert instruction.instructionTypeCanThrow();
@@ -2142,7 +2135,7 @@
         value = getUninitializedDebugLocalValue(register, constraint);
       } else {
         DebugLocalInfo local = getIncomingLocalAtBlock(register, block);
-        TypeLatticeElement phiType = TypeConstraintResolver.typeForConstraint(constraint);
+        TypeElement phiType = TypeConstraintResolver.typeForConstraint(constraint);
         hasImpreciseValues |= !phiType.isPreciseType();
         Phi phi = new Phi(valueNumberGenerator.next(), block, phiType, local, readType);
         if (!block.isSealed()) {
@@ -2181,15 +2174,14 @@
   private Value getUninitializedDebugLocalValue(int register, ValueTypeConstraint typeConstraint) {
     // A debug initiated value must have a precise type constraint.
     assert typeConstraint.isPrecise();
-    TypeLatticeElement type =
-        typeConstraint.isObject() ? getNull() : typeConstraint.toPrimitiveTypeLattice();
+    TypeElement type = typeConstraint.isObject() ? getNull() : typeConstraint.toPrimitiveType();
     if (uninitializedDebugLocalValues == null) {
       uninitializedDebugLocalValues = new Int2ReferenceOpenHashMap<>();
     }
     List<Value> values = uninitializedDebugLocalValues.get(register);
     if (values != null) {
       for (Value value : values) {
-        if (value.getTypeLattice() == type) {
+        if (value.getType() == type) {
           return value;
         }
       }
@@ -2235,14 +2227,14 @@
   // This special write register is needed when changing the scoping of a local variable.
   // See addDebugLocalStart and addDebugLocalEnd.
   private Value writeRegister(
-      int register, TypeLatticeElement typeLattice, ThrowingInfo throwing, DebugLocalInfo local) {
+      int register, TypeElement typeLattice, ThrowingInfo throwing, DebugLocalInfo local) {
     checkRegister(register);
     Value value = new Value(valueNumberGenerator.next(), typeLattice, local);
     currentBlock.writeCurrentDefinition(register, value, throwing);
     return value;
   }
 
-  public Value writeRegister(int register, TypeLatticeElement typeLattice, ThrowingInfo throwing) {
+  public Value writeRegister(int register, TypeElement typeLattice, ThrowingInfo throwing) {
     DebugLocalInfo incomingLocal = getIncomingLocal(register);
     DebugLocalInfo outgoingLocal = getOutgoingLocal(register);
     // If the local info does not change at the current instruction, we need to ensure
@@ -2261,7 +2253,7 @@
   }
 
   public Value writeNumericRegister(int register, NumericType type, ThrowingInfo throwing) {
-    return writeRegister(register, PrimitiveTypeLatticeElement.fromNumericType(type), throwing);
+    return writeRegister(register, PrimitiveTypeElement.fromNumericType(type), throwing);
   }
 
   private DebugLocalInfo getIncomingLocal(int register) {
@@ -2340,7 +2332,7 @@
 
   private void addInstruction(Instruction ir, Position position) {
     assert verifyOutValueType(ir);
-    hasImpreciseValues |= ir.outValue() != null && !ir.outValue().getTypeLattice().isPreciseType();
+    hasImpreciseValues |= ir.outValue() != null && !ir.outValue().getType().isPreciseType();
     ir.setPosition(position);
     attachLocalValues(ir);
     currentBlock.add(ir, metadata);
@@ -2373,11 +2365,11 @@
   private boolean verifyOutValueType(Instruction ir) {
     assert ir.outValue() == null
         || ir.isArrayGet()
-        || ir.evaluate(appView) == ir.outValue().getTypeLattice();
+        || ir.evaluate(appView) == ir.outValue().getType();
     assert ir.outValue() == null
         || !ir.isArrayGet()
-        || ir.evaluate(appView) == ir.outValue().getTypeLattice()
-        || (ir.outValue().getTypeLattice().isBottom() && ir.evaluate(appView).isReference());
+        || ir.evaluate(appView) == ir.outValue().getType()
+        || (ir.outValue().getType().isBottom() && ir.evaluate(appView).isReferenceType());
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d968d31..cae0c84 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -30,7 +30,7 @@
 import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
 import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -45,6 +45,7 @@
 import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -143,6 +144,7 @@
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final TwrCloseResourceRewriter twrCloseResourceRewriter;
   private final BackportedMethodRewriter backportedMethodRewriter;
+  private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final LambdaMerger lambdaMerger;
   private final ClassInliner classInliner;
   private final ClassStaticizer classStaticizer;
@@ -217,13 +219,16 @@
             .collect(Collectors.toList());
     if (options.isDesugaredLibraryCompilation()) {
       // Specific L8 Settings.
-      // BackportedMethodRewriter is needed for retarget core library members and backports.
+      // DesugaredLibraryRetargeter is needed for retarget core library members and backports.
       // InterfaceMethodRewriter is needed for emulated interfaces.
       // LambdaRewriter is needed because if it is missing there are invoke custom on
       // default/static interface methods, and this is not supported by the compiler.
       // DesugaredLibraryAPIConverter is here to duplicate APIs.
       // The rest is nulled out. In addition the rewriting logic fails without lambda rewriting.
-      this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
+      this.desugaredLibraryRetargeter =
+          options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
+              ? null
+              : new DesugaredLibraryRetargeter(appView);
       this.interfaceMethodRewriter =
           options.desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
               ? null
@@ -231,6 +236,10 @@
       this.lambdaRewriter = new LambdaRewriter(appView);
       this.desugaredLibraryAPIConverter =
           new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
+      this.backportedMethodRewriter =
+          options.testing.forceLibBackportsInL8CfToCf
+              ? new BackportedMethodRewriter(appView, this)
+              : null;
       this.twrCloseResourceRewriter = null;
       this.lambdaMerger = null;
       this.covariantReturnTypeAnnotationTransformer = null;
@@ -268,6 +277,10 @@
             ? new TwrCloseResourceRewriter(appView, this)
             : null;
     this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
+    this.desugaredLibraryRetargeter =
+        options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
+            ? null
+            : new DesugaredLibraryRetargeter(appView);
     this.covariantReturnTypeAnnotationTransformer =
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
@@ -450,7 +463,16 @@
 
   private void synthesizeJava8UtilityClass(
       Builder<?> builder, ExecutorService executorService) throws ExecutionException {
-    backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
+    if (backportedMethodRewriter != null) {
+      backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
+    }
+  }
+
+  private void synthesizeRetargetClass(Builder<?> builder, ExecutorService executorService)
+      throws ExecutionException {
+    if (desugaredLibraryRetargeter != null) {
+      desugaredLibraryRetargeter.synthesizeRetargetClasses(builder, executorService, this);
+    }
   }
 
   private void synthesizeEnumUnboxingUtilityClass(
@@ -482,6 +504,8 @@
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
     synthesizeTwrCloseResourceUtilityClass(builder, executor);
     synthesizeJava8UtilityClass(builder, executor);
+    synthesizeRetargetClass(builder, executor);
+
     processCovariantReturnTypeAnnotations(builder);
     generateDesugaredLibraryAPIWrappers(builder, executor);
 
@@ -747,6 +771,7 @@
     printPhase("Utility classes synthesis");
     synthesizeTwrCloseResourceUtilityClass(builder, executorService);
     synthesizeJava8UtilityClass(builder, executorService);
+    synthesizeRetargetClass(builder, executorService);
     handleSynthesizedClassMapping(builder);
     synthesizeEnumUnboxingUtilityClass(builder, executorService);
 
@@ -974,12 +999,7 @@
   private DexType computeOutlineClassType() {
     DexType result;
     int count = 0;
-    String tempPrefix =
-        appView
-            .options()
-            .desugaredLibraryConfiguration
-            .getSynthesizedLibraryClassesPackagePrefix(appView);
-    String prefix = tempPrefix.replace('/', '.');
+    String prefix = appView.options().synthesizedClassPrefix.replace('/', '.');
     do {
       String name =
           prefix + OutlineOptions.CLASS_NAME + (count == 0 ? "" : Integer.toString(count));
@@ -1375,9 +1395,20 @@
       codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
       timing.end();
     }
-    timing.begin("Rewrite backport methods");
-    backportedMethodRewriter.desugar(code);
-    timing.end();
+
+    if (desugaredLibraryRetargeter != null) {
+      // The desugaredLibraryRetargeter should run before backportedMethodRewriter to be able to
+      // perform backport rewriting before the methods can be retargeted.
+      timing.begin("Retarget library methods");
+      desugaredLibraryRetargeter.desugar(code);
+      timing.end();
+    }
+
+    if (backportedMethodRewriter != null) {
+      timing.begin("Rewrite backport methods");
+      backportedMethodRewriter.desugar(code);
+      timing.end();
+    }
 
     timing.begin("Desugar string concat");
     stringConcatRewriter.desugarStringConcats(method.method, code);
@@ -1599,15 +1630,20 @@
     if (method.isInitializer()) {
       if (method.isClassInitializer()) {
         StaticFieldValueAnalysis.run(
-            appView, code, classInitializerDefaultsResult, feedback, code.method);
+            appView, code, classInitializerDefaultsResult, feedback, code.method, timing);
       } else {
         instanceFieldInitializationInfos =
             InstanceFieldValueAnalysis.run(
-                appView, code, classInitializerDefaultsResult, feedback, code.method);
+                appView, code, classInitializerDefaultsResult, feedback, code.method, timing);
       }
     }
     methodOptimizationInfoCollector.collectMethodOptimizationInfo(
-        code.method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos);
+        code.method,
+        code,
+        feedback,
+        dynamicTypeOptimization,
+        instanceFieldInitializationInfos,
+        timing);
   }
 
   public void removeDeadCodeAndFinalizeIR(
@@ -1884,7 +1920,7 @@
     Instruction check = it.previous();
     assert addBefore == check;
     // Forced definition of const-zero
-    Value fixitValue = code.createValue(TypeLatticeElement.getInt());
+    Value fixitValue = code.createValue(TypeElement.getInt());
     Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue);
     fixitDefinition.setBlock(addBefore.getBlock());
     fixitDefinition.setPosition(addBefore.getPosition());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 06c4bdf..bb541cf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -34,7 +34,7 @@
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.CheckCast;
@@ -86,8 +86,8 @@
 
   private Value makeOutValue(Instruction insn, IRCode code) {
     if (insn.outValue() != null) {
-      TypeLatticeElement oldType = insn.outValue().getTypeLattice();
-      TypeLatticeElement newType =
+      TypeElement oldType = insn.outValue().getType();
+      TypeElement newType =
           oldType.fixupClassTypeReferences(appView.graphLense()::lookupType, appView);
       return code.createValue(newType, insn.getLocalInfo());
     }
@@ -123,8 +123,7 @@
             InvokeCustom newInvokeCustom =
                 new InvokeCustom(newCallSite, newOutValue, invokeCustom.inValues());
             iterator.replaceCurrentInstruction(newInvokeCustom);
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != invokeCustom.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != invokeCustom.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
@@ -135,8 +134,7 @@
           if (newHandle != handle) {
             Value newOutValue = makeOutValue(current, code);
             iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
@@ -250,8 +248,7 @@
             Invoke newInvoke =
                 Invoke.create(actualInvokeType, actualTarget, null, newOutValue, newInValues);
             iterator.replaceCurrentInstruction(newInvoke);
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
 
@@ -287,16 +284,14 @@
             Value newOutValue = makeOutValue(current, code);
             iterator.replaceCurrentInstruction(
                 new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(current.outValue().uniquePhiUsers());
             }
           } else if (actualField != field) {
             Value newOutValue = makeOutValue(instanceGet, code);
             iterator.replaceCurrentInstruction(
                 new InstanceGet(newOutValue, instanceGet.object(), actualField));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
@@ -328,15 +323,13 @@
             Value newOutValue = makeOutValue(current, code);
             iterator.replaceCurrentInstruction(
                 new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           } else if (actualField != field) {
             Value newOutValue = makeOutValue(staticGet, code);
             iterator.replaceCurrentInstruction(new StaticGet(newOutValue, actualField));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
@@ -399,11 +392,11 @@
               .replaceInstructionIfTypeChanged(type, NewInstance::new);
         } else if (current.outValue() != null) {
           // For all other instructions, substitute any changed type.
-          TypeLatticeElement typeLattice = current.outValue().getTypeLattice();
-          TypeLatticeElement substituted =
+          TypeElement typeLattice = current.outValue().getType();
+          TypeElement substituted =
               typeLattice.fixupClassTypeReferences(graphLense::lookupType, appView);
           if (substituted != typeLattice) {
-            current.outValue().setTypeLattice(substituted);
+            current.outValue().setType(substituted);
             affectedPhis.addAll(current.outValue().uniquePhiUsers());
           }
         }
@@ -434,7 +427,7 @@
       // TODO(b/150188380): Add API to insert a const instruction with a type lattice.
       Value rewrittenDefaultValue = iterator.insertConstIntInstruction(code, appView.options(), 0);
       iterator.next();
-      rewrittenDefaultValue.setTypeLattice(defaultValueLatticeElement(newType));
+      rewrittenDefaultValue.setType(defaultValueLatticeElement(newType));
       return rewrittenDefaultValue;
     }
     return initialValue;
@@ -455,11 +448,11 @@
     return false;
   }
 
-  private TypeLatticeElement defaultValueLatticeElement(DexType type) {
+  private TypeElement defaultValueLatticeElement(DexType type) {
     if (type.isPrimitiveType()) {
-      return TypeLatticeElement.fromDexType(type, null, appView);
+      return TypeElement.fromDexType(type, null, appView);
     }
-    return TypeLatticeElement.getNull();
+    return TypeElement.getNull();
   }
 
   public DexCallSite rewriteCallSite(DexCallSite callSite, DexEncodedMethod context) {
@@ -568,16 +561,22 @@
     for (int i = 0; i < bootstrapArgs.size(); i++) {
       DexValue argument = bootstrapArgs.get(i);
       DexValue newArgument = null;
-      if (argument instanceof DexValueMethodHandle) {
-        newArgument = rewriteDexValueMethodHandle(argument.asDexValueMethodHandle(), method, use);
-      } else if (argument instanceof DexValueMethodType) {
-        newArgument = rewriteDexMethodType(argument.asDexValueMethodType());
-      } else if (argument instanceof DexValueType) {
-        DexType oldType = ((DexValueType) argument).value;
-        DexType newType = appView.graphLense().lookupType(oldType);
-        if (newType != oldType) {
-          newArgument = new DexValueType(newType);
-        }
+      switch (argument.getValueKind()) {
+        case METHOD_HANDLE:
+          newArgument = rewriteDexValueMethodHandle(argument.asDexValueMethodHandle(), method, use);
+          break;
+        case METHOD_TYPE:
+          newArgument = rewriteDexMethodType(argument.asDexValueMethodType());
+          break;
+        case TYPE:
+          DexType oldType = argument.asDexValueType().value;
+          DexType newType = appView.graphLense().lookupType(oldType);
+          if (newType != oldType) {
+            newArgument = new DexValueType(newType);
+          }
+          break;
+        default:
+          // Intentionally empty.
       }
       if (newArgument != null) {
         if (newBootstrapArgs == null) {
@@ -686,7 +685,7 @@
         Instruction newInstruction = constructor.apply(newType, newOutValue);
         iterator.replaceCurrentInstruction(newInstruction);
         if (newOutValue != null) {
-          if (newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+          if (newOutValue.getType() != current.outValue().getType()) {
             affectedPhis.addAll(newOutValue.uniquePhiUsers());
           } else {
             assert current.hasInvariantOutType();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index ca144f7..8982b5a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
@@ -35,9 +35,9 @@
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
 
   void methodReturnsObjectWithUpperBoundType(
-      DexEncodedMethod method, AppView<?> appView, TypeLatticeElement type);
+      DexEncodedMethod method, AppView<?> appView, TypeElement type);
 
-  void methodReturnsObjectWithLowerBoundType(DexEncodedMethod method, ClassTypeLatticeElement type);
+  void methodReturnsObjectWithLowerBoundType(DexEncodedMethod method, ClassTypeElement type);
 
   void methodMayNotHaveSideEffects(DexEncodedMethod method);
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index 2e8bf77..b1f53e1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -7,10 +7,10 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
@@ -37,13 +37,13 @@
 
   private final AppView<?> appView;
   private final IdentifierNameStringMarker identifierNameStringMarker;
-  private final ClassTypeLatticeElement stringType;
+  private final ClassTypeElement stringType;
   private final ThrowingInfo throwingInfo;
 
   StringSwitchRemover(AppView<?> appView, IdentifierNameStringMarker identifierNameStringMarker) {
     this.appView = appView;
     this.identifierNameStringMarker = identifierNameStringMarker;
-    this.stringType = TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull());
+    this.stringType = TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
     this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
   }
 
@@ -109,7 +109,7 @@
       InvokeVirtual invokeInstruction =
           new InvokeVirtual(
               appView.dexItemFactory().stringMethods.equals,
-              code.createValue(PrimitiveTypeLatticeElement.getInt()),
+              code.createValue(PrimitiveTypeElement.getInt()),
               ImmutableList.of(theSwitch.value(), constStringInstruction.outValue()));
       invokeInstruction.setPosition(Position.syntheticNone());
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
index 7257d49..d7edc8a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -6,9 +6,9 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -59,33 +59,33 @@
     this.builder = builder;
   }
 
-  public static ValueTypeConstraint constraintForType(TypeLatticeElement type) {
+  public static ValueTypeConstraint constraintForType(TypeElement type) {
     // During constraint resolution the type bottom denotes references of not-yet-computed types.
     return type.isBottom() ? ValueTypeConstraint.OBJECT : ValueTypeConstraint.fromTypeLattice(type);
   }
 
-  public static TypeLatticeElement typeForConstraint(ValueTypeConstraint constraint) {
+  public static TypeElement typeForConstraint(ValueTypeConstraint constraint) {
     switch (constraint) {
       case INT_OR_FLOAT_OR_OBJECT:
-        return TypeLatticeElement.getTop();
+        return TypeElement.getTop();
       case OBJECT:
         // If the constraint is object the concrete lattice type will need to be computed.
         // We mark the object type as bottom for now, with the implication that it is of type
         // reference but that it should not contribute to the computation of its join
         // (in potentially self-referencing phis).
-        return TypeLatticeElement.getBottom();
+        return TypeElement.getBottom();
       case INT:
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case INT_OR_FLOAT:
-        return TypeLatticeElement.getSingle();
+        return TypeElement.getSingle();
       case LONG:
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       case LONG_OR_DOUBLE:
-        return TypeLatticeElement.getWide();
+        return TypeElement.getWide();
       default:
         throw new Unreachable("Unexpected constraint type: " + constraint);
     }
@@ -106,7 +106,7 @@
     List<Value> impreciseValues = new ArrayList<>();
     for (BasicBlock block : code.blocks) {
       for (Phi phi : block.getPhis()) {
-        if (!phi.getTypeLattice().isPreciseType()) {
+        if (!phi.getType().isPreciseType()) {
           impreciseValues.add(phi);
         }
         for (Value value : phi.getOperands()) {
@@ -114,8 +114,7 @@
         }
       }
       for (Instruction instruction : block.getInstructions()) {
-        if (instruction.outValue() != null
-            && !instruction.outValue().getTypeLattice().isPreciseType()) {
+        if (instruction.outValue() != null && !instruction.outValue().getType().isPreciseType()) {
           impreciseValues.add(instruction.outValue());
         }
 
@@ -151,7 +150,7 @@
                   "Cannot determine precise type for value: "
                       + stillImprecise.get(0)
                       + ", its imprecise type is: "
-                      + stillImprecise.get(0).getTypeLattice(),
+                      + stillImprecise.get(0).getType(),
                   code.origin,
                   new MethodPosition(code.method.method)));
     }
@@ -161,7 +160,7 @@
     ArrayList<Value> stillImprecise = new ArrayList<>(impreciseValues.size());
     for (Value value : impreciseValues) {
       builder.constrainType(value, getCanonicalTypeConstraint(value, finished));
-      if (!value.getTypeLattice().isPreciseType()) {
+      if (!value.getType().isPreciseType()) {
         stillImprecise.add(value);
       }
     }
@@ -173,10 +172,10 @@
     assert !type.isPrecise();
     Value canonical = canonical(value);
     ValueTypeConstraint constraint;
-    if (array.getTypeLattice().isArrayType()) {
+    if (array.getType().isArrayType()) {
       // If the array type is known it uniquely defines the actual member type.
-      ArrayTypeLatticeElement arrayType = array.getTypeLattice().asArrayTypeLatticeElement();
-      constraint = ValueTypeConstraint.fromTypeLattice(arrayType.getArrayMemberTypeAsValueType());
+      ArrayTypeElement arrayType = array.getType().asArrayType();
+      constraint = ValueTypeConstraint.fromTypeLattice(arrayType.getMemberTypeAsValueType());
     } else {
       // If not, e.g., the array input is null, the canonical value determines the final type.
       constraint = getCanonicalTypeConstraint(canonical, true);
@@ -192,7 +191,7 @@
   }
 
   private ValueTypeConstraint getCanonicalTypeConstraint(Value value, boolean finished) {
-    ValueTypeConstraint type = constraintForType(canonical(value).getTypeLattice());
+    ValueTypeConstraint type = constraintForType(canonical(value).getType());
     switch (type) {
       case INT_OR_FLOAT_OR_OBJECT:
         // There is never a second round for resolving object vs single.
@@ -231,7 +230,7 @@
         ArrayPut put = user.asArrayPut();
         assert value == put.value();
         assert !put.getMemberType().isPrecise();
-        assert put.array().getTypeLattice().isDefinitelyNull();
+        assert put.array().getType().isDefinitelyNull();
       } else {
         assert false;
       }
@@ -244,8 +243,8 @@
     if (canonical1 == canonical2) {
       return;
     }
-    TypeLatticeElement type1 = canonical1.getTypeLattice();
-    TypeLatticeElement type2 = canonical2.getTypeLattice();
+    TypeElement type1 = canonical1.getType();
+    TypeElement type2 = canonical2.getType();
     if (type1.isPreciseType() && type2.isPreciseType()) {
       if (type1 != type2 && constraintForType(type1) != constraintForType(type2)) {
         throw new CompilationError(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index fd0e452..43db91d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
@@ -31,7 +30,6 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -54,13 +52,10 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
@@ -113,15 +108,17 @@
       if (androidApp != null) {
         DexApplication app =
             new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
-        appInfo =
-            options.desugaredLibraryConfiguration.getRewritePrefix().isEmpty()
-                ? new AppInfo(app)
-                : new AppInfoWithClassHierarchy(app);
+        appInfo = new AppInfoWithClassHierarchy(app);
       }
       AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
       BackportedMethodRewriter.RewritableMethods rewritableMethods =
           new BackportedMethodRewriter.RewritableMethods(options, appView);
       rewritableMethods.visit(methods::add);
+      if (appInfo != null) {
+        DesugaredLibraryRetargeter desugaredLibraryRetargeter =
+            new DesugaredLibraryRetargeter(appView);
+        desugaredLibraryRetargeter.visit(methods::add);
+      }
       return methods;
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
@@ -146,49 +143,10 @@
           && !invoke.isInvokeStatic()) {
         continue;
       }
+
       MethodProvider provider = getMethodProviderOrNull(invoke.getInvokedMethod());
       if (provider == null) {
-        if (!rewritableMethods.matchesVirtualRewrite(invoke.getInvokedMethod())) {
-          continue;
-        }
-        // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
-        ResolutionResult resolutionResult =
-            appView
-                .appInfo()
-                .resolveMethod(invoke.getInvokedMethod().holder, invoke.getInvokedMethod());
-        if (resolutionResult.isFailedResolution()) {
-          continue;
-        }
-        DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
-        assert singleTarget != null;
-        provider = getMethodProviderOrNull(singleTarget.method);
-        if (provider == null) {
-          continue;
-        }
-      }
-
-      // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
-      // infinite loops. We do direct resolution. This is a very uncommon case.
-      if (invoke.isInvokeSuper()
-          && rewritableMethods.matchesVirtualRewrite(invoke.getInvokedMethod())) {
-        DexEncodedMethod dexEncodedMethod =
-            appView
-                .appInfo()
-                .withClassHierarchy()
-                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.method.holder);
-        // Final methods can be rewritten as a normal invoke.
-        if (dexEncodedMethod != null && !dexEncodedMethod.isFinal()) {
-          DexMethod retargetMethod =
-              appView
-                  .options()
-                  .desugaredLibraryConfiguration
-                  .retargetMethod(dexEncodedMethod.method, appView);
-          if (retargetMethod != null) {
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
-          }
-          continue;
-        }
+        continue;
       }
 
       provider.rewriteInvoke(invoke, iterator, code, appView, affectedValues);
@@ -222,11 +180,6 @@
     if (!enabled) {
       return;
     }
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      synthesizeEmulatedDispatchMethods(builder);
-    } else {
-      addInterfacesAndForwardingMethods(executorService);
-    }
     if (holders.isEmpty()) {
       return;
     }
@@ -243,8 +196,9 @@
       }
     }
 
-    MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(
-        Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
+    MethodAccessFlags flags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
     ClassAccessFlags classAccessFlags =
         ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
     // Generate the utility classes in a loop since utility classes can require the
@@ -262,7 +216,12 @@
       Code code = provider.generateTemplateMethod(appView.options(), method);
       DexEncodedMethod dexEncodedMethod =
           new DexEncodedMethod(
-              method, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code, true);
+              method,
+              flags,
+              DexAnnotationSet.empty(),
+              ParameterAnnotationsList.empty(),
+              code,
+              true);
       boolean addToMainDexList =
           referencingClasses.stream()
               .anyMatch(clazz -> appView.appInfo().isInMainDexList(clazz.type));
@@ -273,140 +232,27 @@
               method.holder,
               dexEncodedMethod,
               "java8 methods utility class",
-              addToMainDexList);
+              addToMainDexList,
+              appView);
       // The following may add elements to methodsProviders.
       converter.optimizeSynthesizedClass(utilityClass, executorService);
     }
   }
 
-  private void addInterfacesAndForwardingMethods(ExecutorService executorService)
-      throws ExecutionException {
-    assert !appView.options().isDesugaredLibraryCompilation();
-    Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
-    for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
-      map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
-      map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
-    }
-    List<DexEncodedMethod> addedMethods = new ArrayList<>();
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (clazz.superType == null) {
-        assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
-        continue;
-      }
-      DexClass dexClass = appView.definitionFor(clazz.superType);
-      // Only performs computation if superclass is a library class, but not object to filter out
-      // the most common case.
-      if (dexClass != null
-          && dexClass.isLibraryClass()
-          && dexClass.type != appView.dexItemFactory().objectType) {
-        for (DexType dexType : map.keySet()) {
-          if (inherit(dexClass.asLibraryClass(), dexType)) {
-            addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
-          }
-        }
-      }
-    }
-    if (addedMethods.isEmpty()) {
-      return;
-    }
-    converter.processMethodsConcurrently(addedMethods, executorService);
-  }
-
-  private boolean inherit(DexLibraryClass clazz, DexType typeToInherit) {
-    DexLibraryClass current = clazz;
-    while (current.type != appView.dexItemFactory().objectType) {
-      if (current.type == typeToInherit) {
-        return true;
-      }
-      current = appView.definitionFor(current.superType).asLibraryClass();
-    }
-    return false;
-  }
-
-  private List<DexEncodedMethod> addInterfacesAndForwardingMethods(
-      DexProgramClass clazz, List<DexMethod> dexMethods) {
-    // BackportedMethodRewriter emulate dispatch: insertion of a marker interface & forwarding
-    // methods.
-    // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
-    // applies up to 24.
-    List<DexEncodedMethod> newForwardingMethods = new ArrayList<>();
-    for (DexMethod dexMethod : dexMethods) {
-      DexType[] newInterfaces = Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
-      newInterfaces[newInterfaces.length - 1] =
-          BackportedMethodRewriter.dispatchInterfaceTypeFor(appView, dexMethod);
-      clazz.interfaces = new DexTypeList(newInterfaces);
-      DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
-      if (dexEncodedMethod == null) {
-        DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
-        clazz.addVirtualMethod(newMethod);
-        newForwardingMethods.add(newMethod);
-      }
-    }
-    return newForwardingMethods;
-  }
-
-  private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
-    // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
-    // even if this results in invalid code, these classes are never desugared.
-    // In desugared library, emulated interface methods can be overridden by retarget lib members.
-    DexMethod forwardMethod =
-        appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
-    assert forwardMethod != null && forwardMethod != target;
-    return DexEncodedMethod.createDesugaringForwardingMethod(
-        appView.definitionFor(target), clazz, forwardMethod, appView.dexItemFactory());
-  }
-
-  private void synthesizeEmulatedDispatchMethods(Builder<?> builder) {
-    assert appView.options().isDesugaredLibraryCompilation();
-    if (rewritableMethods.getEmulatedDispatchMethods().isEmpty()) {
-      return;
-    }
-    ClassAccessFlags itfAccessFlags =
-        ClassAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_PUBLIC
-                | Constants.ACC_SYNTHETIC
-                | Constants.ACC_ABSTRACT
-                | Constants.ACC_INTERFACE);
-    ClassAccessFlags holderAccessFlags =
-        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
-    for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
-      // Dispatch interface.
-      DexType interfaceType = dispatchInterfaceTypeFor(appView, emulatedDispatchMethod);
-      DexEncodedMethod itfMethod =
-          generateInterfaceDispatchMethod(emulatedDispatchMethod, interfaceType);
-      synthesizeClassWithUniqueMethod(
-          builder,
-          itfAccessFlags,
-          interfaceType,
-          itfMethod,
-          "desugared library dispatch interface",
-          false);
-      // Dispatch holder.
-      DexType holderType = dispatchHolderTypeFor(appView, emulatedDispatchMethod);
-      DexEncodedMethod dispatchMethod =
-          generateHolderDispatchMethod(emulatedDispatchMethod, holderType, itfMethod.method);
-      synthesizeClassWithUniqueMethod(
-          builder,
-          holderAccessFlags,
-          holderType,
-          dispatchMethod,
-          "desugared library dispatch class",
-          false);
-    }
-  }
-
-  private DexProgramClass synthesizeClassWithUniqueMethod(
+  static DexProgramClass synthesizeClassWithUniqueMethod(
       Builder<?> builder,
       ClassAccessFlags accessFlags,
       DexType type,
       DexEncodedMethod uniqueMethod,
       String origin,
-      boolean addToMainDexList) {
+      boolean addToMainDexList,
+      AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
     DexProgramClass newClass =
         new DexProgramClass(
             type,
             null,
-            new SynthesizedOrigin(origin, getClass()),
+            new SynthesizedOrigin(origin, BackportedMethodRewriter.class),
             accessFlags,
             factory.objectType,
             DexTypeList.empty(),
@@ -425,133 +271,45 @@
                 ? DexEncodedMethod.EMPTY_ARRAY
                 : new DexEncodedMethod[] {uniqueMethod},
             factory.getSkipNameValidationForTesting(),
-            getChecksumSupplier(uniqueMethod));
+            getChecksumSupplier(uniqueMethod, appView));
     appView.appInfo().addSynthesizedClass(newClass);
     builder.addSynthesizedClass(newClass, addToMainDexList);
     return newClass;
   }
 
-  private DexEncodedMethod generateInterfaceDispatchMethod(
-      DexMethod emulatedDispatchMethod, DexType interfaceType) {
-    MethodAccessFlags flags =
-        MethodAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
-    DexMethod newMethod =
-        factory.createMethod(
-            interfaceType, emulatedDispatchMethod.proto, emulatedDispatchMethod.name);
-    return new DexEncodedMethod(
-        newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null, true);
-  }
-
-  private DexEncodedMethod generateHolderDispatchMethod(
-      DexMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
-    // The method should look like:
-    // static foo(rcvr, arg0, arg1) {
-    //    if (rcvr instanceof interfaceType) {
-    //      return invoke-interface receiver.foo(arg0, arg1);
-    //    } else {
-    //      return DesugarX.foo(rcvr, arg0, arg1)
-    //    }
-    // We do not deal with complex cases (multiple retargeting of the same signature in the
-    // same inheritance tree, etc., since they do not happen in the most common desugared library.
-    DexMethod desugarMethod =
-        appView
-            .options()
-            .desugaredLibraryConfiguration
-            .retargetMethod(emulatedDispatchMethod, appView);
-    assert desugarMethod != null; // This method is reached only for retarget core lib members.
-    DexMethod newMethod =
-        appView
-            .dexItemFactory()
-            .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.name);
-    return DexEncodedMethod.toEmulateDispatchLibraryMethod(
-        emulatedDispatchMethod.holder,
-        newMethod,
-        desugarMethod,
-        itfMethod,
-        Collections.emptyList(),
-        appView);
-  }
-
-  private ChecksumSupplier getChecksumSupplier(DexEncodedMethod method) {
+  private static ChecksumSupplier getChecksumSupplier(DexEncodedMethod method, AppView<?> appView) {
     if (!appView.options().encodeChecksums) {
       return DexProgramClass::invalidChecksumRequest;
     }
     return c -> method.method.hashCode();
   }
-
-  public static DexType dispatchInterfaceTypeFor(AppView<?> appView, DexMethod method) {
-    return dispatchTypeFor(appView, method, "dispatchInterface");
-  }
-
-  static DexType dispatchHolderTypeFor(AppView<?> appView, DexMethod method) {
-    return dispatchTypeFor(appView, method, "dispatchHolder");
-  }
-
-  private static DexType dispatchTypeFor(AppView<?> appView, DexMethod method, String suffix) {
-    String desugaredLibPrefix =
-        appView.options().desugaredLibraryConfiguration.getSynthesizedLibraryClassesPackagePrefix();
-    String descriptor =
-        "L"
-            + desugaredLibPrefix
-            + UTILITY_CLASS_NAME_PREFIX
-            + '$'
-            + method.holder.getName()
-            + '$'
-            + method.name
-            + '$'
-            + suffix
-            + ';';
-    return appView.dexItemFactory().createType(descriptor);
-  }
-
+  
   private MethodProvider getMethodProviderOrNull(DexMethod method) {
     DexMethod original = appView.graphLense().getOriginalMethodSignature(method);
     assert original != null;
-    Map<DexType, DexType> backportCoreLibraryMembers =
-        appView.options().desugaredLibraryConfiguration.getBackportCoreLibraryMember();
-    if (backportCoreLibraryMembers.containsKey(original.holder)) {
-      DexType newHolder = backportCoreLibraryMembers.get(original.holder);
-      DexMethod newMethod =
-          appView.dexItemFactory().createMethod(newHolder, original.proto, original.name);
-      MethodProvider provider = rewritableMethods.getProvider(newMethod);
-      if (provider != null) {
-        return provider;
-      }
-      RetargetCoreLibraryMethodProvider extraProvider =
-          new RetargetCoreLibraryMethodProvider(newHolder, original, true);
-      // TODO(b/139788786): cache this entry, but without writing into a lock free structure.
-      // rewritableMethods.addProvider(extraProvider);
-      return extraProvider;
-    }
     return rewritableMethods.getProvider(original);
   }
 
-  public static void checkForAssumedLibraryTypes(AppView<?> appView) {
-    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
-    for (DexString methodName : retargetCoreLibMember.keySet()) {
-      for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
-        DexClass typeClass = appView.definitionFor(inType);
-        if (typeClass == null) {
-          RewritableMethods.warnMissingRetargetCoreLibraryMember(inType, appView);
-        }
-      }
-    }
-  }
-
   private static final class RewritableMethods {
 
     // Map backported method to a provider for creating the actual target method (with code).
     private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
-    // Map virtualRewrites hold a methodName->method mapping for virtual methods which are
-    // rewritten while the holder is non final but no superclass implement the method. In this case
-    // d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
-    private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
-    // non final virtual library methods requiring generation of emulated dispatch.
-    private final Set<DexMethod> emulatedDispatchMethods = Sets.newHashSet();
 
     RewritableMethods(InternalOptions options, AppView<?> appView) {
+
+      if (options.testing.forceLibBackportsInL8CfToCf) {
+        DexItemFactory factory = options.itemFactory;
+        initializeJava9OptionalMethodProviders(factory);
+        initializeJava10OptionalMethodProviders(factory);
+        initializeJava11OptionalMethodProviders(factory);
+        initializeStreamMethodProviders(factory);
+        return;
+      }
+
+      if (!options.shouldBackportMethods()) {
+        return;
+      }
+
       DexItemFactory factory = options.itemFactory;
 
       if (options.minApiLevel < AndroidApiLevel.K.getLevel()) {
@@ -565,9 +323,9 @@
       }
 
       // The following providers are currently not implemented at any API level in Android.
-      // They however require the Optional/Stream class to be present, either through
-      // desugared libraries or natively. If Optional/Stream class is not present,
-      // we do not desugar to avoid confusion in error messages.
+      // They however require the Optional/Stream class to be present, either through desugared
+      // libraries or natively. If Optional/Stream class is not present, we do not desugar to
+      // avoid confusion in error messages.
       if (appView.rewritePrefix.hasRewrittenType(factory.optionalType, appView)
           || options.minApiLevel >= AndroidApiLevel.N.getLevel()) {
         initializeJava9OptionalMethodProviders(factory);
@@ -583,27 +341,6 @@
       initializeJava9MethodProviders(factory);
       initializeJava10MethodProviders(factory);
       initializeJava11MethodProviders(factory);
-
-      if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
-        initializeRetargetCoreLibraryMembers(appView);
-      }
-    }
-
-    boolean matchesVirtualRewrite(DexMethod method) {
-      List<DexMethod> dexMethods = virtualRewrites.get(method.name);
-      if (dexMethods == null) {
-        return false;
-      }
-      for (DexMethod dexMethod : dexMethods) {
-        if (method.match(dexMethod)) {
-          return true;
-        }
-      }
-      return false;
-    }
-
-    public Set<DexMethod> getEmulatedDispatchMethods() {
-      return emulatedDispatchMethods;
     }
 
     boolean isEmpty() {
@@ -668,8 +405,9 @@
 
       // int Objects.compare(T a, T b, Comparator<? super T> c)
       name = factory.createString("compare");
-      proto = factory.createProto(factory.intType, factory.objectType, factory.objectType,
-          factory.comparatorType);
+      proto =
+          factory.createProto(
+              factory.intType, factory.objectType, factory.objectType, factory.comparatorType);
       method = factory.createMethod(type, proto, name);
       addProvider(new MethodGenerator(method, BackportedMethods::ObjectsMethods_compare));
 
@@ -1242,8 +980,9 @@
 
       // String String.join(CharSequence, CharSequence...)
       name = factory.createString("join");
-      proto = factory.createProto(factory.stringType, factory.charSequenceType,
-          factory.charSequenceArrayType);
+      proto =
+          factory.createProto(
+              factory.stringType, factory.charSequenceType, factory.charSequenceArrayType);
       method = factory.createMethod(type, proto, name);
       addProvider(
           new MethodGenerator(method, BackportedMethods::StringMethods_joinArray, "joinArray"));
@@ -1278,19 +1017,13 @@
         name = factory.createString("multiplyFull");
         proto = factory.createProto(factory.longType, factory.intType, factory.intType);
         method = factory.createMethod(type, proto, name);
-        addProvider(
-            new MethodGenerator(
-                method,
-                BackportedMethods::MathMethods_multiplyFull));
+        addProvider(new MethodGenerator(method, BackportedMethods::MathMethods_multiplyFull));
 
         // long {Math,StrictMath}.multiplyHigh(long, long)
         name = factory.createString("multiplyHigh");
         proto = factory.createProto(factory.longType, factory.longType, factory.longType);
         method = factory.createMethod(type, proto, name);
-        addProvider(
-            new MethodGenerator(
-                method,
-                BackportedMethods::MathMethods_multiplyHigh));
+        addProvider(new MethodGenerator(method, BackportedMethods::MathMethods_multiplyHigh));
 
         // long {Math,StrictMath}.floorDiv(long, int)
         name = factory.createString("floorDiv");
@@ -1384,8 +1117,7 @@
       proto = factory.createProto(type, factory.objectArrayType);
       method = factory.createMethod(type, proto, name);
       addProvider(
-          new MethodGenerator(
-              method, BackportedMethods::CollectionMethods_listOfArray, "ofArray"));
+          new MethodGenerator(method, BackportedMethods::CollectionMethods_listOfArray, "ofArray"));
 
       // Set<E> Set.of(<args>) for 0 to 10 arguments and Set.of(E[])
       type = factory.setType;
@@ -1405,8 +1137,7 @@
       proto = factory.createProto(type, factory.objectArrayType);
       method = factory.createMethod(type, proto, name);
       addProvider(
-          new MethodGenerator(
-              method, BackportedMethods::CollectionMethods_setOfArray, "ofArray"));
+          new MethodGenerator(method, BackportedMethods::CollectionMethods_setOfArray, "ofArray"));
 
       // Map<K, V> Map.of(<K, V args>) for 0 to 10 pairs and Map.ofEntries(Map.Entry<K, V>[])
       type = factory.mapType;
@@ -1553,24 +1284,24 @@
       // Optional{void,Int,Long,Double}.stream()
       DexType[] optionalTypes =
           new DexType[] {
-              optionalType,
-              factory.optionalDoubleType,
-              factory.optionalLongType,
-              factory.optionalIntType,
+            optionalType,
+            factory.optionalDoubleType,
+            factory.optionalLongType,
+            factory.optionalIntType,
           };
       DexType[] streamReturnTypes =
           new DexType[] {
-              factory.streamType,
-              factory.createType(factory.createString("Ljava/util/stream/DoubleStream;")),
-              factory.createType(factory.createString("Ljava/util/stream/LongStream;")),
-              factory.createType(factory.createString("Ljava/util/stream/IntStream;")),
+            factory.streamType,
+            factory.createType(factory.createString("Ljava/util/stream/DoubleStream;")),
+            factory.createType(factory.createString("Ljava/util/stream/LongStream;")),
+            factory.createType(factory.createString("Ljava/util/stream/IntStream;")),
           };
       TemplateMethodFactory[] streamMethodFactories =
           new TemplateMethodFactory[] {
-              BackportedMethods::OptionalMethods_stream,
-              BackportedMethods::OptionalMethods_streamDouble,
-              BackportedMethods::OptionalMethods_streamLong,
-              BackportedMethods::OptionalMethods_streamInt,
+            BackportedMethods::OptionalMethods_stream,
+            BackportedMethods::OptionalMethods_streamDouble,
+            BackportedMethods::OptionalMethods_streamLong,
+            BackportedMethods::OptionalMethods_streamInt,
           };
       name = factory.createString("stream");
       for (int i = 0; i < optionalTypes.length; i++) {
@@ -1579,24 +1310,20 @@
         proto = factory.createProto(streamReturnType);
         method = factory.createMethod(optional, proto, name);
         addProvider(
-            new StatifyingMethodGenerator(
-                method, streamMethodFactories[i], "stream", optional));
+            new StatifyingMethodGenerator(method, streamMethodFactories[i], "stream", optional));
       }
 
       // Optional{void,Int,Long,Double}.ifPresentOrElse(consumer,runnable)
       DexType[] consumerTypes =
-          new DexType[]{
-              factory.consumerType,
-              factory.doubleConsumer,
-              factory.longConsumer,
-              factory.intConsumer
+          new DexType[] {
+            factory.consumerType, factory.doubleConsumer, factory.longConsumer, factory.intConsumer
           };
       TemplateMethodFactory[] methodFactories =
-          new TemplateMethodFactory[]{
-              BackportedMethods::OptionalMethods_ifPresentOrElse,
-              BackportedMethods::OptionalMethods_ifPresentOrElseDouble,
-              BackportedMethods::OptionalMethods_ifPresentOrElseLong,
-              BackportedMethods::OptionalMethods_ifPresentOrElseInt
+          new TemplateMethodFactory[] {
+            BackportedMethods::OptionalMethods_ifPresentOrElse,
+            BackportedMethods::OptionalMethods_ifPresentOrElseDouble,
+            BackportedMethods::OptionalMethods_ifPresentOrElseLong,
+            BackportedMethods::OptionalMethods_ifPresentOrElseInt
           };
       for (int i = 0; i < optionalTypes.length; i++) {
         DexType optional = optionalTypes[i];
@@ -1613,17 +1340,14 @@
       // Optional{void,Int,Long,Double}.orElseThrow()
       DexType[] optionalTypes =
           new DexType[] {
-              factory.optionalType,
-              factory.optionalDoubleType,
-              factory.optionalLongType,
-              factory.optionalIntType,
+            factory.optionalType,
+            factory.optionalDoubleType,
+            factory.optionalLongType,
+            factory.optionalIntType,
           };
       DexType[] returnTypes =
           new DexType[] {
-              factory.objectType,
-              factory.doubleType,
-              factory.longType,
-              factory.intType,
+            factory.objectType, factory.doubleType, factory.longType, factory.intType,
           };
       MethodInvokeRewriter[] rewriters =
           new MethodInvokeRewriter[] {
@@ -1644,17 +1368,17 @@
       // Optional{void,Int,Long,Double}.isEmpty()
       DexType[] optionalTypes =
           new DexType[] {
-              factory.optionalType,
-              factory.optionalDoubleType,
-              factory.optionalLongType,
-              factory.optionalIntType,
+            factory.optionalType,
+            factory.optionalDoubleType,
+            factory.optionalLongType,
+            factory.optionalIntType,
           };
       TemplateMethodFactory[] methodFactories =
-          new TemplateMethodFactory[]{
-              BackportedMethods::OptionalMethods_isEmpty,
-              BackportedMethods::OptionalMethods_isEmptyDouble,
-              BackportedMethods::OptionalMethods_isEmptyLong,
-              BackportedMethods::OptionalMethods_isEmptyInt
+          new TemplateMethodFactory[] {
+            BackportedMethods::OptionalMethods_isEmpty,
+            BackportedMethods::OptionalMethods_isEmptyDouble,
+            BackportedMethods::OptionalMethods_isEmptyLong,
+            BackportedMethods::OptionalMethods_isEmptyInt
           };
       DexString name = factory.createString("isEmpty");
       for (int i = 0; i < optionalTypes.length; i++) {
@@ -1675,76 +1399,7 @@
       DexProto proto = factory.createProto(factory.streamType, factory.objectType);
       DexMethod method = factory.createMethod(streamType, proto, name);
       addProvider(
-          new MethodGenerator(
-              method, BackportedMethods::StreamMethods_ofNullable, "ofNullable"));
-    }
-
-    private static void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
-      StringDiagnostic warning =
-          new StringDiagnostic(
-              "Cannot retarget core library member "
-                  + type.getName()
-                  + " because the class is missing.");
-      appView.options().reporter.warning(warning);
-    }
-
-    private void initializeRetargetCoreLibraryMembers(AppView<?> appView) {
-      assert appView.appInfo().hasClassHierarchy()
-          : "Class hierarchy required for desugared library.";
-      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-          appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
-      for (DexString methodName : retargetCoreLibMember.keySet()) {
-        for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
-          DexClass typeClass = appView.definitionFor(inType);
-          if (typeClass != null) {
-            DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
-            List<DexEncodedMethod> found = findDexEncodedMethodsWithName(methodName, typeClass);
-            for (DexEncodedMethod encodedMethod : found) {
-              if (!encodedMethod.isStatic()) {
-                virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
-                virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
-                if (InterfaceMethodRewriter.isEmulatedInterfaceDispatch(appView, encodedMethod)) {
-                  // In this case interface method rewriter takes care of it.
-                  continue;
-                } else if (!encodedMethod.isFinal()) {
-                  // Virtual rewrites require emulated dispatch for inheritance.
-                  // The call is rewritten to the dispatch holder class instead.
-                  handleEmulateDispatch(appView, encodedMethod.method);
-                  newHolder = dispatchHolderTypeFor(appView, encodedMethod.method);
-                }
-              }
-              DexProto proto = encodedMethod.method.proto;
-              DexMethod method = appView.dexItemFactory().createMethod(inType, proto, methodName);
-              addProvider(
-                  new RetargetCoreLibraryMethodProvider(
-                      newHolder, method, encodedMethod.isStatic()));
-            }
-          }
-        }
-      }
-    }
-
-    private List<DexEncodedMethod> findDexEncodedMethodsWithName(
-        DexString methodName, DexClass clazz) {
-      List<DexEncodedMethod> found = new ArrayList<>();
-      for (DexEncodedMethod encodedMethod : clazz.methods()) {
-        if (encodedMethod.method.name == methodName) {
-          found.add(encodedMethod);
-        }
-      }
-      assert found.size() > 0 : "Should have found a method (library specifications).";
-      return found;
-    }
-
-    private void handleEmulateDispatch(AppView<?> appView, DexMethod method) {
-      emulatedDispatchMethods.add(method);
-      if (!appView.options().isDesugaredLibraryCompilation()) {
-        // Add rewrite rules so keeps rules are correctly generated in the program.
-        DexType dispatchInterfaceType = dispatchInterfaceTypeFor(appView, method);
-        appView.rewritePrefix.rewriteType(dispatchInterfaceType, dispatchInterfaceType);
-        DexType dispatchHolderType = dispatchHolderTypeFor(appView, method);
-        appView.rewritePrefix.rewriteType(dispatchHolderType, dispatchHolderType);
-      }
+          new MethodGenerator(method, BackportedMethods::StreamMethods_ofNullable, "ofNullable"));
     }
 
     private void addProvider(MethodProvider generator) {
@@ -1779,52 +1434,6 @@
     public abstract boolean requiresGenerationOfCode();
   }
 
-  private static class RetargetCoreLibraryMethodProvider extends MethodProvider {
-
-    private final DexType newHolder;
-    private DexMethod targetMethod;
-    private boolean isStatic;
-
-    RetargetCoreLibraryMethodProvider(DexType newHolder, DexMethod method, boolean isStatic) {
-      super(method);
-      this.newHolder = newHolder;
-      this.isStatic = isStatic;
-    }
-
-    @Override
-    public void rewriteInvoke(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        IRCode code,
-        AppView<?> appView,
-        Set<Value> affectedValues) {
-      iterator.replaceCurrentInstruction(
-          new InvokeStatic(provideMethod(appView), invoke.outValue(), invoke.inValues()));
-    }
-
-    @Override
-    public DexMethod provideMethod(AppView<?> appView) {
-      if (targetMethod != null) {
-        return targetMethod;
-      }
-      DexItemFactory factory = appView.dexItemFactory();
-      DexProto newProto =
-          isStatic ? method.proto : factory.prependTypeToProto(method.holder, method.proto);
-      targetMethod = factory.createMethod(newHolder, newProto, method.name);
-      return targetMethod;
-    }
-
-    @Override
-    public Code generateTemplateMethod(InternalOptions options, DexMethod method) {
-      throw new Unreachable("Does not generate any method.");
-    }
-
-    @Override
-    public boolean requiresGenerationOfCode() {
-      return false;
-    }
-  }
-
   private static final class InvokeRewriter extends MethodProvider {
 
     private final MethodInvokeRewriter rewriter;
@@ -1896,14 +1505,9 @@
       DexItemFactory factory = appView.dexItemFactory();
       String unqualifiedName = method.holder.getName();
       // Avoid duplicate class names between core lib dex file and program dex files.
-      String desugaredLibPrefix =
-          appView
-              .options()
-              .desugaredLibraryConfiguration
-              .getSynthesizedLibraryClassesPackagePrefix(appView);
       String descriptor =
           "L"
-              + desugaredLibPrefix
+              + appView.options().synthesizedClassPrefix
               + UTILITY_CLASS_NAME_PREFIX
               + '$'
               + unqualifiedName
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 76e97df..3d16d19 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -229,7 +229,7 @@
     for (DexProgramClass clazz : newSyntheticMethods.keySet()) {
       List<DexEncodedMethod> newForwardingMethods = newSyntheticMethods.get(clazz);
       if (newForwardingMethods != null) {
-        clazz.appendVirtualMethods(newForwardingMethods);
+        clazz.addVirtualMethods(newForwardingMethods);
         newForwardingMethods.forEach(newSynthesizedMethodConsumer);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 3cadabf..2be1832 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -16,6 +16,9 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -96,7 +99,7 @@
           method.annotations().keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation)));
     }
     // Add the newly constructed methods to the class.
-    clazz.appendVirtualMethods(covariantReturnTypeMethods);
+    clazz.addVirtualMethods(covariantReturnTypeMethods);
   }
 
   // Processes all the dalvik.annotation.codegen.CovariantReturnType and dalvik.annotation.codegen.
@@ -201,22 +204,22 @@
       String name = element.name.toString();
       if (annotation.type == factory.annotationCovariantReturnType) {
         if (name.equals("returnType")) {
-          if (!(element.value instanceof DexValue.DexValueType)) {
+          DexValueType dexValueType = element.value.asDexValueType();
+          if (dexValueType == null) {
             throw new CompilationError(
                 String.format(
                     "Expected element \"returnType\" of CovariantReturnType annotation to "
                         + "reference a type (method: \"%s\", was: %s)",
                     method.toSourceString(), element.value.getClass().getCanonicalName()));
           }
-
-          DexValue.DexValueType dexValueType = (DexValue.DexValueType) element.value;
           covariantReturnTypes.add(dexValueType.value);
         } else if (name.equals("presentAfter")) {
           hasPresentAfterElement = true;
         }
       } else {
         if (name.equals("value")) {
-          if (!(element.value instanceof DexValue.DexValueArray)) {
+          DexValueArray array = element.value.asDexValueArray();
+          if (array == null) {
             throw new CompilationError(
                 String.format(
                     "Expected element \"value\" of CovariantReturnTypes annotation to "
@@ -224,11 +227,10 @@
                     method.toSourceString(), element.value.getClass().getCanonicalName()));
           }
 
-          DexValue.DexValueArray array = (DexValue.DexValueArray) element.value;
           // Handle the inner dalvik.annotation.codegen.CovariantReturnType annotations recursively.
           for (DexValue value : array.getValues()) {
-            assert value instanceof DexValue.DexValueAnnotation;
-            DexValue.DexValueAnnotation innerAnnotation = (DexValue.DexValueAnnotation) value;
+            assert value.isDexValueAnnotation();
+            DexValueAnnotation innerAnnotation = value.asDexValueAnnotation();
             getCovariantReturnTypesFromAnnotation(
                 clazz, method, innerAnnotation.value, covariantReturnTypes);
           }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index ceed9ef..c81eedb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -29,14 +29,15 @@
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -70,7 +71,9 @@
   // Enqueuer and not during IR processing.
   private final Mode mode;
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
-  private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new HashMap<>();
+  private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new IdentityHashMap<>();
+  private final Map<DexClass, List<DexEncodedMethod>> pendingCallBackMethods =
+      new IdentityHashMap<>();
   private final Set<DexMethod> trackedCallBackAPIs;
   private final Set<DexMethod> trackedAPIs;
 
@@ -172,7 +175,9 @@
     // library type), but the enqueuer cannot see that.
     // To avoid too much computation we first look if the method would need to be rewritten if
     // it would override a library method, then check if it overrides a library method.
-    if (encodedMethod.isPrivateMethod() || encodedMethod.isStatic()) {
+    if (encodedMethod.isPrivateMethod()
+        || encodedMethod.isStatic()
+        || encodedMethod.isLibraryMethodOverride().isFalse()) {
       return false;
     }
     DexMethod method = encodedMethod.method;
@@ -195,22 +200,22 @@
 
   private boolean overridesLibraryMethod(DexClass theClass, DexMethod method) {
     // We look up everywhere to see if there is a supertype/interface implementing the method...
-    LinkedList<DexType> workList = new LinkedList<>();
-    Collections.addAll(workList, theClass.interfaces.values);
+    WorkList<DexType> workList = WorkList.newIdentityWorkList();
+    workList.addIfNotSeen(theClass.interfaces.values);
     boolean foundOverrideToRewrite = false;
     // There is no methods with desugared types on Object.
     if (theClass.superType != factory.objectType) {
-      workList.add(theClass.superType);
+      workList.addIfNotSeen(theClass.superType);
     }
-    while (!workList.isEmpty()) {
-      DexType current = workList.removeFirst();
+    while (workList.hasNext()) {
+      DexType current = workList.next();
       DexClass dexClass = appView.definitionFor(current);
       if (dexClass == null) {
         continue;
       }
-      workList.addAll(Arrays.asList(dexClass.interfaces.values));
+      workList.addIfNotSeen(dexClass.interfaces.values);
       if (dexClass.superType != factory.objectType) {
-        workList.add(dexClass.superType);
+        workList.addIfNotSeen(dexClass.superType);
       }
       if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
         continue;
@@ -249,8 +254,9 @@
 
   private synchronized void addCallBackSignature(DexClass dexClass, DexEncodedMethod method) {
     assert dexClass.type == method.method.holder;
-    callBackMethods.putIfAbsent(dexClass, new HashSet<>());
-    callBackMethods.get(dexClass).add(method);
+    if (callBackMethods.computeIfAbsent(dexClass, key -> new HashSet<>()).add(method)) {
+      pendingCallBackMethods.computeIfAbsent(dexClass, key -> new ArrayList<>()).add(method);
+    }
   }
 
   public static DexMethod methodWithVivifiedTypeInSignature(
@@ -289,19 +295,24 @@
     if (appView.options().testing.trackDesugaredAPIConversions) {
       generateTrackDesugaredAPIWarnings(trackedAPIs, "");
       generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
+      trackedAPIs.clear();
+      trackedCallBackAPIs.clear();
     }
     List<DexEncodedMethod> result = new ArrayList<>();
-    for (DexClass dexClass : callBackMethods.keySet()) {
-      List<DexEncodedMethod> dexEncodedMethods =
-          generateCallbackMethods(callBackMethods.get(dexClass), dexClass);
-      dexClass.appendVirtualMethods(dexEncodedMethods);
-      result.addAll(dexEncodedMethods);
-    }
+    pendingCallBackMethods.forEach(
+        (clazz, callbacks) -> {
+          List<DexEncodedMethod> generated =
+              ListUtils.map(callbacks, callback -> generateCallbackMethod(callback, clazz));
+          clazz.addVirtualMethods(generated);
+          result.addAll(generated);
+        });
+    pendingCallBackMethods.clear();
     return result;
   }
 
-  public Map<DexProgramClass, DexProgramClass> synthesizeWrappersAndMapToReverse() {
-    return wrapperSynthesizor.synthesizeWrappersAndMapToReverse();
+  public List<DexProgramClass> synthesizeWrappers(
+      Map<DexType, DexProgramClass> synthesizedWrappers) {
+    return wrapperSynthesizor.synthesizeWrappers(synthesizedWrappers);
   }
 
   public DexClasspathClass synthesizeClasspathMock(
@@ -309,23 +320,21 @@
     return wrapperSynthesizor.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
   }
 
-  private List<DexEncodedMethod> generateCallbackMethods(
-      Set<DexEncodedMethod> originalMethods, DexClass dexClass) {
-    List<DexEncodedMethod> newDexEncodedMethods = new ArrayList<>();
-    for (DexEncodedMethod originalMethod : originalMethods) {
-      DexMethod methodToInstall =
-          methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
-      CfCode cfCode =
-          new APIConverterWrapperCfCodeProvider(
-                  appView, originalMethod.method, null, this, dexClass.isInterface())
-              .generateCfCode();
-      DexEncodedMethod newDexEncodedMethod =
-          wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
-      newDexEncodedMethod.setCode(cfCode, appView);
-      newDexEncodedMethods.add(newDexEncodedMethod);
+  private DexEncodedMethod generateCallbackMethod(
+      DexEncodedMethod originalMethod, DexClass dexClass) {
+    DexMethod methodToInstall =
+        methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
+    CfCode cfCode =
+        new APIConverterWrapperCfCodeProvider(
+                appView, originalMethod.method, null, this, dexClass.isInterface())
+            .generateCfCode();
+    DexEncodedMethod newDexEncodedMethod =
+        wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
+    newDexEncodedMethod.setCode(cfCode, appView);
+    if (originalMethod.isLibraryMethodOverride().isTrue()) {
+      newDexEncodedMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     }
-    assert Sets.newHashSet(newDexEncodedMethods).size() == newDexEncodedMethods.size();
-    return newDexEncodedMethods;
+    return newDexEncodedMethod;
   }
 
   private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
@@ -513,7 +522,7 @@
     DexMethod conversionMethod = createConversionMethod(argType, argType, argVivifiedType);
     // The value is null only if the input is null.
     Value convertedValue =
-        createConversionValue(code, inValue.getTypeLattice().nullability(), argVivifiedType);
+        createConversionValue(code, inValue.getType().nullability(), argVivifiedType);
     return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
   }
 
@@ -524,9 +533,8 @@
     Value outValue = invokeMethod.outValue();
     outValue.replaceUsers(convertedValue);
     // The only user of out value is now the new invoke static, so no type propagation is required.
-    outValue.setTypeLattice(
-        TypeLatticeElement.fromDexType(
-            returnVivifiedType, outValue.getTypeLattice().nullability(), appView));
+    outValue.setType(
+        TypeElement.fromDexType(returnVivifiedType, outValue.getType().nullability(), appView));
     return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(outValue));
   }
 
@@ -557,7 +565,7 @@
   }
 
   private Value createConversionValue(IRCode code, Nullability nullability, DexType valueType) {
-    return code.createValue(TypeLatticeElement.fromDexType(valueType, nullability, appView));
+    return code.createValue(TypeElement.fromDexType(valueType, nullability, appView));
   }
 
   public boolean canConvert(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index bdb4cd9..baa6317 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -110,12 +110,6 @@
     return libraryCompilation;
   }
 
-  public String getSynthesizedLibraryClassesPackagePrefix(AppView<?> appView) {
-    return appView.options().isDesugaredLibraryCompilation()
-        ? synthesizedLibraryClassesPackagePrefix
-        : "";
-  }
-
   public String getSynthesizedLibraryClassesPackagePrefix() {
     return synthesizedLibraryClassesPackagePrefix;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
new file mode 100644
index 0000000..3949767
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -0,0 +1,441 @@
+// 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.ir.desugar;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.*;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+
+public class DesugaredLibraryRetargeter {
+
+  public static final String DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX =
+      "$r8$retargetLibraryMember$virtualDispatch";
+
+  private final AppView<AppInfoWithClassHierarchy> appView;
+  private final Map<DexMethod, DexMethod> retargetLibraryMember = new IdentityHashMap<>();
+  // Map virtualRewrites hold a methodName->method mapping for virtual methods which are
+  // rewritten while the holder is non final but no superclass implement the method. In this case
+  // d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
+  private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
+  // Non final virtual library methods requiring generation of emulated dispatch.
+  private final Set<DexMethod> emulatedDispatchMethods = Sets.newHashSet();
+
+  public DesugaredLibraryRetargeter(AppView<?> appView) {
+    assert appView.appInfo().hasClassHierarchy()
+        : "Class hierarchy required for desugared library.";
+    this.appView = appView.withClassHierarchy();
+    if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+      return;
+    }
+    new RetargetingSetup().setUpRetargeting();
+  }
+
+  public static void checkForAssumedLibraryTypes(AppView<?> appView) {
+    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+    for (DexString methodName : retargetCoreLibMember.keySet()) {
+      for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
+        DexClass typeClass = appView.definitionFor(inType);
+        if (typeClass == null) {
+          warnMissingRetargetCoreLibraryMember(inType, appView);
+        }
+      }
+    }
+  }
+
+  private static void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
+    StringDiagnostic warning =
+        new StringDiagnostic(
+            "Cannot retarget core library member "
+                + type.getName()
+                + " because the class is missing.");
+    appView.options().reporter.warning(warning);
+  }
+
+  // Used by the ListOfBackportedMethods utility.
+  void visit(Consumer<DexMethod> consumer) {
+    retargetLibraryMember.keySet().forEach(consumer);
+  }
+
+  public void desugar(IRCode code) {
+    if (retargetLibraryMember.isEmpty()) {
+      return;
+    }
+
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (!instruction.isInvokeMethod()) {
+        continue;
+      }
+
+      InvokeMethod invoke = instruction.asInvokeMethod();
+      DexMethod retarget = getRetargetLibraryMember(invoke.getInvokedMethod());
+      if (retarget == null) {
+        if (!matchesVirtualRewrite(invoke.getInvokedMethod())) {
+          continue;
+        }
+        // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
+        ResolutionResult resolutionResult =
+            appView
+                .appInfo()
+                .resolveMethod(invoke.getInvokedMethod().holder, invoke.getInvokedMethod());
+        if (resolutionResult.isFailedResolution()) {
+          continue;
+        }
+        DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+        assert singleTarget != null;
+        retarget = getRetargetLibraryMember(singleTarget.method);
+        if (retarget == null) {
+          continue;
+        }
+      }
+
+      // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
+      // infinite loops. We do direct resolution. This is a very uncommon case.
+      if (invoke.isInvokeSuper() && matchesVirtualRewrite(invoke.getInvokedMethod())) {
+        DexEncodedMethod dexEncodedMethod =
+            appView
+                .appInfo()
+                .withClassHierarchy()
+                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.method.holder);
+        // Final methods can be rewritten as a normal invoke.
+        if (dexEncodedMethod != null && !dexEncodedMethod.isFinal()) {
+          DexMethod retargetMethod =
+              appView
+                  .options()
+                  .desugaredLibraryConfiguration
+                  .retargetMethod(dexEncodedMethod.method, appView);
+          if (retargetMethod != null) {
+            iterator.replaceCurrentInstruction(
+                new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
+          }
+          continue;
+        }
+      }
+
+      iterator.replaceCurrentInstruction(
+          new InvokeStatic(retarget, invoke.outValue(), invoke.inValues()));
+    }
+  }
+
+  private DexMethod getRetargetLibraryMember(DexMethod method) {
+    Map<DexType, DexType> backportCoreLibraryMembers =
+        appView.options().desugaredLibraryConfiguration.getBackportCoreLibraryMember();
+    if (backportCoreLibraryMembers.containsKey(method.holder)) {
+      DexType newHolder = backportCoreLibraryMembers.get(method.holder);
+      return appView.dexItemFactory().createMethod(newHolder, method.proto, method.name);
+    }
+    return retargetLibraryMember.get(method);
+  }
+
+  private boolean matchesVirtualRewrite(DexMethod method) {
+    List<DexMethod> dexMethods = virtualRewrites.get(method.name);
+    if (dexMethods == null) {
+      return false;
+    }
+    for (DexMethod dexMethod : dexMethods) {
+      if (method.match(dexMethod)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private class RetargetingSetup {
+
+    private void setUpRetargeting() {
+      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+          appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+      for (DexString methodName : retargetCoreLibMember.keySet()) {
+        for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
+          DexClass typeClass = appView.definitionFor(inType);
+          if (typeClass != null) {
+            DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
+            List<DexEncodedMethod> found = findDexEncodedMethodsWithName(methodName, typeClass);
+            for (DexEncodedMethod encodedMethod : found) {
+              if (!encodedMethod.isStatic()) {
+                virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
+                virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
+                if (InterfaceMethodRewriter.isEmulatedInterfaceDispatch(appView, encodedMethod)) {
+                  // In this case interface method rewriter takes care of it.
+                  continue;
+                } else if (!encodedMethod.isFinal()) {
+                  // Virtual rewrites require emulated dispatch for inheritance.
+                  // The call is rewritten to the dispatch holder class instead.
+                  handleEmulateDispatch(appView, encodedMethod.method);
+                  newHolder = dispatchHolderTypeFor(encodedMethod.method);
+                }
+              }
+              DexProto proto = encodedMethod.method.proto;
+              DexMethod method = appView.dexItemFactory().createMethod(inType, proto, methodName);
+              retargetLibraryMember.put(
+                  method, computeRetargetMethod(method, encodedMethod.isStatic(), newHolder));
+            }
+          }
+        }
+      }
+    }
+
+    private DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
+      DexItemFactory factory = appView.dexItemFactory();
+      DexProto newProto =
+          isStatic ? method.proto : factory.prependTypeToProto(method.holder, method.proto);
+      return factory.createMethod(newHolder, newProto, method.name);
+    }
+
+    private List<DexEncodedMethod> findDexEncodedMethodsWithName(
+        DexString methodName, DexClass clazz) {
+      List<DexEncodedMethod> found = new ArrayList<>();
+      for (DexEncodedMethod encodedMethod : clazz.methods()) {
+        if (encodedMethod.method.name == methodName) {
+          found.add(encodedMethod);
+        }
+      }
+      assert found.size() > 0 : "Should have found a method (library specifications).";
+      return found;
+    }
+
+    private void handleEmulateDispatch(AppView<?> appView, DexMethod method) {
+      emulatedDispatchMethods.add(method);
+      if (!appView.options().isDesugaredLibraryCompilation()) {
+        // Add rewrite rules so keeps rules are correctly generated in the program.
+        DexType dispatchInterfaceType = dispatchInterfaceTypeFor(method);
+        appView.rewritePrefix.rewriteType(dispatchInterfaceType, dispatchInterfaceType);
+        DexType dispatchHolderType = dispatchHolderTypeFor(method);
+        appView.rewritePrefix.rewriteType(dispatchHolderType, dispatchHolderType);
+      }
+    }
+  }
+
+  public void synthesizeRetargetClasses(
+      DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
+      throws ExecutionException {
+    new EmulatedDispatchTreeFixer().fixApp(builder, executorService, converter);
+  }
+
+  // The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
+  // for inserting interfaces on library boundaries and forwarding methods in the program, and to
+  // synthesize the interfaces and emulated dispatch classes in the desugared library.
+  class EmulatedDispatchTreeFixer {
+
+    void fixApp(
+        DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
+        throws ExecutionException {
+      if (appView.options().isDesugaredLibraryCompilation()) {
+        synthesizeEmulatedDispatchMethods(builder);
+      } else {
+        addInterfacesAndForwardingMethods(executorService, converter);
+      }
+    }
+
+    private void addInterfacesAndForwardingMethods(
+        ExecutorService executorService, IRConverter converter) throws ExecutionException {
+      assert !appView.options().isDesugaredLibraryCompilation();
+      Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
+      for (DexMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+        map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
+        map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
+      }
+      List<DexEncodedMethod> addedMethods = new ArrayList<>();
+      for (DexProgramClass clazz : appView.appInfo().classes()) {
+        if (clazz.superType == null) {
+          assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
+          continue;
+        }
+        DexClass dexClass = appView.definitionFor(clazz.superType);
+        // Only performs computation if superclass is a library class, but not object to filter out
+        // the most common case.
+        if (dexClass != null
+            && dexClass.isLibraryClass()
+            && dexClass.type != appView.dexItemFactory().objectType) {
+          for (DexType dexType : map.keySet()) {
+            if (inherit(dexClass.asLibraryClass(), dexType)) {
+              addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
+            }
+          }
+        }
+      }
+      if (addedMethods.isEmpty()) {
+        return;
+      }
+      converter.processMethodsConcurrently(addedMethods, executorService);
+    }
+
+    private boolean inherit(DexLibraryClass clazz, DexType typeToInherit) {
+      DexLibraryClass current = clazz;
+      while (current.type != appView.dexItemFactory().objectType) {
+        if (current.type == typeToInherit) {
+          return true;
+        }
+        current = appView.definitionFor(current.superType).asLibraryClass();
+      }
+      return false;
+    }
+
+    private List<DexEncodedMethod> addInterfacesAndForwardingMethods(
+        DexProgramClass clazz, List<DexMethod> dexMethods) {
+      // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
+      // methods.
+      // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
+      // applies up to 24.
+      List<DexEncodedMethod> newForwardingMethods = new ArrayList<>();
+      for (DexMethod dexMethod : dexMethods) {
+        DexType[] newInterfaces =
+            Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
+        newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(dexMethod);
+        clazz.interfaces = new DexTypeList(newInterfaces);
+        DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
+        if (dexEncodedMethod == null) {
+          DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
+          clazz.addVirtualMethod(newMethod);
+          newForwardingMethods.add(newMethod);
+        }
+      }
+      return newForwardingMethods;
+    }
+
+    private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
+      // NOTE: Never add a forwarding method to methods of classes unknown or coming from
+      // android.jar
+      // even if this results in invalid code, these classes are never desugared.
+      // In desugared library, emulated interface methods can be overridden by retarget lib members.
+      DexMethod forwardMethod =
+          appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
+      assert forwardMethod != null && forwardMethod != target;
+      return DexEncodedMethod.createDesugaringForwardingMethod(
+          appView.definitionFor(target), clazz, forwardMethod, appView.dexItemFactory());
+    }
+
+    private void synthesizeEmulatedDispatchMethods(DexApplication.Builder<?> builder) {
+      assert appView.options().isDesugaredLibraryCompilation();
+      if (emulatedDispatchMethods.isEmpty()) {
+        return;
+      }
+      ClassAccessFlags itfAccessFlags =
+          ClassAccessFlags.fromSharedAccessFlags(
+              Constants.ACC_PUBLIC
+                  | Constants.ACC_SYNTHETIC
+                  | Constants.ACC_ABSTRACT
+                  | Constants.ACC_INTERFACE);
+      ClassAccessFlags holderAccessFlags =
+          ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+      for (DexMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+        // Dispatch interface.
+        DexType interfaceType = dispatchInterfaceTypeFor(emulatedDispatchMethod);
+        DexEncodedMethod itfMethod =
+            generateInterfaceDispatchMethod(emulatedDispatchMethod, interfaceType);
+        BackportedMethodRewriter.synthesizeClassWithUniqueMethod(
+            builder,
+            itfAccessFlags,
+            interfaceType,
+            itfMethod,
+            "desugared library dispatch interface",
+            false,
+            appView);
+        // Dispatch holder.
+        DexType holderType = dispatchHolderTypeFor(emulatedDispatchMethod);
+        DexEncodedMethod dispatchMethod =
+            generateHolderDispatchMethod(emulatedDispatchMethod, holderType, itfMethod.method);
+        BackportedMethodRewriter.synthesizeClassWithUniqueMethod(
+            builder,
+            holderAccessFlags,
+            holderType,
+            dispatchMethod,
+            "desugared library dispatch class",
+            false,
+            appView);
+      }
+    }
+
+    private DexEncodedMethod generateInterfaceDispatchMethod(
+        DexMethod emulatedDispatchMethod, DexType interfaceType) {
+      MethodAccessFlags flags =
+          MethodAccessFlags.fromSharedAccessFlags(
+              Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
+      DexMethod newMethod =
+          appView
+              .dexItemFactory()
+              .createMethod(
+                  interfaceType, emulatedDispatchMethod.proto, emulatedDispatchMethod.name);
+      return new DexEncodedMethod(
+          newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null, true);
+    }
+
+    private DexEncodedMethod generateHolderDispatchMethod(
+        DexMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
+      // The method should look like:
+      // static foo(rcvr, arg0, arg1) {
+      //    if (rcvr instanceof interfaceType) {
+      //      return invoke-interface receiver.foo(arg0, arg1);
+      //    } else {
+      //      return DesugarX.foo(rcvr, arg0, arg1)
+      //    }
+      // We do not deal with complex cases (multiple retargeting of the same signature in the
+      // same inheritance tree, etc., since they do not happen in the most common desugared library.
+      DexMethod desugarMethod =
+          appView
+              .options()
+              .desugaredLibraryConfiguration
+              .retargetMethod(emulatedDispatchMethod, appView);
+      assert desugarMethod != null; // This method is reached only for retarget core lib members.
+      DexMethod newMethod =
+          appView
+              .dexItemFactory()
+              .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.name);
+      return DexEncodedMethod.toEmulateDispatchLibraryMethod(
+          emulatedDispatchMethod.holder,
+          newMethod,
+          desugarMethod,
+          itfMethod,
+          Collections.emptyList(),
+          appView);
+    }
+  }
+
+  private DexType dispatchInterfaceTypeFor(DexMethod method) {
+    return dispatchTypeFor(method, "dispatchInterface");
+  }
+
+  private DexType dispatchHolderTypeFor(DexMethod method) {
+    return dispatchTypeFor(method, "dispatchHolder");
+  }
+
+  private DexType dispatchTypeFor(DexMethod method, String suffix) {
+    String descriptor =
+        "L"
+            + appView
+                .options()
+                .desugaredLibraryConfiguration
+                .getSynthesizedLibraryClassesPackagePrefix()
+            + DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX
+            + '$'
+            + method.holder.getName()
+            + '$'
+            + method.name
+            + '$'
+            + suffix
+            + ';';
+    return appView.dexItemFactory().createType(descriptor);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 3527f62..30bc78e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -144,14 +144,9 @@
   }
 
   private DexType createWrapperType(DexType type, String suffix) {
-    String desugaredLibPrefix =
-        appView
-            .options()
-            .desugaredLibraryConfiguration
-            .getSynthesizedLibraryClassesPackagePrefix(appView);
     return factory.createType(
         "L"
-            + desugaredLibPrefix
+            + appView.options().synthesizedClassPrefix
             + WRAPPER_PREFIX
             + type.toString().replace('.', '$')
             + suffix
@@ -511,52 +506,33 @@
   void finalizeWrappersForD8(
       DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
       throws ExecutionException {
-    Map<DexType, DexProgramClass> synthesizedWrappers = synthesizeWrappers();
-    registerAndProcessWrappers(builder, irConverter, executorService, synthesizedWrappers.values());
+    List<DexProgramClass> synthesizedWrappers = synthesizeWrappers(new IdentityHashMap<>());
+    registerAndProcessWrappers(builder, irConverter, executorService, synthesizedWrappers);
   }
 
-  private Map<DexType, DexProgramClass> synthesizeWrappers() {
-    Map<DexType, DexProgramClass> synthesizedWrappers = new IdentityHashMap<>();
+  List<DexProgramClass> synthesizeWrappers(Map<DexType, DexProgramClass> synthesizedWrappers) {
+    List<DexProgramClass> additions = new ArrayList<>();
     // Generating a wrapper may require other wrappers to be generated, iterate until fix point.
     while (synthesizedWrappers.size() != typeWrappers.size() + vivifiedTypeWrappers.size()) {
       for (DexType type : typeWrappers.keySet()) {
         DexType typeWrapperType = typeWrappers.get(type);
         if (!synthesizedWrappers.containsKey(typeWrapperType)) {
-          synthesizedWrappers.put(
-              typeWrapperType, generateTypeWrapper(getValidClassToWrap(type), typeWrapperType));
+          DexProgramClass wrapper = generateTypeWrapper(getValidClassToWrap(type), typeWrapperType);
+          synthesizedWrappers.put(typeWrapperType, wrapper);
+          additions.add(wrapper);
         }
       }
       for (DexType type : vivifiedTypeWrappers.keySet()) {
         DexType vivifiedTypeWrapperType = vivifiedTypeWrappers.get(type);
         if (!synthesizedWrappers.containsKey(vivifiedTypeWrapperType)) {
-          synthesizedWrappers.put(
-              vivifiedTypeWrapperType,
-              generateVivifiedTypeWrapper(getValidClassToWrap(type), vivifiedTypeWrapperType));
+          DexProgramClass wrapper =
+              generateVivifiedTypeWrapper(getValidClassToWrap(type), vivifiedTypeWrapperType);
+          synthesizedWrappers.put(vivifiedTypeWrapperType, wrapper);
+          additions.add(wrapper);
         }
       }
     }
-    return synthesizedWrappers;
-  }
-
-  private Map<DexType, DexType> reverseWrapperMap() {
-    Map<DexType, DexType> reverseWrapperMap = new IdentityHashMap<>();
-    for (DexType type : typeWrappers.keySet()) {
-      reverseWrapperMap.put(typeWrappers.get(type), vivifiedTypeWrappers.get(type));
-    }
-    for (DexType type : vivifiedTypeWrappers.keySet()) {
-      reverseWrapperMap.put(vivifiedTypeWrappers.get(type), typeWrappers.get(type));
-    }
-    return reverseWrapperMap;
-  }
-
-  Map<DexProgramClass, DexProgramClass> synthesizeWrappersAndMapToReverse() {
-    Map<DexType, DexProgramClass> synthesizedWrappers = synthesizeWrappers();
-    Map<DexType, DexType> reverseMap = reverseWrapperMap();
-    Map<DexProgramClass, DexProgramClass> wrappersAndReverse = new IdentityHashMap<>();
-    for (DexProgramClass wrapper : synthesizedWrappers.values()) {
-      wrappersAndReverse.put(wrapper, synthesizedWrappers.get(reverseMap.get(wrapper.type)));
-    }
-    return wrappersAndReverse;
+    return additions;
   }
 
   private void registerAndProcessWrappers(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 403f2f0..1a4c416 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -196,9 +196,9 @@
           DexCallSite callSite = instruction.asInvokeCustom().getCallSite();
           reportStaticInterfaceMethodHandle(encodedMethod.method, callSite.bootstrapMethod);
           for (DexValue arg : callSite.bootstrapArgs) {
-            if (arg instanceof DexValue.DexValueMethodHandle) {
-              reportStaticInterfaceMethodHandle(encodedMethod.method,
-                  ((DexValue.DexValueMethodHandle) arg).value);
+            if (arg.isDexValueMethodHandle()) {
+              reportStaticInterfaceMethodHandle(
+                  encodedMethod.method, arg.asDexValueMethodHandle().value);
             }
           }
           continue;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index ec1b05f..87c3424 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -667,14 +667,14 @@
       // For all instantiation points for which the compiler creates lambda$
       // methods, it creates these methods in the same class/interface.
       DexMethod implMethod = descriptor.implHandle.asMethod();
-      DexClass implMethodHolder = definitionFor(implMethod.holder);
+      DexProgramClass implMethodHolder = definitionFor(implMethod.holder).asProgramClass();
       return allowMethodModification
           ? modifyLambdaImplementationMethod(implMethod, implMethodHolder)
           : createSyntheticAccessor(implMethod, implMethodHolder);
     }
 
     private DexEncodedMethod modifyLambdaImplementationMethod(
-        DexMethod implMethod, DexClass implMethodHolder) {
+        DexMethod implMethod, DexProgramClass implMethodHolder) {
       return implMethodHolder
           .getMethodCollection()
           .replaceDirectMethodWithVirtualMethod(
@@ -701,7 +701,7 @@
     }
 
     private DexEncodedMethod createSyntheticAccessor(
-        DexMethod implMethod, DexClass implMethodHolder) {
+        DexMethod implMethod, DexProgramClass implMethodHolder) {
       MethodAccessFlags accessorFlags =
           MethodAccessFlags.fromSharedAccessFlags(
               Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC, false);
@@ -725,7 +725,7 @@
                   registry -> registry.registerInvokeDirect(implMethod)),
               true);
 
-      implMethodHolder.appendVirtualMethod(accessorEncodedMethod);
+      implMethodHolder.addVirtualMethod(accessorEncodedMethod);
       return accessorEncodedMethod;
     }
   }
@@ -761,7 +761,7 @@
 
       // We may arrive here concurrently so we need must update the methods of the class atomically.
       synchronized (accessorClass) {
-        accessorClass.appendDirectMethod(accessorEncodedMethod);
+        accessorClass.addDirectMethod(accessorEncodedMethod);
       }
 
       return accessorEncodedMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 770f170..4ee35d6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -323,8 +323,7 @@
       // The out value might be empty in case it was optimized out.
       lambdaInstanceValue =
           code.createValue(
-              TypeLatticeElement.fromDexType(
-                  lambdaClass.type, Nullability.maybeNull(), getAppView()));
+              TypeElement.fromDexType(lambdaClass.type, Nullability.maybeNull(), getAppView()));
     } else {
       affectedValues.add(lambdaInstanceValue);
     }
@@ -352,8 +351,8 @@
     //    result:
     //      NewInstance   rResult <-  LambdaClass
     //      Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
-    lambdaInstanceValue.setTypeLattice(
-        lambdaInstanceValue.getTypeLattice().asReferenceTypeLatticeElement().asDefinitelyNotNull());
+    lambdaInstanceValue.setType(
+        lambdaInstanceValue.getType().asReferenceType().asDefinitelyNotNull());
     NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
     instructions.replaceCurrentInstruction(newInstance);
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index 834a492..4b33d2f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
@@ -15,7 +15,8 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
@@ -212,11 +213,11 @@
     }
 
     // Extract recipe.
-    DexValue recipeValue = bootstrapArgs.get(0);
-    if (!(recipeValue instanceof DexValue.DexValueString)) {
+    DexValueString recipeValue = bootstrapArgs.get(0).asDexValueString();
+    if (recipeValue == null) {
       throw error(method, "bootstrap method argument `recipe` must be a string");
     }
-    String recipe = ((DexValue.DexValueString) recipeValue).getValue().toString();
+    String recipe = recipeValue.getValue().toString();
 
     // Collect chunks and patch the instruction.
     ConcatBuilder builder = new ConcatBuilder(appView, code, blocks, instructions);
@@ -273,8 +274,8 @@
   }
 
   private static String convertToString(DexMethod method, DexValue value) {
-    if (value instanceof DexValue.DexValueString) {
-      return ((DexValue.DexValueString) value).getValue().toString();
+    if (value.isDexValueString()) {
+      return value.asDexValueString().getValue().toString();
     }
     throw error(method,
         "const arg referenced from `recipe` is not supported: " + value.getClass().getName());
@@ -338,8 +339,8 @@
       instructions.previous();
 
       // new-instance v0, StringBuilder
-      TypeLatticeElement stringBuilderTypeLattice =
-          TypeLatticeElement.fromDexType(factory.stringBuilderType, definitelyNotNull(), appView);
+      TypeElement stringBuilderTypeLattice =
+          TypeElement.fromDexType(factory.stringBuilderType, definitelyNotNull(), appView);
       Value sbInstance = code.createValue(stringBuilderTypeLattice);
       appendInstruction(new NewInstance(factory.stringBuilderType, sbInstance));
 
@@ -361,8 +362,7 @@
       Value concatValue = invokeCustom.outValue();
       if (concatValue == null) {
         // The out value might be empty in case it was optimized out.
-        concatValue =
-            code.createValue(TypeLatticeElement.stringClassType(appView, definitelyNotNull()));
+        concatValue = code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
       }
 
       // Replace the instruction.
@@ -440,8 +440,7 @@
 
       @Override
       Value getOrCreateValue() {
-        Value value =
-            code.createValue(TypeLatticeElement.stringClassType(appView, definitelyNotNull()));
+        Value value = code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
         appendInstruction(
             new ConstString(
                 value,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java b/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
index cc97597..7219eb0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
@@ -46,15 +46,14 @@
       }
       Value outValue = current.outValue();
       // TODO(b/129859039): We may need similar concept when adding/testing assume-range
-      if (outValue.getTypeLattice().isPrimitive()
-          || outValue.getTypeLattice().isNullType()) {
+      if (outValue.getType().isPrimitiveType() || outValue.getType().isNullType()) {
         continue;
       }
       // Split block if needed
       BasicBlock insertionBlock =
           block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
       // Replace usages of out-value by the out-value of the AssumeNone instruction.
-      Value aliasedValue = code.createValue(outValue.getTypeLattice(), outValue.getLocalInfo());
+      Value aliasedValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
       outValue.replaceUsers(aliasedValue);
       // Insert AssumeNone instruction.
       Assume<NoAssumption> assumeNone =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 6bb9b03..9102f53 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -68,7 +68,7 @@
       Value outValue = assumeDynamicTypeInstruction.outValue();
 
       // Check if we need to run the type analysis for the affected values of the out-value.
-      if (!outValue.getTypeLattice().equals(inValue.getTypeLattice())) {
+      if (!outValue.getType().equals(inValue.getType())) {
         affectedValues.addAll(outValue.affectedValues());
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 627aec5..368b58a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.Assume;
@@ -223,7 +223,7 @@
       }
       argumentsSeen++;
       Value originalArg = instr.asArgument().outValue();
-      if (originalArg.hasLocalInfo() || !originalArg.getTypeLattice().isReference()) {
+      if (originalArg.hasLocalInfo() || !originalArg.getType().isReferenceType()) {
         continue;
       }
       int argIndex = argumentsSeen - 1;
@@ -241,7 +241,7 @@
           continue;
         }
       }
-      TypeLatticeElement dynamicUpperBoundType =
+      TypeElement dynamicUpperBoundType =
           callSiteOptimizationInfo.getDynamicUpperBoundType(argIndex);
       if (dynamicUpperBoundType == null) {
         continue;
@@ -255,8 +255,8 @@
         continue;
       }
       Value specializedArg;
-      if (dynamicUpperBoundType.strictlyLessThan(originalArg.getTypeLattice(), appView)) {
-        specializedArg = code.createValue(originalArg.getTypeLattice());
+      if (dynamicUpperBoundType.strictlyLessThan(originalArg.getType(), appView)) {
+        specializedArg = code.createValue(originalArg.getType());
         affectedValues.addAll(originalArg.affectedValues());
         originalArg.replaceUsers(specializedArg);
         Assume<DynamicTypeAssumption> assumeType =
@@ -267,12 +267,12 @@
       } else {
         specializedArg = originalArg;
       }
-      assert specializedArg != null && specializedArg.getTypeLattice().isReference();
+      assert specializedArg != null && specializedArg.getType().isReferenceType();
       if (dynamicUpperBoundType.isDefinitelyNotNull()) {
         // If we already knew `arg` is never null, e.g., receiver, skip adding non-null.
-        if (!specializedArg.getTypeLattice().isDefinitelyNotNull()) {
-          Value nonNullArg = code.createValue(
-              specializedArg.getTypeLattice().asReferenceTypeLatticeElement().asMeetWithNotNull());
+        if (!specializedArg.getType().isDefinitelyNotNull()) {
+          Value nonNullArg =
+              code.createValue(specializedArg.getType().asReferenceType().asMeetWithNotNull());
           affectedValues.addAll(specializedArg.affectedValues());
           specializedArg.replaceUsers(nonNullArg);
           Assume<NonNullAssumption> assumeNotNull =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 1e02c2b..025c178 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -30,7 +30,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -395,7 +395,7 @@
             }
             DexField field = put.getField();
             Value value = put.value();
-            TypeLatticeElement valueType = value.getTypeLattice();
+            TypeElement valueType = value.getType();
             if (clazz.definesStaticField(field)) {
               if (isReadBefore.contains(field)) {
                 // Promoting this put to a class constant would cause a previous static-get
@@ -439,7 +439,7 @@
                   isWrittenBefore.remove(field);
                 }
                 continue;
-              } else if (valueType.isReference() && valueType.isDefinitelyNotNull()) {
+              } else if (valueType.isReferenceType() && valueType.isDefinitelyNotNull()) {
                 finalFieldPuts.put(field, put);
                 continue;
               }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index f7755d4..5b78d8a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -25,7 +25,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
 import com.android.tools.r8.ir.code.ArrayLength;
@@ -260,8 +260,8 @@
           }
 
           Value value = ifInstruction.lhs();
-          if (!value.getTypeLattice().isReference()) {
-            assert value.getTypeLattice().isPrimitive();
+          if (!value.getType().isReferenceType()) {
+            assert value.getType().isPrimitiveType();
             continue;
           }
 
@@ -1186,12 +1186,10 @@
 
   private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) {
     // TODO(sgjesse): Insert cast if required.
-    TypeLatticeElement returnType =
-        TypeLatticeElement.fromDexType(
-            invoke.getInvokedMethod().proto.returnType, maybeNull(), appView);
-    TypeLatticeElement argumentType =
-        TypeLatticeElement.fromDexType(
-            getArgumentType(invoke, argumentIndex), maybeNull(), appView);
+    TypeElement returnType =
+        TypeElement.fromDexType(invoke.getInvokedMethod().proto.returnType, maybeNull(), appView);
+    TypeElement argumentType =
+        TypeElement.fromDexType(getArgumentType(invoke, argumentIndex), maybeNull(), appView);
     return appView.enableWholeProgramOptimizations()
         ? argumentType.lessThanOrEqual(returnType, appView)
         : argumentType.equals(returnType);
@@ -1244,7 +1242,7 @@
             // Make sure that we are only narrowing information here. Note, in cases where
             // we cannot find the definition of types, computing lessThanOrEqual will
             // return false unless it is object.
-            if (argument.getTypeLattice().lessThanOrEqual(outValue.getTypeLattice(), appView)) {
+            if (argument.getType().lessThanOrEqual(outValue.getType(), appView)) {
               affectedValues.addAll(outValue.affectedValues());
               assumeDynamicTypeRemover.markUsersForRemoval(outValue);
               mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
@@ -1362,17 +1360,17 @@
     // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
     // elimination may lead to verification errors. See b/123269162.
     if (options.canHaveArtCheckCastVerifierBug()) {
-      if (inValue.getTypeLattice().isNullType()
+      if (inValue.getType().isNullType()
           && castType.isArrayType()
           && castType.toBaseType(dexItemFactory).isFloatType()) {
         return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
       }
     }
 
-    TypeLatticeElement inTypeLattice = inValue.getTypeLattice();
-    TypeLatticeElement outTypeLattice = outValue.getTypeLattice();
-    TypeLatticeElement castTypeLattice =
-        TypeLatticeElement.fromDexType(castType, inTypeLattice.nullability(), appView);
+    TypeElement inTypeLattice = inValue.getType();
+    TypeElement outTypeLattice = outValue.getType();
+    TypeElement castTypeLattice =
+        TypeElement.fromDexType(castType, inTypeLattice.nullability(), appView);
 
     assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability());
 
@@ -1417,9 +1415,9 @@
     }
 
     Value inValue = instanceOf.value();
-    TypeLatticeElement inType = inValue.getTypeLattice();
-    TypeLatticeElement instanceOfType =
-        TypeLatticeElement.fromDexType(instanceOf.type(), inType.nullability(), appView);
+    TypeElement inType = inValue.getType();
+    TypeElement instanceOfType =
+        TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView);
     Value aliasValue = inValue.getAliasedValue();
 
     InstanceOfResult result = InstanceOfResult.UNKNOWN;
@@ -1442,8 +1440,7 @@
 
       if (result == InstanceOfResult.UNKNOWN) {
         if (inType.isClassType()
-            && isNeverInstantiatedDirectlyOrIndirectly(
-                inType.asClassTypeLatticeElement().getClassType())) {
+            && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) {
           // The type of the in-value is a program class, and is never instantiated directly or
           // indirectly. This, the in-value must be null, meaning that the instance-of instruction
           // will always evaluate to false.
@@ -1456,7 +1453,7 @@
             inValue.getSpecificAliasedValue(
                 value -> !value.isPhi() && value.definition.isAssumeDynamicType());
         if (aliasedValue != null) {
-          TypeLatticeElement dynamicType =
+          TypeElement dynamicType =
               aliasedValue
                   .definition
                   .asAssumeDynamicType()
@@ -1476,7 +1473,7 @@
           new ConstNumber(
               new Value(
                   code.valueNumberGenerator.next(),
-                  TypeLatticeElement.getInt(),
+                  TypeElement.getInt(),
                   instanceOf.outValue().getLocalInfo()),
               result == InstanceOfResult.TRUE ? 1 : 0);
       it.replaceCurrentInstruction(newInstruction);
@@ -2053,8 +2050,8 @@
           for (ConstInstruction value : values) {
             stringValues.add(value.outValue());
           }
-          Value invokeValue = code.createValue(
-              newArray.outValue().getTypeLattice(), newArray.getLocalInfo());
+          Value invokeValue =
+              code.createValue(newArray.outValue().getType(), newArray.getLocalInfo());
           InvokeNewArray invoke =
               new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
           for (Value value : newArray.inValues()) {
@@ -2462,11 +2459,11 @@
         } else if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
           if (theIf.isZeroTest()) {
             if (!lhs.isConstNumber()) {
-              TypeLatticeElement l = lhs.getTypeLattice();
-              if (l.isReference() && lhs.isNeverNull()) {
+              TypeElement l = lhs.getType();
+              if (l.isReferenceType() && lhs.isNeverNull()) {
                 simplifyIfWithKnownCondition(code, block, theIf, 1);
               } else {
-                if (!l.isPrimitive() && !l.isNullable()) {
+                if (!l.isPrimitiveType() && !l.isNullable()) {
                   simplifyIfWithKnownCondition(code, block, theIf, 1);
                 }
               }
@@ -2721,12 +2718,12 @@
       }
 
       if (dominatorTree.get().dominatedBy(block, dominator)) {
-        if (newValue.getTypeLattice().lessThanOrEqual(value.getTypeLattice(), appView)) {
+        if (newValue.getType().lessThanOrEqual(value.getType(), appView)) {
           value.replaceUsers(newValue);
           block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead();
           constantWithValueIterator.remove();
           changed = true;
-        } else if (value.getTypeLattice().isNullType()) {
+        } else if (value.getType().isNullType()) {
           // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of
           // the use sites of `value` without introducing a type error.
         }
@@ -2875,7 +2872,7 @@
                          (theIf.getType() == Type.EQ &&
                            trueNumber.isIntegerOne() &&
                            falseNumber.isIntegerZero())) {
-                Value newOutValue = code.createValue(phi.getTypeLattice(), phi.getLocalInfo());
+                Value newOutValue = code.createValue(phi.getType(), phi.getLocalInfo());
                 ConstNumber cstToUse = trueNumber.isIntegerOne() ? trueNumber : falseNumber;
                 BasicBlock phiBlock = phi.getBlock();
                 Position phiPosition = phiBlock.getPosition();
@@ -3107,7 +3104,7 @@
                   new InvokeVirtual(
                       dexItemFactory.throwableMethods.initCause,
                       code.createValue(
-                          TypeLatticeElement.fromDexType(
+                          TypeElement.fromDexType(
                               dexItemFactory.throwableType, maybeNull(), appView)),
                       initCauseArguments);
               initCause.setPosition(current.getPosition());
@@ -3316,8 +3313,7 @@
   }
 
   private Value addConstString(IRCode code, InstructionListIterator iterator, String s) {
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+    TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
     Value value = code.createValue(typeLattice);
     ThrowingInfo throwingInfo =
         options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
@@ -3351,7 +3347,7 @@
     DexType javaIoPrintStreamType = dexItemFactory.javaIoPrintStreamType;
     Value out =
         code.createValue(
-            TypeLatticeElement.fromDexType(javaIoPrintStreamType, definitelyNotNull(), appView));
+            TypeElement.fromDexType(javaIoPrintStreamType, definitelyNotNull(), appView));
 
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print");
@@ -3388,7 +3384,7 @@
       eol.link(successor);
 
       Value argument = arguments.get(i);
-      if (!argument.getTypeLattice().isReference()) {
+      if (!argument.getType().isReferenceType()) {
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, primitive)));
       } else {
         // Insert "if (argument != null) ...".
@@ -3418,7 +3414,7 @@
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, nul)));
         iterator = isNotNullBlock.listIterator(code);
         iterator.setInsertionPosition(position);
-        value = code.createValue(TypeLatticeElement.classClassType(appView, definitelyNotNull()));
+        value = code.createValue(TypeElement.classClassType(appView, definitelyNotNull()));
         iterator.add(
             new InvokeVirtual(
                 dexItemFactory.objectMembers.getClass, value, ImmutableList.of(arguments.get(i))));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 463bfce..d225220 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -250,7 +250,7 @@
       for (int index = 0; index < arguments.size(); index++) {
         Value argument = arguments.get(index);
         if ((argument.isArgument()
-            || (argument.getTypeLattice().isReference() && argument.isNeverNull()))
+                || (argument.getType().isReferenceType() && argument.isNeverNull()))
             && hints.get(index)) {
           // 5-4 instructions per parameter check are expected to be removed.
           instructionLimit += 4;
@@ -304,15 +304,15 @@
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     Value receiver = invoke.getReceiver();
-    if (receiver.getTypeLattice().isDefinitelyNull()) {
+    if (receiver.getType().isDefinitelyNull()) {
       // A definitely null receiver will throw an error on call site.
       whyAreYouNotInliningReporter.reportReceiverDefinitelyNull();
       return null;
     }
 
     InlineAction action = new InlineAction(singleTarget, invoke, reason);
-    if (receiver.getTypeLattice().isNullable()) {
-      assert !receiver.getTypeLattice().isDefinitelyNull();
+    if (receiver.getType().isNullable()) {
+      assert !receiver.getType().isDefinitelyNull();
       // When inlining an instance method call, we need to preserve the null check for the
       // receiver. Therefore, if the receiver may be null and the candidate inlinee does not
       // throw if the receiver is null before any other side effect, then we must synthesize a
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 0c53a4c..82ea8ab 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -101,8 +101,8 @@
               // the out-value of the cast instruction is a more precise type than the in-value,
               // otherwise we could introduce type errors.
               Value oldReceiver = newCheckCast.object();
-              TypeLatticeElement oldReceiverType = oldReceiver.getTypeLattice();
-              TypeLatticeElement newReceiverType = newReceiver.getTypeLattice();
+              TypeElement oldReceiverType = oldReceiver.getType();
+              TypeElement newReceiverType = newReceiver.getType();
               if (newReceiverType.lessThanOrEqual(oldReceiverType, appView)
                   && dominatorTree.dominatedBy(block, devirtualizedInvoke.getBlock())) {
                 assert nonNull.src() == oldReceiver;
@@ -166,10 +166,9 @@
         // (out <-) invoke-virtual a, ... A#foo
         if (holderType != invoke.getInvokedMethod().holder) {
           Value receiver = invoke.getReceiver();
-          TypeLatticeElement receiverTypeLattice = receiver.getTypeLattice();
-          TypeLatticeElement castTypeLattice =
-              TypeLatticeElement.fromDexType(
-                  holderType, receiverTypeLattice.nullability(), appView);
+          TypeElement receiverTypeLattice = receiver.getType();
+          TypeElement castTypeLattice =
+              TypeElement.fromDexType(holderType, receiverTypeLattice.nullability(), appView);
           // Avoid adding trivial cast and up-cast.
           // We should not use strictlyLessThan(castType, receiverType), which detects downcast,
           // due to side-casts, e.g., A (unused) < I, B < I, and cast from A to B.
@@ -262,7 +261,7 @@
    */
   private DexMethod rebindVirtualInvokeToMostSpecific(
       DexMethod target, Value receiver, DexType context) {
-    if (!receiver.getTypeLattice().isClassType()) {
+    if (!receiver.getType().isClassType()) {
       return target;
     }
     DexEncodedMethod encodedTarget = appView.definitionFor(target);
@@ -273,9 +272,7 @@
       return target;
     }
     DexType receiverType =
-        appView
-            .graphLense()
-            .lookupType(receiver.getTypeLattice().asClassTypeLatticeElement().getClassType());
+        appView.graphLense().lookupType(receiver.getType().asClassType().getClassType());
     if (receiverType == target.holder) {
       // Virtual invoke is already as specific as it can get.
       return target;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 0a314aa..8048e3a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -11,8 +11,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -61,8 +61,8 @@
         continue;
       }
 
-      TypeLatticeElement dynamicUpperBoundType;
-      ClassTypeLatticeElement dynamicLowerBoundType;
+      TypeElement dynamicUpperBoundType;
+      ClassTypeElement dynamicLowerBoundType;
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
         DexMethod invokedMethod = invoke.getInvokedMethod();
@@ -75,7 +75,7 @@
         if (invokedMethod.holder.isArrayType()
             && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) {
           dynamicUpperBoundType =
-              TypeLatticeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
+              TypeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
           dynamicLowerBoundType = null;
         } else {
           DexEncodedMethod singleTarget =
@@ -110,14 +110,14 @@
       Value outValue = current.outValue();
       boolean isTrivial =
           (dynamicUpperBoundType == null
-                  || !dynamicUpperBoundType.strictlyLessThan(outValue.getTypeLattice(), appView))
+                  || !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView))
               && dynamicLowerBoundType == null;
       if (isTrivial) {
         continue;
       }
 
       if (dynamicUpperBoundType == null) {
-        dynamicUpperBoundType = outValue.getTypeLattice();
+        dynamicUpperBoundType = outValue.getType();
       }
 
       // Split block if needed (only debug instructions are allowed after the throwing
@@ -126,8 +126,7 @@
           block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
 
       // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
-      Value specializedOutValue =
-          code.createValue(outValue.getTypeLattice(), outValue.getLocalInfo());
+      Value specializedOutValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
       outValue.replaceUsers(specializedOutValue);
 
       // Insert AssumeDynamicType instruction.
@@ -154,9 +153,9 @@
    *
    * <p>If the method has no normal exits, then null is returned.
    */
-  public TypeLatticeElement computeDynamicReturnType(DexEncodedMethod method, IRCode code) {
+  public TypeElement computeDynamicReturnType(DexEncodedMethod method, IRCode code) {
     assert method.method.proto.returnType.isReferenceType();
-    List<TypeLatticeElement> returnedTypes = new ArrayList<>();
+    List<TypeElement> returnedTypes = new ArrayList<>();
     for (BasicBlock block : code.blocks) {
       JumpInstruction exitInstruction = block.exit();
       if (exitInstruction.isReturn()) {
@@ -164,19 +163,17 @@
         returnedTypes.add(returnValue.getDynamicUpperBoundType(appView));
       }
     }
-    return returnedTypes.isEmpty() ? null : TypeLatticeElement.join(returnedTypes, appView);
+    return returnedTypes.isEmpty() ? null : TypeElement.join(returnedTypes, appView);
   }
 
-  public ClassTypeLatticeElement computeDynamicLowerBoundType(
-      DexEncodedMethod method, IRCode code) {
+  public ClassTypeElement computeDynamicLowerBoundType(DexEncodedMethod method, IRCode code) {
     assert method.method.proto.returnType.isReferenceType();
-    ClassTypeLatticeElement result = null;
+    ClassTypeElement result = null;
     for (BasicBlock block : code.blocks) {
       JumpInstruction exitInstruction = block.exit();
       if (exitInstruction.isReturn()) {
         Value returnValue = exitInstruction.asReturn().returnValue();
-        ClassTypeLatticeElement dynamicLowerBoundType =
-            returnValue.getDynamicLowerBoundType(appView);
+        ClassTypeElement dynamicLowerBoundType = returnValue.getDynamicLowerBoundType(appView);
         if (dynamicLowerBoundType == null) {
           return null;
         }
@@ -184,7 +181,7 @@
           result = dynamicLowerBoundType;
         } else if (dynamicLowerBoundType.equalUpToNullability(result)) {
           if (dynamicLowerBoundType.nullability() != result.nullability()) {
-            result = dynamicLowerBoundType.join(result, appView).asClassTypeLatticeElement();
+            result = dynamicLowerBoundType.join(result, appView).asClassType();
           }
         } else {
           return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index ebf6e57..5dba772 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -149,7 +149,7 @@
           // Check if the call could throw a NPE as a result of the receiver being null.
           if (current.isInvokeMethodWithReceiver()) {
             Value receiver = current.asInvokeMethodWithReceiver().getReceiver().getAliasedValue();
-            if (receiver.getTypeLattice().isNullable()) {
+            if (receiver.getType().isNullable()) {
               continue;
             }
           }
@@ -195,39 +195,42 @@
         .filter(a -> a.getValue().size() > 1)
         .sorted((a, b) -> Integer.compare(b.getValue().size(), a.getValue().size()))
         .limit(MAX_CANONICALIZED_CALL)
-        .forEach((entry) -> {
-          InvokeMethod invoke = entry.getKey();
-          if (Log.ENABLED) {
-            if (factory.libraryMethodsWithReturnValueDependingOnlyOnArguments
-                .contains(invoke.getInvokedMethod())) {
-              numberOfLibraryCallCanonicalization += entry.getValue().size() - 1;
-            } else {
-              numberOfProgramCallCanonicalization += entry.getValue().size() - 1;
-            }
-          }
-          Value canonicalizedValue = code.createValue(
-              invoke.outValue().getTypeLattice(), invoke.outValue().getLocalInfo());
-          Invoke canonicalizedInvoke =
-              Invoke.create(
-                  invoke.getType(),
-                  invoke.getInvokedMethod(),
-                  null,
-                  canonicalizedValue,
-                  invoke.inValues());
-          // Note that it is fine to use any position, since the invoke has no side effects, which
-          // is guaranteed not to throw. That is, we will never have a stack trace with this call.
-          // Nonetheless, here we pick the position of the very first invocation.
-          Position firstInvocationPosition = entry.getValue().get(0).definition.getPosition();
-          canonicalizedInvoke.setPosition(firstInvocationPosition);
-          if (invoke.inValues().size() > 0) {
-            insertCanonicalizedInvokeWithInValues(code, canonicalizedInvoke);
-          } else {
-            insertCanonicalizedInvokeWithoutInValues(code, canonicalizedInvoke);
-          }
-          for (Value oldOutValue : entry.getValue()) {
-            deadInvocations.put(oldOutValue.definition.asInvokeMethod(), canonicalizedValue);
-          }
-        });
+        .forEach(
+            (entry) -> {
+              InvokeMethod invoke = entry.getKey();
+              if (Log.ENABLED) {
+                if (factory.libraryMethodsWithReturnValueDependingOnlyOnArguments.contains(
+                    invoke.getInvokedMethod())) {
+                  numberOfLibraryCallCanonicalization += entry.getValue().size() - 1;
+                } else {
+                  numberOfProgramCallCanonicalization += entry.getValue().size() - 1;
+                }
+              }
+              Value canonicalizedValue =
+                  code.createValue(invoke.outValue().getType(), invoke.outValue().getLocalInfo());
+              Invoke canonicalizedInvoke =
+                  Invoke.create(
+                      invoke.getType(),
+                      invoke.getInvokedMethod(),
+                      null,
+                      canonicalizedValue,
+                      invoke.inValues());
+              // Note that it is fine to use any position, since the invoke has no side effects,
+              // which
+              // is guaranteed not to throw. That is, we will never have a stack trace with this
+              // call.
+              // Nonetheless, here we pick the position of the very first invocation.
+              Position firstInvocationPosition = entry.getValue().get(0).definition.getPosition();
+              canonicalizedInvoke.setPosition(firstInvocationPosition);
+              if (invoke.inValues().size() > 0) {
+                insertCanonicalizedInvokeWithInValues(code, canonicalizedInvoke);
+              } else {
+                insertCanonicalizedInvokeWithoutInValues(code, canonicalizedInvoke);
+              }
+              for (Value oldOutValue : entry.getValue()) {
+                deadInvocations.put(oldOutValue.definition.asInvokeMethod(), canonicalizedValue);
+              }
+            });
 
     if (!deadInvocations.isEmpty()) {
       for (BasicBlock block : code.blocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index d45bbb0..b56dece 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.ConstClass;
@@ -631,8 +631,8 @@
 
       // Insert monitor-enter and monitor-exit instructions if the method is synchronized.
       if (shouldSynthesizeMonitorEnterExit) {
-        TypeLatticeElement throwableType =
-            TypeLatticeElement.fromDexType(
+        TypeElement throwableType =
+            TypeElement.fromDexType(
                 dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView);
 
         code.prepareBlocksForCatchHandlers();
@@ -709,8 +709,7 @@
         if (target.isStatic()) {
           lockValue =
               code.createValue(
-                  TypeLatticeElement.fromDexType(
-                      dexItemFactory.objectType, definitelyNotNull(), appView));
+                  TypeElement.fromDexType(dexItemFactory.objectType, definitelyNotNull(), appView));
           monitorEnterBlockIterator.add(new ConstClass(lockValue, target.method.holder));
         } else {
           lockValue = entryBlock.getInstructions().getFirst().asArgument().outValue();
@@ -763,7 +762,7 @@
 
       InstructionListIterator iterator = initClassBlock.listIterator(code);
       iterator.setInsertionPosition(entryBlock.exit().getPosition());
-      iterator.add(new InitClass(code.createValue(TypeLatticeElement.getInt()), target.holder()));
+      iterator.add(new InitClass(code.createValue(TypeElement.getInt()), target.holder()));
     }
 
     private void synthesizeNullCheckForReceiver(AppView<?> appView, IRCode code) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 7e25c8a..4bf7a6d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -139,11 +139,10 @@
           .createMaterializingInstruction(appView, code, instruction);
     }
 
-    TypeLatticeElement typeLattice = instruction.outValue().getTypeLattice();
+    TypeElement typeLattice = instruction.outValue().getType();
     if (returnValueRule.isField()) {
       DexField field = returnValueRule.getField();
-      assert typeLattice
-          == TypeLatticeElement.fromDexType(field.type, Nullability.maybeNull(), appView);
+      assert typeLattice == TypeElement.fromDexType(field.type, Nullability.maybeNull(), appView);
 
       DexEncodedField staticField = appView.appInfo().lookupStaticTarget(field.holder, field);
       if (staticField == null) {
@@ -446,7 +445,7 @@
     }
     DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
     if (clazz != null) {
-      Value dest = code.createValue(TypeLatticeElement.getInt());
+      Value dest = code.createValue(TypeElement.getInt());
       iterator.replaceCurrentInstruction(new InitClass(dest, clazz.type));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 4fa0312..15904c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -214,10 +214,10 @@
               if (knownToBeNonNullValue.isArgument()
                   || !dominatedUsers.isEmpty()
                   || !dominatedPhiUsersWithPositions.isEmpty()) {
-                TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+                TypeElement typeLattice = knownToBeNonNullValue.getType();
                 Value nonNullValue =
                     code.createValue(
-                        typeLattice.asReferenceTypeLatticeElement().asMeetWithNotNull(),
+                        typeLattice.asReferenceType().asMeetWithNotNull(),
                         knownToBeNonNullValue.getLocalInfo());
                 affectedValues.addAll(knownToBeNonNullValue.affectedValues());
                 Assume<NonNullAssumption> nonNull =
@@ -316,11 +316,11 @@
         // ...
         // A: non_null_rcv <- non-null(rcv)
         // ...y
-        TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
-        assert typeLattice.isReference();
+        TypeElement typeLattice = knownToBeNonNullValue.getType();
+        assert typeLattice.isReferenceType();
         Value nonNullValue =
             code.createValue(
-                typeLattice.asReferenceTypeLatticeElement().asMeetWithNotNull(),
+                typeLattice.asReferenceType().asMeetWithNotNull(),
                 knownToBeNonNullValue.getLocalInfo());
         affectedValues.addAll(knownToBeNonNullValue.affectedValues());
         Assume<NonNullAssumption> nonNull =
@@ -369,9 +369,9 @@
   }
 
   private static boolean isNullableReferenceTypeWithUsers(Value value) {
-    TypeLatticeElement type = value.getTypeLattice();
-    return type.isReference()
-        && type.asReferenceTypeLatticeElement().isNullable()
+    TypeElement type = value.getType();
+    return type.isReferenceType()
+        && type.asReferenceType().isNullable()
         && value.numberOfAllUsers() > 0;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 4ce7ede..ab9fa71 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -30,10 +30,10 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -277,7 +277,7 @@
         inValues.add(
             builder.readRegister(register, ValueTypeConstraint.fromNumericType(numericType)));
       }
-      TypeLatticeElement latticeElement = PrimitiveTypeLatticeElement.fromNumericType(numericType);
+      TypeElement latticeElement = PrimitiveTypeElement.fromNumericType(numericType);
       Value outValue =
           builder.writeRegister(outline.argumentCount(), latticeElement, ThrowingInfo.CAN_THROW);
       Instruction newInstruction = null;
@@ -359,8 +359,8 @@
 
     @Override
     public int createInstruction(IRBuilder builder, Outline outline, int argumentMapIndex) {
-      TypeLatticeElement latticeElement =
-          TypeLatticeElement.fromDexType(clazz, definitelyNotNull(), builder.appView);
+      TypeElement latticeElement =
+          TypeElement.fromDexType(clazz, definitelyNotNull(), builder.appView);
       Value outValue =
           builder.writeRegister(outline.argumentCount(), latticeElement, ThrowingInfo.CAN_THROW);
       Instruction newInstruction = new NewInstance(clazz, outValue);
@@ -495,8 +495,8 @@
       }
       Value outValue = null;
       if (hasOutValue) {
-        TypeLatticeElement latticeElement =
-            TypeLatticeElement.fromDexType(method.proto.returnType, maybeNull(), builder.appView);
+        TypeElement latticeElement =
+            TypeElement.fromDexType(method.proto.returnType, maybeNull(), builder.appView);
         outValue =
             builder.writeRegister(outline.argumentCount(), latticeElement, ThrowingInfo.CAN_THROW);
       }
@@ -933,7 +933,7 @@
 
     private boolean supportedArgumentType(Value value) {
       // All non array types are supported.
-      if (!value.getTypeLattice().isArrayType()) {
+      if (!value.getType().isArrayType()) {
         return true;
       }
       // Avoid array type elements which have interfaces, as Art does not have the same semantics
@@ -942,14 +942,13 @@
           && appView.options().isGeneratingClassFiles()) {
         return true;
       }
-      ArrayTypeLatticeElement arrayTypeLatticeElement =
-          value.getTypeLattice().asArrayTypeLatticeElement();
-      TypeLatticeElement arrayBaseType = arrayTypeLatticeElement.getArrayBaseTypeLattice();
-      if (arrayBaseType.isPrimitive()) {
+      ArrayTypeElement arrayType = value.getType().asArrayType();
+      TypeElement arrayBaseType = arrayType.getBaseType();
+      if (arrayBaseType.isPrimitiveType()) {
         return true;
       }
       if (arrayBaseType.isClassType()) {
-        return arrayBaseType.asClassTypeLatticeElement().getInterfaces().size() == 0;
+        return arrayBaseType.asClassType().getInterfaces().size() == 0;
       }
       return false;
     }
@@ -958,30 +957,29 @@
       assert supportedArgumentType(value);
       DexItemFactory itemFactory = appView.options().itemFactory;
       DexType objectType = itemFactory.objectType;
-      TypeLatticeElement valueLatticeElement = value.getTypeLattice();
-      if (valueLatticeElement.isClassType()) {
-        ClassTypeLatticeElement valueClassTypeLatticeElement =
-            value.getTypeLattice().asClassTypeLatticeElement();
+      TypeElement valueType = value.getType();
+      if (valueType.isClassType()) {
+        ClassTypeElement valueClassType = value.getType().asClassType();
         // For values of lattice type java.lang.Object and only one interface use the interface as
         // the type of the outline argument. If there are several interfaces these interfaces don't
         // have a common super interface nor are they implemented by a common superclass so the
         // argument type of the outline will be java.lang.Object.
-        if (valueClassTypeLatticeElement.getClassType() == objectType
-            && valueClassTypeLatticeElement.getInterfaces().size() == 1) {
-          return valueClassTypeLatticeElement.getInterfaces().iterator().next();
+        if (valueClassType.getClassType() == objectType
+            && valueClassType.getInterfaces().size() == 1) {
+          return valueClassType.getInterfaces().iterator().next();
         } else {
-          return valueClassTypeLatticeElement.getClassType();
+          return valueClassType.getClassType();
         }
-      } else if (valueLatticeElement.isArrayType()) {
-        return value.getTypeLattice().asArrayTypeLatticeElement().getArrayType(itemFactory);
-      } else if (valueLatticeElement.isNullType()) {
+      } else if (valueType.isArrayType()) {
+        return value.getType().asArrayType().toDexType(itemFactory);
+      } else if (valueType.isNullType()) {
         // For values which are always null use the actual type at the call site.
         return argumentTypeFromInvoke(invoke, argumentIndex);
       } else {
-        assert valueLatticeElement.isPrimitive();
-        assert valueLatticeElement.asPrimitiveTypeLatticeElement().hasDexType();
-        DexType type = valueLatticeElement.asPrimitiveTypeLatticeElement().toDexType(itemFactory);
-        if (valueLatticeElement.isInt()) {
+        assert valueType.isPrimitiveType();
+        assert valueType.asPrimitiveType().hasDexType();
+        DexType type = valueType.asPrimitiveType().toDexType(itemFactory);
+        if (valueType.isInt()) {
           // In the type lattice boolean, byte, short and char are all int. However, as the
           // outline argument type use the actual primitive type at the call site.
           assert type == itemFactory.intType;
@@ -1488,8 +1486,8 @@
         if (outline.argumentTypes.get(i).isBooleanType()) {
           builder.addBooleanNonThisArgument(i);
         } else {
-          TypeLatticeElement typeLattice =
-              TypeLatticeElement.fromDexType(outline.argumentTypes.get(i), maybeNull(), appView);
+          TypeElement typeLattice =
+              TypeElement.fromDexType(outline.argumentTypes.get(i), maybeNull(), appView);
           builder.addNonThisArgument(i, typeLattice);
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 055eeca..1b56e8c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
@@ -60,8 +60,7 @@
         }
         if (type != null) {
           affectedValues.addAll(current.outValue().affectedValues());
-          TypeLatticeElement typeLattice =
-              TypeLatticeElement.classClassType(appView, definitelyNotNull());
+          TypeElement typeLattice = TypeElement.classClassType(appView, definitelyNotNull());
           Value value = code.createValue(typeLattice, current.getLocalInfo());
           ConstClass constClass = new ConstClass(value, type);
           it.replaceCurrentInstruction(constClass);
@@ -90,7 +89,7 @@
     if (in.hasLocalInfo()) {
       return null;
     }
-    TypeLatticeElement inType = in.getTypeLattice();
+    TypeElement inType = in.getType();
     // Check the receiver is either class type or array type. Also make sure it is not
     // nullable.
     if (!(inType.isClassType() || inType.isArrayType())
@@ -99,8 +98,8 @@
     }
     DexType type =
         inType.isClassType()
-            ? inType.asClassTypeLatticeElement().getClassType()
-            : inType.asArrayTypeLatticeElement().getArrayType(dexItemFactory);
+            ? inType.asClassType().getClassType()
+            : inType.asArrayType().toDexType(dexItemFactory);
     DexType baseType = type.toBaseType(dexItemFactory);
     // Make sure base type is a class type.
     if (!baseType.isClassType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index e8799da..d626168 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -135,7 +135,7 @@
       InvokeVirtual classLoaderInvoke =
           serviceLoaderLoad.inValues().get(1).definition.asInvokeVirtual();
       boolean isGetClassLoaderOnConstClassOrNull =
-          serviceLoaderLoad.inValues().get(1).getTypeLattice().isNullType()
+          serviceLoaderLoad.inValues().get(1).getType().isNullType()
               || (classLoaderInvoke != null
                   && classLoaderInvoke.inValues().size() == 1
                   && classLoaderInvoke.getReceiver().getAliasedValue().isConstClass()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
index f6e6e70..e0f991d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -47,7 +47,7 @@
     this.code = code;
     this.receiver = code.getThis();
     assert !receiver.hasAliasedValue();
-    assert receiver.getTypeLattice().isClassType();
+    assert receiver.getType().isClassType();
   }
 
   public OptionalBool computeReturnsReceiver() {
@@ -87,12 +87,12 @@
       return OptionalBool.TRUE;
     }
 
-    ClassTypeLatticeElement valueType = value.getTypeLattice().asClassTypeLatticeElement();
+    ClassTypeElement valueType = value.getType().asClassType();
     if (valueType == null) {
       return OptionalBool.FALSE;
     }
 
-    ClassTypeLatticeElement receiverType = receiver.getTypeLattice().asClassTypeLatticeElement();
+    ClassTypeElement receiverType = receiver.getType().asClassType();
     if (!valueType.isRelatedTo(receiverType, appView)) {
       // Guaranteed not to return the receiver.
       return OptionalBool.FALSE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
index 832e8b9..833ceaa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -98,7 +98,7 @@
           new Phi(
               code.valueNumberGenerator.next(),
               block,
-              TypeLatticeElement.fromDexType(field.type, maybeNull(), appView),
+              TypeElement.fromDexType(field.type, maybeNull(), appView),
               null,
               RegisterReadType.NORMAL);
       ins.put(block, phi);
@@ -112,9 +112,9 @@
       // phis until we are done with replacing fields reads.
       phi.addOperands(operands, false);
 
-      TypeLatticeElement phiType = phi.computePhiType(appView);
-      assert phiType.lessThanOrEqual(phi.getTypeLattice(), appView);
-      phi.setTypeLattice(phiType);
+      TypeElement phiType = phi.computePhiType(appView);
+      assert phiType.lessThanOrEqual(phi.getType(), appView);
+      phi.setType(phiType);
 
       value = phi;
     }
@@ -153,8 +153,8 @@
       InstructionListIterator it = block.listIterator(code, root);
       // If we met newInstance it means that default value is supposed to be used.
       if (field.type.isPrimitiveType()) {
-        defaultValue = code.createValue(
-            TypeLatticeElement.fromDexType(field.type, definitelyNotNull(), appView));
+        defaultValue =
+            code.createValue(TypeElement.fromDexType(field.type, definitelyNotNull(), appView));
         ConstNumber defaultValueInsn = new ConstNumber(defaultValue, 0);
         defaultValueInsn.setPosition(root.getPosition());
         it.add(defaultValueInsn);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 22344de..81f68de 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
@@ -175,7 +175,7 @@
     }
     DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
     FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
-    ClassTypeLatticeElement dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
+    ClassTypeElement dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
     if (dynamicLowerBoundType == null
         || !dynamicLowerBoundType.equals(optimizationInfo.getDynamicUpperBoundType())) {
       return EligibilityStatus.NOT_A_SINGLETON_FIELD;
@@ -1031,7 +1031,7 @@
       }
 
       // A definitely null receiver will throw an error on call site.
-      if (receiver.getTypeLattice().nullability().isDefinitelyNull()) {
+      if (receiver.getType().nullability().isDefinitelyNull()) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 5e8b552..84b952b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -18,14 +18,15 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
-import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -33,6 +34,7 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CodeOptimization;
@@ -106,16 +108,15 @@
     enumsUnboxingCandidates.remove(enumClass.type);
   }
 
-  private DexProgramClass getEnumUnboxingCandidateOrNull(TypeLatticeElement lattice) {
+  private DexProgramClass getEnumUnboxingCandidateOrNull(TypeElement lattice) {
     if (lattice.isClassType()) {
-      DexType classType = lattice.asClassTypeLatticeElement().getClassType();
+      DexType classType = lattice.asClassType().getClassType();
       return getEnumUnboxingCandidateOrNull(classType);
     }
     if (lattice.isArrayType()) {
-      ArrayTypeLatticeElement arrayLattice = lattice.asArrayTypeLatticeElement();
-      if (arrayLattice.getArrayBaseTypeLattice().isClassType()) {
-        DexType classType =
-            arrayLattice.getArrayBaseTypeLattice().asClassTypeLatticeElement().getClassType();
+      ArrayTypeElement arrayLattice = lattice.asArrayType();
+      if (arrayLattice.getBaseType().isClassType()) {
+        DexType classType = arrayLattice.getBaseType().asClassType().getClassType();
         return getEnumUnboxingCandidateOrNull(classType);
       }
     }
@@ -135,7 +136,7 @@
       for (Instruction instruction : block.getInstructions()) {
         Value outValue = instruction.outValue();
         if (outValue != null) {
-          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(outValue.getTypeLattice());
+          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(outValue.getType());
           if (enumClass != null) {
             Reason reason = validateEnumUsages(code, outValue, enumClass);
             if (reason == Reason.ELIGIBLE) {
@@ -147,7 +148,7 @@
               eligibleEnums.add(enumClass.type);
             }
           }
-          if (outValue.getTypeLattice().isNullType()) {
+          if (outValue.getType().isNullType()) {
             addNullDependencies(outValue.uniqueUsers(), eligibleEnums);
           }
         }
@@ -160,17 +161,33 @@
             markEnumAsUnboxable(
                 Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
           }
+        } else if (instruction.isInvokeStatic()) {
+          // TODO(b/150370354): Since we temporary allow enum unboxing on enums with values and
+          // valueOf static methods only if such methods are unused, such methods cannot be
+          // called. the long term solution is to simply move called methods to a companion class,
+          // as any static helper method, and remove these checks.
+          DexMethod invokedMethod = instruction.asInvokeStatic().getInvokedMethod();
+          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
+          if (enumClass != null) {
+            if (factory.enumMethods.isValueOfMethod(invokedMethod, enumClass)) {
+              markEnumAsUnboxable(Reason.VALUE_OF_INVOKE, enumClass);
+            } else if (factory.enumMethods.isValuesMethod(invokedMethod, enumClass)) {
+              markEnumAsUnboxable(Reason.VALUES_INVOKE, enumClass);
+            } else {
+              assert false; // We do not allow any other static call in unboxing candidates.
+            }
+          }
         }
       }
       f