CheckCast removal after class inlining

Bug: 113129394
Change-Id: Ibc8747f9773f513d9f9c99b831d9d32afa21ba37
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java
index 9416a7b..57151fa 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -38,6 +38,11 @@
   }
 
   @Override
+  public boolean isCheckCast() {
+    return true;
+  }
+
+  @Override
   public void registerUse(UseRegistry registry) {
     registry.registerCheckCast(getType());
   }
diff --git a/src/main/java/com/android/tools/r8/code/ConstString.java b/src/main/java/com/android/tools/r8/code/ConstString.java
index d165de5..ed7847a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/code/ConstString.java
@@ -45,6 +45,11 @@
   }
 
   @Override
+  public boolean isConstString() {
+    return true;
+  }
+
+  @Override
   public String toString(ClassNameMapper naming) {
     return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index c8b5154..fbb93ca 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -122,6 +122,14 @@
     this.offset = offset;
   }
 
+  public boolean isCheckCast() {
+    return false;
+  }
+
+  public boolean isConstString() {
+    return false;
+  }
+
   public boolean isSimpleNop() {
     return !isPayload() && this instanceof Nop;
   }
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 17d407b..f8b85de 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
@@ -1672,8 +1672,7 @@
           //   A < B
           //   A a = ...
           //   B b = (B) a;
-          assert inTypeLattice.isNull()
-              || outTypeLattice.asNullable().equals(inTypeLattice.asNullable());
+          assert TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, outTypeLattice);
           needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
           removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
         } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 0ef4597..6999f9a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -137,6 +137,7 @@
     // of roots to avoid infinite inlining. Looping makes possible for some roots to
     // become eligible after other roots are inlined.
 
+    boolean anyInlinedMethods = false;
     boolean repeat;
     do {
       repeat = false;
@@ -169,18 +170,24 @@
         }
 
         // Inline the class instance.
-        boolean anyInlinedMethods = processor.processInlining(code, inliner);
+        anyInlinedMethods |= processor.processInlining(code, inliner);
 
         // Restore normality.
         code.removeAllTrivialPhis();
         assert code.isConsistentSSA();
-        if (anyInlinedMethods) {
-          codeRewriter.simplifyIf(code);
-        }
         rootsIterator.remove();
         repeat = true;
       }
     } while (repeat);
+
+    if (anyInlinedMethods) {
+      // If a method was inlined we may be able to remove check-cast instructions because we may
+      // have more information about the types of the arguments at the call site. This is
+      // particularly important for bridge methods.
+      codeRewriter.removeCasts(code);
+      // If a method was inlined we may be able to prune additional branches.
+      codeRewriter.simplifyIf(code);
+    }
   }
 
   private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/RemoveCheckCastAfterClassInlining.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/RemoveCheckCastAfterClassInlining.java
new file mode 100644
index 0000000..d52725e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/RemoveCheckCastAfterClassInlining.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.checkcast;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class RemoveCheckCastAfterClassInlining extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    AndroidApp input = readClasses(Lambda.class, Lambda.Consumer.class);
+    AndroidApp output =
+        compileWithR8(
+            input,
+            keepMainProguardConfiguration(Lambda.class),
+            options -> options.enableMinification = false);
+
+    // Extract main method.
+    CodeInspector inspector = new CodeInspector(output);
+    ClassSubject classSubject = inspector.clazz(Lambda.class);
+    MethodSubject methodSubject = classSubject.mainMethod();
+    assertThat(methodSubject, isPresent());
+
+    DexEncodedMethod method = methodSubject.getMethod();
+    assertTrue(method.hasCode());
+
+    DexCode code = method.getCode().asDexCode();
+    int numberOfConstStringInstructions = 0;
+    for (Instruction instruction : code.instructions) {
+      // Make sure that we do not load a const-string and then subsequently use a check-cast
+      // instruction to check if it is actually a string.
+      assertFalse(instruction.isCheckCast());
+      if (instruction.isConstString()) {
+        numberOfConstStringInstructions++;
+      }
+    }
+
+    // Sanity check that load() was actually inlined.
+    assertThat(
+        classSubject.method("void", "load", ImmutableList.of(Lambda.Consumer.class.getName())),
+        not(isPresent()));
+    assertEquals(2, numberOfConstStringInstructions);
+  }
+}
+
+class Lambda {
+
+  interface Consumer<T> {
+    void accept(T value);
+  }
+
+  public static void main(String... args) {
+    load(s -> System.out.println(s));
+    // Other code…
+    load(s -> System.out.println(s));
+  }
+
+  public static void load(Consumer<String> c) {
+    c.accept("Hello!");
+  }
+}