Maintain canonical interface bridges.

Bug: b/228791247
Change-Id: I81e2d06133c15a57c0547c66313cc7fdddafe1cc
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index b925309..6a72328 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -85,6 +85,7 @@
 import java.util.Set;
 import java.util.Stack;
 import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -128,6 +129,7 @@
     private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
     private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
     private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
+    private final Map<DexMethod, ProgramMethod> keptMethodBridges = new ConcurrentHashMap<>();
     private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
         new ConcurrentLinkedQueue<>();
     private final InternalOptions options;
@@ -627,16 +629,24 @@
           // TODO(b/143643942): For fullmode, this check should probably be removed.
           return;
         }
-        ProgramMethod resolutionMethod =
-            new ProgramMethod(
-                resolutionResult.getResolvedHolder().asProgramClass(),
-                resolutionResult.getResolvedMethod());
-        ProgramMethod methodToKeep =
-            canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())
-                ? new ProgramMethod(
-                    originalClazz,
-                    resolutionMethod.getDefinition().toForwardingMethod(originalClazz, appView))
-                : resolutionMethod;
+        ProgramMethod resolutionMethod = resolutionResult.getResolvedProgramMethod();
+        ProgramMethod methodToKeep;
+        if (canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition())) {
+          DexMethod methodToKeepReference =
+              resolutionMethod.getReference().withHolder(originalClazz, appView.dexItemFactory());
+          methodToKeep =
+              keptMethodBridges.computeIfAbsent(
+                  methodToKeepReference,
+                  k ->
+                      new ProgramMethod(
+                          originalClazz,
+                          resolutionMethod
+                              .getDefinition()
+                              .toForwardingMethod(originalClazz, appView)));
+          assert methodToKeepReference.equals(methodToKeep.getReference());
+        } else {
+          methodToKeep = resolutionMethod;
+        }
 
         delayedRootSetActionItems.add(
             new InterfaceMethodSyntheticBridgeAction(
diff --git a/src/test/java/com/android/tools/r8/shaking/interfacebridge/MultipleRulesRegression228791247Test.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleRulesRegression228791247Test.java
similarity index 70%
rename from src/test/java/com/android/tools/r8/shaking/interfacebridge/MultipleRulesRegression228791247Test.java
rename to src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleRulesRegression228791247Test.java
index d3d9804..c61fa16 100644
--- a/src/test/java/com/android/tools/r8/shaking/interfacebridge/MultipleRulesRegression228791247Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/MultipleRulesRegression228791247Test.java
@@ -1,12 +1,10 @@
 // Copyright (c) 2022, 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.shaking.interfacebridge;
+package com.android.tools.r8.shaking.methods.interfaces;
 
 import static com.android.tools.r8.references.Reference.classFromClass;
-import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -44,22 +42,13 @@
     // Regression adds two rules causes the forwarding method to be generated twice.
     String rule1 = "-keep class " + classFromClass(J.class).getTypeName() + "{ void *oo(); }";
     String rule2 = "-keep class " + classFromClass(J.class).getTypeName() + "{ void fo*(); }";
-    try {
-      testForR8(parameters.getBackend())
-          .addProgramClasses(I.class, J.class, A.class, TestClass.class)
-          .addKeepMainRule(TestClass.class)
-          .addKeepRules(rule1, rule2)
-          .setMinApi(parameters.getApiLevel())
-          .run(parameters.getRuntime(), TestClass.class)
-          .assertSuccessWithOutput(EXPECTED);
-      assertTrue(
-          parameters.isCfRuntime()
-              || parameters
-                  .getApiLevel()
-                  .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport()));
-    } catch (CompilationFailedException e) {
-      assertTrue(parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport()));
-    }
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, J.class, A.class, TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(rule1, rule2)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   public interface I {