Split type resolving in two phases.

In general we may need a fixed point computation of types to fully resolve all
types in the presence of array-put and array-get in DEX input.

Bug: 119217869, 119401913
Change-Id: I468dec7df4f714e897186f076b9b7cd8612fff8d
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 192b36f..dd6a952 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
@@ -26,6 +26,8 @@
     NARROWING,  // updating with more specific info, e.g., passing the return value of the inlinee.
   }
 
+  private final boolean mayHaveImpreciseTypes;
+
   private Mode mode = Mode.UNSET;
 
   private final AppInfo appInfo;
@@ -33,9 +35,15 @@
 
   private final Deque<Value> worklist = new ArrayDeque<>();
 
-  public TypeAnalysis(AppInfo appInfo, DexEncodedMethod encodedMethod) {
+  public TypeAnalysis(
+      AppInfo appInfo, DexEncodedMethod encodedMethod, boolean mayHaveImpreciseTypes) {
     this.appInfo = appInfo;
     this.context = encodedMethod;
+    this.mayHaveImpreciseTypes = mayHaveImpreciseTypes;
+  }
+
+  public TypeAnalysis(AppInfo appInfo, DexEncodedMethod encodedMethod) {
+    this(appInfo, encodedMethod, false);
   }
 
   private void analyze() {
@@ -107,11 +115,13 @@
   }
 
   private void analyzeValue(Value value) {
+    TypeLatticeElement previous = value.getTypeLattice();
     TypeLatticeElement derived =
         value.isPhi()
             ? value.asPhi().computePhiType(appInfo)
             : value.definition.evaluate(appInfo);
-    assert derived.isPreciseType();
+    assert mayHaveImpreciseTypes || derived.isPreciseType();
+    assert !previous.isPreciseType() || derived.isPreciseType();
     updateTypeOfValue(value, derived);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 1f4d942..edfff9c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.code.AgetShort;
 import com.android.tools.r8.code.AgetWide;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -21,12 +22,13 @@
 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.ir.conversion.TypeConstraintResolver;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.Arrays;
 
-public class ArrayGet extends Instruction {
+public class ArrayGet extends Instruction implements ImpreciseMemberTypeInstruction {
 
   private MemberType type;
 
@@ -47,6 +49,7 @@
     return inValues.get(1);
   }
 
+  @Override
   public MemberType getMemberType() {
     return type;
   }
@@ -188,23 +191,30 @@
       case DOUBLE:
         return TypeLatticeElement.DOUBLE;
       case INT_OR_FLOAT:
+        return checkConstraint(dest(), ValueType.INT_OR_FLOAT);
       case LONG_OR_DOUBLE:
-        throw new Unreachable("Unexpected imprecise type: " + getMemberType());
+        return checkConstraint(dest(), ValueType.LONG_OR_DOUBLE);
       default:
         throw new Unreachable("Unexpected member type: " + getMemberType());
     }
   }
 
+  private static TypeLatticeElement checkConstraint(Value value, ValueType constraint) {
+    TypeLatticeElement latticeElement = value.constrainedType(constraint);
+    if (latticeElement != null) {
+      return latticeElement;
+    }
+    throw new CompilationError(
+        "Failure to constrain value: " + value + " by constraint: " + constraint);
+  }
+
   @Override
   public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
     return array() == value;
   }
 
   @Override
-  public boolean constrainType() {
-    if (!type.isPrecise()) {
-      type = MemberType.constrainedType(type, ValueType.fromTypeLattice(dest().getTypeLattice()));
-    }
-    return type != null;
+  public void constrainType(TypeConstraintResolver constraintResolver) {
+    constraintResolver.constrainArrayMemberType(type, dest(), array(), t -> type = t);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 32e7259..7263948 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -19,12 +19,13 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.Arrays;
 
-public class ArrayPut extends Instruction {
+public class ArrayPut extends Instruction implements ImpreciseMemberTypeInstruction {
 
   // Input values are ordered according to the stack order of the Java bytecode astore.
   private static final int ARRAY_INDEX = 0;
@@ -53,6 +54,7 @@
     return inValues.get(VALUE_INDEX);
   }
 
+  @Override
   public MemberType getMemberType() {
     return type;
   }
@@ -181,10 +183,7 @@
   }
 
   @Override
-  public boolean constrainType() {
-    if (!type.isPrecise()) {
-      type = MemberType.constrainedType(type, ValueType.fromTypeLattice(value().getTypeLattice()));
-    }
-    return type != null;
+  public void constrainType(TypeConstraintResolver constraintResolver) {
+    constraintResolver.constrainArrayMemberType(type, value(), array(), t -> type = t);
   }
 }
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 9990066..4d8f8d2 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
@@ -263,7 +263,6 @@
 
   @Override
   public TypeLatticeElement evaluate(AppInfo appInfo) {
-    assert outValue().getTypeLattice().isPreciseType();
     return outValue().getTypeLattice();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index e3fb465..2900491 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DexField;
+import java.util.Collections;
 import java.util.List;
 
 public abstract class FieldInstruction extends Instruction {
@@ -11,20 +12,15 @@
   private MemberType type;
   private final DexField field;
 
-  protected FieldInstruction(MemberType type, DexField field, Value dest, Value value) {
-    super(dest, value);
-    assert type != null;
-    assert field != null;
-    this.type = type;
-    this.field = field;
+  protected FieldInstruction(DexField field, Value dest, Value value) {
+    this(field, dest, Collections.singletonList(value));
   }
 
-  protected FieldInstruction(MemberType type, DexField field, Value dest, List<Value> inValues) {
+  protected FieldInstruction(DexField field, Value dest, List<Value> inValues) {
     super(dest, inValues);
-    assert type != null;
     assert field != null;
-    this.type = type;
     this.field = field;
+    this.type = MemberType.fromDexType(field.type);
   }
 
   public MemberType getType() {
@@ -35,8 +31,6 @@
     return field;
   }
 
-  abstract Value getFieldInOrOutValue();
-
   @Override
   public boolean isFieldInstruction() {
     return true;
@@ -52,14 +46,4 @@
     // TODO(jsjeon): what if the target field is known to be non-null?
     return true;
   }
-
-  @Override
-  public boolean constrainType() {
-    if (!type.isPrecise()) {
-      type =
-          MemberType.constrainedType(
-              type, ValueType.fromTypeLattice(getFieldInOrOutValue().getTypeLattice()));
-    }
-    return type != null;
-  }
 }
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 dd0e174..58ada51 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
@@ -465,7 +465,7 @@
     assert consistentDefUseChains();
     assert validThrowingInstructions();
     assert noCriticalEdges();
-    assert verifyNoImpreciseTypes();
+    assert verifyNoImpreciseOrBottomTypes();
     return true;
   }
 
@@ -675,26 +675,19 @@
     return true;
   }
 
-  public boolean verifyNoImpreciseTypes() {
+  public boolean verifyNoImpreciseOrBottomTypes() {
     return verifySSATypeLattice(
         v -> {
           assert v.getTypeLattice().isPreciseType();
-          if (v.definition != null) {
-            assert !v.definition.isArrayGet()
-                || v.definition.asArrayGet().getMemberType().isPrecise();
-            assert !v.definition.isArrayPut()
-                || v.definition.asArrayPut().getMemberType().isPrecise();
-            assert !v.definition.isFieldInstruction()
-                || v.definition.asFieldInstruction().getType().isPrecise();
-          }
+          // For now we assume no bottom types on IR values. We may want to reconsider this for
+          // representing unreachable code.
+          assert !v.getTypeLattice().isBottom();
+          assert !(v.definition instanceof ImpreciseMemberTypeInstruction)
+              || ((ImpreciseMemberTypeInstruction) v.definition).getMemberType().isPrecise();
           return true;
         });
   }
 
-  public boolean verifyNoBottomTypes() {
-    return verifySSATypeLattice(v -> !v.getTypeLattice().isBottom());
-  }
-
   private boolean verifySSATypeLattice(Predicate<Value> tester) {
     for (BasicBlock block : blocks) {
       for (Instruction instruction : block.getInstructions()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java b/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java
new file mode 100644
index 0000000..2b5bc02
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.code;
+
+import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
+
+public interface ImpreciseMemberTypeInstruction {
+  MemberType getMemberType();
+
+  void constrainType(TypeConstraintResolver constraintResolver);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index e92f97b..66a6c3e 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
@@ -29,8 +29,8 @@
 
 public class InstanceGet extends FieldInstruction {
 
-  public InstanceGet(MemberType type, Value dest, Value object, DexField field) {
-    super(type, field, dest, object);
+  public InstanceGet(Value dest, Value object, DexField field) {
+    super(field, dest, object);
   }
 
   public Value dest() {
@@ -43,11 +43,6 @@
   }
 
   @Override
-  Value getFieldInOrOutValue() {
-    return dest();
-  }
-
-  @Override
   public void buildDex(DexBuilder builder) {
     int destRegister = builder.allocatedRegister(dest(), getNumber());
     int objectRegister = builder.allocatedRegister(object(), getNumber());
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 8bd5061..2d70317 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -27,8 +27,8 @@
 
 public class InstancePut extends FieldInstruction {
 
-  public InstancePut(MemberType type, DexField field, Value object, Value value) {
-    super(type, field, null, Arrays.asList(object, value));
+  public InstancePut(DexField field, Value object, Value value) {
+    super(field, null, Arrays.asList(object, value));
     assert object().verifyCompatible(ValueType.OBJECT);
     assert value().verifyCompatible(ValueType.fromDexType(field.type));
   }
@@ -42,11 +42,6 @@
   }
 
   @Override
-  Value getFieldInOrOutValue() {
-    return value();
-  }
-
-  @Override
   public void buildDex(DexBuilder builder) {
     com.android.tools.r8.code.Instruction instruction;
     int valueRegister = builder.allocatedRegister(value(), getNumber());
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index eb4fbe7..422f160 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1253,8 +1253,4 @@
     assert !instructionTypeCanThrow() || getPosition().isSome() || getPosition().isSyntheticNone();
     return true;
   }
-
-  public boolean constrainType() {
-    return true;
-  }
 }
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 ec150da..537ca84 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
@@ -27,8 +27,8 @@
 
 public class StaticGet extends FieldInstruction {
 
-  public StaticGet(MemberType type, Value dest, DexField field) {
-    super(type, field, dest, (Value) null);
+  public StaticGet(Value dest, DexField field) {
+    super(field, dest, (Value) null);
   }
 
   public Value dest() {
@@ -36,11 +36,6 @@
   }
 
   @Override
-  Value getFieldInOrOutValue() {
-    return dest();
-  }
-
-  @Override
   public void buildDex(DexBuilder builder) {
     com.android.tools.r8.code.Instruction instruction;
     int dest = builder.allocatedRegister(dest(), getNumber());
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index e36acb7..ba33cbf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -24,8 +24,8 @@
 
 public class StaticPut extends FieldInstruction {
 
-  public StaticPut(MemberType type, Value source, DexField field) {
-    super(type, field, null, source);
+  public StaticPut(Value source, DexField field) {
+    super(field, null, source);
   }
 
   public Value inValue() {
@@ -34,11 +34,6 @@
   }
 
   @Override
-  Value getFieldInOrOutValue() {
-    return inValue();
-  }
-
-  @Override
   public void buildDex(DexBuilder builder) {
     com.android.tools.r8.code.Instruction instruction;
     int src = builder.allocatedRegister(inValue(), getNumber());
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 c597702..5bb39b1 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
@@ -53,6 +53,7 @@
 import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ImpreciseMemberTypeInstruction;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -93,11 +94,9 @@
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.StringDiagnostic;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -405,7 +404,7 @@
   private int nextBlockNumber = 0;
 
   // Flag indicating if any instructions have imprecise internal types (eg, int|float member types)
-  private List<Instruction> impreciseInstructions = null;
+  private List<ImpreciseMemberTypeInstruction> impreciseInstructions = null;
 
   // Flag indicating if any values have imprecise types.
   private boolean hasImpreciseValues = false;
@@ -599,30 +598,16 @@
     ir.removeAllTrivialPhis(this);
     ir.removeUnreachableBlocks();
 
-    // Constrain all values to precise types.
-    if (hasImpreciseValues) {
-      TypeConstraintResolver resolver = new TypeConstraintResolver();
-      resolver.resolve(ir, this, options.reporter);
+    // Compute precise types for all values.
+    if (hasImpreciseValues || impreciseInstructions != null) {
+      // In DEX we may need to constrain all values and instructions to precise types.
+      assert source instanceof DexSourceCode;
+      new TypeConstraintResolver(this, options.reporter)
+          .resolve(impreciseInstructions, ir, appInfo, method, context);
+    } else {
+      new TypeAnalysis(appInfo, context).widening(method, ir);
     }
 
-    // Constrain instructions to precise types (internally these will use the value types).
-    if (impreciseInstructions != null) {
-      for (Instruction instruction : impreciseInstructions) {
-        if (!instruction.constrainType()) {
-          throw options.reporter.fatalError(
-              new StringDiagnostic(
-                  "Cannot determine precise type for instruction: " + instruction,
-                  origin,
-                  new MethodPosition(method.method)));
-        }
-      }
-    }
-
-    assert ir.verifyNoImpreciseTypes();
-
-    new TypeAnalysis(appInfo, context).widening(method, ir);
-
-    assert ir.verifyNoBottomTypes();
     assert ir.isConsistentSSA();
 
     // Clear the code so we don't build multiple times.
@@ -635,7 +620,7 @@
     value.constrainType(constraint, method.method, origin, options.reporter);
   }
 
-  private void addImpreciseInstruction(Instruction instruction) {
+  private void addImpreciseInstruction(ImpreciseMemberTypeInstruction instruction) {
     if (impreciseInstructions == null) {
       impreciseInstructions = new ArrayList<>();
     }
@@ -1217,16 +1202,12 @@
   }
 
   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, TypeLatticeElement.fromDexType(field.type, true, appInfo), ThrowingInfo.CAN_THROW);
-    out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
-    InstanceGet instruction = new InstanceGet(type, out, in, field);
+    out.setKnownToBeBoolean(field.type == getFactory().booleanType);
+    InstanceGet instruction = new InstanceGet(out, in, field);
     assert instruction.instructionTypeCanThrow();
-    if (!type.isPrecise()) {
-      addImpreciseInstruction(instruction);
-    }
     addInstruction(instruction);
   }
 
@@ -1239,13 +1220,9 @@
   }
 
   public void addInstancePut(int value, int object, DexField field) {
-    MemberType type = MemberType.fromDexType(field.type);
     Value objectValue = readRegister(object, ValueType.OBJECT);
-    Value valueValue = readRegister(value, ValueType.fromMemberType(type));
-    InstancePut instruction = new InstancePut(type, field, objectValue, valueValue);
-    if (!type.isPrecise()) {
-      addImpreciseInstruction(instruction);
-    }
+    Value valueValue = readRegister(value, ValueType.fromDexType(field.type));
+    InstancePut instruction = new InstancePut(field, objectValue, valueValue);
     add(instruction);
   }
 
@@ -1575,26 +1552,18 @@
   }
 
   public void addStaticGet(int dest, DexField field) {
-    MemberType type = MemberType.fromDexType(field.type);
     Value out = writeRegister(
         dest, TypeLatticeElement.fromDexType(field.type, true, appInfo), ThrowingInfo.CAN_THROW);
-    out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
-    StaticGet instruction = new StaticGet(type, out, field);
+    out.setKnownToBeBoolean(field.type == getFactory().booleanType);
+    StaticGet instruction = new StaticGet(out, field);
     assert instruction.instructionTypeCanThrow();
-    if (!type.isPrecise()) {
-      addImpreciseInstruction(instruction);
-    }
     addInstruction(instruction);
   }
 
   public void addStaticPut(int value, DexField field) {
-    MemberType type = MemberType.fromDexType(field.type);
-    Value in = readRegister(value, ValueType.fromMemberType(type));
-    StaticPut instruction = new StaticPut(type, in, field);
+    Value in = readRegister(value, ValueType.fromDexType(field.type));
+    StaticPut instruction = new StaticPut(in, field);
     assert instruction.instructionTypeCanThrow();
-    if (!type.isPrecise()) {
-      addImpreciseInstruction(instruction);
-    }
     add(instruction);
   }
 
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 a42b87b..d7f56ee 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
@@ -212,7 +212,6 @@
           if (actualField != field) {
             InstanceGet newInstanceGet =
                 new InstanceGet(
-                    instanceGet.getType(),
                     makeOutValue(instanceGet, code, newSSAValues),
                     instanceGet.object(),
                     actualField);
@@ -224,8 +223,7 @@
           DexField actualField = graphLense.lookupField(field);
           if (actualField != field) {
             InstancePut newInstancePut =
-                new InstancePut(
-                    instancePut.getType(), actualField, instancePut.object(), instancePut.value());
+                new InstancePut(actualField, instancePut.object(), instancePut.value());
             iterator.replaceCurrentInstruction(newInstancePut);
           }
         } else if (current.isStaticGet()) {
@@ -234,8 +232,7 @@
           DexField actualField = graphLense.lookupField(field);
           if (actualField != field) {
             StaticGet newStaticGet =
-                new StaticGet(
-                    staticGet.getType(), makeOutValue(staticGet, code, newSSAValues), actualField);
+                new StaticGet(makeOutValue(staticGet, code, newSSAValues), actualField);
             iterator.replaceCurrentInstruction(newStaticGet);
           }
         } else if (current.isStaticPut()) {
@@ -243,8 +240,7 @@
           DexField field = staticPut.getField();
           DexField actualField = graphLense.lookupField(field);
           if (actualField != field) {
-            StaticPut newStaticPut =
-                new StaticPut(staticPut.getType(), staticPut.inValue(), actualField);
+            StaticPut newStaticPut = new StaticPut(staticPut.inValue(), actualField);
             iterator.replaceCurrentInstruction(newStaticPut);
           }
         } else if (current.isCheckCast()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
index 927c432..01fa20c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -5,21 +5,31 @@
 
 import com.android.tools.r8.errors.CompilationError;
 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.ir.analysis.type.ArrayTypeLatticeElement;
+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.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ImpreciseMemberTypeInstruction;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Type constraint resolver that ensures that all SSA values have a "precise" type, ie, every value
@@ -39,8 +49,15 @@
  */
 public class TypeConstraintResolver {
 
+  private final IRBuilder builder;
+  private final Reporter reporter;
   private final Map<Value, Value> unificationParents = new HashMap<>();
 
+  public TypeConstraintResolver(IRBuilder builder, Reporter reporter) {
+    this.builder = builder;
+    this.reporter = reporter;
+  }
+
   public static ValueType constraintForType(TypeLatticeElement type) {
     if (type.isTop()) {
       return ValueType.INT_OR_FLOAT_OR_NULL;
@@ -96,7 +113,23 @@
     }
   }
 
-  public void resolve(IRCode code, IRBuilder builder, Reporter reporter) {
+  public void resolve(
+      List<ImpreciseMemberTypeInstruction> impreciseInstructions,
+      IRCode code,
+      AppInfo appInfo,
+      DexEncodedMethod method,
+      DexEncodedMethod context) {
+    // Round one will resolve at least all object vs single types.
+    List<Value> remainingImpreciseValues = resolveRoundOne(code);
+    // Round two will resolve any remaining single and wide types. These can depend on the types
+    // of array instructions, thus we need to complete the type fixed point prior to resolving.
+    new TypeAnalysis(appInfo, context, true).widening(method, code);
+    // Round two resolves any remaining imprecision and finally selects a final precise type for
+    // any unconstrained imprecise type.
+    resolveRoundTwo(code, impreciseInstructions, remainingImpreciseValues);
+  }
+
+  private List<Value> resolveRoundOne(IRCode code) {
     List<Value> impreciseValues = new ArrayList<>();
     for (BasicBlock block : code.blocks) {
       for (Phi phi : block.getPhis()) {
@@ -123,38 +156,113 @@
         }
       }
     }
-    for (Value value : impreciseValues) {
-      builder.constrainType(value, getCanonicalTypeConstraint(value));
-      if (!value.getTypeLattice().isPreciseType()) {
-        throw reporter.fatalError(
-            new StringDiagnostic(
-                "Cannot determine precise type for value: "
-                    + value
-                    + ", its imprecise type is: "
-                    + value.getTypeLattice(),
-                code.origin,
-                new MethodPosition(code.method.method)));
+    return constrainValues(false, impreciseValues);
+  }
+
+  private void resolveRoundTwo(
+      IRCode code,
+      List<ImpreciseMemberTypeInstruction> impreciseInstructions,
+      List<Value> remainingImpreciseValues) {
+    if (impreciseInstructions != null) {
+      for (ImpreciseMemberTypeInstruction impreciseInstruction : impreciseInstructions) {
+        impreciseInstruction.constrainType(this);
       }
     }
+    ArrayList<Value> stillImprecise = constrainValues(true, remainingImpreciseValues);
+    if (!stillImprecise.isEmpty()) {
+      throw reporter.fatalError(
+          new StringDiagnostic(
+              "Cannot determine precise type for value: "
+                  + stillImprecise.get(0)
+                  + ", its imprecise type is: "
+                  + stillImprecise.get(0).getTypeLattice(),
+              code.origin,
+              new MethodPosition(code.method.method)));
+    }
+  }
+
+  private ArrayList<Value> constrainValues(boolean finished, List<Value> impreciseValues) {
+    ArrayList<Value> stillImprecise = new ArrayList<>(impreciseValues.size());
+    for (Value value : impreciseValues) {
+      builder.constrainType(value, getCanonicalTypeConstraint(value, finished));
+      if (!value.getTypeLattice().isPreciseType()) {
+        stillImprecise.add(value);
+      }
+    }
+    return stillImprecise;
+  }
+
+  public void constrainArrayMemberType(
+      MemberType type, Value value, Value array, Consumer<MemberType> setter) {
+    assert !type.isPrecise();
+    Value canonical = canonical(value);
+    ValueType constraint;
+    if (array.getTypeLattice().isArrayType()) {
+      // If the array type is known it uniquely defines the actual member type.
+      ArrayTypeLatticeElement arrayType = array.getTypeLattice().asArrayTypeLatticeElement();
+      constraint = ValueType.fromDexType(arrayType.getArrayElementType(builder.getFactory()));
+    } else {
+      // If not, e.g., the array input is null, the canonical value determines the final type.
+      constraint = getCanonicalTypeConstraint(canonical, true);
+    }
+    // Constrain the canonical value by the final and precise type constraint and set the member.
+    // A refinement of the value type will then be propagated in constrainValues of "round two".
+    builder.constrainType(canonical, constraint);
+    setter.accept(MemberType.constrainedType(type, constraint));
   }
 
   private void merge(Value value1, Value value2) {
     link(canonical(value1), canonical(value2));
   }
 
-  private ValueType getCanonicalTypeConstraint(Value value) {
+  private ValueType getCanonicalTypeConstraint(Value value, boolean finished) {
     ValueType type = constraintForType(canonical(value).getTypeLattice());
     switch (type) {
-      case INT_OR_FLOAT:
       case INT_OR_FLOAT_OR_NULL:
-        return ValueType.INT;
+        // There is never a second round for resolving object vs single.
+        assert !finished;
+        return ValueType.INT_OR_FLOAT;
+      case INT_OR_FLOAT:
+        assert !finished || verifyNoConstrainedUses(value);
+        return finished ? ValueType.INT : type;
       case LONG_OR_DOUBLE:
-        return ValueType.LONG;
+        assert !finished || verifyNoConstrainedUses(value);
+        return finished ? ValueType.LONG : type;
       default:
         return type;
     }
   }
 
+  private static boolean verifyNoConstrainedUses(Value value) {
+    return verifyNoConstrainedUses(value, ImmutableSet.of());
+  }
+
+  private static boolean verifyNoConstrainedUses(Value value, Set<Value> assumeNoConstrainedUses) {
+    for (Instruction user : value.uniqueUsers()) {
+      if (user.isIf()) {
+        If ifInstruction = user.asIf();
+        if (ifInstruction.isZeroTest()) {
+          continue;
+        }
+        Value other = ifInstruction.inValues().get(1 - ifInstruction.inValues().indexOf(value));
+        if (assumeNoConstrainedUses.contains(other)) {
+          continue;
+        }
+        assert verifyNoConstrainedUses(
+            other,
+            ImmutableSet.<Value>builder().addAll(assumeNoConstrainedUses).add(value).build());
+      } else if (user.isArrayPut()) {
+        ArrayPut put = user.asArrayPut();
+        assert value == put.value();
+        assert !put.getMemberType().isPrecise();
+        assert put.array().getTypeLattice().isDefinitelyNull();
+      } else {
+        assert false;
+      }
+    }
+    return true;
+  }
+
   // Link two values as having the same type.
   private void link(Value canonical1, Value canonical2) {
     if (canonical1 == canonical2) {
@@ -196,5 +304,4 @@
     }
     return value;
   }
-
 }
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 b89a185..b92d772 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
@@ -22,7 +22,6 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
@@ -291,7 +290,7 @@
     // reading the value of INSTANCE field created for singleton lambda class.
     if (lambdaClass.isStateless()) {
       instructions.replaceCurrentInstruction(
-          new StaticGet(MemberType.OBJECT, lambdaInstanceValue, lambdaClass.instanceField));
+          new StaticGet(lambdaInstanceValue, lambdaClass.instanceField));
       // Note that since we replace one throwing operation with another we don't need
       // to have any special handling for catch handlers.
       return;
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 c6aabc8..dfb87c6 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
@@ -69,7 +69,6 @@
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
@@ -3264,8 +3263,8 @@
     DexMethod printLn = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "println");
 
     iterator.add(
-        new StaticGet(MemberType.OBJECT, out,
-            dexItemFactory.createField(javaLangSystemType, javaIoPrintStreamType, "out")));
+        new StaticGet(
+            out, dexItemFactory.createField(javaLangSystemType, javaIoPrintStreamType, "out")));
 
     Value value = addConstString(code, iterator, "INVOKE ");
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
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 51da91d..c137cfd 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
@@ -148,9 +148,11 @@
 
     // We need to insert remapped values and in case the capture field
     // of type Object optionally cast to expected field.
-    InstanceGet newInstanceGet = new InstanceGet(instanceGet.getType(),
-        createValueForType(context, fieldType), instanceGet.object(),
-        mapCaptureField(context.factory, field.clazz, field));
+    InstanceGet newInstanceGet =
+        new InstanceGet(
+            createValueForType(context, fieldType),
+            instanceGet.object(),
+            mapCaptureField(context.factory, field.clazz, field));
     context.instructions().replaceCurrentInstruction(newInstanceGet);
 
     if (fieldType.isPrimitiveType() || fieldType == context.factory.objectType) {
@@ -185,7 +187,6 @@
   public void patch(CodeProcessor context, StaticGet staticGet) {
     context.instructions().replaceCurrentInstruction(
         new StaticGet(
-            staticGet.getType(),
             context.code.createValue(
                 TypeLatticeElement.fromDexType(staticGet.getField().type, true, context.appInfo)),
             mapSingletonInstanceField(context.factory, staticGet.getField())));
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 9147fe8..093ac43 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
@@ -20,7 +20,6 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
@@ -305,7 +304,6 @@
           assert outValue != null;
           it.replaceCurrentInstruction(
               new StaticGet(
-                  MemberType.fromDexType(field.type),
                   code.createValue(
                       TypeLatticeElement.fromDexType(field.type, true, classStaticizer.appInfo),
                       outValue.getLocalInfo()),
@@ -320,9 +318,7 @@
         StaticPut staticPut = instruction.asStaticPut();
         DexField field = mapFieldIfMoved(staticPut.getField());
         if (field != staticPut.getField()) {
-          it.replaceCurrentInstruction(
-              new StaticPut(MemberType.fromDexType(field.type), staticPut.inValue(), field)
-          );
+          it.replaceCurrentInstruction(new StaticPut(staticPut.inValue(), field));
         }
         continue;
       }
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 7904cd1..ac239bf 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -157,13 +157,11 @@
           }
           if (instruction.isStaticPut()) {
             StaticPut staticPut = instruction.asStaticPut();
-            iterator.replaceCurrentInstruction(
-                new StaticPut(staticPut.getType(), newIn, field));
+            iterator.replaceCurrentInstruction(new StaticPut(newIn, field));
           } else {
             assert instruction.isInstancePut();
             InstancePut instancePut = instruction.asInstancePut();
-            iterator.replaceCurrentInstruction(
-                new InstancePut(instancePut.getType(), field, instancePut.object(), newIn));
+            iterator.replaceCurrentInstruction(new InstancePut(field, instancePut.object(), newIn));
           }
           encodedMethod.markUseIdentifierNameString();
         } else if (instruction.isInvokeMethod()) {
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 529f11c..3618a5e 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -553,13 +553,6 @@
   // Tests where the output of R8 fails when run with Art.
   private static final Multimap<String, TestCondition> failingRunWithArt =
       new ImmutableListMultimap.Builder<String, TestCondition>()
-          // TODO(b/119217869): Re-enable this test once fixed.
-          .put(
-              "614-checker-dump-constant-location",
-              TestCondition.match(
-                  TestCondition.tools(DexTool.DX),
-                  TestCondition.R8_COMPILER,
-                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // The growth limit test fails after processing by R8 because R8 will eliminate an
           // "unneeded" const store. The following reflective call to the VM's GC will then see the
           // large array as still live and the subsequent allocations will fail to reach the desired
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 3eb3744..221d427 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -99,10 +99,7 @@
             test));
         fullTestList.add(makeTest(Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.DEBUG,
             test));
-        // TODO(b/119217869): Re-enable this test when fixed.
-        if (!test.equals("regress_37726195.Regress")) {
-          fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
-        }
+        fullTestList.add(makeTest(Input.DX, CompilerUnderTest.R8, CompilationMode.RELEASE, test));
       }
       fullTestList.add(
           makeTest(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
index f508934..ac73d7d 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
 import java.util.function.Consumer;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class ArrayTypeTest extends TypeAnalysisTestBase {
@@ -26,13 +25,11 @@
   }
 
   @Test
-  @Ignore("b/119401913")
   public void testArray() throws Exception {
     buildAndCheckIR("arrayTest", arrayTestInspector(appInfo));
   }
 
   @Test
-  @Ignore("b/119401913")
   public void testNestedArray() throws Exception {
     buildAndCheckIR("nestedArrayTest", nestedArrayTestInspector(appInfo));
   }