Merge commit '26c1e34b046bd1e27d04a9fa7829e6f5be09abc7' into dev-release

Change-Id: I05a95232b173abb343230b9f855c707ab8ffeac4
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
index 5b9e401..362a9e0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.InstanceInitializerMerger.Builder;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
@@ -25,12 +26,12 @@
 public class InstanceInitializerMergerCollection {
 
   private final List<InstanceInitializerMerger> instanceInitializerMergers;
-  private final Map<InstanceInitializerDescription, InstanceInitializerMerger>
+  private final Map<InstanceInitializerDescription, List<InstanceInitializerMerger>>
       equivalentInstanceInitializerMergers;
 
   private InstanceInitializerMergerCollection(
       List<InstanceInitializerMerger> instanceInitializerMergers,
-      Map<InstanceInitializerDescription, InstanceInitializerMerger>
+      Map<InstanceInitializerDescription, List<InstanceInitializerMerger>>
           equivalentInstanceInitializerMergers) {
     this.instanceInitializerMergers = instanceInitializerMergers;
     this.equivalentInstanceInitializerMergers = equivalentInstanceInitializerMergers;
@@ -75,7 +76,7 @@
                   }
                 }));
 
-    Map<InstanceInitializerDescription, InstanceInitializerMerger>
+    Map<InstanceInitializerDescription, List<InstanceInitializerMerger>>
         equivalentInstanceInitializerMergers = new LinkedHashMap<>();
     buildersByDescription.forEach(
         (description, builder) -> {
@@ -88,7 +89,9 @@
               buildersWithoutDescription.addAll(
                   instanceInitializerMerger.getInstanceInitializers());
             } else {
-              equivalentInstanceInitializerMergers.put(description, instanceInitializerMerger);
+              equivalentInstanceInitializerMergers
+                  .computeIfAbsent(description, ignoreKey(ArrayList::new))
+                  .add(instanceInitializerMerger);
             }
           }
         });
@@ -127,7 +130,7 @@
 
   public void forEach(Consumer<InstanceInitializerMerger> consumer) {
     instanceInitializerMergers.forEach(consumer);
-    equivalentInstanceInitializerMergers.values().forEach(consumer);
+    IterableUtils.flatten(equivalentInstanceInitializerMergers.values()).forEach(consumer);
   }
 
   public void setObsolete() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
index c8455fc..c425cb7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
@@ -27,7 +27,7 @@
 
 public class SplitBranch extends CodeRewriterPass<AppInfo> {
 
-  private static final boolean ALLOW_PARTIAL_REWRITE = true;
+  private static final boolean ALLOW_PARTIAL_REWRITE = false;
 
   public SplitBranch(AppView<?> appView) {
     super(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index e204f8e..6812690 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -24,7 +24,6 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
@@ -34,14 +33,10 @@
 import com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring.DesugarInvoke;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
-import org.objectweb.asm.Opcodes;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context of lambda instantiation
@@ -64,16 +59,12 @@
   public static final String JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
   public static final String R8_LAMBDA_ACCESSOR_METHOD_PREFIX = "$r8$lambda$";
 
-  private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
-
   final AppView<?> appView;
   final LambdaInstructionDesugaring desugaring;
   public final DexType type;
   public LambdaDescriptor descriptor;
   public final DexMethod constructor;
-  final DexMethod classConstructor;
   private final DexMethod factoryMethod;
-  public final DexField lambdaField;
   public final Target target;
 
   // Considered final but is set after due to circularity in allocation.
@@ -101,15 +92,6 @@
 
     this.target = createTarget(accessedFrom);
 
-    boolean statelessSingleton = isStatelessSingleton();
-    this.classConstructor =
-        statelessSingleton
-            ? factory.createMethod(type, constructorProto, factory.classConstructorMethodName)
-            : null;
-    this.lambdaField =
-        statelessSingleton
-            ? factory.createField(type, type, factory.lambdaInstanceFieldName)
-            : null;
     this.factoryMethod =
         useFactoryMethodForConstruction
                 || appView.options().testing.alwaysGenerateLambdaFactoryMethods
@@ -147,7 +129,6 @@
   private void synthesizeLambdaClass(
       SyntheticProgramClassBuilder builder, DesugarInvoke desugarInvoke) {
     builder.setInterfaces(descriptor.interfaces);
-    synthesizeStaticFields(builder);
     synthesizeInstanceFields(builder);
     synthesizeDirectMethods(builder);
     synthesizeVirtualMethods(builder, desugarInvoke);
@@ -162,10 +143,6 @@
             appView.dexItemFactory().createString("f$" + index));
   }
 
-  public final boolean isStatelessSingleton() {
-    return appView.options().createSingletonsForStatelessLambdas && descriptor.isStateless();
-  }
-
   public boolean hasFactoryMethod() {
     return factoryMethod != null;
   }
@@ -217,15 +194,12 @@
 
   // Synthesize direct methods.
   private void synthesizeDirectMethods(SyntheticProgramClassBuilder builder) {
-    boolean statelessSingleton = isStatelessSingleton();
-    List<DexEncodedMethod> methods = new ArrayList<>(statelessSingleton ? 2 : 1);
+    List<DexEncodedMethod> methods = new ArrayList<>(hasFactoryMethod() ? 2 : 1);
 
     // Constructor.
     MethodAccessFlags accessFlags =
         MethodAccessFlags.fromSharedAccessFlags(
-            (statelessSingleton ? Constants.ACC_PRIVATE : Constants.ACC_PUBLIC)
-                | Constants.ACC_SYNTHETIC,
-            true);
+            Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true);
     methods.add(
         DexEncodedMethod.syntheticBuilder()
             .setMethod(constructor)
@@ -236,19 +210,6 @@
             .build());
 
     // Class constructor for stateless lambda classes.
-    if (statelessSingleton) {
-      methods.add(
-          DexEncodedMethod.syntheticBuilder()
-              .setMethod(classConstructor)
-              .setAccessFlags(
-                  MethodAccessFlags.fromSharedAccessFlags(
-                      Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true))
-              .setCode(LambdaClassConstructorSourceCode.build(this))
-              // The api level is computed when tracing.
-              .disableAndroidApiLevelCheck()
-              .build());
-      feedback.classInitializerMayBePostponed(methods.get(1));
-    }
     if (hasFactoryMethod()) {
       methods.add(
           DexEncodedMethod.syntheticBuilder()
@@ -283,43 +244,6 @@
     builder.setInstanceFields(fields);
   }
 
-  // Synthesize static fields to represent singleton instance.
-  private void synthesizeStaticFields(SyntheticProgramClassBuilder builder) {
-    if (isStatelessSingleton()) {
-      // Create instance field for stateless lambda.
-      assert this.lambdaField != null;
-      builder.setStaticFields(
-          Collections.singletonList(
-              DexEncodedField.syntheticBuilder()
-                  .setField(this.lambdaField)
-                  .setAccessFlags(
-                      FieldAccessFlags.fromSharedAccessFlags(
-                          Constants.ACC_PUBLIC
-                              | Constants.ACC_FINAL
-                              | Constants.ACC_SYNTHETIC
-                              | Constants.ACC_STATIC))
-                  .setStaticValue(DexValueNull.NULL)
-                  // The api level is computed when tracing.
-                  .disableAndroidApiLevelCheck()
-                  .build()));
-    }
-  }
-
-  public static int getAsmOpcodeForInvokeType(MethodHandleType type) {
-    switch (type) {
-      case INVOKE_INTERFACE:
-        return Opcodes.INVOKEINTERFACE;
-      case INVOKE_STATIC:
-        return Opcodes.INVOKESTATIC;
-      case INVOKE_DIRECT:
-        return Opcodes.INVOKESPECIAL;
-      case INVOKE_INSTANCE:
-        return Opcodes.INVOKEVIRTUAL;
-      default:
-        throw new Unreachable("Unexpected method handle type: " + type);
-    }
-  }
-
   // Creates a delegation target for this particular lambda class. Note that we
   // should always be able to create targets for the lambdas we support.
   private Target createTarget(ProgramMethod accessedFrom) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
deleted file mode 100644
index ddcb68d..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2017, 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.desugar;
-
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfNew;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
-import com.android.tools.r8.cf.code.CfStaticFieldWrite;
-import com.android.tools.r8.graph.CfCode;
-import com.google.common.collect.ImmutableList;
-import org.objectweb.asm.Opcodes;
-
-// Source code representing synthesized lambda class constructor.
-// Used for stateless lambdas to instantiate singleton instance.
-final class LambdaClassConstructorSourceCode {
-
-  public static CfCode build(LambdaClass lambda) {
-    int maxStack = 2;
-    int maxLocals = 0;
-    return new CfCode(
-        lambda.type,
-        maxStack,
-        maxLocals,
-        ImmutableList.of(
-            new CfNew(lambda.type),
-            new CfStackInstruction(Opcode.Dup),
-            new CfInvoke(Opcodes.INVOKESPECIAL, lambda.constructor, false),
-            new CfStaticFieldWrite(lambda.lambdaField, lambda.lambdaField),
-            new CfReturnVoid()));
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index b49e21a..d92a51a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
-import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
@@ -144,11 +143,6 @@
           new CfInvoke(Opcodes.INVOKESTATIC, lambdaClass.getFactoryMethod(), false));
     }
 
-    if (lambdaClass.isStatelessSingleton()) {
-      return ImmutableList.of(
-          new CfStaticFieldRead(lambdaClass.lambdaField, lambdaClass.lambdaField));
-    }
-
     DexTypeList captureTypes = lambdaClass.descriptor.captures;
     Deque<CfInstruction> replacement = new ArrayDeque<>(3 + captureTypes.size() * 2);
     replacement.add(new CfNew(lambdaClass.getType()));
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 2f02791..1a15081 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -170,7 +170,9 @@
   }
 
   private boolean isInvokeSuperToAbstractMethod(DexClassAndMethod method, InvokeType invokeType) {
-    return method.getAccessFlags().isAbstract() && invokeType.isSuper();
+    return options.canHaveSuperInvokeToAbstractMethodBug()
+        && method.getAccessFlags().isAbstract()
+        && invokeType.isSuper();
   }
 
   public static DexField validMemberRebindingTargetFor(
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
index 02b5231..a6e1da7 100644
--- a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
@@ -68,8 +68,8 @@
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
-    ProgramMethodSet monomorphicVirtualMethods =
-        computeMonomorphicVirtualRootMethods(executorService);
+    // TODO(b/335584013): Re-enable monomorphic method analysis.
+    ProgramMethodSet monomorphicVirtualMethods = ProgramMethodSet.empty();
     ProgramMethodMap<ProgramMethod> singleCallerMethods =
         new SingleCallerScanner(appView, monomorphicVirtualMethods)
             .getSingleCallerMethods(executorService);
@@ -87,6 +87,7 @@
   // deal with (rooted) virtual methods that do not override abstract/interface methods. In order to
   // also deal with virtual methods that override abstract/interface methods we would need to record
   // calls to the abstract/interface methods as calls to the non-abstract virtual method.
+  @SuppressWarnings("UnusedMethod")
   private ProgramMethodSet computeMonomorphicVirtualRootMethods(ExecutorService executorService)
       throws ExecutionException {
     ImmediateProgramSubtypingInfo immediateSubtypingInfo =
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 707d77a..065408c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -488,11 +488,6 @@
   // Boolean value indicating that byte code pass through may be enabled.
   public boolean enableCfByteCodePassThrough = false;
 
-  // Flag to control the representation of stateless lambdas.
-  // See b/222081665 for context.
-  public boolean createSingletonsForStatelessLambdas =
-      System.getProperty("com.android.tools.r8.createSingletonsForStatelessLambdas") != null;
-
   // TODO(b/293591931): Remove this flag when records are stable in Platform
   //  Flag to allow record annotations in DEX. See b/231930852 for context.
   private final boolean emitRecordAnnotationsInDex =
@@ -2491,7 +2486,7 @@
    * <p>Note that if the compilation is not desugaring to a min-api or targeting DEX at a min-api,
    * then the bug is assumed to be present as the CF output could be further compiled to any target.
    */
-  private boolean canHaveBugPresentUntil(AndroidApiLevel level) {
+  private boolean canHaveBugPresentUntilExclusive(AndroidApiLevel level) {
     if (desugarState.isOn() || isGeneratingDex()) {
       return level == null || !hasMinApi(level);
     }
@@ -2783,7 +2778,7 @@
   //
   // See b/69364976 and b/77996377.
   public boolean canHaveBoundsCheckEliminationBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.M);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.M);
   }
 
   // MediaTek JIT compilers for KitKat phones did not implement the not
@@ -2799,7 +2794,7 @@
   // assumed to not change. If the receiver register is reused for something else the verifier
   // will fail and the code will not run.
   public boolean canHaveThisTypeVerifierBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.M);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.M);
   }
 
   // Art crashes if we do dead reference elimination of the receiver in release mode and Art
@@ -2808,13 +2803,13 @@
   //
   // See b/116683601 and b/116837585.
   public boolean canHaveThisJitCodeDebuggingBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.Q);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.Q);
   }
 
   // The dalvik jit had a bug where the long operations add, sub, or, xor and and would write
   // the first part of the result long before reading the second part of the input longs.
   public boolean canHaveOverlappingLongRegisterBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.L);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.L);
   }
 
   // Some dalvik versions found in the wild perform invalid JIT compilation of cmp-long
@@ -2847,7 +2842,7 @@
   //
   // See b/75408029.
   public boolean canHaveCmpLongBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.L);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.L);
   }
 
   // Some Lollipop VMs crash if there is a const instruction between a cmp and an if instruction.
@@ -2875,7 +2870,7 @@
   //
   // See b/115552239.
   public boolean canHaveCmpIfFloatBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.M);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.M);
   }
 
   // Some Lollipop VMs incorrectly optimize code with mul2addr instructions. In particular,
@@ -2897,7 +2892,7 @@
   //
   // This issue has only been observed on a Verizon Ellipsis 8 tablet. See b/76115465.
   public boolean canHaveMul2AddrBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.M);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.M);
   }
 
   // Some Marshmallow VMs create an incorrect doubly-linked list of instructions. When the VM
@@ -2906,7 +2901,7 @@
   //
   // See b/77842465.
   public boolean canHaveDex2OatLinkedListBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.N);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.N);
   }
 
   // dex2oat on Marshmallow VMs does aggressive inlining which can eat up all the memory on
@@ -2914,7 +2909,7 @@
   //
   // See b/111960171
   public boolean canHaveDex2OatInliningIssue() {
-    return canHaveBugPresentUntil(AndroidApiLevel.N);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.N);
   }
 
   // Art 7.0.0 and later Art JIT may perform an invalid optimization if a string new-instance does
@@ -2922,7 +2917,7 @@
   //
   // See b/78493232 and b/80118070.
   public boolean canHaveArtStringNewInitBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.Q);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.Q);
   }
 
   // Dalvik tracing JIT may perform invalid optimizations when int/float values are converted to
@@ -2930,7 +2925,7 @@
   //
   // See b/77496850.
   public boolean canHaveNumberConversionRegisterAllocationBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.L);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.L);
   }
 
   // Some Lollipop mediatek VMs have a peculiar bug where the inliner crashes if there is a
@@ -2943,7 +2938,7 @@
   //
   // See b/68378480.
   public boolean canHaveForwardingInitInliningBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.M);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.M);
   }
 
   // Some Lollipop x86_64 VMs have a bug causing a segfault if an exception handler directly targets
@@ -2956,7 +2951,7 @@
   // See b/111337896.
   public boolean canHaveExceptionTargetingLoopHeaderBug() {
     assert isGeneratingDex();
-    return !debug && canHaveBugPresentUntil(AndroidApiLevel.M);
+    return !debug && canHaveBugPresentUntilExclusive(AndroidApiLevel.M);
   }
 
   // The Dalvik tracing JIT can trace past the end of the instruction stream and end up
@@ -2971,7 +2966,7 @@
   // We also could not insert any dead code (e.g. a return) because that would make mediatek
   // dominator calculations on 7.0.0 crash. See b/128926846.
   public boolean canHaveTracingPastInstructionsStreamBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.L);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.L);
   }
 
   // The art verifier incorrectly propagates type information for the following pattern:
@@ -2998,7 +2993,7 @@
   //
   // Fixed in Android Q, see b/120985556.
   public boolean canHaveArtInstanceOfVerifierBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.Q);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.Q);
   }
 
   // Some Art Lollipop version do not deal correctly with long-to-int conversions.
@@ -3021,7 +3016,7 @@
   public boolean canHaveLongToIntBug() {
     // We have only seen this happening on Lollipop arm64 backends. We have tested on
     // Marshmallow and Nougat arm64 devices and they do not have the bug.
-    return canHaveBugPresentUntil(AndroidApiLevel.M);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.M);
   }
 
   // The Art VM for Android N through P has a bug in the JIT that means that if the same
@@ -3034,7 +3029,7 @@
   //
   // See b/120164595.
   public boolean canHaveExceptionTypeBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.Q);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.Q);
   }
 
   // Art 4.0.4 fails with a verification error when a null-literal is being passed directly to an
@@ -3042,7 +3037,7 @@
   // elimination of check-cast instructions where the value being cast is the constant null.
   // See b/123269162.
   public boolean canHaveArtCheckCastVerifierBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.J);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.J);
   }
 
   // The verifier will merge A[] and B[] to Object[], even when both A and B implement an interface
@@ -3066,7 +3061,7 @@
   //
   // See b/131349148
   public boolean canHaveDalvikCatchHandlerVerificationBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.L);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.L);
   }
 
   // Having an invoke instruction that targets an abstract method on a non-abstract class will fail
@@ -3074,7 +3069,7 @@
   //
   // See b/132953944.
   public boolean canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.L);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.L);
   }
 
   // On dalvik we see issues when using an int value in places where a boolean, byte, char, or short
@@ -3088,14 +3083,14 @@
   //
   // See also b/134304597 and b/124152497.
   public boolean canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.L);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.L);
   }
 
   // The standard library prior to API 19 did not contain a ZipFile that implemented Closable.
   //
   // See b/177532008.
   public boolean canHaveZipFileWithMissingCloseableBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.K);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.K);
   }
 
   // Some versions of Dalvik had a bug where a switch with a MAX_INT key would still go to
@@ -3103,7 +3098,7 @@
   //
   // See b/177790310.
   public boolean canHaveSwitchMaxIntBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.K);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.K);
   }
 
   // On Dalvik the methods Integer.parseInt and Long.parseLong does not support strings with a '+'
@@ -3119,7 +3114,15 @@
   //
   // See b/215573892.
   public boolean canHaveSuperInvokeBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.N);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.N);
+  }
+
+  // Android 5.0 does not correctly handle invoke-super instructions that resolve to an abstract
+  // method.
+  //
+  // See b/213581039.
+  public boolean canHaveSuperInvokeToAbstractMethodBug() {
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.L_MR1);
   }
 
   // Some Dalvik and Art MVs does not support interface invokes to Object
@@ -3141,13 +3144,13 @@
   //
   // See b/326661821 (details in b/326661821#comment33).
   public boolean canHaveInvokeInterfaceToObjectMethodBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.U);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.U);
   }
 
   // Until we fully drop support for API levels < 16, we have to emit an empty annotation set to
   // work around a DALVIK bug. See b/36951668.
   public boolean canHaveDalvikEmptyAnnotationSetBug() {
-    return canHaveBugPresentUntil(AndroidApiLevel.J_MR1);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.J_MR1);
   }
 
   public boolean canHaveNonReboundConstructorInvoke() {
@@ -3160,13 +3163,13 @@
   // b/238399429 Some art 6 vms have issues with multiple monitors in the same method
   // Don't inline code with monitors into methods that already have monitors.
   public boolean canHaveIssueWithInlinedMonitors() {
-    return canHaveBugPresentUntil(AndroidApiLevel.N);
+    return canHaveBugPresentUntilExclusive(AndroidApiLevel.N);
   }
 
   // b/272725341. ART 11 and 12 re-introduced hard verification errors when unable to compute
   // subtype relationship when no other verification issues exists in code.
   public boolean canHaveVerifyErrorForUnknownUnusedReturnValue() {
-    return isGeneratingDex() && canHaveBugPresentUntil(AndroidApiLevel.T);
+    return isGeneratingDex() && canHaveBugPresentUntilExclusive(AndroidApiLevel.T);
   }
 
   public boolean canInitNewInstanceUsingSuperclassConstructor() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/IllegalSingleCallerInliningOfSiblingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/IllegalSingleCallerInliningOfSiblingTest.java
new file mode 100644
index 0000000..cd42876
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/IllegalSingleCallerInliningOfSiblingTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2024, 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.inliner;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IllegalSingleCallerInliningOfSiblingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.SINGLE_CALLER))
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "A");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      I i = System.currentTimeMillis() > 0 ? new B() : new C();
+      i.m();
+
+      new A().m();
+    }
+  }
+
+  interface I {
+
+    void m();
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  @NoVerticalClassMerging
+  static class A {
+
+    public void m() {
+      System.out.println("A");
+    }
+  }
+
+  static class B extends A implements I {}
+
+  @NoHorizontalClassMerging
+  static class C implements I {
+
+    @Override
+    public void m() {
+      System.out.println("C");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleCallerInlineOutlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleCallerInlineOutlineTest.java
new file mode 100644
index 0000000..e87b010
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleCallerInlineOutlineTest.java
@@ -0,0 +1,145 @@
+// Copyright (c) 2024, 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.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SingleCallerInlineOutlineTest extends TestBase {
+
+  @Parameter(0)
+  public boolean allowInliningOfOutlines;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, allow inlining: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.outline.threshold = 2;
+              options.outline.minSize = 2;
+              options.getTestingOptions().allowInliningOfOutlines = allowInliningOfOutlines;
+            })
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject printerClassSubject = inspector.clazz(Printer.class);
+              assertThat(printerClassSubject, isPresent());
+
+              MethodSubject helloMethodSubject =
+                  printerClassSubject.uniqueMethodWithOriginalName("hello");
+              assertThat(helloMethodSubject, isPresent());
+
+              MethodSubject worldMethodSubject =
+                  printerClassSubject.uniqueMethodWithOriginalName("world");
+              assertThat(worldMethodSubject, isPresent());
+
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              MethodSubject outlineCandidateMethodSubject =
+                  mainClassSubject.uniqueMethodWithOriginalName("outlineCandidate");
+              assertThat(outlineCandidateMethodSubject, isPresent());
+
+              ClassSubject outlineClassSubject =
+                  inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(Main.class, 0));
+              // Note that the outline class is present even when `allowInliningOfOutlines`
+              // is true, since we do not run another round of tree shaking after the single
+              // caller inliner pass.
+              assertThat(outlineClassSubject, isPresent());
+
+              if (allowInliningOfOutlines) {
+                assertThat(
+                    outlineCandidateMethodSubject,
+                    allOf(invokesMethod(helloMethodSubject), invokesMethod(worldMethodSubject)));
+              } else {
+                assertThat(
+                    outlineCandidateMethodSubject,
+                    invokesMethod(outlineClassSubject.uniqueMethod()));
+              }
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  static class Main {
+
+    static boolean alwaysFalse;
+
+    public static void main(String[] args) {
+      outlineCandidate();
+
+      // This will be pruned in the primary optimization pass. At this point R8 still does not know
+      // that the `alwaysFalse` field is false.
+      if (alwaysFalse()) {
+        alwaysFalse = true;
+      }
+
+      // This will be pruned in the second optimization pass after the outliner has run. After the
+      // second round of tree shaking, outlineCandidate2() will be removed, and the synthetic
+      // outline will have a single caller.
+      if (alwaysFalse) {
+        outlineCandidate2();
+      }
+    }
+
+    static boolean alwaysFalse() {
+      return false;
+    }
+
+    @NeverInline
+    static void outlineCandidate() {
+      Printer.hello();
+      Printer.world();
+    }
+
+    @NeverInline
+    static void outlineCandidate2() {
+      Printer.hello();
+      Printer.world();
+    }
+  }
+
+  static class Printer {
+
+    @NeverInline
+    static void hello() {
+      System.out.print("Hello");
+    }
+
+    @NeverInline
+    static void world() {
+      System.out.println(", world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
index c7e6cce..5d02b1b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -76,7 +76,11 @@
               MethodSubject isSupported = main.uniqueMethodWithOriginalName("isSupported");
               assertThat(isSupported, isPresent());
               assertEquals(
-                  kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72) ? 1 : 0,
+                  kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72)
+                          && (parameters.isCfRuntime()
+                              || parameters.getApiLevel().equals(AndroidApiLevel.B))
+                      ? 1
+                      : 0,
                   countCall(isSupported, "checkParameterIsNotNull"));
 
               // In general cases, null check won't be invoked only once or twice, hence no subtle
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
index eb4d99c..bf0e5cf 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -14,10 +15,11 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.apimodel.ApiModelingTestHelper;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.codeinspector.CodeMatchers;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,6 +61,14 @@
         .apply(
             setMockApiLevelForMethod(
                 LibrarySub.class.getDeclaredMethod("getSystemService"), AndroidApiLevel.B))
+        .apply(
+            setMockApiLevelForMethod(
+                Reference.method(
+                    Reference.classFromClass(LibrarySubSub.class),
+                    "getSystemService",
+                    Collections.emptyList(),
+                    null),
+                AndroidApiLevel.B))
         .compile()
         .addRunClasspathClasses(libraryClasses)
         .inspect(
@@ -66,13 +76,21 @@
               MethodSubject getSystemService =
                   inspector.clazz(Main.class).uniqueMethodWithOriginalName("getSystemService");
               assertThat(getSystemService, isPresent());
-              // We should never rebind this call to LibraryBase::getSystemService since this can
-              // cause errors when verifying the code on a device where the image has a definition
-              // but it is abstract. For more information, see b/213581039.
+              // We should only rebind this call to LibraryBase::getSystemService when compiling to
+              // Android 5.1 or above since this can cause errors when verifying the code on a
+              // device where the image has a definition but it is abstract. For more information,
+              // see b/213581039.
+              //
+              // Due to b/215573892 we also select LibrarySubSub rather than LibraryBase when
+              // compiling to Android 5.1.
+              Class<?> expectedRebindingTarget =
+                  parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
+                      ? LibrarySubSub.class
+                      : LibraryBase.class;
               assertThat(
                   getSystemService,
-                  CodeMatchers.invokesMethodWithHolderAndName(
-                      typeName(LibrarySubSub.class), "getSystemService"));
+                  invokesMethodWithHolderAndName(
+                      typeName(expectedRebindingTarget), "getSystemService"));
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("LibrarySub::getSystemService");