Remove chains of CheckCast on subtypes.

Bug:
Change-Id: Ib87ca85780375258f6b172dfeb18c51255ab1c6b
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 6a4777d..d43dc36 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
@@ -454,6 +454,7 @@
       assert !options.debug;
       inliner.performInlining(method, code, callGraph);
     }
+    codeRewriter.removeCastChains(code);
     codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
     codeRewriter.commonSubexpressionElimination(code);
     codeRewriter.simplifyArrayConstruction(code);
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 ddc83f8..981f673 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Binop;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -541,42 +542,45 @@
     assert code.isConsistentGraph();
   }
 
-  // For supporting assert javac adds the static field $assertionsDisabled to all classes which
-  // have methods with assertions. This is used to support the Java VM -ea flag.
-  //
-  //  The class:
-  //
-  //  class A {
-  //    void m() {
-  //      assert xxx;
-  //    }
-  //  }
-  //
-  //  Is compiled into:
-  //
-  //  class A {
-  //    static boolean $assertionsDisabled;
-  //    static {
-  //      $assertionsDisabled = A.class.desiredAssertionStatus();
-  //    }
-  //
-  //    // method with "assert xxx";
-  //    void m() {
-  //      if (!$assertionsDisabled) {
-  //        if (xxx) {
-  //          throw new AssertionError(...);
-  //        }
-  //      }
-  //    }
-  //  }
-  //
-  //  With the rewriting below (and other rewritings) the resulting code is:
-  //
-  //  class A {
-  //    void m() {
-  //    }
-  //  }
-  //
+
+  /**
+   * For supporting assert javac adds the static field $assertionsDisabled to all classes which
+   * have methods with assertions. This is used to support the Java VM -ea flag.
+   *
+   * The class:
+   * <pre>
+   * class A {
+   *   void m() {
+   *     assert xxx;
+   *   }
+   * }
+   * </pre>
+   * Is compiled into:
+   * <pre>
+   * class A {
+   *   static boolean $assertionsDisabled;
+   *   static {
+   *     $assertionsDisabled = A.class.desiredAssertionStatus();
+   *   }
+   *
+   *   // method with "assert xxx";
+   *   void m() {
+   *     if (!$assertionsDisabled) {
+   *       if (xxx) {
+   *         throw new AssertionError(...);
+   *       }
+   *     }
+   *   }
+   * }
+   * </pre>
+   * With the rewriting below (and other rewritings) the resulting code is:
+   * <pre>
+   * class A {
+   *   void m() {
+   *   }
+   * }
+   * </pre>
+   */
   public void disableAssertions(IRCode code) {
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
@@ -600,6 +604,28 @@
     }
   }
 
+  /**
+   * Due to inlining, we might see chains of casts on subtypes. It suffices to cast to the lowest
+   * subtype, as that will fail if a cast on a supertype would have failed.
+   */
+  public void removeCastChains(IRCode code) {
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      Instruction current = it.next();
+      if (current.isCheckCast()
+          && current.outValue() != null && current.outValue().isUsed()
+          && current.outValue().numberOfPhiUsers() == 0) {
+        CheckCast checkCast = current.asCheckCast();
+        if (checkCast.outValue().uniqueUsers().stream().allMatch(
+            user -> user.isCheckCast()
+                && user.asCheckCast().getType().isSubtypeOf(checkCast.getType(), appInfo))) {
+          checkCast.outValue().replaceUsers(checkCast.inValues().get(0));
+          it.remove();
+        }
+      }
+    }
+  }
+
   private boolean canBeFolded(Instruction instruction) {
     return (instruction.isBinop() && instruction.asBinop().canBeFolded()) ||
         (instruction.isUnop() && instruction.asUnop().canBeFolded());
@@ -1240,7 +1266,7 @@
   }
 
   private Value addConstString(IRCode code, InstructionListIterator iterator, String s) {
-    Value value = code.createValue(MoveType.OBJECT);;
+    Value value = code.createValue(MoveType.OBJECT);
     iterator.add(new ConstString(value, dexItemFactory.createString(s)));
     return value;
   }
@@ -1279,7 +1305,7 @@
     iterator.add(new ConstString(value, dexItemFactory.createString("INVOKE ")));
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
 
-    value = code.createValue(MoveType.OBJECT);;
+    value = code.createValue(MoveType.OBJECT);
     iterator.add(
         new ConstString(value, dexItemFactory.createString(method.method.qualifiedName())));
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));