Merge "Remove end-marker before removing chained check-cast."
diff --git a/build.gradle b/build.gradle
index a542ea4..e6432e4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1243,6 +1243,8 @@
     }
 
     dependsOn getJarsFromSupportLibs
+    // R8.jar is required for running bootstrap tests.
+    dependsOn R8
     testLogging.exceptionFormat = 'full'
     if (project.hasProperty('print_test_stdout')) {
         testLogging.showStandardStreams = true
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 6fd04b6..fd85c25 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -26,6 +26,7 @@
 import java.util.Collection;
 import java.util.concurrent.ExecutionException;
 
+@Keep
 public class ExtractMarker {
   public static class VdexOrigin extends Origin {
 
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 43989ff..6fe8ad0 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -28,6 +28,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 
+@Keep
 public class GenerateMainDexList {
   private final Timing timing = new Timing("maindex");
   private final InternalOptions options;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 0d4e79d..aeaac60 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.0-dev";
+  public static final String LABEL = "1.4.1-dev";
 
   private Version() {
   }
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 b9be068..d703c88 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.outType(), phi.getLocalInfo());
+    super(phi.getNumber(), phi.getTypeLattice(), 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 08af875..d640a30 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -68,7 +68,7 @@
   }
 
   private TypeLatticeElement getLatticeElement(DexType type) {
-    return TypeLatticeElement.fromDexType(appInfo, type, true);
+    return TypeLatticeElement.fromDexType(type, appInfo, true);
   }
 
   public Map<Value, DexType> 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 c24490c..2ea0593 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
@@ -124,6 +124,6 @@
 
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
-    builder.addConst(type, state.push(type).register, value);
+    builder.addConst(type.toTypeLattice(), state.push(type).register, value);
   }
 }
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 1d8313d..48c1aa9 100644
--- a/src/main/java/com/android/tools/r8/code/Const.java
+++ b/src/main/java/com/android/tools/r8/code/Const.java
@@ -3,8 +3,8 @@
 // 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.code.SingleConstant;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
@@ -58,7 +58,8 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    ValueType type = value == 0 ? ValueType.INT_OR_FLOAT_OR_NULL : ValueType.INT_OR_FLOAT;
-    builder.addConst(type, AA, value);
+    TypeLatticeElement typeLattice =
+        value == 0 ? TypeLatticeElement.BOTTOM : TypeLatticeElement.SINGLE;
+    builder.addConst(typeLattice, 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 24dae68..c4d46e2 100644
--- a/src/main/java/com/android/tools/r8/code/Const16.java
+++ b/src/main/java/com/android/tools/r8/code/Const16.java
@@ -3,8 +3,8 @@
 // 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.code.SingleConstant;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
@@ -52,7 +52,8 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    ValueType type = value == 0 ? ValueType.INT_OR_FLOAT_OR_NULL : ValueType.INT_OR_FLOAT;
-    builder.addConst(type, AA, value);
+    TypeLatticeElement typeLattice =
+        value == 0 ? TypeLatticeElement.BOTTOM : TypeLatticeElement.SINGLE;
+    builder.addConst(typeLattice, 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 497a9ed..edfcb28 100644
--- a/src/main/java/com/android/tools/r8/code/Const4.java
+++ b/src/main/java/com/android/tools/r8/code/Const4.java
@@ -3,8 +3,8 @@
 // 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.code.SingleConstant;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
@@ -58,7 +58,8 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    ValueType type = value == 0 ? ValueType.INT_OR_FLOAT_OR_NULL : ValueType.INT_OR_FLOAT;
-    builder.addConst(type, A, value);
+    TypeLatticeElement typeLattice =
+        value == 0 ? TypeLatticeElement.BOTTOM : TypeLatticeElement.SINGLE;
+    builder.addConst(typeLattice, 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 ecaf61f..70e31c8 100644
--- a/src/main/java/com/android/tools/r8/code/ConstHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
@@ -3,8 +3,8 @@
 // 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.code.SingleConstant;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.StringUtils;
@@ -58,7 +58,8 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    ValueType type = value == 0 ? ValueType.INT_OR_FLOAT_OR_NULL : ValueType.INT_OR_FLOAT;
-    builder.addConst(type, AA, value);
+    TypeLatticeElement typeLattice =
+        value == 0 ? TypeLatticeElement.BOTTOM : TypeLatticeElement.SINGLE;
+    builder.addConst(typeLattice, 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 509b00b..1820172 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.code.ValueType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 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(ValueType.LONG_OR_DOUBLE, AA, decodedValue());
+    builder.addConst(TypeLatticeElement.WIDE, 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 4b74eb4..ad7e84f 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.code.ValueType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 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(ValueType.LONG_OR_DOUBLE, AA, decodedValue());
+    builder.addConst(TypeLatticeElement.WIDE, 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 f09fd14..0b75213 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.code.ValueType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 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(ValueType.LONG_OR_DOUBLE, AA, decodedValue());
+    builder.addConst(TypeLatticeElement.WIDE, 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 762e2ee..e139b35 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.code.ValueType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 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(ValueType.LONG_OR_DOUBLE, AA, decodedValue());
+    builder.addConst(TypeLatticeElement.WIDE, AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/MoveType.java b/src/main/java/com/android/tools/r8/code/MoveType.java
index a9ff6e0..ac0af87 100644
--- a/src/main/java/com/android/tools/r8/code/MoveType.java
+++ b/src/main/java/com/android/tools/r8/code/MoveType.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.code;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.ValueType;
 
 public enum  MoveType {
@@ -29,14 +30,14 @@
     }
   }
 
-  public ValueType toValueType() {
+  public TypeLatticeElement toTypeLattice() {
     switch (this) {
       case SINGLE:
-        return ValueType.INT_OR_FLOAT;
+        return TypeLatticeElement.SINGLE;
       case WIDE:
-        return ValueType.LONG_OR_DOUBLE;
+        return TypeLatticeElement.WIDE;
       case OBJECT:
-        return ValueType.OBJECT;
+        return TypeLatticeElement.REFERENCE;
       default:
         throw new Unreachable("Unexpected move type: " + this);
     }
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
index b5db1ae..7cbf816 100644
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
@@ -73,6 +73,22 @@
       }
       throw new AssertionError("Unknown: " + this);
     }
+
+    public static MultidexStrategy parse(String value) {
+      switch (value) {
+        case "off":
+          return OFF;
+        case "given_shard":
+          return GIVEN_SHARD;
+        case "minimal":
+          return MINIMAL;
+        case "best_effort":
+          return BEST_EFFORT;
+        default:
+          throw new RuntimeException(
+              "Multidex argument must be either 'off', 'given_shard', 'minimal' or 'best_effort'.");
+      }
+    }
   }
 
   private static class Options {
@@ -129,7 +145,7 @@
       }
       string = OptionsParsing.tryParseSingle(context, "--multidex", null);
       if (string != null) {
-        options.multidexMode = MultidexStrategy.valueOf(string.toUpperCase());
+        options.multidexMode = MultidexStrategy.parse(string);
         continue;
       }
       string = OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
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 3d83104..f2cc53f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -301,10 +301,21 @@
   }
 
   public IRCode buildInliningIRForTesting(
-      InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
+      InternalOptions options, ValueNumberGenerator valueNumberGenerator, AppInfo appInfo) {
     checkIfObsolete();
     return buildInliningIR(
-        null, GraphLense.getIdentityLense(), options, valueNumberGenerator, null, Origin.unknown());
+        appInfo,
+        GraphLense.getIdentityLense(),
+        options,
+        valueNumberGenerator,
+        null,
+        Origin.unknown());
+  }
+
+  public IRCode buildInliningIRForTesting(
+      InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
+    checkIfObsolete();
+    return buildInliningIRForTesting(options, valueNumberGenerator, null);
   }
 
   public IRCode buildInliningIR(
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 b02222f..7254577 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -70,12 +70,14 @@
   public static final DexType nullValueType = new DexType(new DexString("NULL"));
 
   public static final DexString unknownTypeName = new DexString("UNKNOWN");
+  public static final DexType unknownType = new DexType(unknownTypeName);
 
   private static final IdentityHashMap<DexItem, DexItem> internalSentinels =
       new IdentityHashMap<>(
           ImmutableMap.of(
               catchAllType, catchAllType,
               nullValueType, nullValueType,
+              unknownType, unknownType,
               unknownTypeName, unknownTypeName));
 
   public DexItemFactory() {
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
index c8f2b8c..0c80f96 100644
--- 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
@@ -52,7 +52,7 @@
 
   @Override
   public TypeLatticeElement arrayGet(AppInfo appInfo) {
-    return fromDexType(appInfo, getArrayElementType(appInfo.dexItemFactory), true);
+    return fromDexType(getArrayElementType(appInfo.dexItemFactory), appInfo, true);
   }
 
   @Override
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/BottomTypeLatticeElement.java
index 81b4488..b9a45d2 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/BottomTypeLatticeElement.java
@@ -18,7 +18,7 @@
     return this;
   }
 
-  public static BottomTypeLatticeElement getInstance() {
+  static BottomTypeLatticeElement 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/ClassTypeLatticeElement.java
index 3efc915..72825a9 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/ClassTypeLatticeElement.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
 import java.util.Set;
 
@@ -47,9 +46,4 @@
     return this;
   }
 
-  @Override
-  public TypeLatticeElement arrayGet(AppInfo appInfo) {
-    return objectType(appInfo, 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/DoubleTypeLatticeElement.java
index a5f0b18..7273f19 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/DoubleTypeLatticeElement.java
@@ -6,7 +6,7 @@
 public class DoubleTypeLatticeElement extends WideTypeLatticeElement {
   private static final DoubleTypeLatticeElement INSTANCE = new DoubleTypeLatticeElement();
 
-  public static DoubleTypeLatticeElement getInstance() {
+  static DoubleTypeLatticeElement 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/FloatTypeLatticeElement.java
index d0e5f48..511d6b9 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/FloatTypeLatticeElement.java
@@ -6,7 +6,7 @@
 public class FloatTypeLatticeElement extends SingleTypeLatticeElement {
   private static final FloatTypeLatticeElement INSTANCE = new FloatTypeLatticeElement();
 
-  public static FloatTypeLatticeElement getInstance() {
+  static FloatTypeLatticeElement 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/IntTypeLatticeElement.java
index 8b71446..02594a4 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/IntTypeLatticeElement.java
@@ -6,7 +6,7 @@
 public class IntTypeLatticeElement extends SingleTypeLatticeElement {
   private static final IntTypeLatticeElement INSTANCE = new IntTypeLatticeElement();
 
-  public static IntTypeLatticeElement getInstance() {
+  static IntTypeLatticeElement 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/LongTypeLatticeElement.java
index 92b3e3d..e60eaa1 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/LongTypeLatticeElement.java
@@ -6,7 +6,7 @@
 public class LongTypeLatticeElement extends WideTypeLatticeElement {
   private static final LongTypeLatticeElement INSTANCE = new LongTypeLatticeElement();
 
-  public static LongTypeLatticeElement getInstance() {
+  static LongTypeLatticeElement getInstance() {
     return INSTANCE;
   }
 
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/PrimitiveTypeLatticeElement.java
index 2eb732d..7cd6a6a 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/PrimitiveTypeLatticeElement.java
@@ -19,7 +19,7 @@
 
   @Override
   TypeLatticeElement asNullable() {
-    return TopTypeLatticeElement.getInstance();
+    return TypeLatticeElement.TOP;
   }
 
   @Override
@@ -44,13 +44,13 @@
       case 'S':
       case 'C':
       case 'I':
-        return IntTypeLatticeElement.getInstance();
+        return TypeLatticeElement.INT;
       case 'F':
-        return FloatTypeLatticeElement.getInstance();
+        return TypeLatticeElement.FLOAT;
       case 'J':
-        return LongTypeLatticeElement.getInstance();
+        return TypeLatticeElement.LONG;
       case 'D':
-        return DoubleTypeLatticeElement.getInstance();
+        return TypeLatticeElement.DOUBLE;
       case 'V':
         throw new InternalCompilerError("No value type for void type.");
       default:
@@ -64,13 +64,13 @@
       case CHAR:
       case SHORT:
       case INT:
-        return IntTypeLatticeElement.getInstance();
+        return TypeLatticeElement.INT;
       case FLOAT:
-        return FloatTypeLatticeElement.getInstance();
+        return TypeLatticeElement.FLOAT;
       case LONG:
-        return LongTypeLatticeElement.getInstance();
+        return TypeLatticeElement.LONG;
       case DOUBLE:
-        return DoubleTypeLatticeElement.getInstance();
+        return TypeLatticeElement.DOUBLE;
       default:
         throw new Unreachable("Invalid numeric type '" + numericType + "'");
     }
@@ -83,21 +83,21 @@
     }
     if (t1.isSingle()) {
       if (t2.isSingle()) {
-        return SingleTypeLatticeElement.getInstance();
+        return TypeLatticeElement.SINGLE;
       }
       assert t2.isWide();
-      return TopTypeLatticeElement.getInstance();
+      return TypeLatticeElement.TOP;
     }
     assert t1.isWide();
     if (t2.isWide()) {
-      return WideTypeLatticeElement.getInstance();
+      return TypeLatticeElement.WIDE;
     }
     assert t2.isSingle();
-    return TopTypeLatticeElement.getInstance();
+    return TypeLatticeElement.TOP;
   }
 
   public static TypeLatticeElement meet(TypeLatticeElement t1, TypeLatticeElement t2) {
-    // TODO(b/72693244): !t1.isReference() && !t2.isReference();
+    assert !t1.isReference() && !t2.isReference();
     // TODO(b/72693244): propagate constraints backward, e.g.,
     //   vz <- add vx(1, INT) vy(0, INT_OR_FLOAT_OR_NULL)
     if (t1 == t2) {
@@ -125,7 +125,7 @@
         return t2;
       }
     }
-    return BottomTypeLatticeElement.getInstance();
+    return TypeLatticeElement.BOTTOM;
   }
 
 }
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
index f700b15..387d6c5 100644
--- 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
@@ -12,8 +12,10 @@
 import java.util.stream.Collectors;
 
 public class ReferenceTypeLatticeElement extends TypeLatticeElement {
-  private static final ReferenceTypeLatticeElement NULL =
+  private static final ReferenceTypeLatticeElement NULL_INSTANCE =
       new ReferenceTypeLatticeElement(DexItemFactory.nullValueType, true);
+  private static final ReferenceTypeLatticeElement REFERENCE_INSTANCE =
+      new ReferenceTypeLatticeElement(DexItemFactory.unknownType, true);
 
   final DexType type;
   final Set<DexType> interfaces;
@@ -28,8 +30,12 @@
     this.interfaces = Collections.unmodifiableSet(interfaces);
   }
 
-  public static ReferenceTypeLatticeElement getNullTypeLatticeElement() {
-    return NULL;
+  static ReferenceTypeLatticeElement getNullTypeLatticeElement() {
+    return NULL_INSTANCE;
+  }
+
+  static ReferenceTypeLatticeElement getReferenceTypeLatticeElement() {
+    return REFERENCE_INSTANCE;
   }
 
   @Override
@@ -38,8 +44,13 @@
   }
 
   @Override
+  public boolean isReferenceInstance() {
+    return type == DexItemFactory.unknownType;
+  }
+
+  @Override
   TypeLatticeElement asNullable() {
-    assert isNull();
+    assert isNull() || isReferenceInstance();
     return this;
   }
 
@@ -50,8 +61,7 @@
 
   @Override
   public TypeLatticeElement arrayGet(AppInfo appInfo) {
-    assert isNull();
-    return this;
+    return isNull() ? this : BOTTOM;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/SingleTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/SingleTypeLatticeElement.java
index 0128b5e..2f719df 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/SingleTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/SingleTypeLatticeElement.java
@@ -13,7 +13,7 @@
     super();
   }
 
-  public static SingleTypeLatticeElement getInstance() {
+  static SingleTypeLatticeElement getInstance() {
     return SINGLE_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/TopTypeLatticeElement.java
index eeb22d3..b028963 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/TopTypeLatticeElement.java
@@ -18,7 +18,7 @@
     return this;
   }
 
-  public static TopTypeLatticeElement getInstance() {
+  static TopTypeLatticeElement 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 ffbd3fd..b8988c1 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
@@ -84,12 +84,12 @@
         TypeLatticeElement derived;
         if (argumentsSeen < 0) {
           // Receiver
-          derived = fromDexType(appInfo, encodedMethod.method.holder,
+          derived = fromDexType(encodedMethod.method.holder, appInfo,
               // Now we try inlining even when the receiver could be null.
               encodedMethod != context);
         } else {
           DexType argType = encodedMethod.method.proto.parameters.values[argumentsSeen];
-          derived = fromDexType(appInfo, argType, true);
+          derived = fromDexType(argType, appInfo, true);
         }
         argumentsSeen++;
         updateTypeOfValue(outValue, derived);
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
index f0ae028..ed30610 100644
--- 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
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Value;
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayDeque;
@@ -22,7 +23,20 @@
 /**
  * The base abstraction of lattice elements for local type analysis.
  */
-abstract public class TypeLatticeElement {
+public abstract class TypeLatticeElement {
+  public static final BottomTypeLatticeElement BOTTOM = BottomTypeLatticeElement.getInstance();
+  public static final TopTypeLatticeElement TOP = TopTypeLatticeElement.getInstance();
+  public static final IntTypeLatticeElement INT = IntTypeLatticeElement.getInstance();
+  public static final FloatTypeLatticeElement FLOAT = FloatTypeLatticeElement.getInstance();
+  public static final SingleTypeLatticeElement SINGLE = SingleTypeLatticeElement.getInstance();
+  public static final LongTypeLatticeElement LONG = LongTypeLatticeElement.getInstance();
+  public static final DoubleTypeLatticeElement DOUBLE = DoubleTypeLatticeElement.getInstance();
+  public static final WideTypeLatticeElement WIDE = WideTypeLatticeElement.getInstance();
+  public static final ReferenceTypeLatticeElement NULL =
+      ReferenceTypeLatticeElement.getNullTypeLatticeElement();
+  public static final ReferenceTypeLatticeElement REFERENCE =
+      ReferenceTypeLatticeElement.getReferenceTypeLatticeElement();
+
   private final boolean isNullable;
 
   TypeLatticeElement(boolean isNullable) {
@@ -33,10 +47,6 @@
     return isNullable;
   }
 
-  public boolean isNull() {
-    return false;
-  }
-
   /**
    * Defines how to join with null or switch to nullable lattice element.
    *
@@ -50,7 +60,7 @@
    * @return {@link TypeLatticeElement} a similar lattice element with nullable flag flipped.
    */
   public TypeLatticeElement asNonNullable() {
-    return BottomTypeLatticeElement.getInstance();
+    return BOTTOM;
   }
 
   String isNullableString() {
@@ -74,7 +84,7 @@
       return l1;
     }
     if (l1.isTop() || l2.isTop()) {
-      return TopTypeLatticeElement.getInstance();
+      return TOP;
     }
     if (l1.isNull()) {
       return l2.asNullable();
@@ -86,16 +96,25 @@
       return l2.isPrimitive()
           ? PrimitiveTypeLatticeElement.join(
               l1.asPrimitiveTypeLatticeElement(), l2.asPrimitiveTypeLatticeElement())
-          : TopTypeLatticeElement.getInstance();
+          : TOP;
     }
     if (l2.isPrimitive()) {
       // By the above case, !(l1.isPrimitive())
-      return TopTypeLatticeElement.getInstance();
+      return TOP;
     }
-    // From now on, l1 and l2 are reference types, i.e., either ArrayType or ClassType.
+    // From now on, l1 and l2 are reference types, but might be imprecise yet.
+    assert l1.isReference() && l2.isReference();
+    if (!l1.isPreciseType() || !l2.isPreciseType()) {
+      if (l1.isReferenceInstance()) {
+        return l1;
+      }
+      assert l2.isReferenceInstance();
+      return l2;
+    }
+    // From now on, l1 and l2 are precise reference types, i.e., either ArrayType or ClassType.
     boolean isNullable = l1.isNullable() || l2.isNullable();
     if (l1.getClass() != l2.getClass()) {
-      return objectType(appInfo, isNullable);
+      return objectClassType(appInfo, isNullable);
     }
     // From now on, l1.getClass() == l2.getClass()
     if (l1.isArrayType()) {
@@ -122,7 +141,7 @@
       assert a1BaseReferenceType.isClassType() && a2BaseReferenceType.isClassType();
       // If any nestings hit zero object is the join.
       if (a1Nesting == 0 || a2Nesting == 0) {
-        return objectType(appInfo, isNullable);
+        return objectClassType(appInfo, isNullable);
       }
       // If the nestings differ the join is the smallest nesting level.
       if (a1Nesting != a2Nesting) {
@@ -238,7 +257,7 @@
 
   public static TypeLatticeElement join(
       AppInfo appInfo, Stream<DexType> types, boolean isNullable) {
-    return join(appInfo, types.map(t -> fromDexType(appInfo, t, isNullable)));
+    return join(appInfo, types.map(t -> fromDexType(t, appInfo, isNullable)));
   }
 
   /**
@@ -350,7 +369,20 @@
         || isDouble();
   }
 
-  static ClassTypeLatticeElement objectType(AppInfo appInfo, boolean isNullable) {
+  public boolean isNull() {
+    return false;
+  }
+
+  public boolean isReferenceInstance() {
+    return false;
+  }
+
+  public int requiredRegisters() {
+    assert !isBottom() && !isTop();
+    return isWide() ? 2 : 1;
+  }
+
+  public static ClassTypeLatticeElement objectClassType(AppInfo appInfo, boolean isNullable) {
     return new ClassTypeLatticeElement(appInfo.dexItemFactory.objectType, isNullable);
   }
 
@@ -360,9 +392,17 @@
         isNullable);
   }
 
-  public static TypeLatticeElement fromDexType(AppInfo appInfo, DexType type, boolean isNullable) {
+  public static TypeLatticeElement classClassType(AppInfo appInfo) {
+    return fromDexType(appInfo.dexItemFactory.classType, appInfo, false);
+  }
+
+  public static TypeLatticeElement stringClassType(AppInfo appInfo) {
+    return fromDexType(appInfo.dexItemFactory.stringType, appInfo, false);
+  }
+
+  public static TypeLatticeElement fromDexType(DexType type, AppInfo appInfo, boolean isNullable) {
     if (type == DexItemFactory.nullValueType) {
-      return ReferenceTypeLatticeElement.getNullTypeLatticeElement();
+      return NULL;
     }
     if (type.isPrimitiveType()) {
       return PrimitiveTypeLatticeElement.fromDexType(type);
@@ -379,16 +419,60 @@
     return new ArrayTypeLatticeElement(type, isNullable);
   }
 
+  public static TypeLatticeElement fromDexType(DexType type) {
+    if (type == DexItemFactory.nullValueType) {
+      return NULL;
+    }
+    return fromTypeDescriptorChar((char) type.descriptor.content[0]);
+  }
+
+  public static TypeLatticeElement fromTypeDescriptorChar(char descriptor) {
+    switch (descriptor) {
+      case 'L':
+        // TODO(jsjeon): class type with Object?
+      case '[':
+        // TODO(jsjeon): array type with Object?
+        return REFERENCE;
+      default:
+        return PrimitiveTypeLatticeElement.fromTypeDescriptorChar(descriptor);
+    }
+  }
+
+  public static TypeLatticeElement fromMemberType(MemberType type) {
+    switch (type) {
+      case BOOLEAN:
+      case BYTE:
+      case CHAR:
+      case SHORT:
+      case INT:
+        return INT;
+      case FLOAT:
+        return FLOAT;
+      case INT_OR_FLOAT:
+        return SINGLE;
+      case LONG:
+        return LONG;
+      case DOUBLE:
+        return DOUBLE;
+      case LONG_OR_DOUBLE:
+        return WIDE;
+      case OBJECT:
+        return REFERENCE;
+      default:
+        throw new Unreachable("Unexpected member type: " + type);
+    }
+  }
+
   public static TypeLatticeElement newArray(DexType arrayType, boolean isNullable) {
     return new ArrayTypeLatticeElement(arrayType, isNullable);
   }
 
   public TypeLatticeElement arrayGet(AppInfo appInfo) {
-    return BottomTypeLatticeElement.getInstance();
+    return BOTTOM;
   }
 
   public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
-    TypeLatticeElement castTypeLattice = fromDexType(appInfo, castType, isNullable());
+    TypeLatticeElement castTypeLattice = fromDexType(castType, appInfo, isNullable());
     if (lessThanOrEqual(appInfo, this, castTypeLattice)) {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/WideTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/WideTypeLatticeElement.java
index ec0b846..53c0da1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/WideTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/WideTypeLatticeElement.java
@@ -13,7 +13,7 @@
     super();
   }
 
-  public static WideTypeLatticeElement getInstance() {
+  static WideTypeLatticeElement getInstance() {
     return WIDE_INSTANCE;
   }
 
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 296fba5..cc154e9 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,6 +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.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -139,20 +140,20 @@
       ConstNumber newConst;
       if (type == NumericType.INT) {
         int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
-        Value value = code.createValue(ValueType.INT, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else if (type == NumericType.LONG) {
         long result = foldLongs(leftConst.getLongValue(), rightConst.getLongValue());
-        Value value = code.createValue(ValueType.LONG, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.LONG, getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else if (type == NumericType.FLOAT) {
         float result = foldFloat(leftConst.getFloatValue(), rightConst.getFloatValue());
-        Value value = code.createValue(ValueType.FLOAT, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.FLOAT, getLocalInfo());
         newConst = new ConstNumber(value, Float.floatToIntBits(result));
       } else {
         assert type == NumericType.DOUBLE;
         double result = foldDouble(leftConst.getDoubleValue(), rightConst.getDoubleValue());
-        Value value = code.createValue(ValueType.DOUBLE, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.DOUBLE, getLocalInfo());
         newConst = new ConstNumber(value, Double.doubleToLongBits(result));
       }
       return new ConstLatticeElement(newConst);
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 0c8f214..e222eaa 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.IntTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -107,7 +106,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return IntTypeLatticeElement.getInstance();
+    return TypeLatticeElement.INT;
   }
 
   @Override
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 8a2c169..ae508e8 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
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 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.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -1174,10 +1175,11 @@
     return block;
   }
 
-  public static BasicBlock createRethrowBlock(IRCode code, Position position) {
+  public static BasicBlock createRethrowBlock(
+      IRCode code, Position position, TypeLatticeElement guardTypeLattice) {
     BasicBlock block = new BasicBlock();
-    MoveException moveException =
-        new MoveException(new Value(code.valueNumberGenerator.next(), ValueType.OBJECT, null));
+    MoveException moveException = new MoveException(
+        new Value(code.valueNumberGenerator.next(), guardTypeLattice, null));
     moveException.setPosition(position);
     Throw throwInstruction = new Throw(moveException.outValue);
     throwInstruction.setPosition(position);
@@ -1438,11 +1440,13 @@
       Consumer<BasicBlock> onNewBlock) {
     List<BasicBlock> predecessors = this.getPredecessors();
     boolean hasMoveException = entry().isMoveException();
+    TypeLatticeElement exceptionTypeLattice = null;
     MoveException move = null;
     Position position = entry().getPosition();
     if (hasMoveException) {
       // Remove the move-exception instruction.
       move = entry().asMoveException();
+      exceptionTypeLattice = move.outValue().getTypeLattice();
       assert move.getDebugValues().isEmpty();
       getInstructions().remove(0);
     }
@@ -1458,7 +1462,10 @@
       newBlock.setNumber(nextBlockNumber++);
       newPredecessors.add(newBlock);
       if (hasMoveException) {
-        Value value = new Value(valueNumberGenerator.next(), ValueType.OBJECT, move.getLocalInfo());
+        Value value = new Value(
+            valueNumberGenerator.next(),
+            exceptionTypeLattice,
+            move.getLocalInfo());
         values.add(value);
         MoveException newMove = new MoveException(value);
         newBlock.add(newMove);
@@ -1483,7 +1490,7 @@
           new Phi(
               valueNumberGenerator.next(),
               this,
-              ValueType.OBJECT,
+              exceptionTypeLattice,
               move.getLocalInfo(),
               RegisterReadType.NORMAL);
       phi.addOperands(values);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index fbbe2fd..7caaff9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppInfo;
 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.code.Phi.RegisterReadType;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.google.common.collect.ImmutableList;
@@ -363,8 +364,11 @@
 
     int i = 0;
     if (downcast != null) {
+      Value receiver = invoke.inValues().get(0);
+      TypeLatticeElement castTypeLattice = TypeLatticeElement.fromDexType(
+          downcast, appInfo, receiver.getTypeLattice().isNullable());
       CheckCast castInstruction =
-          new CheckCast(code.createValue(ValueType.OBJECT), invoke.inValues().get(0), downcast);
+          new CheckCast(code.createValue(castTypeLattice), receiver, downcast);
       castInstruction.setPosition(invoke.getPosition());
 
       // Splice in the check cast operation.
@@ -544,7 +548,7 @@
             new Phi(
                 code.valueNumberGenerator.next(),
                 newExitBlock,
-                returnType,
+                returnType.toTypeLattice(),
                 null,
                 RegisterReadType.NORMAL);
         phi.addOperands(operands);
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 239e404..4c2529d 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
@@ -11,9 +11,11 @@
 import com.android.tools.r8.code.CmplFloat;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 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.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.LongInterval;
@@ -179,7 +181,7 @@
           result = (int) Math.signum(left - right);
         }
       }
-      Value value = code.createValue(ValueType.INT, getLocalInfo());
+      Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
       ConstNumber newConst = new ConstNumber(value, result);
       return new ConstLatticeElement(newConst);
     } else if (leftLattice.isValueRange() && rightLattice.isConst()) {
@@ -207,7 +209,7 @@
       return Bottom.getInstance();
     }
     int result = Integer.signum(Long.compare(leftRange.getMin(), rightRange.getMin()));
-    Value value = code.createValue(ValueType.INT, getLocalInfo());
+    Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
     ConstNumber newConst = new ConstNumber(value, result);
     return new ConstLatticeElement(newConst);
   }
@@ -226,4 +228,10 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfCmp(bias, type));
   }
+
+  @Override
+  public TypeLatticeElement evaluate(AppInfo appInfo) {
+    return TypeLatticeElement.INT;
+  }
+
 }
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 2da242e..401656c 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
@@ -105,7 +105,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo, appInfo.dexItemFactory.classType, false);
+    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.classType, appInfo, false);
   }
 
   @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 b319dbb..ffc4359 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
@@ -91,7 +91,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo, appInfo.dexItemFactory.methodHandleType, false);
+    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, appInfo, false);
   }
 
   @Override
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 4159c31..81d7b28 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
@@ -91,7 +91,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo, appInfo.dexItemFactory.methodTypeType, false);
+    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, appInfo, false);
   }
 
   @Override
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 73cf1c2..fa15046 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,15 +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.BottomTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.DoubleTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.FloatTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.IntTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.LongTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.ReferenceTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.SingleTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.WideTypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.NumberUtils;
@@ -49,8 +41,10 @@
   }
 
   public static ConstNumber copyOf(IRCode code, ConstNumber original) {
-    Value newValue =
-        new Value(code.valueNumberGenerator.next(), original.outType(), original.getLocalInfo());
+    Value newValue = new Value(
+        code.valueNumberGenerator.next(),
+        original.outValue().getTypeLattice(),
+        original.getLocalInfo());
     return new ConstNumber(newValue, original.getRawValue());
   }
 
@@ -278,22 +272,23 @@
     // TODO(b/72693244): IR builder should know the type and assign a proper type lattice.
     switch (outType()) {
       case OBJECT:
-        return ReferenceTypeLatticeElement.getNullTypeLatticeElement();
+        assert isZero();
+        return TypeLatticeElement.NULL;
       case INT:
-        return IntTypeLatticeElement.getInstance();
+        return TypeLatticeElement.INT;
       case FLOAT:
-        return FloatTypeLatticeElement.getInstance();
+        return TypeLatticeElement.FLOAT;
       case LONG:
-        return LongTypeLatticeElement.getInstance();
+        return TypeLatticeElement.LONG;
       case DOUBLE:
-        return DoubleTypeLatticeElement.getInstance();
+        return TypeLatticeElement.DOUBLE;
       case INT_OR_FLOAT:
-        return SingleTypeLatticeElement.getInstance();
+        return TypeLatticeElement.SINGLE;
       case LONG_OR_DOUBLE:
-        return WideTypeLatticeElement.getInstance();
+        return TypeLatticeElement.DOUBLE;
       case INT_OR_FLOAT_OR_NULL:
       default:
-        return BottomTypeLatticeElement.getInstance();
+        return TypeLatticeElement.BOTTOM;
     }
   }
 }
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 b5a3b90..85fd784 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
@@ -28,7 +28,9 @@
 
   public static ConstString copyOf(IRCode code, ConstString original) {
     Value newValue =
-        new Value(code.valueNumberGenerator.next(), original.outType(), original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(),
+            original.outValue().getTypeLattice(),
+            original.getLocalInfo());
     return new ConstString(newValue, original.getValue());
   }
 
@@ -131,6 +133,6 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo, appInfo.dexItemFactory.stringType, false);
+    return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.stringType, appInfo, false);
   }
 }
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 1427e12..0578506 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
@@ -3,14 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+
 // Value that has a fixed register allocated. These are used for inserting spill, restore, and phi
 // moves in the spilling register allocator.
 public class FixedRegisterValue extends Value {
   private final int register;
 
-  public FixedRegisterValue(ValueType type, int register) {
+  public FixedRegisterValue(TypeLatticeElement typeLattice, int register) {
     // Set local info to null since these values are never representatives of live-ranges.
-    super(-1, type, null);
+    super(-1, typeLattice, null);
     setNeedsRegister(true);
     this.register = register;
   }
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 1cafe2a..b1e4e28 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
@@ -6,6 +6,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
@@ -683,16 +684,20 @@
     return thisValue;
   }
 
-  public Value createValue(ValueType valueType, DebugLocalInfo local) {
-    return new Value(valueNumberGenerator.next(), valueType, local);
+  public Value createValue(TypeLatticeElement typeLattice, DebugLocalInfo local) {
+    return new Value(valueNumberGenerator.next(), typeLattice, local);
   }
 
-  public Value createValue(ValueType valueType) {
-    return createValue(valueType, null);
+  public Value createValue(TypeLatticeElement typeLattice) {
+    return createValue(typeLattice, null);
+  }
+
+  public Value createValue(DebugLocalInfo local) {
+    return createValue(TypeLatticeElement.BOTTOM, local);
   }
 
   public ConstNumber createIntConstant(int value) {
-    Value out = createValue(ValueType.INT);
+    Value out = createValue(TypeLatticeElement.INT);
     return new ConstNumber(out, value);
   }
 
@@ -701,7 +706,7 @@
   }
 
   public ConstNumber createConstNull() {
-    Value out = createValue(ValueType.OBJECT);
+    Value out = createValue(TypeLatticeElement.NULL);
     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 266069b..4fdb047 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
@@ -228,12 +228,13 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
+    ValueType ifType = inValues.get(0).type;
     if (inValues.size() == 1) {
-      builder.add(new CfIf(type, inValues.get(0).type, builder.getLabel(getTrueTarget())));
+      builder.add(new CfIf(type, ifType, builder.getLabel(getTrueTarget())));
       return;
     }
     assert inValues.size() == 2;
     assert inValues.get(0).type == inValues.get(1).type;
-    builder.add(new CfIfCmp(type, inValues.get(0).type, builder.getLabel(getTrueTarget())));
+    builder.add(new CfIfCmp(type, ifType, builder.getLabel(getTrueTarget())));
   }
 }
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 0a919bb..0c8c372 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
@@ -135,7 +135,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo, field.type, true);
+    return TypeLatticeElement.fromDexType(field.type, appInfo, true);
   }
 
   @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 c5dc5e4..8e2446b 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,6 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.IntTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -87,7 +86,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return IntTypeLatticeElement.getInstance();
+    return TypeLatticeElement.INT;
   }
 
   @Override
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 6e7c207..b755336 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
@@ -280,6 +280,6 @@
     if (returnType.isVoidType()) {
       throw new Unreachable("void methods have no type.");
     }
-    return TypeLatticeElement.fromDexType(appInfo, returnType, true);
+    return TypeLatticeElement.fromDexType(returnType, appInfo, true);
   }
 }
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 4b34ca1..ad3d344 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,6 +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.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -116,7 +117,7 @@
       ConstNumber newConst;
       if (type == NumericType.INT) {
         int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
-        Value value = code.createValue(ValueType.INT, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.INT, getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else {
         assert type == NumericType.LONG;
@@ -128,7 +129,7 @@
           right = rightConst.getLongValue();
         }
         long result = foldLongs(leftConst.getLongValue(), right);
-        Value value = code.createValue(ValueType.LONG, getLocalInfo());
+        Value value = code.createValue(TypeLatticeElement.LONG, getLocalInfo());
         newConst = new ConstNumber(value, result);
       }
       return new ConstLatticeElement(newConst);
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 c92ac50..562b33d 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
@@ -94,14 +94,15 @@
     return true;
   }
 
-  private Set<DexType> collectExceptionTypes(DexItemFactory dexItemFactory) {
-    Set<DexType> exceptionTypes = new HashSet<>(getBlock().getPredecessors().size());
-    for (BasicBlock block : getBlock().getPredecessors()) {
+  public static Set<DexType> collectExceptionTypes(
+      BasicBlock currentBlock, DexItemFactory dexItemFactory) {
+    Set<DexType> exceptionTypes = new HashSet<>(currentBlock.getPredecessors().size());
+    for (BasicBlock block : currentBlock.getPredecessors()) {
       int size = block.getCatchHandlers().size();
       List<BasicBlock> targets = block.getCatchHandlers().getAllTargets();
       List<DexType> guards = block.getCatchHandlers().getGuards();
       for (int i = 0; i < size; i++) {
-        if (targets.get(i) == getBlock()) {
+        if (targets.get(i) == currentBlock) {
           DexType guard = guards.get(i);
           exceptionTypes.add(
               guard == dexItemFactory.catchAllType
@@ -115,14 +116,14 @@
 
   @Override
   public DexType computeVerificationType(TypeVerificationHelper helper) {
-    return helper.join(collectExceptionTypes(helper.getFactory()));
+    return helper.join(collectExceptionTypes(getBlock(), helper.getFactory()));
   }
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    Set<DexType> exceptionTypes = collectExceptionTypes(appInfo.dexItemFactory);
+    Set<DexType> exceptionTypes = collectExceptionTypes(getBlock(), appInfo.dexItemFactory);
     return TypeLatticeElement.join(
         appInfo,
-        exceptionTypes.stream().map(t -> TypeLatticeElement.fromDexType(appInfo, t, false)));
+        exceptionTypes.stream().map(t -> TypeLatticeElement.fromDexType(t, appInfo, false)));
   }
 }
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 16b2922..86e7440 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,6 +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.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -81,8 +83,8 @@
     LatticeElement sourceLattice = getLatticeElement.apply(source());
     if (sourceLattice.isConst()) {
       ConstNumber sourceConst = sourceLattice.asConst().getConstNumber();
-      ValueType valueType = ValueType.fromNumericType(type);
-      Value value = code.createValue(valueType, getLocalInfo());
+      TypeLatticeElement typeLattice = PrimitiveTypeLatticeElement.fromNumericType(type);
+      Value value = code.createValue(typeLattice, getLocalInfo());
       ConstNumber newConst;
       if (type == NumericType.INT) {
         newConst = new ConstNumber(value, -sourceConst.getIntValue());
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 8dbc38b..29b263f 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
@@ -107,7 +107,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo, clazz, false);
+    return TypeLatticeElement.fromDexType(clazz, appInfo, false);
   }
 
   @Override
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 1037c34..55918cc 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
@@ -12,6 +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.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -35,8 +37,8 @@
     LatticeElement sourceLattice = getLatticeElement.apply(source());
     if (sourceLattice.isConst()) {
       ConstNumber sourceConst = sourceLattice.asConst().getConstNumber();
-      ValueType valueType = ValueType.fromNumericType(type);
-      Value value = code.createValue(valueType, getLocalInfo());
+      TypeLatticeElement typeLattice  = PrimitiveTypeLatticeElement.fromNumericType(type);
+      Value value = code.createValue(typeLattice, getLocalInfo());
       ConstNumber newConst;
       if (type == NumericType.INT) {
         newConst = new ConstNumber(value, ~sourceConst.getIntValue());
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 f3301e7..c688e6d 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
@@ -8,6 +8,7 @@
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock.EdgeType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -41,10 +42,10 @@
   public Phi(
       int number,
       BasicBlock block,
-      ValueType type,
+      TypeLatticeElement typeLattice,
       DebugLocalInfo local,
       RegisterReadType readType) {
-    super(number, type, local);
+    super(number, typeLattice, local);
     this.block = block;
     this.readType = readType;
     block.addPhi(this);
@@ -95,7 +96,7 @@
           assert readType == RegisterReadType.DEBUG;
           BasicBlock block = getBlock();
           InstructionListIterator it = block.listIterator();
-          Value value = new Value(builder.getValueNumberGenerator().next(), type, null);
+          Value value = new Value(builder.getValueNumberGenerator().next(), getTypeLattice(), null);
           Position position = block.getPosition();
           Instruction definition = new DebugLocalUninitialized(value);
           definition.setBlock(block);
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 46aa5db..d9dabef 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
@@ -5,14 +5,15 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 
 public class StackValue extends Value {
 
   private final int height;
   private final DexType objectType;
 
-  private StackValue(DexType objectType, ValueType valueType, int height) {
-    super(Value.UNDEFINED_NUMBER, valueType, null);
+  private StackValue(DexType objectType, TypeLatticeElement typeLattice, int height) {
+    super(Value.UNDEFINED_NUMBER, typeLattice, null);
     this.height = height;
     this.objectType = objectType;
     assert height >= 0;
@@ -20,12 +21,12 @@
 
   public static StackValue forObjectType(DexType type, int height) {
     assert DexItemFactory.nullValueType == type || type.isClassType() || type.isArrayType();
-    return new StackValue(type, ValueType.OBJECT, height);
+    return new StackValue(type, TypeLatticeElement.fromDexType(type), height);
   }
 
   public static StackValue forNonObjectType(ValueType valueType, int height) {
     assert valueType.isPreciseType() && !valueType.isObject();
-    return new StackValue(null, valueType, height);
+    return new StackValue(null, valueType.toTypeLattice(), height);
   }
 
   public int getHeight() {
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 50591b0..9ef7ed4 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
@@ -145,7 +145,7 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    return TypeLatticeElement.fromDexType(appInfo, field.type, true);
+    return TypeLatticeElement.fromDexType(field.type, appInfo, true);
   }
 
   @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 48b8f5b..26c2e60 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.analysis.type.BottomTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
@@ -43,6 +42,7 @@
               new MethodPosition(method)));
     }
     type = meet;
+    typeLattice = meet.toTypeLattice();
   }
 
   public void markNonDebugLocalRead() {
@@ -113,7 +113,8 @@
 
   public static final int UNDEFINED_NUMBER = -1;
 
-  public static final Value UNDEFINED = new Value(UNDEFINED_NUMBER, ValueType.OBJECT, null);
+  public static final Value UNDEFINED =
+      new Value(UNDEFINED_NUMBER, TypeLatticeElement.BOTTOM, null);
 
   protected final int number;
   // TODO(b/72693244): deprecate once typeLattice is landed.
@@ -134,12 +135,13 @@
   private boolean knownToBeBoolean = false;
   private LongInterval valueRange;
   private DebugData debugData;
-  private TypeLatticeElement typeLattice = BottomTypeLatticeElement.getInstance();
+  private TypeLatticeElement typeLattice;
 
-  public Value(int number, ValueType type, DebugLocalInfo local) {
+  public Value(int number, TypeLatticeElement typeLattice, DebugLocalInfo local) {
     this.number = number;
-    this.type = type;
+    this.type = ValueType.fromTypeLattice(typeLattice);
     this.debugData = local == null ? null : new DebugData(local);
+    this.typeLattice = typeLattice;
   }
 
   public boolean isFixedRegisterValue() {
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 99f949d..2d53749 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,6 +7,7 @@
 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.TypeLatticeElement;
 
 public enum ValueType {
   OBJECT,
@@ -181,4 +182,54 @@
         throw new Unreachable("Invalid numeric type '" + type + "'");
     }
   }
+
+  public static ValueType fromTypeLattice(TypeLatticeElement typeLatticeElement) {
+    if (typeLatticeElement.isBottom()) {
+      return INT_OR_FLOAT_OR_NULL;
+    }
+    if (typeLatticeElement.isReference()) {
+      return OBJECT;
+    }
+    if (typeLatticeElement.isInt()) {
+      return INT;
+    }
+    if (typeLatticeElement.isFloat()) {
+      return FLOAT;
+    }
+    if (typeLatticeElement.isLong()) {
+      return LONG;
+    }
+    if (typeLatticeElement.isDouble()) {
+      return DOUBLE;
+    }
+    if (typeLatticeElement.isSingle()) {
+      return INT_OR_FLOAT;
+    }
+    if (typeLatticeElement.isWide()) {
+      return LONG_OR_DOUBLE;
+    }
+    throw new Unreachable("Invalid type lattice '" + typeLatticeElement + "'");
+  }
+
+  public TypeLatticeElement toTypeLattice() {
+    switch (this) {
+      case OBJECT:
+        return TypeLatticeElement.REFERENCE;
+      case INT:
+        return TypeLatticeElement.INT;
+      case FLOAT:
+        return TypeLatticeElement.FLOAT;
+      case INT_OR_FLOAT:
+        return TypeLatticeElement.SINGLE;
+      case LONG:
+        return TypeLatticeElement.LONG;
+      case DOUBLE:
+        return TypeLatticeElement.DOUBLE;
+      case LONG_OR_DOUBLE:
+        return TypeLatticeElement.WIDE;
+      case INT_OR_FLOAT_OR_NULL:
+      default:
+        return TypeLatticeElement.BOTTOM;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 6fbafcb..9e14033 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -22,10 +22,10 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfState.Snapshot;
 import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
 import com.android.tools.r8.origin.Origin;
@@ -347,9 +347,9 @@
       if (type.isBooleanType()) {
         builder.addBooleanNonThisArgument(argumentRegister++);
       } else {
-        ValueType valueType = ValueType.fromDexType(type);
-        builder.addNonThisArgument(argumentRegister, valueType);
-        argumentRegister += valueType.requiredRegisters();
+        TypeLatticeElement typeLattice = TypeLatticeElement.fromDexType(type);
+        builder.addNonThisArgument(argumentRegister, typeLattice);
+        argumentRegister += typeLattice.requiredRegisters();
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index b588408..b7c99b1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -40,10 +40,10 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -71,7 +71,7 @@
   private Position currentPosition = null;
   private final CanonicalPositions canonicalPositions;
 
-  private final List<ValueType> argumentTypes;
+  private final List<TypeLatticeElement> argumentTypes;
 
   private List<DexDebugEntry> debugEntries = null;
   // In case of inlining the position of the invoke in the caller.
@@ -150,9 +150,9 @@
       builder.addThisArgument(register);
       ++register;
     }
-    for (ValueType type : argumentTypes) {
-      builder.addNonThisArgument(register, type);
-      register += type.requiredRegisters();
+    for (TypeLatticeElement typeLattice : argumentTypes) {
+      builder.addNonThisArgument(register, typeLattice);
+      register += typeLattice.requiredRegisters();
     }
   }
 
@@ -303,12 +303,12 @@
         arrayFilledDataPayloadResolver.getData(payloadOffset));
   }
 
-  private List<ValueType> computeArgumentTypes() {
-    List<ValueType> types = new ArrayList<>(proto.parameters.size());
+  private List<TypeLatticeElement> computeArgumentTypes() {
+    List<TypeLatticeElement> types = new ArrayList<>(proto.parameters.size());
     String shorty = proto.shorty.toString();
     for (int i = 1; i < proto.shorty.size; i++) {
-      ValueType valueType = ValueType.fromTypeDescriptorChar(shorty.charAt(i));
-      types.add(valueType);
+      TypeLatticeElement typeLattice = TypeLatticeElement.fromTypeDescriptorChar(shorty.charAt(i));
+      types.add(typeLattice);
     }
     return types;
   }
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 deac5eb..b471e12 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
@@ -20,6 +20,8 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 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.code.Add;
 import com.android.tools.r8.ir.code.And;
 import com.android.tools.r8.ir.code.Argument;
@@ -118,6 +120,11 @@
  * http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf
  */
 public class IRBuilder {
+  private static final TypeLatticeElement INT = TypeLatticeElement.INT;
+  private static final TypeLatticeElement FLOAT = TypeLatticeElement.FLOAT;
+  private static final TypeLatticeElement LONG = TypeLatticeElement.LONG;
+  private static final TypeLatticeElement DOUBLE = TypeLatticeElement.DOUBLE;
+  private static final TypeLatticeElement NULL = TypeLatticeElement.NULL;
 
   public static final int INITIAL_BLOCK_OFFSET = -1;
 
@@ -672,7 +679,10 @@
     int moveExceptionDest = source.getMoveExceptionRegister(targetIndex);
     Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
     if (moveExceptionDest >= 0) {
-      Value out = writeRegister(moveExceptionDest, ValueType.OBJECT, ThrowingInfo.NO_THROW, null);
+      Set<DexType> exceptionTypes = MoveException.collectExceptionTypes(currentBlock, getFactory());
+      TypeLatticeElement typeLattice = TypeLatticeElement.join(appInfo,
+          exceptionTypes.stream().map(t -> TypeLatticeElement.fromDexType(t, appInfo, false)));
+      Value out = writeRegister(moveExceptionDest, typeLattice, ThrowingInfo.NO_THROW, null);
       MoveException moveException = new MoveException(out);
       moveException.setPosition(position);
       currentBlock.add(moveException);
@@ -719,20 +729,23 @@
 
   public void addThisArgument(int register) {
     DebugLocalInfo local = getOutgoingLocal(register);
-    Value value = writeRegister(register, ValueType.OBJECT, ThrowingInfo.NO_THROW, local);
+    // TODO(b/72693244): Update nullability if this is for building inlinee's IR.
+    TypeLatticeElement receiver =
+        TypeLatticeElement.fromDexType(method.method.getHolder(), appInfo, false);
+    Value value = writeRegister(register, receiver, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value));
     value.markAsThis();
   }
 
-  public void addNonThisArgument(int register, ValueType valueType) {
+  public void addNonThisArgument(int register, TypeLatticeElement typeLattice) {
     DebugLocalInfo local = getOutgoingLocal(register);
-    Value value = writeRegister(register, valueType, ThrowingInfo.NO_THROW, local);
+    Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value));
   }
 
   public void addBooleanNonThisArgument(int register) {
     DebugLocalInfo local = getOutgoingLocal(register);
-    Value value = writeRegister(register, ValueType.INT, ThrowingInfo.NO_THROW, local);
+    Value value = writeRegister(register, INT, ThrowingInfo.NO_THROW, local);
     value.setKnownToBeBoolean(true);
     addInstruction(new Argument(value));
   }
@@ -756,7 +769,8 @@
       // 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.outType(), ThrowingInfo.NO_THROW, local);
+      Value out = writeRegister(
+          register, incomingValue.getTypeLattice(), ThrowingInfo.NO_THROW, local);
       DebugLocalWrite write = new DebugLocalWrite(out, incomingValue);
       addInstruction(write);
     }
@@ -831,7 +845,8 @@
   public void addArrayGet(MemberType type, int dest, int array, int index) {
     Value in1 = readRegister(array, ValueType.OBJECT);
     Value in2 = readRegister(index, ValueType.INT);
-    Value out = writeRegister(dest, ValueType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+    Value out = writeRegister(
+        dest, TypeLatticeElement.fromMemberType(type), ThrowingInfo.CAN_THROW);
     out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
     ArrayGet instruction = new ArrayGet(type, out, in1, in2);
     assert instruction.instructionTypeCanThrow();
@@ -840,7 +855,7 @@
 
   public void addArrayLength(int dest, int array) {
     Value in = readRegister(array, ValueType.OBJECT);
-    Value out = writeRegister(dest, ValueType.INT, ThrowingInfo.CAN_THROW);
+    Value out = writeRegister(dest, INT, ThrowingInfo.CAN_THROW);
     ArrayLength instruction = new ArrayLength(out, in);
     assert instruction.instructionTypeCanThrow();
     add(instruction);
@@ -856,7 +871,9 @@
 
   public void addCheckCast(int value, DexType type) {
     Value in = readRegister(value, ValueType.OBJECT);
-    Value out = writeRegister(value, ValueType.OBJECT, ThrowingInfo.CAN_THROW);
+    TypeLatticeElement castTypeLattice =
+        TypeLatticeElement.fromDexType(type, appInfo, in.getTypeLattice().isNullable());
+    Value out = writeRegister(value, castTypeLattice, ThrowingInfo.CAN_THROW);
     CheckCast instruction = new CheckCast(out, in, type);
     assert instruction.instructionTypeCanThrow();
     add(instruction);
@@ -865,41 +882,42 @@
   public void addCmp(NumericType type, Bias bias, int dest, int left, int right) {
     Value in1 = readNumericRegister(left, type);
     Value in2 = readNumericRegister(right, type);
-    Value out = writeRegister(dest, ValueType.INT, ThrowingInfo.NO_THROW);
+    Value out = writeRegister(dest, INT, ThrowingInfo.NO_THROW);
     Cmp instruction = new Cmp(type, bias, out, in1, in2);
     assert !instruction.instructionTypeCanThrow();
     add(instruction);
   }
 
-  public void addConst(ValueType type, int dest, long value) {
-    Value out = writeRegister(dest, type, ThrowingInfo.NO_THROW);
+  public void addConst(TypeLatticeElement typeLattice, int dest, long value) {
+    Value out = writeRegister(dest, typeLattice, ThrowingInfo.NO_THROW);
     ConstNumber instruction = new ConstNumber(out, value);
     assert !instruction.instructionTypeCanThrow();
     add(instruction);
   }
 
   public void addLongConst(int dest, long value) {
-    add(new ConstNumber(writeRegister(dest, ValueType.LONG, ThrowingInfo.NO_THROW), value));
+    add(new ConstNumber(writeRegister(dest, LONG, ThrowingInfo.NO_THROW), value));
   }
 
   public void addDoubleConst(int dest, long value) {
-    add(new ConstNumber(writeRegister(dest, ValueType.DOUBLE, ThrowingInfo.NO_THROW), value));
+    add(new ConstNumber(writeRegister(dest, DOUBLE, ThrowingInfo.NO_THROW), value));
   }
 
   public void addIntConst(int dest, long value) {
-    add(new ConstNumber(writeRegister(dest, ValueType.INT, ThrowingInfo.NO_THROW), value));
+    add(new ConstNumber(writeRegister(dest, INT, ThrowingInfo.NO_THROW), value));
   }
 
   public void addFloatConst(int dest, long value) {
-    add(new ConstNumber(writeRegister(dest, ValueType.FLOAT, ThrowingInfo.NO_THROW), value));
+    add(new ConstNumber(writeRegister(dest, FLOAT, ThrowingInfo.NO_THROW), value));
   }
 
   public void addNullConst(int dest) {
-    add(new ConstNumber(writeRegister(dest, ValueType.OBJECT, ThrowingInfo.NO_THROW), 0L));
+    add(new ConstNumber(writeRegister(dest, NULL, ThrowingInfo.NO_THROW), 0L));
   }
 
   public void addConstClass(int dest, DexType type) {
-    Value out = writeRegister(dest, ValueType.OBJECT, ThrowingInfo.CAN_THROW);
+    TypeLatticeElement typeLattice = TypeLatticeElement.classClassType(appInfo);
+    Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstClass instruction = new ConstClass(out, type);
     assert instruction.instructionTypeCanThrow();
     add(instruction);
@@ -912,7 +930,9 @@
           "Const-method-handle",
           null /* sourceString */);
     }
-    Value out = writeRegister(dest, ValueType.OBJECT, ThrowingInfo.CAN_THROW);
+    TypeLatticeElement typeLattice =
+        TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, appInfo, false);
+    Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstMethodHandle instruction = new ConstMethodHandle(out, methodHandle);
     add(instruction);
   }
@@ -924,13 +944,16 @@
           "Const-method-type",
           null /* sourceString */);
     }
-    Value out = writeRegister(dest, ValueType.OBJECT, ThrowingInfo.CAN_THROW);
+    TypeLatticeElement typeLattice =
+        TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, appInfo, false);
+    Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstMethodType instruction = new ConstMethodType(out, methodType);
     add(instruction);
   }
 
   public void addConstString(int dest, DexString string) {
-    Value out = writeRegister(dest, ValueType.OBJECT, ThrowingInfo.CAN_THROW);
+    TypeLatticeElement typeLattice = TypeLatticeElement.stringClassType(appInfo);
+    Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstString instruction = new ConstString(out, string);
     add(instruction);
   }
@@ -971,7 +994,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, type, ThrowingInfo.NO_THROW);
+        Value out = writeRegister(dest, in.getTypeLattice(), ThrowingInfo.NO_THROW);
         addInstruction(new DebugLocalWrite(out, in));
         return;
       }
@@ -1067,7 +1090,8 @@
     }
   }
 
-  public void addIfZero(If.Type type, ValueType operandType, int value, int trueTargetOffset, int falseTargetOffset) {
+  public void addIfZero(
+      If.Type type, ValueType operandType, int value, int trueTargetOffset, int falseTargetOffset) {
     if (trueTargetOffset == falseTargetOffset) {
       addTrivialIf(trueTargetOffset, falseTargetOffset);
     } else {
@@ -1079,7 +1103,8 @@
   public void addInstanceGet(int dest, int object, DexField field) {
     MemberType type = MemberType.fromDexType(field.type);
     Value in = readRegister(object, ValueType.OBJECT);
-    Value out = writeRegister(dest, ValueType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+    Value out = writeRegister(
+        dest, TypeLatticeElement.fromDexType(field.type, appInfo, true), ThrowingInfo.CAN_THROW);
     out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
     InstanceGet instruction = new InstanceGet(type, out, in, field);
     assert instruction.instructionTypeCanThrow();
@@ -1088,7 +1113,7 @@
 
   public void addInstanceOf(int dest, int value, DexType type) {
     Value in = readRegister(value, ValueType.OBJECT);
-    Value out = writeRegister(dest, ValueType.INT, ThrowingInfo.CAN_THROW);
+    Value out = writeRegister(dest, INT, ThrowingInfo.CAN_THROW);
     InstanceOf instruction = new InstanceOf(out, in, type);
     assert instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -1359,7 +1384,8 @@
     assert invoke.outValue() == null;
     assert invoke.instructionTypeCanThrow();
     DexType outType = invoke.getReturnType();
-    Value outValue = writeRegister(dest, ValueType.fromDexType(outType), ThrowingInfo.CAN_THROW);
+    Value outValue =
+        writeRegister(dest, TypeLatticeElement.fromDexType(outType), ThrowingInfo.CAN_THROW);
     outValue.setKnownToBeBoolean(outType.isBooleanType());
     invoke.setOutValue(outValue);
   }
@@ -1389,7 +1415,8 @@
   public void addNewArrayEmpty(int dest, int size, DexType type) {
     assert type.isArrayType();
     Value in = readRegister(size, ValueType.INT);
-    Value out = writeRegister(dest, ValueType.OBJECT, ThrowingInfo.CAN_THROW);
+    TypeLatticeElement arrayTypeLattice = TypeLatticeElement.fromDexType(type, appInfo, false);
+    Value out = writeRegister(dest, arrayTypeLattice, ThrowingInfo.CAN_THROW);
     NewArrayEmpty instruction = new NewArrayEmpty(out, in, type);
     assert instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -1400,7 +1427,8 @@
   }
 
   public void addNewInstance(int dest, DexType type) {
-    Value out = writeRegister(dest, ValueType.OBJECT, ThrowingInfo.CAN_THROW);
+    TypeLatticeElement instanceType = TypeLatticeElement.fromDexType(type, appInfo, false);
+    Value out = writeRegister(dest, instanceType, ThrowingInfo.CAN_THROW);
     NewInstance instruction = new NewInstance(type, out);
     assert instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -1426,7 +1454,8 @@
 
   public void addStaticGet(int dest, DexField field) {
     MemberType type = MemberType.fromDexType(field.type);
-    Value out = writeRegister(dest, ValueType.fromMemberType(type), ThrowingInfo.CAN_THROW);
+    Value out = writeRegister(
+        dest, TypeLatticeElement.fromDexType(field.type, appInfo, true), ThrowingInfo.CAN_THROW);
     out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
     StaticGet instruction = new StaticGet(type, out, field);
     assert instruction.instructionTypeCanThrow();
@@ -1683,8 +1712,8 @@
 
   public Value readRegister(int register, ValueType type) {
     DebugLocalInfo local = getIncomingLocal(register);
-    Value value =
-        readRegister(register, type, currentBlock, EdgeType.NON_EDGE, RegisterReadType.NORMAL);
+    Value value = readRegister(
+        register, type, currentBlock, EdgeType.NON_EDGE, RegisterReadType.NORMAL);
     // Check that any information about a current-local is consistent with the read.
     if (local != null && value.getLocalInfo() != local && !value.isUninitializedLocal()) {
       throw new InvalidDebugInfoException(
@@ -1759,7 +1788,9 @@
         value = getUninitializedDebugLocalValue(register, type);
       } else {
         DebugLocalInfo local = getIncomingLocalAtBlock(register, block);
-        Phi phi = new Phi(valueNumberGenerator.next(), block, type, local, readType);
+        // TODO(b/72693244): Use BOTTOM, then run type analysis at the end of IR building.
+        Phi phi = new Phi(
+            valueNumberGenerator.next(), block, type.toTypeLattice(), local, readType);
         if (!block.isSealed()) {
           block.addIncompletePhi(register, phi, readingEdge);
           value = phi;
@@ -1811,7 +1842,7 @@
     // Create a new SSA value for the uninitialized local value.
     // Note that the uninitialized local value must not itself have local information, so that it
     // does not contribute to the visible/live-range of the local variable.
-    Value value = new Value(valueNumberGenerator.next(), type, null);
+    Value value = new Value(valueNumberGenerator.next(), type.toTypeLattice(), null);
     values.add(value);
     return value;
   }
@@ -1830,14 +1861,14 @@
   }
 
   public Value readLongLiteral(long constant) {
-    Value value = new Value(valueNumberGenerator.next(), ValueType.LONG, null);
+    Value value = new Value(valueNumberGenerator.next(), LONG, null);
     ConstNumber number = new ConstNumber(value, constant);
     add(number);
     return number.outValue();
   }
 
   public Value readIntLiteral(long constant) {
-    Value value = new Value(valueNumberGenerator.next(), ValueType.INT, null);
+    Value value = new Value(valueNumberGenerator.next(), INT, null);
     ConstNumber number = new ConstNumber(value, constant);
     add(number);
     return number.outValue();
@@ -1846,14 +1877,14 @@
   // This special write register is needed when changing the scoping of a local variable.
   // See addDebugLocalStart and addDebugLocalEnd.
   private Value writeRegister(
-      int register, ValueType type, ThrowingInfo throwing, DebugLocalInfo local) {
+      int register, TypeLatticeElement typeLattice, ThrowingInfo throwing, DebugLocalInfo local) {
     checkRegister(register);
-    Value value = new Value(valueNumberGenerator.next(), type, local);
+    Value value = new Value(valueNumberGenerator.next(), typeLattice, local);
     currentBlock.writeCurrentDefinition(register, value, throwing);
     return value;
   }
 
-  public Value writeRegister(int register, ValueType type, ThrowingInfo throwing) {
+  public Value writeRegister(int register, TypeLatticeElement 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
@@ -1868,11 +1899,11 @@
         (incomingLocal == null || incomingLocal != outgoingLocal)
             ? null
             : readRegisterForDebugLocal(register, incomingLocal);
-    return writeRegister(register, type, throwing, outgoingLocal);
+    return writeRegister(register, typeLattice, throwing, outgoingLocal);
   }
 
   public Value writeNumericRegister(int register, NumericType type, ThrowingInfo throwing) {
-    return writeRegister(register, ValueType.fromNumericType(type), throwing);
+    return writeRegister(register, PrimitiveTypeLatticeElement.fromNumericType(type), throwing);
   }
 
   private DebugLocalInfo getIncomingLocal(int register) {
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 a662e1e..7e5759a 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
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
 import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -34,7 +35,6 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -143,7 +143,7 @@
     this.options = options;
     this.printer = printer;
     this.codeRewriter = new CodeRewriter(this, libraryMethodsReturningReceiver(), options);
-    this.stringConcatRewriter = new StringConcatRewriter(options.itemFactory);
+    this.stringConcatRewriter = new StringConcatRewriter(appInfo);
     this.lambdaRewriter = options.enableDesugaring ? new LambdaRewriter(this) : null;
     this.interfaceMethodRewriter =
         (options.enableDesugaring && enableInterfaceMethodDesugaring())
@@ -151,8 +151,8 @@
     this.twrCloseResourceRewriter =
         (options.enableDesugaring && enableTwrCloseResourceDesugaring())
             ? new TwrCloseResourceRewriter(this) : null;
-    this.lambdaMerger = options.enableLambdaMerging
-        ? new LambdaMerger(appInfo.dexItemFactory, options.reporter) : null;
+    this.lambdaMerger =
+        options.enableLambdaMerging ? new LambdaMerger(appInfo, options.reporter) : null;
     this.covariantReturnTypeAnnotationTransformer =
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
@@ -643,6 +643,26 @@
     }
   }
 
+  public void optimizeMethodOnSynthesizedClass(DexProgramClass clazz, DexEncodedMethod method) {
+    if (!method.isProcessed()) {
+      try {
+        enterCachedClass(clazz);
+        // Process the generated method, but don't apply any outlining.
+        optimizeSynthesizedMethod(method);
+      } finally {
+        leaveCachedClass(clazz);
+      }
+    }
+  }
+
+  public void optimizeSynthesizedMethod(DexEncodedMethod method) {
+    if (!method.isProcessed()) {
+      // Process the generated method, but don't apply any outlining.
+      processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
+          Outliner::noProcessing);
+    }
+  }
+
   private void enterCachedClass(DexProgramClass clazz) {
     DexProgramClass previous = cachedClasses.put(clazz.type, clazz);
     assert previous == null;
@@ -653,12 +673,6 @@
     assert existing == clazz;
   }
 
-  public void optimizeSynthesizedMethod(DexEncodedMethod method) {
-    // Process the generated method, but don't apply any outlining.
-    processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
-        Outliner::noProcessing);
-  }
-
   private String logCode(InternalOptions options, DexEncodedMethod method) {
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
@@ -948,7 +962,7 @@
 
   private void finalizeToDex(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     // Workaround massive dex2oat memory use for self-recursive methods.
-    CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(code, options);
+    CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(code, options, appInfo);
     // Perform register allocation.
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
     method.setCode(code, registerAllocator, options);
@@ -1061,7 +1075,7 @@
     Instruction check = it.previous();
     assert addBefore == check;
     // Forced definition of const-zero
-    Value fixitValue = code.createValue(ValueType.INT);
+    Value fixitValue = code.createValue(TypeLatticeElement.INT);
     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/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index d17233f..22a5bc2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.If;
@@ -278,28 +279,29 @@
     state.beginTransactionSynthetic();
 
     // Record types for arguments.
-    Int2ReferenceMap<ValueType> argumentLocals = recordArgumentTypes();
-    Int2ReferenceMap<ValueType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
+    Int2ReferenceMap<TypeLatticeElement> argumentLocals = recordArgumentTypes(builder);
+    Int2ReferenceMap<TypeLatticeElement> initializedLocals =
+        new Int2ReferenceOpenHashMap<>(argumentLocals);
     // Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
     for (Object o : node.localVariables) {
       LocalVariableNode local = (LocalVariableNode) o;
       Type localType;
-      ValueType localValueType;
+      TypeLatticeElement localValueTypeLattice;
       switch (application.getAsmType(local.desc).getSort()) {
         case Type.OBJECT:
         case Type.ARRAY: {
           localType = JarState.NULL_TYPE;
-          localValueType = ValueType.OBJECT;
+          localValueTypeLattice = TypeLatticeElement.REFERENCE;
           break;
         }
         case Type.LONG: {
           localType = Type.LONG_TYPE;
-          localValueType = ValueType.LONG;
+          localValueTypeLattice = TypeLatticeElement.LONG;
           break;
         }
         case Type.DOUBLE: {
           localType = Type.DOUBLE_TYPE;
-          localValueType = ValueType.DOUBLE;
+          localValueTypeLattice = TypeLatticeElement.DOUBLE;
           break;
         }
         case Type.BOOLEAN:
@@ -308,12 +310,12 @@
         case Type.SHORT:
         case Type.INT: {
           localType = Type.INT_TYPE;
-          localValueType = ValueType.INT;
+          localValueTypeLattice = TypeLatticeElement.INT;
           break;
         }
         case Type.FLOAT: {
           localType = Type.FLOAT_TYPE;
-          localValueType = ValueType.FLOAT;
+          localValueTypeLattice = TypeLatticeElement.FLOAT;
           break;
         }
         case Type.VOID:
@@ -322,11 +324,11 @@
           throw new Unreachable("Invalid local variable type: " );
       }
       int localRegister = state.getLocalRegister(local.index, localType);
-      ValueType existingLocalType = initializedLocals.get(localRegister);
-      if (existingLocalType == null) {
+      TypeLatticeElement existingLocalTypeLattice = initializedLocals.get(localRegister);
+      if (existingLocalTypeLattice == null) {
         int writeRegister = state.writeLocal(local.index, localType);
         assert writeRegister == localRegister;
-        initializedLocals.put(localRegister, localValueType);
+        initializedLocals.put(localRegister, localValueTypeLattice);
       }
     }
 
@@ -375,31 +377,31 @@
       builder.addThisArgument(slot.register);
     }
     for (Type type : parameterTypes) {
-      ValueType valueType = valueType(type);
+      TypeLatticeElement typeLattice = typeLattice(type);
       Slot slot = state.readLocal(argumentRegister, type);
       if (type == Type.BOOLEAN_TYPE) {
         builder.addBooleanNonThisArgument(slot.register);
       } else {
-        builder.addNonThisArgument(slot.register, valueType);
+        builder.addNonThisArgument(slot.register, typeLattice);
       }
-      argumentRegister += valueType.requiredRegisters();
+      argumentRegister += typeLattice.requiredRegisters();
     }
   }
 
-  private Int2ReferenceMap<ValueType> recordArgumentTypes() {
-    Int2ReferenceMap<ValueType> initializedLocals =
+  private Int2ReferenceMap<TypeLatticeElement> recordArgumentTypes(IRBuilder builder) {
+    Int2ReferenceMap<TypeLatticeElement> initializedLocals =
         new Int2ReferenceOpenHashMap<>(node.localVariables.size());
     int argumentRegister = 0;
     if (!isStatic()) {
       Type thisType = application.getAsmType(clazz.descriptor.toString());
       int register = state.writeLocal(argumentRegister++, thisType);
-      initializedLocals.put(register, valueType(thisType));
+      initializedLocals.put(register, typeLattice(thisType));
     }
     for (Type type : parameterTypes) {
-      ValueType valueType = valueType(type);
+      TypeLatticeElement typeLattice = typeLattice(type);
       int register = state.writeLocal(argumentRegister, type);
-      argumentRegister += valueType.requiredRegisters();
-      initializedLocals.put(register, valueType);
+      argumentRegister += typeLattice.requiredRegisters();
+      initializedLocals.put(register, typeLattice);
     }
     return initializedLocals;
   }
@@ -937,6 +939,10 @@
     }
   }
 
+  private static TypeLatticeElement typeLattice(Type type) {
+    return valueType(type).toTypeLattice();
+  }
+
   private static MemberType memberType(Type type) {
     switch (type.getSort()) {
       case Type.ARRAY:
@@ -2691,7 +2697,10 @@
     }
   }
 
-  private static void addArgument(List<ValueType> types, List<Integer> registers, Type type,
+  private static void addArgument(
+      List<ValueType> types,
+      List<Integer> registers,
+      Type type,
       Slot slot) {
     assert slot.isCompatibleWith(type);
     types.add(valueType(type));
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 283c163..c773eff 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
@@ -73,7 +73,7 @@
     if (insn.outValue() == null) {
       return null;
     } else {
-      return code.createValue(insn.outType(), insn.getLocalInfo());
+      return code.createValue(insn.outValue().getTypeLattice(), insn.getLocalInfo());
     }
   }
 
@@ -119,7 +119,8 @@
           if (newHandle != handle) {
             ConstMethodHandle newInstruction =
                 new ConstMethodHandle(
-                    code.createValue(current.outType(), current.getLocalInfo()),
+                    code.createValue(
+                        current.outValue().getTypeLattice(), current.getLocalInfo()),
                     newHandle);
             iterator.replaceCurrentInstruction(newInstruction);
           }
@@ -144,7 +145,8 @@
             // Fix up the return type if needed.
             if (actualTarget.proto.returnType != invokedMethod.proto.returnType
                 && newInvoke.outValue() != null) {
-              Value newValue = code.createValue(newInvoke.outType(), invoke.getLocalInfo());
+              Value newValue = code.createValue(
+                  newInvoke.outValue().getTypeLattice(), invoke.getLocalInfo());
               newInvoke.outValue().replaceUsers(newValue);
               CheckCast cast =
                   new CheckCast(
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 03a3818..01555da 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
@@ -128,6 +128,8 @@
   }
 
   private DexProgramClass synthesizeLambdaClass() {
+    DexMethod mainMethod = rewriter.factory
+        .createMethod(type, descriptor.erasedProto, descriptor.name);
     DexProgramClass clazz =
         new DexProgramClass(
             type,
@@ -146,8 +148,11 @@
             synthesizeStaticFields(),
             synthesizeInstanceFields(),
             synthesizeDirectMethods(),
-            synthesizeVirtualMethods(),
+            synthesizeVirtualMethods(mainMethod),
             rewriter.factory.getSkipNameValidationForTesting());
+    // Optimize main method.
+    rewriter.converter.optimizeMethodOnSynthesizedClass(
+        clazz, clazz.lookupVirtualMethod(mainMethod));
     // The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
     // ModificationException we must use synchronization.
     synchronized (synthesizedFrom) {
@@ -177,13 +182,11 @@
   }
 
   // Synthesize virtual methods.
-  private DexEncodedMethod[] synthesizeVirtualMethods() {
+  private DexEncodedMethod[] synthesizeVirtualMethods(DexMethod mainMethod) {
     DexEncodedMethod[] methods = new DexEncodedMethod[1 + descriptor.bridges.size()];
     int index = 0;
 
     // Synthesize main method.
-    DexMethod mainMethod = rewriter.factory
-        .createMethod(type, descriptor.erasedProto, descriptor.name);
     methods[index++] =
         new DexEncodedMethod(
             mainMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
index ccccb21..2aa4630 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
-import java.util.Collections;
+import com.google.common.collect.ImmutableList;
 
 // Source code representing synthesized lambda class constructor.
 // Used for stateless lambdas to instantiate singleton instance.
@@ -25,8 +25,11 @@
     int instance = nextRegister(ValueType.OBJECT);
     add(builder -> builder.addNewInstance(instance, lambda.type));
     add(builder -> builder.addInvoke(
-        Invoke.Type.DIRECT, lambda.constructor, lambda.constructor.proto,
-        Collections.singletonList(ValueType.OBJECT), Collections.singletonList(instance)));
+        Invoke.Type.DIRECT,
+        lambda.constructor,
+        lambda.constructor.proto,
+        ImmutableList.of(ValueType.OBJECT),
+        ImmutableList.of(instance)));
 
     // Assign to a field.
     add(builder -> builder.addStaticPut(instance, lambda.instanceField));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 9d815ce..872f7ac 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -479,7 +480,7 @@
   private int addPrimitiveUnboxing(int register, DexType primitiveType, DexType boxType) {
     DexMethod method = getUnboxMethod(primitiveType.descriptor.content[0], boxType);
 
-    List<ValueType> argValueTypes = Collections.singletonList(ValueType.OBJECT);
+    List<ValueType> argValueTypes = ImmutableList.of(ValueType.OBJECT);
     List<Integer> argRegisters = Collections.singletonList(register);
     add(builder -> builder.addInvoke(Invoke.Type.VIRTUAL,
         method, method.proto, argValueTypes, argRegisters));
@@ -502,7 +503,7 @@
     DexMethod method = factory.createMethod(boxType, proto, factory.valueOfMethodName);
 
     ValueType valueType = ValueType.fromDexType(primitiveType);
-    List<ValueType> argValueTypes = Collections.singletonList(valueType);
+    List<ValueType> argValueTypes = ImmutableList.of(valueType);
     List<Integer> argRegisters = Collections.singletonList(register);
     add(builder -> builder.addInvoke(Invoke.Type.STATIC,
         method, method.proto, argValueTypes, argRegisters));
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 9d475c3..129ed58 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
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -25,7 +26,6 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
@@ -276,7 +276,8 @@
     Value lambdaInstanceValue = invoke.outValue();
     if (lambdaInstanceValue == null) {
       // The out value might be empty in case it was optimized out.
-      lambdaInstanceValue = code.createValue(ValueType.OBJECT);
+      lambdaInstanceValue = code.createValue(
+          TypeLatticeElement.fromDexType(lambdaClass.type, appInfo, true));
     }
 
     // For stateless lambdas we replace InvokeCustom instruction with StaticGet
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 399ae92..c805494 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
@@ -6,12 +6,14 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 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.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
@@ -22,7 +24,6 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -43,6 +44,7 @@
   private static final String TO_STRING = "toString";
   private static final String APPEND = "append";
 
+  private final AppInfo appInfo;
   private final DexItemFactory factory;
 
   private final DexMethod makeConcat;
@@ -54,9 +56,10 @@
   private final Map<DexType, DexMethod> paramTypeToAppendMethod = new IdentityHashMap<>();
   private final DexMethod defaultAppendMethod;
 
-  public StringConcatRewriter(DexItemFactory factory) {
-    assert factory != null;
-    this.factory = factory;
+  public StringConcatRewriter(AppInfo appInfo) {
+    this.appInfo = appInfo;
+    assert appInfo.dexItemFactory != null;
+    this.factory = appInfo.dexItemFactory;
 
     DexType factoryType = factory.createType(CONCAT_FACTORY_TYPE_DESCR);
     DexType callSiteType = factory.createType(CALLSITE_TYPE_DESCR);
@@ -159,7 +162,7 @@
     }
 
     // Collect chunks.
-    ConcatBuilder builder = new ConcatBuilder(code, blocks, instructions);
+    ConcatBuilder builder = new ConcatBuilder(appInfo, code, blocks, instructions);
     for (int i = 0; i < paramCount; i++) {
       builder.addChunk(arguments.get(i),
           paramTypeToAppendMethod.getOrDefault(parameters[i], defaultAppendMethod));
@@ -214,7 +217,7 @@
     String recipe = ((DexValue.DexValueString) recipeValue).getValue().toString();
 
     // Collect chunks and patch the instruction.
-    ConcatBuilder builder = new ConcatBuilder(code, blocks, instructions);
+    ConcatBuilder builder = new ConcatBuilder(appInfo, code, blocks, instructions);
     StringBuilder acc = new StringBuilder();
     int argIndex = 0;
     int constArgIndex = 0;
@@ -276,6 +279,7 @@
   }
 
   private final class ConcatBuilder {
+    private final AppInfo appInfo;
     private final IRCode code;
     private final ListIterator<BasicBlock> blocks;
     private final InstructionListIterator instructions;
@@ -284,7 +288,11 @@
     private final List<Chunk> chunks = new ArrayList<>();
 
     private ConcatBuilder(
-        IRCode code, ListIterator<BasicBlock> blocks, InstructionListIterator instructions) {
+        AppInfo appInfo,
+        IRCode code,
+        ListIterator<BasicBlock> blocks,
+        InstructionListIterator instructions) {
+      this.appInfo = appInfo;
       this.code = code;
       this.blocks = blocks;
       this.instructions = instructions;
@@ -328,7 +336,9 @@
       instructions.previous();
 
       // new-instance v0, StringBuilder
-      Value sbInstance = code.createValue(ValueType.OBJECT);
+      TypeLatticeElement stringBuilderTypeLattice =
+          TypeLatticeElement.fromDexType(factory.stringBuilderType, appInfo, false);
+      Value sbInstance = code.createValue(stringBuilderTypeLattice);
       appendInstruction(new NewInstance(factory.stringBuilderType, sbInstance));
 
       // invoke-direct {v0}, void StringBuilder.<init>()
@@ -349,7 +359,7 @@
       Value concatValue = invokeCustom.outValue();
       if (concatValue == null) {
         // The out value might be empty in case it was optimized out.
-        concatValue = code.createValue(ValueType.OBJECT);
+        concatValue = code.createValue(TypeLatticeElement.stringClassType(appInfo));
       }
 
       // Replace the instruction.
@@ -427,7 +437,7 @@
 
       @Override
       Value getOrCreateValue() {
-        Value value = code.createValue(ValueType.OBJECT);
+        Value value = code.createValue(TypeLatticeElement.stringClassType(appInfo));
         appendInstruction(new ConstString(value, factory.createString(str)));
         return value;
       }
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 2c76ef5..02c06da 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
@@ -76,7 +76,6 @@
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
@@ -270,7 +269,7 @@
   // For method with many self-recursive calls, insert a try-catch to disable inlining.
   // Marshmallow dex2oat aggressively inlines and eats up all the memory on devices.
   public static void disableDex2OatInliningForSelfRecursiveMethods(
-      IRCode code, InternalOptions options) {
+      IRCode code, InternalOptions options, AppInfo appInfo) {
     if (!options.canHaveDex2OatInliningIssue() || code.hasCatchHandlers()) {
       // Catch handlers disables inlining, so if the method already has catch handlers
       // there is nothing to do.
@@ -294,11 +293,14 @@
       splitIterator.previous();
       BasicBlock newBlock = splitIterator.split(code, 1);
       // Generate rethrow block.
-      BasicBlock rethrowBlock =
-          BasicBlock.createRethrowBlock(code, lastSelfRecursiveCall.getPosition());
+      DexType guard = options.itemFactory.throwableType;
+      BasicBlock rethrowBlock = BasicBlock.createRethrowBlock(
+          code,
+          lastSelfRecursiveCall.getPosition(),
+          TypeLatticeElement.fromDexType(guard, appInfo, true));
       code.blocks.add(rethrowBlock);
       // Add catch handler to the block containing the last recursive call.
-      newBlock.addCatchHandler(rethrowBlock, options.itemFactory.throwableType);
+      newBlock.addCatchHandler(rethrowBlock, guard);
     }
   }
 
@@ -1259,9 +1261,7 @@
                   Value argument = invoke.arguments().get(argumentIndex);
                   assert invoke.outType().verifyCompatible(argument.outType());
                   invoke.outValue().replaceUsers(argument);
-                  if (!options.isGeneratingClassFiles()) {
-                    invoke.setOutValue(null);
-                  }
+                  invoke.setOutValue(null);
                 }
               }
             }
@@ -1656,11 +1656,11 @@
       }
 
       TypeLatticeElement inTypeLattice = inValue.getTypeLattice();
-      // TODO(b/72693244): Soon, there won't be a value with Bottom at this point.
-      if (!inTypeLattice.isBottom()) {
+      // TODO(b/72693244): Soon, there won't be a value with imprecise type at this point.
+      if (inTypeLattice.isPreciseType() || inTypeLattice.isNull()) {
         TypeLatticeElement outTypeLattice = outValue.getTypeLattice();
         TypeLatticeElement castTypeLattice =
-            TypeLatticeElement.fromDexType(appInfo, castType, inTypeLattice.isNullable());
+            TypeLatticeElement.fromDexType(castType, appInfo, inTypeLattice.isNullable());
         // 1) Trivial cast.
         //   A a = ...
         //   A a' = (A) a;
@@ -2159,7 +2159,8 @@
           for (ConstInstruction value : values) {
             stringValues.add(value.outValue());
           }
-          Value invokeValue = code.createValue(newArray.outType(), newArray.getLocalInfo());
+          Value invokeValue = code.createValue(
+              newArray.outValue().getTypeLattice(), newArray.getLocalInfo());
           InvokeNewArray invoke =
               new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
           for (Value value : newArray.inValues()) {
@@ -2668,7 +2669,7 @@
         InstructionListIterator throwNullInsnIterator = throwNullBlock.listIterator();
 
         // Insert 'null' constant.
-        Value nullValue = code.createValue(ValueType.OBJECT, gotoInsn.getLocalInfo());
+        Value nullValue = code.createValue(TypeLatticeElement.NULL, gotoInsn.getLocalInfo());
         ConstNumber nullConstant = new ConstNumber(nullValue, 0);
         nullConstant.setPosition(insn.getPosition());
         throwNullInsnIterator.add(nullConstant);
@@ -2750,7 +2751,7 @@
                          (theIf.getType() == Type.EQ &&
                            trueNumber.isIntegerOne() &&
                            falseNumber.isIntegerZero())) {
-                Value newOutValue = code.createValue(phi.outType(), phi.getLocalInfo());
+                Value newOutValue = code.createValue(phi.getTypeLattice(), phi.getLocalInfo());
                 ConstNumber cstToUse = trueNumber.isIntegerOne() ? trueNumber : falseNumber;
                 BasicBlock phiBlock = phi.getBlock();
                 Position phiPosition = phiBlock.getPosition();
@@ -2977,7 +2978,8 @@
   }
 
   private Value addConstString(IRCode code, InstructionListIterator iterator, String s) {
-    Value value = code.createValue(ValueType.OBJECT);
+    TypeLatticeElement typeLattice = TypeLatticeElement.stringClassType(appInfo);
+    Value value = code.createValue(typeLattice);
     iterator.add(new ConstString(value, dexItemFactory.createString(s)));
     return value;
   }
@@ -3004,9 +3006,10 @@
 
     // Now that the block is split there should not be any catch handlers in the block.
     assert !block.hasCatchHandlers();
-    Value out = code.createValue(ValueType.OBJECT);
     DexType javaLangSystemType = dexItemFactory.createType("Ljava/lang/System;");
     DexType javaIoPrintStreamType = dexItemFactory.createType("Ljava/io/PrintStream;");
+    Value out = code.createValue(
+        TypeLatticeElement.fromDexType(javaIoPrintStreamType, appInfo, false));
 
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print");
@@ -3016,11 +3019,11 @@
         new StaticGet(MemberType.OBJECT, out,
             dexItemFactory.createField(javaLangSystemType, javaIoPrintStreamType, "out")));
 
-    Value value = code.createValue(ValueType.OBJECT);
+    Value value = code.createValue(TypeLatticeElement.stringClassType(appInfo));
     iterator.add(new ConstString(value, dexItemFactory.createString("INVOKE ")));
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
 
-    value = code.createValue(ValueType.OBJECT);
+    value = code.createValue(TypeLatticeElement.stringClassType(appInfo));
     iterator.add(
         new ConstString(value, dexItemFactory.createString(method.method.qualifiedName())));
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
@@ -3046,7 +3049,7 @@
       eol.link(successor);
 
       Value argument = arguments.get(i);
-      if (argument.outType() != ValueType.OBJECT) {
+      if (!argument.getTypeLattice().isReference()) {
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, primitive)));
       } else {
         // Insert "if (argument != null) ...".
@@ -3074,7 +3077,7 @@
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, nul)));
         iterator = isNotNullBlock.listIterator();
         iterator.setInsertionPosition(position);
-        value = code.createValue(ValueType.OBJECT);
+        value = code.createValue(TypeLatticeElement.classClassType(appInfo));
         iterator.add(new InvokeVirtual(dexItemFactory.objectMethods.getClass, value,
             ImmutableList.of(arguments.get(i))));
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
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 9f57e52..37cfb44 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
@@ -110,12 +110,12 @@
           Value receiver = invoke.getReceiver();
           TypeLatticeElement receiverTypeLattice = receiver.getTypeLattice();
           TypeLatticeElement castTypeLattice =
-              TypeLatticeElement.fromDexType(appInfo, holderType, receiverTypeLattice.isNullable());
+              TypeLatticeElement.fromDexType(holderType, appInfo, receiverTypeLattice.isNullable());
           // 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.
-          // TODO(b/72693244): Soon, there won't be a value with Bottom at this point.
-          if (receiverTypeLattice.isBottom()
+          // TODO(b/72693244): Soon, there won't be a value with imprecise type at this point.
+          if (!receiverTypeLattice.isPreciseType()
               || !TypeLatticeElement.lessThanOrEqual(
                   appInfo, receiverTypeLattice, castTypeLattice)) {
             Value newReceiver = null;
@@ -140,8 +140,8 @@
             if (newReceiver == null) {
               newReceiver =
                   receiver.definition != null
-                      ? code.createValue(receiver.outType(), receiver.definition.getLocalInfo())
-                      : code.createValue(receiver.outType());
+                      ? code.createValue(receiverTypeLattice, receiver.definition.getLocalInfo())
+                      : code.createValue(receiverTypeLattice);
               // Cache the new receiver with a narrower type to avoid redundant checkcast.
               castedReceiverCache.putIfAbsent(receiver, new IdentityHashMap<>());
               castedReceiverCache.get(receiver).put(holderType, newReceiver);
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 6aa1853..98de5be 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexItem;
 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.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -22,7 +23,6 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import java.util.function.Predicate;
@@ -68,19 +68,19 @@
       ProguardMemberRule rule, IRCode code, Instruction instruction) {
     // Check if this value can be assumed constant.
     Instruction replacement = null;
-    ValueType valueType = instruction.outValue().outType();
+    TypeLatticeElement typeLattice = instruction.outValue().getTypeLattice();
     if (rule != null && rule.hasReturnValue() && rule.getReturnValue().isSingleValue()) {
-      Value value = code.createValue(valueType, instruction.getLocalInfo());
-      assert valueType != ValueType.OBJECT || rule.getReturnValue().isNull();
+      Value value = code.createValue(typeLattice, instruction.getLocalInfo());
+      assert !typeLattice.isReference() || rule.getReturnValue().isNull();
       replacement = new ConstNumber(value, rule.getReturnValue().getSingleValue());
     }
     if (replacement == null &&
         rule != null && rule.hasReturnValue() && rule.getReturnValue().isField()) {
       DexField field = rule.getReturnValue().getField();
-      assert ValueType.fromDexType(field.type) == valueType;
+      assert TypeLatticeElement.fromDexType(field.type) == typeLattice;
       DexEncodedField staticField = appInfo.lookupStaticTarget(field.clazz, field);
       if (staticField != null) {
-        Value value = code.createValue(valueType, instruction.getLocalInfo());
+        Value value = code.createValue(typeLattice, instruction.getLocalInfo());
         replacement = staticField.getStaticValue().asConstInstruction(false, value);
       } else {
         throw new CompilationError(field.clazz.toSourceString() + "." + field.name.toString() +
@@ -164,8 +164,8 @@
             }
             if (target.getOptimizationInfo().returnsConstant()) {
               long constant = target.getOptimizationInfo().getReturnedConstant();
-              ValueType valueType = invoke.outType();
-              Value value = code.createValue(valueType, invoke.getLocalInfo());
+              Value value = code.createValue(
+                  invoke.outValue().getTypeLattice(), invoke.getLocalInfo());
               Instruction knownConstReturn = new ConstNumber(value, constant);
               invoke.outValue().replaceUsers(value);
               invoke.setOutValue(null);
@@ -252,7 +252,7 @@
         if (target != null) {
           // Remove writes to dead (i.e. never read) fields.
           if (!isFieldRead(target, true)) {
-            iterator.remove();
+            iterator.removeOrReplaceByDebugLocalRead();
           }
         }
       }
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 536eac8..eed1c47 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
@@ -16,7 +16,6 @@
 import com.android.tools.r8.ir.code.NonNull;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -119,9 +118,9 @@
         // ...
         // A: non_null_rcv <- non-null(rcv)
         // ...y
-        // TODO(b/72693244): Attach lattice when Value is created.
-        Value nonNullValue =
-            code.createValue(ValueType.OBJECT, knownToBeNonNullValue.getLocalInfo());
+        Value nonNullValue = code.createValue(
+            knownToBeNonNullValue.getTypeLattice(),
+            knownToBeNonNullValue.getLocalInfo());
         nonNullValueCollector.add(nonNullValue);
         NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
         nonNull.setPosition(current.getPosition());
@@ -219,9 +218,9 @@
               }
               // Avoid adding a non-null for the value without meaningful users.
               if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
-                // TODO(b/72693244): Attach lattice when Value is created.
                 Value nonNullValue = code.createValue(
-                    knownToBeNonNullValue.outType(), knownToBeNonNullValue.getLocalInfo());
+                    knownToBeNonNullValue.getTypeLattice(),
+                    knownToBeNonNullValue.getLocalInfo());
                 nonNullValueCollector.add(nonNullValue);
                 NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, theIf);
                 InstructionListIterator targetIterator = target.listIterator();
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 e02222f..708dc12 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
@@ -24,6 +24,7 @@
 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.TypeLatticeElement;
 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;
@@ -1031,8 +1032,8 @@
     public void buildPrelude(IRBuilder builder) {
       // Fill in the Argument instructions in the argument block.
       for (int i = 0; i < outline.arguments.size(); i++) {
-        ValueType valueType = outline.arguments.get(i).outType();
-        builder.addNonThisArgument(i, valueType);
+        TypeLatticeElement typeLattice = outline.arguments.get(i).getTypeLattice();
+        builder.addNonThisArgument(i, typeLattice);
       }
     }
 
@@ -1076,8 +1077,8 @@
       Value outValue = null;
       if (template.outValue() != null) {
         Value value = template.outValue();
-        outValue = builder
-            .writeRegister(outline.argumentCount(), value.outType(), ThrowingInfo.CAN_THROW);
+        outValue = builder.writeRegister(
+            outline.argumentCount(), value.getTypeLattice(), ThrowingInfo.CAN_THROW);
       }
 
       Instruction newInstruction = null;
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 714c28b..8ed50ce 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
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner;
 
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -13,7 +14,6 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.LinkedList;
@@ -89,7 +89,7 @@
           new Phi(
               code.valueNumberGenerator.next(),
               block,
-              ValueType.fromDexType(field.type),
+              TypeLatticeElement.fromDexType(field.type),
               null,
               RegisterReadType.NORMAL);
       ins.put(block, phi);
@@ -137,7 +137,7 @@
     assert root == valueProducingInsn;
     if (defaultValue == null) {
       // If we met newInstance it means that default value is supposed to be used.
-      defaultValue = code.createValue(ValueType.fromDexType(field.type));
+      defaultValue = code.createValue(TypeLatticeElement.fromDexType(field.type));
       ConstNumber defaultValueInsn = new ConstNumber(defaultValue, 0);
       defaultValueInsn.setPosition(root.getPosition());
       LinkedList<Instruction> instructions = block.getInstructions();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
index eb7826e..030cd4f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.lambda;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -139,6 +140,7 @@
     }
   };
 
+  public final AppInfo appInfo;
   public final DexItemFactory factory;
   public final Kotlin kotlin;
 
@@ -156,13 +158,15 @@
   public final ListIterator<BasicBlock> blocks;
   private InstructionListIterator instructions;
 
-  CodeProcessor(DexItemFactory factory,
+  CodeProcessor(
+      AppInfo appInfo,
       Function<DexType, Strategy> strategyProvider,
       LambdaTypeVisitor lambdaChecker,
       DexEncodedMethod method, IRCode code) {
 
+    this.appInfo = appInfo;
     this.strategyProvider = strategyProvider;
-    this.factory = factory;
+    this.factory = appInfo.dexItemFactory;
     this.kotlin = factory.kotlin;
     this.lambdaChecker = lambdaChecker;
     this.method = method;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index d1b56f2..270f8ba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
@@ -98,6 +99,7 @@
   // should not be happening very frequently and we ignore possible overhead.
   private final Set<DexEncodedMethod> methodsToReprocess = Sets.newIdentityHashSet();
 
+  private final AppInfo appInfo;
   private final DexItemFactory factory;
   private final Kotlin kotlin;
   private final DiagnosticsHandler reporter;
@@ -109,8 +111,9 @@
   // Lambda visitor throwing Unreachable on each lambdas it sees.
   private final LambdaTypeVisitor lambdaChecker;
 
-  public LambdaMerger(DexItemFactory factory, DiagnosticsHandler reporter) {
-    this.factory = factory;
+  public LambdaMerger(AppInfo appInfo, DiagnosticsHandler reporter) {
+    this.appInfo = appInfo;
+    this.factory = appInfo.dexItemFactory;
     this.kotlin = factory.kotlin;
     this.reporter = reporter;
 
@@ -346,7 +349,7 @@
 
   private final class AnalysisStrategy extends CodeProcessor {
     private AnalysisStrategy(DexEncodedMethod method, IRCode code) {
-      super(LambdaMerger.this.factory,
+      super(LambdaMerger.this.appInfo,
           LambdaMerger.this::strategyProvider, lambdaInvalidator, method, code);
     }
 
@@ -383,7 +386,7 @@
 
   private final class ApplyStrategy extends CodeProcessor {
     private ApplyStrategy(DexEncodedMethod method, IRCode code) {
-      super(LambdaMerger.this.factory,
+      super(LambdaMerger.this.appInfo,
           LambdaMerger.this::strategyProvider, lambdaChecker, method, code);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
index 19d6bda..5177e4a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
@@ -45,7 +46,7 @@
       if (group.isSingletonLambda(lambda)) {
         int id = group.lambdaId(lambda);
         add(builder -> builder.addNewInstance(instance, groupClassType));
-        add(builder -> builder.addConst(ValueType.INT, lambdaId, id));
+        add(builder -> builder.addConst(TypeLatticeElement.INT, lambdaId, id));
         add(builder -> builder.addInvoke(Type.DIRECT,
             lambdaConstructorMethod, lambdaConstructorMethod.proto, argTypes, argRegisters));
         add(builder -> builder.addStaticPut(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index 76bf797..f677b46 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
@@ -231,7 +232,7 @@
     @Override
     void prepareSuperConstructorCall(int receiverRegister) {
       int arityRegister = nextRegister(ValueType.INT);
-      add(builder -> builder.addConst(ValueType.INT, arityRegister, arity));
+      add(builder -> builder.addConst(TypeLatticeElement.INT, arityRegister, arity));
       add(builder -> builder.addInvoke(Type.DIRECT, lambdaInitializer, lambdaInitializer.proto,
           Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
           Lists.newArrayList(receiverRegister, arityRegister)));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index e265756..538d7be 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.InstanceGet;
@@ -22,7 +23,6 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
 import com.android.tools.r8.ir.optimize.lambda.CodeProcessor;
 import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
@@ -113,7 +113,9 @@
   @Override
   public void patch(CodeProcessor context, NewInstance newInstance) {
     NewInstance patchedNewInstance = new NewInstance(
-        group.getGroupClassType(), context.code.createValue(ValueType.OBJECT));
+        group.getGroupClassType(),
+        context.code.createValue(
+            TypeLatticeElement.fromDexType(newInstance.clazz, context.appInfo, false)));
     context.instructions().replaceCurrentInstruction(patchedNewInstance);
   }
 
@@ -157,7 +159,9 @@
 
     // Since all captured values of non-primitive types are stored in fields of type
     // java.lang.Object, we need to cast them to appropriate type to satisfy the verifier.
-    Value newValue = context.code.createValue(ValueType.OBJECT, newInstanceGet.getLocalInfo());
+    TypeLatticeElement castTypeLattice =
+        TypeLatticeElement.fromDexType(fieldType, context.appInfo, false);
+    Value newValue = context.code.createValue(castTypeLattice, newInstanceGet.getLocalInfo());
     newInstanceGet.outValue().replaceUsers(newValue);
     CheckCast cast = new CheckCast(newValue, newInstanceGet.outValue(), fieldType);
     cast.setPosition(newInstanceGet.getPosition());
@@ -180,7 +184,10 @@
   @Override
   public void patch(CodeProcessor context, StaticGet staticGet) {
     context.instructions().replaceCurrentInstruction(
-        new StaticGet(staticGet.getType(), context.code.createValue(ValueType.OBJECT),
+        new StaticGet(
+            staticGet.getType(),
+            context.code.createValue(
+                TypeLatticeElement.fromDexType(staticGet.getField().type, context.appInfo, true)),
             mapSingletonInstanceField(context.factory, staticGet.getField())));
   }
 
@@ -195,7 +202,7 @@
     DexType lambda = method.holder;
 
     // Create constant with lambda id.
-    Value lambdaIdValue = context.code.createValue(ValueType.INT);
+    Value lambdaIdValue = context.code.createValue(TypeLatticeElement.INT);
     ConstNumber lambdaId = new ConstNumber(lambdaIdValue, group.lambdaId(lambda));
     lambdaId.setPosition(invoke.getPosition());
     context.instructions().previous();
@@ -214,7 +221,8 @@
 
   private Value createValueForType(CodeProcessor context, DexType returnType) {
     return returnType == context.factory.voidType ? null :
-        context.code.createValue(ValueType.fromDexType(returnType));
+        context.code.createValue(
+            TypeLatticeElement.fromDexType(returnType, context.appInfo, true));
   }
 
   private List<Value> mapInitializerArgs(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 916679d..669baf9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
@@ -23,7 +24,6 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Outliner;
@@ -244,7 +244,7 @@
       Value newValue = null;
       Value outValue = invoke.outValue();
       if (outValue != null) {
-        newValue = code.createValue(outValue.outType());
+        newValue = code.createValue(outValue.getTypeLattice());
         DebugLocalInfo localInfo = outValue.getLocalInfo();
         if (localInfo != null) {
           newValue.setLocalInfo(localInfo);
@@ -272,7 +272,9 @@
           it.replaceCurrentInstruction(
               new StaticGet(
                   MemberType.fromDexType(field.type),
-                  code.createValue(ValueType.fromDexType(field.type), outValue.getLocalInfo()),
+                  code.createValue(
+                      TypeLatticeElement.fromDexType(field.type, classStaticizer.appInfo, true),
+                      outValue.getLocalInfo()),
                   field
               )
           );
@@ -300,7 +302,8 @@
           Value outValue = invoke.outValue();
           Value newOutValue = method.proto.returnType.isVoidType() ? null
               : code.createValue(
-                  ValueType.fromDexType(method.proto.returnType),
+                  TypeLatticeElement.fromDexType(
+                      method.proto.returnType, classStaticizer.appInfo, true),
                   outValue == null ? null : outValue.getLocalInfo());
           it.replaceCurrentInstruction(
               new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index b4995c9..9345bb9 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.cf.FixedLocalValue;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.And;
 import com.android.tools.r8.ir.code.ArithmeticBinop;
@@ -27,7 +28,6 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Sub;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.regalloc.RegisterPositions.Type;
 import com.android.tools.r8.logging.Log;
@@ -2673,8 +2673,8 @@
     return true;
   }
 
-  private Value createValue(ValueType type) {
-    Value value = code.createValue(type, null);
+  private Value createValue(TypeLatticeElement typeLattice) {
+    Value value = code.createValue(typeLattice, null);
     value.setNeedsRegister(true);
     return value;
   }
@@ -2726,7 +2726,7 @@
             argument.isLinked() ||
             argument == previous ||
             argument.hasRegisterConstraint()) {
-          newArgument = createValue(argument.outType());
+          newArgument = createValue(argument.getTypeLattice());
           Move move = new Move(newArgument, argument);
           move.setBlock(invoke.getBlock());
           replaceArgument(invoke, i, newArgument);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index 031d161..daa1e30 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -139,22 +139,25 @@
       if (move.definition.isArgument()) {
         Argument argument = move.definition.asArgument();
         int argumentRegister = argument.outValue().getLiveIntervals().getRegister();
-        Value to = new FixedRegisterValue(argument.outType(), move.dst);
-        Value from = new FixedRegisterValue(argument.outType(), argumentRegister);
+        Value to =
+            new FixedRegisterValue(argument.outValue().getTypeLattice(), move.dst);
+        Value from =
+            new FixedRegisterValue(argument.outValue().getTypeLattice(), argumentRegister);
         instruction = new Move(to, from);
       } else {
         assert move.definition.isOutConstant();
         ConstInstruction definition = move.definition.getOutConstantConstInstruction();
         if (definition.isConstNumber()) {
-          Value to = new FixedRegisterValue(move.definition.outType(), move.dst);
+          Value to =
+              new FixedRegisterValue(move.definition.outValue().getTypeLattice(), move.dst);
           instruction = new ConstNumber(to, definition.asConstNumber().getRawValue());
         } else {
           throw new Unreachable("Unexpected definition");
         }
       }
     } else {
-      Value to = new FixedRegisterValue(move.type.toValueType(), move.dst);
-      Value from = new FixedRegisterValue(move.type.toValueType(), valueMap.get(move.src));
+      Value to = new FixedRegisterValue(move.type.toTypeLattice(), move.dst);
+      Value from = new FixedRegisterValue(move.type.toTypeLattice(), valueMap.get(move.src));
       instruction = new Move(to, from);
     }
     instruction.setPosition(position);
@@ -176,9 +179,9 @@
       // (taking the value map into account). If not, we can reuse the temp register instead
       // of generating a new one.
       Value to = new FixedRegisterValue(
-          moveWithSrc.type.toValueType(), tempRegister + usedTempRegisters);
+          moveWithSrc.type.toTypeLattice(), tempRegister + usedTempRegisters);
       Value from = new FixedRegisterValue(
-          moveWithSrc.type.toValueType(), valueMap.get(moveWithSrc.src));
+          moveWithSrc.type.toTypeLattice(), valueMap.get(moveWithSrc.src));
       Move instruction = new Move(to, from);
       instruction.setPosition(position);
       insertAt.add(instruction);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index cd9824f..164e083 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
@@ -177,7 +178,8 @@
   @Override
   public final void buildPrelude(IRBuilder builder) {
     if (receiver != null) {
-      receiverValue = builder.writeRegister(receiverRegister, ValueType.OBJECT, NO_THROW);
+      receiverValue = builder.writeRegister(
+          receiverRegister, TypeLatticeElement.fromDexType(receiver), NO_THROW);
       builder.add(new Argument(receiverValue));
       receiverValue.markAsThis();
     }
@@ -185,8 +187,8 @@
     // Fill in the Argument instructions in the argument block.
     DexType[] parameters = proto.parameters.values;
     for (int i = 0; i < parameters.length; i++) {
-      ValueType valueType = ValueType.fromDexType(parameters[i]);
-      Value paramValue = builder.writeRegister(paramRegisters[i], valueType, NO_THROW);
+      TypeLatticeElement typeLattice = TypeLatticeElement.fromDexType(parameters[i]);
+      Value paramValue = builder.writeRegister(paramRegisters[i], typeLattice, NO_THROW);
       paramValues[i] = paramValue;
       builder.add(new Argument(paramValue));
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 37e6de9..051b81f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -42,6 +42,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -188,8 +189,11 @@
     }
   }
 
-  private void parseError(DexDefinition item, Origin origin, GenericSignatureFormatError e) {
-    StringBuilder message = new StringBuilder("Invalid signature for ");
+  private void parseError(
+      DexDefinition item, Origin origin, String signature, GenericSignatureFormatError e) {
+    StringBuilder message = new StringBuilder("Invalid signature '");
+    message.append(signature);
+    message.append("' for ");
     if (item.isDexClass()) {
       message.append("class ");
       message.append((item.asDexClass()).getType().toSourceString());
@@ -202,43 +206,54 @@
       message.append(item.toSourceString());
     }
     message.append(".\n");
+    message.append("Signature is ignored and will not be present in the output.\n");
+    message.append("Parser error: ");
     message.append(e.getMessage());
     reporter.warning(new StringDiagnostic(message.toString(), origin));
   }
 
   private void renameTypesInGenericSignatures() {
     for (DexClass clazz : appInfo.classes()) {
-      clazz.annotations = rewriteGenericSignatures(clazz.annotations,
-          genericSignatureParser::parseClassSignature,
-          e -> parseError(clazz, clazz.getOrigin(), e));
-      clazz.forEachField(field ->
-          field.annotations = rewriteGenericSignatures(
-              field.annotations, genericSignatureParser::parseFieldSignature,
-              e -> parseError(field, clazz.getOrigin(), e)));
-      clazz.forEachMethod(method ->
-        method.annotations = rewriteGenericSignatures(
-            method.annotations, genericSignatureParser::parseMethodSignature,
-            e -> parseError(method, clazz.getOrigin(), e)));
+      clazz.annotations =
+          rewriteGenericSignatures(
+              clazz.annotations,
+              genericSignatureParser::parseClassSignature,
+              (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e));
+      clazz.forEachField(
+          field ->
+              field.annotations =
+                  rewriteGenericSignatures(
+                      field.annotations,
+                      genericSignatureParser::parseFieldSignature,
+                      (signature, e) -> parseError(field, clazz.getOrigin(), signature, e)));
+      clazz.forEachMethod(
+          method ->
+              method.annotations =
+                  rewriteGenericSignatures(
+                      method.annotations,
+                      genericSignatureParser::parseMethodSignature,
+                      (signature, e) -> parseError(method, clazz.getOrigin(), signature, e)));
     }
   }
 
   private DexAnnotationSet rewriteGenericSignatures(
       DexAnnotationSet annotations,
       Consumer<String> parser,
-      Consumer<GenericSignatureFormatError> parseError) {
+      BiConsumer<String, GenericSignatureFormatError> parseError) {
     // There can be no more than one signature annotation in an annotation set.
     final int VALID = -1;
     int invalid = VALID;
     for (int i = 0; i < annotations.annotations.length && invalid == VALID; i++) {
       DexAnnotation annotation = annotations.annotations[i];
       if (DexAnnotation.isSignatureAnnotation(annotation, appInfo.dexItemFactory)) {
+        String signature = DexAnnotation.getSignature(annotation);
         try {
-          parser.accept(DexAnnotation.getSignature(annotation));
+          parser.accept(signature);
           annotations.annotations[i] = DexAnnotation.createSignatureAnnotation(
               genericSignatureRewriter.getRenamedSignature(),
               appInfo.dexItemFactory);
         } catch (GenericSignatureFormatError e) {
-          parseError.accept(e);
+          parseError.accept(signature, e);
           invalid = i;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 8f9e2f5..59e0782 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -130,7 +130,7 @@
           assert iterator.peekPrevious() == fieldPut;
           iterator.previous();
           // Prepare $decoupled just before $fieldPut
-          Value newIn = code.createValue(in.outType(), in.getLocalInfo());
+          Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
           ConstString decoupled = new ConstString(newIn, itemBasedString);
           decoupled.setPosition(fieldPut.getPosition());
           // If the current block has catch handler, split into two blocks.
@@ -192,7 +192,7 @@
             assert iterator.peekPrevious() == invoke;
             iterator.previous();
             // Prepare $decoupled just before $invoke
-            Value newIn = code.createValue(in.outType(), in.getLocalInfo());
+            Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
             ConstString decoupled = new ConstString(newIn, itemBasedString);
             decoupled.setPosition(invoke.getPosition());
             changes[positionOfIdentifier] = newIn;
@@ -239,7 +239,7 @@
               assert iterator.peekPrevious() == invoke;
               iterator.previous();
               // Prepare $decoupled just before $invoke
-              Value newIn = code.createValue(in.outType(), in.getLocalInfo());
+              Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
               ConstString decoupled = new ConstString(newIn, itemBasedString);
               decoupled.setPosition(invoke.getPosition());
               changes[i] = newIn;
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index fe002ec..68d634e 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1354,8 +1354,12 @@
               "lang.Character.isWhitespaceI.Character_isWhitespace_A01",
               match(runtimes(Runtime.ART_V4_0_4)))
           .put("lang.Character.getDirectionalityI.Character_getDirectionality_A01", any())
-          .put("lang.Character.UnicodeBlock.ofC.UnicodeBlock_of_A01", any())
-          .put("lang.Character.UnicodeBlock.ofI.UnicodeBlock_of_A01", any())
+          .put(
+              "lang.Character.UnicodeBlock.ofC.UnicodeBlock_of_A01",
+              match(artRuntimesFromAndJava(Runtime.ART_V4_4_4)))
+          .put(
+              "lang.Character.UnicodeBlock.ofI.UnicodeBlock_of_A01",
+              match(artRuntimesFromAndJava(Runtime.ART_V4_4_4)))
           .put("lang.Character.isLowerCaseI.Character_isLowerCase_A01", anyDexVm())
           .put("lang.Process.waitFor.Process_waitFor_A01", anyDexVm())
           .put("lang.System.getProperties.System_getProperties_A01", anyDexVm())
@@ -1527,7 +1531,7 @@
               match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
           .put(
               "lang.reflect.AccessibleObject.setAccessibleZ.AccessibleObject_setAccessible_A04",
-              match(runtimes(Runtime.ART_V4_4_4, Runtime.JAVA)))
+              match(artRuntimesUpToAndJava(Runtime.ART_V4_4_4)))
           .put(
               "lang.reflect.AccessibleObject.setAccessible_Ljava_lang_reflect_AccessibleObjectZ.AccessibleObject_setAccessible_A04",
               match(artRuntimesUpToAndJava(Runtime.ART_V6_0_1)))
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 7f6280f..fa35d0d 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -709,6 +709,11 @@
     }
   }
 
+  protected ProcessResult runOnVMRaw(AndroidApp app, Class<?> mainClass, Backend backend)
+      throws IOException {
+    return runOnVMRaw(app, mainClass.getCanonicalName(), backend);
+  }
+
   protected ProcessResult runOnVMRaw(AndroidApp app, String mainClass, Backend backend)
       throws IOException {
     switch (backend) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e34282d..f5eaa25 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -93,7 +93,7 @@
   public static final String SMALI_BUILD_DIR = TESTS_BUILD_DIR + "smali/";
 
   public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
-  public final static String PATH_SEPARATOR = File.pathSeparator;
+  public static final String PATH_SEPARATOR = File.pathSeparator;
   public static final String DEFAULT_DEX_FILENAME = "classes.dex";
   public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
 
@@ -106,6 +106,8 @@
   private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
   private static final String PROGUARD = PROGUARD5_2_1;
 
+  public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
+
   public enum DexVm {
     ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
     ART_4_0_4_HOST(Version.V4_0_4, Kind.HOST),
@@ -997,7 +999,7 @@
 
   public static ProcessResult forkR8Jar(Path dir, String... args)
       throws IOException, InterruptedException {
-    String r8Jar = Paths.get(LIBS_DIR,  "r8.jar").toAbsolutePath().toString();
+    String r8Jar = R8_JAR.toAbsolutePath().toString();
     return forkJavaWithJar(dir, r8Jar, Arrays.asList(args));
   }
 
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
new file mode 100644
index 0000000..02df3d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -0,0 +1,205 @@
+// 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.cf;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.google.common.io.ByteStreams.toByteArray;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassHierarchyVerifier;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.base.Charsets;
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class BootstrapCurrentEqualityTest extends TestBase {
+
+  private static final String R8_NAME = "com.android.tools.r8.R8";
+  private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
+
+  private static final String HELLO_NAME = "hello.Hello";
+  private static final String[] KEEP_HELLO = {
+    "-keep class " + HELLO_NAME + " {", "  public static void main(...);", "}",
+  };
+
+  private class R8Result {
+
+    final ProcessResult processResult;
+    final Path outputJar;
+    final String pgMap;
+
+    R8Result(ProcessResult processResult, Path outputJar, String pgMap) {
+      this.processResult = processResult;
+      this.outputJar = outputJar;
+      this.pgMap = pgMap;
+    }
+
+    @Override
+    public String toString() {
+      return processResult.toString() + "\n\n" + pgMap;
+    }
+  }
+
+  private static Path r8R8Debug;
+  private static Path r8R8Release;
+
+  @ClassRule public static TemporaryFolder testFolder = new TemporaryFolder();
+
+  @BeforeClass
+  public static void beforeAll() throws Exception {
+    r8R8Debug = compileR8(CompilationMode.DEBUG);
+    r8R8Release = compileR8(CompilationMode.RELEASE);
+  }
+
+  private static Path compileR8(CompilationMode mode) throws Exception {
+    // Run R8 on r8.jar.
+    Path output = runR8(ToolHelper.R8_JAR, testFolder.newFolder().toPath(), mode);
+    // Check that all non-abstract classes in the R8'd R8 implement all abstract/interface methods
+    // from their supertypes. This is a sanity check for the tree shaking and minification.
+    AndroidApp app = AndroidApp.builder().addProgramFile(output).build();
+    new ClassHierarchyVerifier(new CodeInspector(app)).run();
+    return output;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path helloJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
+    ProcessResult runResult = ToolHelper.runJava(helloJar, "hello.Hello");
+    assertEquals(0, runResult.exitCode);
+    compareR8(helloJar, runResult, KEEP_HELLO, "hello.Hello");
+  }
+
+  private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
+      throws Exception {
+    R8Result runR8Debug =
+        runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--debug");
+    assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar, args).toString());
+    R8Result runR8Release =
+        runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--release");
+    assertEquals(runResult.toString(), ToolHelper.runJava(runR8Release.outputJar, args).toString());
+    RunR8AndCheck(r8R8Debug, program, runR8Debug, keep, "--debug");
+    RunR8AndCheck(r8R8Debug, program, runR8Release, keep, "--release");
+    RunR8AndCheck(r8R8Release, program, runR8Debug, keep, "--debug");
+    RunR8AndCheck(r8R8Release, program, runR8Release, keep, "--release");
+  }
+
+  private void RunR8AndCheck(Path r8, Path program, R8Result result, String[] keep, String mode)
+      throws Exception {
+    R8Result runR8R8 = runExternalR8(r8, program, temp.newFolder().toPath(), keep, mode);
+    // Check that the process outputs (exit code, stdout, stderr) are the same.
+    assertEquals(result.toString(), runR8R8.toString());
+    // Check that the output jars are the same.
+    assertProgramsEqual(result.outputJar, runR8R8.outputJar);
+  }
+
+  private static Path runR8(Path inputJar, Path outputPath, CompilationMode mode) throws Exception {
+    Path outputJar = outputPath.resolve("output.jar");
+    ToolHelper.runR8(
+        R8Command.builder()
+            .setMode(mode)
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
+            .addProgramFiles(inputJar)
+            .addProguardConfigurationFiles(MAIN_KEEP)
+            .build());
+    return outputJar;
+  }
+
+  private R8Result runExternalR8(
+      Path r8Jar, Path inputJar, Path output, String[] keepRules, String mode) throws Exception {
+    Path pgConfigFile = output.resolve("keep.rules");
+    Path outputJar = output.resolve("output.jar");
+    Path pgMapFile = output.resolve("map.txt");
+    FileUtils.writeTextFile(pgConfigFile, keepRules);
+    ProcessResult processResult =
+        ToolHelper.runJava(
+            r8Jar,
+            R8_NAME,
+            "--lib",
+            ToolHelper.JAVA_8_RUNTIME,
+            "--classfile",
+            inputJar.toString(),
+            "--output",
+            outputJar.toString(),
+            "--pg-conf",
+            pgConfigFile.toString(),
+            mode,
+            "--pg-map-output",
+            pgMapFile.toString());
+    assertEquals(0, processResult.exitCode);
+    String pgMap = FileUtils.readTextFile(pgMapFile, Charsets.UTF_8);
+    return new R8Result(processResult, outputJar, pgMap);
+  }
+
+  private static void assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception {
+    if (filesAreEqual(expectedJar, actualJar)) {
+      return;
+    }
+    ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar);
+    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar);
+    assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
+    for (String descriptor : expected.getClassDescriptors()) {
+      assertArrayEquals(
+          "Class " + descriptor + " differs",
+          getClassAsBytes(expected, descriptor),
+          getClassAsBytes(actual, descriptor));
+    }
+  }
+
+  private static boolean filesAreEqual(Path file1, Path file2) throws IOException {
+    long size = Files.size(file1);
+    long sizeOther = Files.size(file2);
+    if (size != sizeOther) {
+      return false;
+    }
+    if (size < 4096) {
+      return Arrays.equals(Files.readAllBytes(file1), Files.readAllBytes(file2));
+    }
+    int byteRead1 = 0;
+    int byteRead2 = 0;
+    try (FileInputStream fs1 = new FileInputStream(file1.toString());
+        FileInputStream fs2 = new FileInputStream(file2.toString())) {
+      BufferedInputStream bs1 = new BufferedInputStream(fs1);
+      BufferedInputStream bs2 = new BufferedInputStream(fs2);
+      while (byteRead1 == byteRead2 && byteRead1 != -1) {
+        byteRead1 = bs1.read();
+        byteRead2 = bs2.read();
+      }
+    }
+    return byteRead1 == byteRead2;
+  }
+
+  private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+    Collections.sort(descriptorList);
+    return descriptorList;
+  }
+
+  private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+      throws Exception {
+    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 04b6dc0..a459a9c 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -73,17 +74,18 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
+    AppInfo appInfo = new AppInfo(application);
 
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode code = method.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeA = methodA.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeB = methodB.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
@@ -156,14 +158,15 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
+    AppInfo appInfo = new AppInfo(application);
 
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode code = method.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeA = methodA.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA), valueNumberGenerator, options);
@@ -236,23 +239,24 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
+    AppInfo appInfo = new AppInfo(application);
 
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode code = method.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     // Build three copies of a and b for inlining three times.
     List<IRCode> additionalCode = new ArrayList<>();
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodA = getMethod(application, signatureA);
-      IRCode codeA = methodA.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+      IRCode codeA = methodA.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
       additionalCode.add(codeA);
     }
 
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodB = getMethod(application, signatureB);
-      IRCode codeB = methodB.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+      IRCode codeB = methodB.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
       additionalCode.add(codeB);
     }
 
@@ -372,17 +376,18 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
+    AppInfo appInfo = new AppInfo(application);
 
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode code = method.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeA = methodA.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeB = methodB.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
@@ -486,17 +491,18 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
+    AppInfo appInfo = new AppInfo(application);
 
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode code = method.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeA = methodA.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeB = methodB.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
@@ -599,17 +605,18 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
+    AppInfo appInfo = new AppInfo(application);
 
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode code = method.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeA = methodA.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeB = methodB.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
@@ -713,23 +720,24 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
+    AppInfo appInfo = new AppInfo(application);
 
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode code = method.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     // Build three copies of a and b for inlining three times.
     List<IRCode> additionalCode = new ArrayList<>();
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodA = getMethod(application, signatureA);
-      IRCode codeA = methodA.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+      IRCode codeA = methodA.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
       additionalCode.add(codeA);
     }
 
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodB = getMethod(application, signatureB);
-      IRCode codeB = methodB.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+      IRCode codeB = methodB.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
       additionalCode.add(codeB);
     }
 
@@ -872,23 +880,24 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
+    AppInfo appInfo = new AppInfo(application);
 
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode code = method.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     // Build three copies of a and b for inlining three times.
     List<IRCode> additionalCode = new ArrayList<>();
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodA = getMethod(application, signatureA);
-      IRCode codeA = methodA.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+      IRCode codeA = methodA.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
       additionalCode.add(codeA);
     }
 
     for (int i = 0; i < 3; i++) {
       DexEncodedMethod methodB = getMethod(application, signatureB);
-      IRCode codeB = methodB.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+      IRCode codeB = methodB.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
       additionalCode.add(codeB);
     }
 
@@ -1121,17 +1130,18 @@
 
     InternalOptions options = new InternalOptions();
     DexApplication application = buildApplication(builder, options);
+    AppInfo appInfo = new AppInfo(application);
 
     // Return the processed method for inspection.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
-    IRCode code = method.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode code = method.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodA = getMethod(application, signatureA);
-    IRCode codeA = methodA.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeA = methodA.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     DexEncodedMethod methodB = getMethod(application, signatureB);
-    IRCode codeB = methodB.buildInliningIRForTesting(new InternalOptions(), valueNumberGenerator);
+    IRCode codeB = methodB.buildInliningIRForTesting(options, valueNumberGenerator, appInfo);
 
     return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 0cf2f53..86e1d78 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -20,7 +21,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
@@ -364,8 +364,10 @@
       BasicBlock newReturnBlock = iterator.split(code);
       // Modify the code to make the inserted block add the constant 10 to the original return
       // value.
-      Value newConstValue = new Value(test.valueNumberGenerator.next(), ValueType.INT, null);
-      Value newReturnValue = new Value(test.valueNumberGenerator.next(), ValueType.INT, null);
+      Value newConstValue =
+          new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null);
+      Value newReturnValue =
+          new Value(test.valueNumberGenerator.next(), TypeLatticeElement.INT, null);
       Value oldReturnValue = newReturnBlock.listIterator().next().asReturn().returnValue();
       newReturnBlock.listIterator().next().asReturn().returnValue().replaceUsers(newReturnValue);
       Instruction constInstruction = new ConstNumber(newConstValue, 10);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index c618dad..109a6cb 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -120,9 +120,9 @@
       DexType mainClass = appInfo.dexItemFactory.createType(
           DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          InvokeVirtual.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, true),
-          NonNull.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, false),
-          NewInstance.class, fromDexType(appInfo, assertionErrorType, false));
+          InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
+          NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, false),
+          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
       forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
     });
   }
@@ -136,9 +136,9 @@
       DexType mainClass = appInfo.dexItemFactory.createType(
           DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          InvokeVirtual.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, true),
-          NonNull.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, false),
-          NewInstance.class, fromDexType(appInfo, assertionErrorType, false));
+          InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
+          NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, false),
+          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
       forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
     });
   }
@@ -153,8 +153,8 @@
           DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           // An element inside a non-null array could be null.
-          ArrayGet.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, true),
-          NewInstance.class, fromDexType(appInfo, assertionErrorType, false));
+          ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
+          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
       forEachOutValue(irCode, (v, l) -> {
         if (l.isArrayType()) {
           ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
@@ -179,8 +179,8 @@
           DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           // An element inside a non-null array could be null.
-          ArrayGet.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, true),
-          NewInstance.class, fromDexType(appInfo, assertionErrorType, false));
+          ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
+          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
       forEachOutValue(irCode, (v, l) -> {
         if (l.isArrayType()) {
           ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
@@ -206,11 +206,11 @@
       DexType testClass = appInfo.dexItemFactory.createType(
           DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          Argument.class, fromDexType(appInfo, testClass, true),
-          NonNull.class, fromDexType(appInfo, testClass, false),
+          Argument.class, fromDexType(testClass, appInfo, true),
+          NonNull.class, fromDexType(testClass, appInfo, false),
           // instance may not be initialized.
-          InstanceGet.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, true),
-          NewInstance.class, fromDexType(appInfo, assertionErrorType, false));
+          InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
+          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
       forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
     });
   }
@@ -226,11 +226,11 @@
       DexType testClass = appInfo.dexItemFactory.createType(
           DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          Argument.class, fromDexType(appInfo, testClass, true),
-          NonNull.class, fromDexType(appInfo, testClass, false),
+          Argument.class, fromDexType(testClass, appInfo, true),
+          NonNull.class, fromDexType(testClass, appInfo, false),
           // instance may not be initialized.
-          InstanceGet.class, fromDexType(appInfo, appInfo.dexItemFactory.stringType, true),
-          NewInstance.class, fromDexType(appInfo, assertionErrorType, false));
+          InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
+          NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
       forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
     });
   }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index 75cef4d..fd92348 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -58,11 +58,10 @@
 @RunWith(Parameterized.class)
 public class TypeAnalysisTest extends SmaliTestBase {
   private static final InternalOptions TEST_OPTIONS = new InternalOptions();
-  private static final TypeLatticeElement NULL =
-      ReferenceTypeLatticeElement.getNullTypeLatticeElement();
-  private static final TypeLatticeElement SINGLE = SingleTypeLatticeElement.getInstance();
-  private static final TypeLatticeElement INT = IntTypeLatticeElement.getInstance();
-  private static final TypeLatticeElement LONG = LongTypeLatticeElement.getInstance();
+  private static final TypeLatticeElement NULL = TypeLatticeElement.NULL;
+  private static final TypeLatticeElement SINGLE = TypeLatticeElement.SINGLE;
+  private static final TypeLatticeElement INT = TypeLatticeElement.INT;
+  private static final TypeLatticeElement LONG = TypeLatticeElement.LONG;
 
   private final String dirName;
   private final String smaliFileName;
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index eb2c54b..0898891 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -59,15 +59,23 @@
   }
 
   private TopTypeLatticeElement top() {
-    return TopTypeLatticeElement.getInstance();
+    return TypeLatticeElement.TOP;
   }
 
   private BottomTypeLatticeElement bottom() {
-    return BottomTypeLatticeElement.getInstance();
+    return TypeLatticeElement.BOTTOM;
+  }
+
+  private SingleTypeLatticeElement single() {
+    return TypeLatticeElement.SINGLE;
+  }
+
+  private WideTypeLatticeElement wide() {
+    return TypeLatticeElement.WIDE;
   }
 
   private TypeLatticeElement element(DexType type) {
-    return TypeLatticeElement.fromDexType(appInfo, type, true);
+    return TypeLatticeElement.fromDexType(type, appInfo, true);
   }
 
   private ArrayTypeLatticeElement array(int nesting, DexType base) {
@@ -101,6 +109,22 @@
   }
 
   @Test
+  public void joinDifferentKindsIsTop() {
+    assertEquals(
+        top(),
+        join(element(factory.intType), element(factory.stringType)));
+    assertEquals(
+        top(),
+        join(element(factory.stringType), element(factory.doubleType)));
+    assertEquals(
+        top(),
+        join(single(), element(factory.objectType)));
+    assertEquals(
+        top(),
+        join(element(factory.objectType), wide()));
+  }
+
+  @Test
   public void joinBottomIsUnit() {
     DexType charSequence = factory.createType("Ljava/lang/CharSequence;");
     assertEquals(
@@ -115,6 +139,19 @@
   }
 
   @Test
+  public void joinPrimitiveTypes() {
+    assertEquals(
+        single(),
+        join(element(factory.intType), element(factory.floatType)));
+    assertEquals(
+        wide(),
+        join(element(factory.longType), element(factory.doubleType)));
+    assertEquals(
+        top(),
+        join(element(factory.intType), element(factory.longType)));
+  }
+
+  @Test
   public void joinClassTypes() {
     DexType charSequence = factory.createType("Ljava/lang/CharSequence;");
     assertEquals(
@@ -297,6 +334,11 @@
         join(
             array(1, factory.intType),
             array(1, factory.floatType)));
+    assertEquals(
+        element(factory.objectType),
+        join(
+            array(1, factory.longType),
+            array(1, factory.intType)));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 2b92ab1..fb8e674 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.Div;
@@ -16,7 +17,6 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.utils.InternalOptions;
@@ -69,14 +69,14 @@
     block.setNumber(0);
     Position position = Position.testingPosition();
 
-    Value v3 = new Value(3, ValueType.LONG, null);
+    Value v3 = new Value(3, TypeLatticeElement.LONG, null);
     v3.setNeedsRegister(true);
     new MockLiveIntervals(v3);
     Instruction instruction = new ConstNumber(v3, 0);
     instruction.setPosition(position);
     block.add(instruction);
 
-    Value v0 = new Value(0, ValueType.LONG, null);
+    Value v0 = new Value(0, TypeLatticeElement.LONG, null);
     v0.setNeedsRegister(true);
     new MockLiveIntervals(v0);
     instruction = new ConstNumber(v0, 10);
@@ -87,14 +87,14 @@
     instruction.setPosition(position);
     block.add(instruction);
 
-    Value v2 = new Value(2, ValueType.INT, null);
+    Value v2 = new Value(2, TypeLatticeElement.INT, null);
     v2.setNeedsRegister(true);
     new MockLiveIntervals(v2);
     instruction = new ConstNumber(v2, 10);
     instruction.setPosition(position);
     block.add(instruction);
 
-    Value v1 = new Value(1, ValueType.INT, null);
+    Value v1 = new Value(1, TypeLatticeElement.INT, null);
     v1.setNeedsRegister(true);
     new MockLiveIntervals(v1);
     instruction = new Move(v1 ,v2);
@@ -105,7 +105,7 @@
     instruction.setPosition(position);
     block.add(instruction);
 
-    Value v0_2 = new Value(0, ValueType.LONG, null);
+    Value v0_2 = new Value(0, TypeLatticeElement.LONG, null);
     v0_2.setNeedsRegister(true);
     new MockLiveIntervals(v0_2);
     instruction = new ConstNumber(v0_2, 10);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 6470876..d254847 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -6,6 +6,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -19,7 +21,6 @@
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.LinkedList;
@@ -49,7 +50,7 @@
     block2.setFilledForTesting();
     BasicBlock block1 = new BasicBlock();
     block1.setNumber(1);
-    Value value = new Value(0, ValueType.INT, null);
+    Value value = new Value(0, TypeLatticeElement.INT, null);
     Instruction number = new ConstNumber(value, 0);
     number.setPosition(position);
     block1.add(number);
@@ -110,7 +111,7 @@
 
     BasicBlock block0 = new BasicBlock();
     block0.setNumber(0);
-    Value value = new Value(0, ValueType.OBJECT, null);
+    Value value = new Value(0, TypeLatticeElement.fromDexType(DexItemFactory.catchAllType), null);
     instruction = new Argument(value);
     instruction.setPosition(position);
     block0.add(instruction);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynthesizedLambdaClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynthesizedLambdaClass.java
new file mode 100644
index 0000000..dc1c40b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynthesizedLambdaClass.java
@@ -0,0 +1,58 @@
+// 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.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+
+public class InlineSynthesizedLambdaClass extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    AndroidApp input = readClasses(Lambda.class, Lambda.Consumer.class);
+    AndroidApp output =
+        compileWithR8(
+            input,
+            String.join(
+                System.lineSeparator(),
+                keepMainProguardConfiguration(Lambda.class),
+                "-allowaccessmodification"),
+            options -> options.enableMinification = false);
+
+    // Check that everything has been inlined into main.
+    CodeInspector inspector = new CodeInspector(output);
+    assertEquals(1, inspector.allClasses().size());
+
+    ClassSubject classSubject = inspector.clazz(Lambda.class);
+    assertThat(classSubject, isPresent());
+    assertEquals(1, classSubject.allMethods().size());
+
+    // Check that the program gives the expected result.
+    assertEquals(runOnJava(Lambda.class), runOnArt(output, Lambda.class));
+  }
+}
+
+class Lambda {
+
+  interface Consumer<T> {
+    void accept(T value);
+  }
+
+  public static void main(String... args) {
+    load(s -> System.out.println(s));
+    load(s -> System.out.println(s));
+  }
+
+  public static void load(Consumer<String> c) {
+    c.accept("Hello!");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 8102882..6d824ec 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -6,12 +6,12 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.InternalOptions;
 import org.junit.Test;
 
@@ -47,13 +47,13 @@
   @Test
   public void equalityOfConstantOperands() {
     RegisterAllocator allocator = new MockRegisterAllocator();
-    Value value0 = new Value(0, ValueType.INT, null);
+    Value value0 = new Value(0, TypeLatticeElement.INT, null);
     ConstNumber const0 = new ConstNumber(value0, 0);
-    Value value1 = new Value(1, ValueType.INT, null);
+    Value value1 = new Value(1, TypeLatticeElement.INT, null);
     ConstNumber const1 = new ConstNumber(value1, 1);
-    Value value2 = new Value(2, ValueType.INT, null);
+    Value value2 = new Value(2, TypeLatticeElement.INT, null);
     ConstNumber const2 = new ConstNumber(value2, 2);
-    Value value3 = new Value(2, ValueType.INT, null);
+    Value value3 = new Value(2, TypeLatticeElement.INT, null);
     Add add0 = new Add(NumericType.INT, value3, value0, value1);
     add0.setPosition(Position.none());
     Add add1 = new Add(NumericType.INT, value3, value0, value2);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index 9866b34..ddbe2c8 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -4,10 +4,10 @@
 package com.android.tools.r8.ir.regalloc;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
@@ -61,18 +61,21 @@
     MyRegisterAllocator allocator = new MyRegisterAllocator(code, options);
     // Setup live an inactive live interval with ranges [0, 10[ and [20, 30[ with only
     // uses in the first interval and which is linked to another interval.
-    LiveIntervals inactiveIntervals = new LiveIntervals(new Value(0, ValueType.INT, null));
+    LiveIntervals inactiveIntervals =
+        new LiveIntervals(new Value(0, TypeLatticeElement.INT, null));
     inactiveIntervals.addRange(new LiveRange(0, 10));
     inactiveIntervals.addUse(new LiveIntervalsUse(0, 10));
     inactiveIntervals.addUse(new LiveIntervalsUse(4, 10));
     inactiveIntervals.addRange(new LiveRange(20, 30));
     inactiveIntervals.setRegister(0);
-    LiveIntervals linked = new LiveIntervals(new Value(1, ValueType.INT, null));
+    LiveIntervals linked =
+        new LiveIntervals(new Value(1, TypeLatticeElement.INT, null));
     linked.setRegister(1);
     inactiveIntervals.link(linked);
     allocator.addInactiveIntervals(inactiveIntervals);
     // Setup an unhandled interval that overlaps the inactive interval.
-    LiveIntervals unhandledIntervals = new LiveIntervals(new Value(2, ValueType.INT, null));
+    LiveIntervals unhandledIntervals =
+        new LiveIntervals(new Value(2, TypeLatticeElement.INT, null));
     unhandledIntervals.addRange(new LiveRange(12, 24));
     // Split the overlapping inactive intervals and check that after the split, the second
     // part of the inactive interval is unhandled and will therefore get a new register
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
new file mode 100644
index 0000000..a8708eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -0,0 +1,77 @@
+// 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.naming;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumMinification extends TestBase {
+
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public EnumMinification(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    AndroidApp output =
+        ToolHelper.runR8(
+            R8Command.builder()
+                .addClassProgramData(ToolHelper.getClassAsBytes(Main.class), Origin.unknown())
+                .addClassProgramData(ToolHelper.getClassAsBytes(Enum.class), Origin.unknown())
+                .addProguardConfiguration(
+                    ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
+                .setProgramConsumer(emptyConsumer(backend))
+                .build());
+
+    // TODO(117299356): valueOf on enum fails for minified enums.
+    ProcessResult result = runOnVMRaw(output, Main.class, backend);
+    assertEquals(1, result.exitCode);
+    assertThat(
+        result.stderr,
+        containsString(
+            backend == Backend.DEX
+                ? ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+                    ? "java.lang.NoSuchMethodException"
+                    : "java.lang.NullPointerException"
+                : "java.lang.IllegalArgumentException"));
+  }
+}
+
+class Main {
+
+  public static void main(String[] args) {
+    Enum e = Enum.valueOf("VALUE1");
+    System.out.println(e);
+  }
+}
+
+enum Enum {
+  VALUE1,
+  VALUE2
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index 3240365..8d62033 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -498,7 +498,7 @@
     testSingleClass("Outer", "X", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer", "Expected L at position 1");
+          "Invalid signature 'X' for class Outer", "Expected L at position 1");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
   }
 
@@ -507,7 +507,7 @@
     testSingleClass("Outer", "<L", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer", "Unexpected end of signature at position 3");
+          "Invalid signature '<L' for class Outer", "Unexpected end of signature at position 3");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
   }
 
@@ -516,7 +516,7 @@
     testSingleClass("Outer$ExtendsInner", "X", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer$ExtendsInner", "Expected L at position 1");
+          "Invalid signature 'X' for class Outer$ExtendsInner", "Expected L at position 1");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")));
   }
 
@@ -525,7 +525,7 @@
     testSingleClass("Outer$Inner$ExtendsInnerInner", "X", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer$Inner$ExtendsInnerInner",
+          "Invalid signature 'X' for class Outer$Inner$ExtendsInnerInner",
           "Expected L at position 1");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")));
   }
@@ -548,8 +548,11 @@
     String signature = "LOuter<TT;>.com/example/Inner;";
     testSingleClass("Outer$ExtendsInner", signature, diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
-      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer$ExtendsInner", "Expected ; at position 16");
+      DiagnosticsChecker.checkDiagnostic(
+          diagnostics.warnings.get(0),
+          this::isOriginUnknown,
+          "Invalid signature '" + signature + "' for class Outer$ExtendsInner",
+          "Expected ; at position 16");
     }, inspector -> {
       noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
     });
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index 4c81632..f73b8d2 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -278,7 +278,7 @@
       assertEquals(1, diagnostics.warnings.size());
       // TODO(sgjesse): The position 2 reported here is one off.
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for field",
+          "Invalid signature 'X' for field",
           "java.lang.String Fields.anX",
           "Expected L, [ or T at position 2");
     }, inspector -> noSignatureAttribute(lookupAnX(inspector)));
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index a1ab483..d9d4774 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -299,7 +299,7 @@
     testSingleMethod("generic", "X", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for method",
+          "Invalid signature 'X' for method",
           "java.lang.Throwable Methods.generic(java.lang.Throwable, Methods$Inner)",
           "Expected ( at position 1");
     }, inspector -> noSignatureAttribute(lookupGeneric(inspector)));
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 6331f5d..90013b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -17,6 +17,12 @@
 
   public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
 
+  public final List<FoundMethodSubject> allMethods() {
+    ImmutableList.Builder<FoundMethodSubject> builder = ImmutableList.builder();
+    forAllMethods(builder::add);
+    return builder.build();
+  }
+
   public MethodSubject method(Method method) {
     List<String> parameters = new ArrayList<>();
     for (Class<?> parameterType : method.getParameterTypes()) {