Version 2.1.54

Cherry-pick: Handle invoke-virtual/range when hoisting bridge methods
CL: https://ci.chromium.org/p/r8/g/main_all/console

Bug: 161403944
Change-Id: I12c06dcf6aab4cdb87f3842f4263378074ceda31
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 7874bb9..3cd22ef 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.1.53";
+  public static final String LABEL = "2.1.54";
 
   private Version() {
   }
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 86abac6..9d93b40 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -183,6 +183,14 @@
     return null;
   }
 
+  public boolean isInvokeVirtualRange() {
+    return false;
+  }
+
+  public InvokeVirtualRange asInvokeVirtualRange() {
+    return null;
+  }
+
   public boolean isSimpleNop() {
     return !isPayload() && this instanceof Nop;
   }
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
index 64e9b6d..cd78458 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtualRange.java
@@ -39,6 +39,16 @@
   }
 
   @Override
+  public boolean isInvokeVirtualRange() {
+    return true;
+  }
+
+  @Override
+  public InvokeVirtualRange asInvokeVirtualRange() {
+    return this;
+  }
+
+  @Override
   public void registerUse(UseRegistry registry) {
     registry.registerInvokeVirtual(getMethod());
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 31e239b..5f87490 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.InvokeVirtualRange;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
@@ -334,6 +335,12 @@
                 invoke.A, methodToInvoke, invoke.C, invoke.D, invoke.E, invoke.F, invoke.G);
         newInvoke.setOffset(invoke.getOffset());
         newInstructions[i] = newInvoke;
+      } else if (instruction.isInvokeVirtualRange()) {
+        InvokeVirtualRange invoke = instruction.asInvokeVirtualRange();
+        InvokeVirtualRange newInvoke =
+            new InvokeVirtualRange(invoke.CCCC, invoke.AA, methodToInvoke);
+        newInvoke.setOffset(invoke.getOffset());
+        newInstructions[i] = newInvoke;
       } else {
         newInstructions[i] = instruction;
       }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
index 16db7d8..410d418 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgeHoistingAccessibilityTest.java
@@ -13,7 +13,9 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses;
+import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.AWithRangedInvoke;
 import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.User;
+import com.android.tools.r8.bridgeremoval.hoisting.testclasses.BridgeHoistingAccessibilityTestClasses.UserWithRangedInvoke;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
@@ -45,6 +47,28 @@
                 .transform(),
             transformer(C.class)
                 .setBridge(C.class.getDeclaredMethod("bridgeC", Object.class))
+                .transform(),
+            transformer(BWithRangedInvoke.class)
+                .setBridge(
+                    BWithRangedInvoke.class.getDeclaredMethod(
+                        "bridgeB",
+                        Object.class,
+                        int.class,
+                        int.class,
+                        int.class,
+                        int.class,
+                        int.class))
+                .transform(),
+            transformer(CWithRangedInvoke.class)
+                .setBridge(
+                    CWithRangedInvoke.class.getDeclaredMethod(
+                        "bridgeC",
+                        Object.class,
+                        int.class,
+                        int.class,
+                        int.class,
+                        int.class,
+                        int.class))
                 .transform())
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
@@ -54,7 +78,7 @@
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello world!");
+        .assertSuccessWithOutputLines("Hello world!", "Hello 12345 world! 12345");
   }
 
   private void inspect(CodeInspector inspector) {
@@ -69,6 +93,11 @@
 
     ClassSubject cClassSubject = inspector.clazz(C.class);
     assertThat(cClassSubject, isPresent());
+
+    ClassSubject axClassSubject = inspector.clazz(AWithRangedInvoke.class);
+    assertThat(axClassSubject, isPresent());
+    assertThat(axClassSubject.uniqueMethodWithName("m"), isPresent());
+    assertThat(axClassSubject.uniqueMethodWithName("bridgeC"), isPresent());
   }
 
   static class TestClass {
@@ -77,6 +106,10 @@
       C instance = new C();
       System.out.print(instance.bridgeB("Hello"));
       System.out.println(User.invokeBridgeC(instance));
+
+      CWithRangedInvoke instanceWithRangedInvoke = new CWithRangedInvoke();
+      System.out.print(instanceWithRangedInvoke.bridgeB("Hello ", 1, 2, 3, 4, 5));
+      System.out.println(UserWithRangedInvoke.invokeBridgeC(instanceWithRangedInvoke));
     }
   }
 
@@ -102,4 +135,27 @@
       return (String) m((String) o);
     }
   }
+
+  @NeverMerge
+  static class BWithRangedInvoke extends AWithRangedInvoke {
+
+    // This bridge cannot be hoisted to A, since it would then become inaccessible to the call site
+    // in TestClass.main().
+    @NeverInline
+    /*bridge*/ String bridgeB(Object o, int a, int b, int c, int d, int e) {
+      return (String) m((String) o, a, b, c, d, e);
+    }
+  }
+
+  @NeverClassInline
+  public static class CWithRangedInvoke extends BWithRangedInvoke {
+
+    // This bridge is invoked from another package. However, this does not prevent us from hoisting
+    // the bridge to B, although B is not public, since users from outside this package can still
+    // access bridgeC() via class C. From B, the bridge can be hoisted again to A.
+    @NeverInline
+    public /*bridge*/ String bridgeC(Object o, int a, int b, int c, int d, int e) {
+      return (String) m((String) o, a, b, c, d, e);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
index bc91e00..55daf92 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/testclasses/BridgeHoistingAccessibilityTestClasses.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest;
+import com.android.tools.r8.bridgeremoval.hoisting.BridgeHoistingAccessibilityTest.CWithRangedInvoke;
 
 public class BridgeHoistingAccessibilityTestClasses {
 
@@ -26,4 +27,21 @@
       return instance.bridgeC(" world!");
     }
   }
+
+  @NeverMerge
+  public static class AWithRangedInvoke {
+
+    @NeverInline
+    public Object m(String arg, int a, int b, int c, int d, int e) {
+      return System.currentTimeMillis() > 0 ? arg + a + b + c + d + e : null;
+    }
+  }
+
+  public static class UserWithRangedInvoke {
+
+    @NeverInline
+    public static String invokeBridgeC(CWithRangedInvoke instance) {
+      return instance.bridgeC(" world! ", 1, 2, 3, 4, 5);
+    }
+  }
 }