Avoid direct use of ValueType fields during Value creations.

From now on, new SSA values should declare initial type lattice during
construction.

------

Main changes in this CL are how new SSA values are introduced during
IR building. Plus, optimizations that insert new SSA values are also
rewritten.

Other than that, most likely drop-in replacement of ValueType.XYZ with
TypeLatticeElement.XYZ or similar conversions, utils, etc.

Bug: 72693244
Change-Id: I927a40f7a5993627d7857dce2d682cde3d474537
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/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..262f670 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)
@@ -948,7 +948,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 +1061,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/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 4e7f421..1ac301a 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);
     }
   }
 
@@ -1656,11 +1658,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;
@@ -2155,7 +2157,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()) {
@@ -2664,7 +2667,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);
@@ -2746,7 +2749,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();
@@ -2973,7 +2976,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;
   }
@@ -3000,9 +3004,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");
@@ -3012,11 +3017,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)));
@@ -3042,7 +3047,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) ...".
@@ -3070,7 +3075,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..6fcade8 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);
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/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/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/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