Compute bridge info for declared bridges using invoke-virtual

Bug: 153147967
Change-Id: I184fa31a3e0ccb06a0bfd70684657c34a2450df9
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 20337fd..03f1415 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -257,6 +257,10 @@
     assert parameterAnnotationsList != null;
   }
 
+  public DexType returnType() {
+    return method.proto.returnType;
+  }
+
   public ParameterAnnotationsList liveParameterAnnotations(AppView<AppInfoWithLiveness> appView) {
     return parameterAnnotationsList.keepIf(
         annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
@@ -299,6 +303,10 @@
     return accessFlags.isAbstract();
   }
 
+  public boolean isBridge() {
+    return accessFlags.isBridge();
+  }
+
   public boolean isFinal() {
     return accessFlags.isFinal();
   }
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 57c923c..ca2cdd4 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
@@ -440,6 +440,10 @@
     return uniqueUsers = ImmutableSet.copyOf(users);
   }
 
+  public boolean hasSingleUniqueUser() {
+    return uniqueUsers().size() == 1;
+  }
+
   public Instruction singleUniqueUser() {
     assert ImmutableSet.copyOf(users).size() == 1;
     return users.getFirst();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 4889b2e..d1ddce9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -85,6 +85,7 @@
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
+import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
@@ -126,6 +127,7 @@
       DynamicTypeOptimization dynamicTypeOptimization,
       InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos,
       Timing timing) {
+    identifyBridgeInfo(method, code, feedback, timing);
     identifyClassInlinerEligibility(method, code, feedback, timing);
     identifyParameterUsages(method, code, feedback, timing);
     identifyReturnsArgument(method, code, feedback, timing);
@@ -142,6 +144,13 @@
     computeNonNullParamOnNormalExits(feedback, code, timing);
   }
 
+  private void identifyBridgeInfo(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    timing.begin("Identify bridge info");
+    feedback.setBridgeInfo(method, BridgeAnalyzer.analyzeMethod(method, code));
+    timing.end();
+  }
+
   private void identifyClassInlinerEligibility(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
     timing.begin("Identify class inliner eligibility");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
new file mode 100644
index 0000000..8f28fd5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2020, 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.info.bridge;
+
+import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BooleanUtils;
+
+public class BridgeAnalyzer {
+
+  /** Returns a {@link BridgeInfo} object describing this method if it is recognized as a bridge. */
+  public static BridgeInfo analyzeMethod(DexEncodedMethod method, IRCode code) {
+    // TODO(b/154263783): Consider computing BridgeInfo also for non-declared bridges.
+    if (!method.isBridge() || code.blocks.size() > 1) {
+      return failure();
+    }
+
+    // Scan through the instructions one-by-one. We expect a sequence of Argument instructions,
+    // followed by a (possibly empty) sequence of CheckCast instructions, followed by a single
+    // InvokeMethod instruction, followed by an optional CheckCast instruction, followed by a Return
+    // instruction.
+    InvokeVirtual uniqueInvoke = null;
+    CheckCast uniqueReturnCast = null;
+    for (Instruction instruction : code.entryBlock().getInstructions()) {
+      switch (instruction.opcode()) {
+        case ARGUMENT:
+          break;
+
+        case ASSUME:
+          break;
+
+        case CHECK_CAST:
+          {
+            CheckCast checkCast = instruction.asCheckCast();
+            if (!analyzeCheckCast(method, checkCast, uniqueInvoke)) {
+              return failure();
+            }
+            // If we have moved past the single invoke instruction, then record that this cast is
+            // the cast instruction for the result value.
+            if (uniqueInvoke != null) {
+              if (uniqueReturnCast != null) {
+                return failure();
+              }
+              uniqueReturnCast = checkCast;
+            }
+            break;
+          }
+
+        case INVOKE_VIRTUAL:
+          {
+            if (uniqueInvoke != null) {
+              return failure();
+            }
+            InvokeVirtual invoke = instruction.asInvokeVirtual();
+            if (!analyzeInvokeVirtual(invoke)) {
+              return failure();
+            }
+            // Record that we have seen the single invoke instruction.
+            uniqueInvoke = invoke;
+            break;
+          }
+
+        case RETURN:
+          if (!analyzeReturn(instruction.asReturn(), uniqueInvoke, uniqueReturnCast)) {
+            return failure();
+          }
+          break;
+
+        default:
+          return failure();
+      }
+    }
+
+    assert uniqueInvoke != null;
+    return new VirtualBridgeInfo(uniqueInvoke.getInvokedMethod());
+  }
+
+  private static boolean analyzeCheckCast(
+      DexEncodedMethod method, CheckCast checkCast, InvokeMethod invoke) {
+    return invoke == null
+        ? analyzeCheckCastBeforeInvoke(checkCast)
+        : analyzeCheckCastAfterInvoke(method, checkCast, invoke);
+  }
+
+  private static boolean analyzeCheckCastBeforeInvoke(CheckCast checkCast) {
+    Value object = checkCast.object().getAliasedValue();
+    // It must be casting one of the arguments.
+    if (!object.isArgument()) {
+      return false;
+    }
+    int argumentIndex = object.definition.asArgument().getIndex();
+    Value castValue = checkCast.outValue();
+    // The out value cannot have any phi users since there is only one block.
+    assert !castValue.hasPhiUsers();
+    // It is not allowed to have any other users than the invoke instruction.
+    if (castValue.hasDebugUsers() || !castValue.hasSingleUniqueUser()) {
+      return false;
+    }
+    InvokeMethod invoke = castValue.singleUniqueUser().asInvokeMethod();
+    if (invoke == null) {
+      return false;
+    }
+    if (invoke.arguments().size() <= argumentIndex) {
+      return false;
+    }
+    int parameterIndex = argumentIndex - BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver());
+    // It is not allowed to cast the receiver.
+    if (parameterIndex == -1) {
+      return false;
+    }
+    // The type of the cast must match the type of the parameter.
+    if (checkCast.getType() != invoke.getInvokedMethod().proto.getParameter(parameterIndex)) {
+      return false;
+    }
+    // It must be forwarded at the same argument index.
+    Value argument = invoke.getArgument(argumentIndex);
+    if (argument != castValue) {
+      return false;
+    }
+    return true;
+  }
+
+  private static boolean analyzeCheckCastAfterInvoke(
+      DexEncodedMethod method, CheckCast checkCast, InvokeMethod invoke) {
+    Value returnValue = invoke.outValue();
+    Value uncastValue = checkCast.object().getAliasedValue();
+    Value castValue = checkCast.outValue();
+    // The out value cannot have any phi users since there is only one block.
+    assert !castValue.hasPhiUsers();
+    // It must cast the result to the return type of the enclosing method and return the cast value.
+    return uncastValue == returnValue
+        && checkCast.getType() == method.returnType()
+        && !castValue.hasDebugUsers()
+        && castValue.hasSingleUniqueUser()
+        && castValue.singleUniqueUser().isReturn();
+  }
+
+  private static boolean analyzeInvokeVirtual(InvokeVirtual invoke) {
+    // All of the forwarded arguments of the enclosing method must be in the same argument position.
+    for (int argumentIndex = 0; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+      Value argument = invoke.getArgument(argumentIndex);
+      if (argument.isArgument() && argumentIndex != argument.definition.asArgument().getIndex()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private static boolean analyzeReturn(Return ret, InvokeMethod invoke, CheckCast returnCast) {
+    // If we haven't seen an invoke this is not a bridge.
+    if (invoke == null) {
+      return false;
+    }
+    // It must not return a value, or the return value must be the result value of the invoke.
+    return ret.isReturnVoid()
+        || ret.returnValue() == (returnCast != null ? returnCast : invoke).outValue();
+  }
+
+  private static BridgeInfo failure() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
index a8a935e..2683d2f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
@@ -7,4 +7,13 @@
  * A piece of optimization info that is computed for bridge methods. The info stores details about
  * the behavior of the bridge.
  */
-public abstract class BridgeInfo {}
+public abstract class BridgeInfo {
+
+  public boolean isVirtualBridgeInfo() {
+    return false;
+  }
+
+  public VirtualBridgeInfo asVirtualBridgeInfo() {
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
new file mode 100644
index 0000000..a07abdc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2020, 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.info.bridge;
+
+import com.android.tools.r8.graph.DexMethod;
+
+/**
+ * Optimization info computed for bridge methods that use an invoke-virtual instruction.
+ *
+ * <p>If the method is {@code String A.m(Object)} and {@link #invokedMethod} is {@code Object
+ * B.m(String)}, then the bridge is implemented as:
+ *
+ * <pre>
+ *   java.lang.String A.m(java.lang.Object o) {
+ *     v0 <- Argument
+ *     v1 <- CheckCast v0, java.lang.String
+ *     v2 <- InvokeVirtual { v0, v1 }, java.lang.Object B.m(java.lang.String)
+ *     v3 <- CheckCast v2, java.lang.String
+ *     Return v3
+ *   }
+ * </pre>
+ *
+ * <p>This currently does not allow any permutation of the argument order, and it also does not
+ * allow constants to be passed as arguments.
+ */
+public class VirtualBridgeInfo extends BridgeInfo {
+
+  // The targeted method.
+  private final DexMethod invokedMethod;
+
+  public VirtualBridgeInfo(DexMethod invokedMethod) {
+    this.invokedMethod = invokedMethod;
+  }
+
+  public DexMethod getInvokedMethod() {
+    return invokedMethod;
+  }
+
+  @Override
+  public boolean isVirtualBridgeInfo() {
+    return true;
+  }
+
+  @Override
+  public VirtualBridgeInfo asVirtualBridgeInfo() {
+    return this;
+  }
+}