Merge commit 'ad39dcb28b565277e9600ad0909e8f86cb79e95d' into dev-release
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 6c30ad3..bec1872 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -144,4 +144,14 @@
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     return frame.popInitialized(appView, config, dexItemFactory.objectType).push(config, type);
   }
+
+  @Override
+  public boolean isCheckCast() {
+    return true;
+  }
+
+  @Override
+  public CfCheckCast asCheckCast() {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index b137991..f21fc0c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -412,4 +412,12 @@
 
   public abstract CfFrameState evaluate(
       CfFrameState frame, AppView<?> appView, CfAnalysisConfig config);
+
+  public boolean isCheckCast() {
+    return false;
+  }
+
+  public CfCheckCast asCheckCast() {
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 43e7099..535a891 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfMonitor;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfTryCatch;
@@ -534,6 +535,16 @@
   }
 
   @Override
+  public boolean hasMonitorInstructions() {
+    for (CfInstruction instruction : getInstructions()) {
+      if (instruction instanceof CfMonitor) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
   public IRCode buildIR(
       ProgramMethod method,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 1077fb9..d0417b4 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -117,6 +117,10 @@
     return false;
   }
 
+  public boolean hasMonitorInstructions() {
+    return false;
+  }
+
   public boolean isThrowExceptionCode() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 7e55f8e..09db76d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.dex.code.DexInstruction;
+import com.android.tools.r8.dex.code.DexMonitorEnter;
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.dex.code.DexSwitchPayload;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
@@ -377,6 +378,16 @@
   }
 
   @Override
+  public boolean hasMonitorInstructions() {
+    for (DexInstruction instruction : instructions) {
+      if (instruction instanceof DexMonitorEnter) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
   public Code asCode() {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 3ab128b..3abc2e4 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -229,6 +229,11 @@
   }
 
   @Override
+  public boolean hasMonitorInstructions() {
+    return asCfCode().hasMonitorInstructions();
+  }
+
+  @Override
   public int estimatedSizeForInlining() {
     return asCfCode().estimatedSizeForInlining();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index b997f78..b6095d1 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoVirtualMethodMerging;
 import com.android.tools.r8.horizontalclassmerging.policies.NoWeakerAccessPrivileges;
 import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.NotTwoInitsWithMonitors;
 import com.android.tools.r8.horizontalclassmerging.policies.OnlyClassesWithStaticDefinitionsAndNoClassInitializer;
 import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
 import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
@@ -292,6 +293,9 @@
         new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
         new NoWeakerAccessPrivileges(appView, immediateSubtypingInfo),
         new PreventClassMethodAndDefaultMethodCollisions(appView, immediateSubtypingInfo));
+    if (appView.options().canHaveIssueWithInlinedMonitors()) {
+      builder.add(new NotTwoInitsWithMonitors());
+    }
   }
 
   private static void addMultiClassPoliciesForMergingNonSyntheticClasses(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotTwoInitsWithMonitors.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotTwoInitsWithMonitors.java
new file mode 100644
index 0000000..d5cebe2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotTwoInitsWithMonitors.java
@@ -0,0 +1,28 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public class NotTwoInitsWithMonitors extends AtMostOneClassThatMatchesPolicy {
+
+  @Override
+  public boolean atMostOneOf(DexProgramClass clazz) {
+    for (ProgramMethod initializer : clazz.programInstanceInitializers()) {
+      DexEncodedMethod definition = initializer.getDefinition();
+      if (definition.isSynchronized() || definition.getCode().hasMonitorInstructions()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public String getName() {
+    return "NotTwoInitsWithMonitors";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 3050bdc..4c3fc63 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -8,7 +8,9 @@
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -24,6 +26,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
@@ -31,8 +34,10 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.synthetic.CheckCastSourceCode;
 import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.ir.synthetic.InstanceOfSourceCode;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.TraversalContinuation;
@@ -51,9 +56,12 @@
   private final AppView<?> appView;
   private final AndroidApiLevelCompute apiLevelCompute;
 
+  private final DexTypeList objectParams;
+
   public ApiInvokeOutlinerDesugaring(AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
     this.appView = appView;
     this.apiLevelCompute = apiLevelCompute;
+    this.objectParams = DexTypeList.create(new DexType[] {appView.dexItemFactory().objectType});
   }
 
   @Override
@@ -67,7 +75,7 @@
       CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
     ComputedApiLevel computedApiLevel =
-        getComputedApiLevelInstructionOnHolderWithMinApi(instruction);
+        getComputedApiLevelInstructionOnHolderWithMinApi(instruction, context);
     if (computedApiLevel.isGreaterThan(appView.computedMinApiLevel())) {
       return desugarLibraryCall(
           methodProcessingContext.createUniqueContext(),
@@ -82,16 +90,13 @@
 
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
-    if (context.getDefinition().isD8R8Synthesized()) {
-      return false;
-    }
-    return getComputedApiLevelInstructionOnHolderWithMinApi(instruction)
+    return getComputedApiLevelInstructionOnHolderWithMinApi(instruction, context)
         .isGreaterThan(appView.computedMinApiLevel());
   }
 
   private ComputedApiLevel getComputedApiLevelInstructionOnHolderWithMinApi(
-      CfInstruction instruction) {
-    if (!instruction.isInvoke() && !instruction.isFieldInstruction()) {
+      CfInstruction instruction, ProgramMethod context) {
+    if (context.getDefinition().isD8R8Synthesized()) {
       return appView.computedMinApiLevel();
     }
     DexReference reference;
@@ -101,8 +106,14 @@
         return appView.computedMinApiLevel();
       }
       reference = cfInvoke.getMethod();
-    } else {
+    } else if (instruction.isFieldInstruction()) {
       reference = instruction.asFieldInstruction().getField();
+    } else if (instruction.isCheckCast()) {
+      reference = instruction.asCheckCast().getType();
+    } else if (instruction.isInstanceOf()) {
+      reference = instruction.asInstanceOf().getType();
+    } else {
+      return appView.computedMinApiLevel();
     }
     if (!reference.getContextType().isClassType()) {
       return appView.computedMinApiLevel();
@@ -119,7 +130,7 @@
       return appView.computedMinApiLevel();
     }
     // Check for protected or package private access flags before outlining.
-    if (holder.isInterface()) {
+    if (holder.isInterface() || instruction.isCheckCast() || instruction.isInstanceOf()) {
       return referenceApiLevel;
     } else {
       DexEncodedMember<?, ?> definition =
@@ -167,7 +178,10 @@
       DexItemFactory factory,
       ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
       ProgramMethod context) {
-    assert instruction.isInvoke() || instruction.isFieldInstruction();
+    assert instruction.isInvoke()
+        || instruction.isFieldInstruction()
+        || instruction.isCheckCast()
+        || instruction.isInstanceOf();
     ProgramMethod outlinedMethod =
         ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
     eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
@@ -199,6 +213,10 @@
                   .setApiLevelForCode(apiLevel);
               if (instruction.isInvoke()) {
                 setCodeForInvoke(syntheticMethodBuilder, instruction.asInvoke(), factory);
+              } else if (instruction.isCheckCast()) {
+                setCodeForCheckCast(syntheticMethodBuilder, instruction.asCheckCast(), factory);
+              } else if (instruction.isInstanceOf()) {
+                setCodeForInstanceOf(syntheticMethodBuilder, instruction.asInstanceOf(), factory);
               } else {
                 assert instruction.isCfInstruction();
                 setCodeForFieldInstruction(
@@ -273,6 +291,30 @@
                     .build());
   }
 
+  private void setCodeForCheckCast(
+      SyntheticMethodBuilder methodBuilder, CfCheckCast instruction, DexItemFactory factory) {
+    DexClass target = appView.definitionFor(instruction.getType());
+    assert target != null;
+    methodBuilder
+        .setProto(factory.createProto(target.getType(), objectParams))
+        .setCode(
+            m ->
+                CheckCastSourceCode.create(appView, m.getHolderType(), target.getType())
+                    .generateCfCode());
+  }
+
+  private void setCodeForInstanceOf(
+      SyntheticMethodBuilder methodBuilder, CfInstanceOf instruction, DexItemFactory factory) {
+    DexClass target = appView.definitionFor(instruction.getType());
+    assert target != null;
+    methodBuilder
+        .setProto(factory.createProto(factory.booleanType, objectParams))
+        .setCode(
+            m ->
+                InstanceOfSourceCode.create(appView, m.getHolderType(), target.getType())
+                    .generateCfCode());
+  }
+
   private boolean verifyLibraryHolderAndInvoke(
       DexClass libraryHolder, DexMethod apiMethod, boolean isVirtualInvoke) {
     DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 69d51be..0db211d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -153,6 +154,10 @@
       return false;
     }
 
+    if (canHaveIssuesWithMonitors(singleTarget, method)) {
+      return false;
+    }
+
     // We should never even try to inline something that is processed concurrently. It can lead
     // to non-deterministic behaviour as the inlining IR could be built from either original output
     // or optimized code. Right now this happens for the class class staticizer, as it just
@@ -199,6 +204,22 @@
     return true;
   }
 
+  private boolean canHaveIssuesWithMonitors(ProgramMethod singleTarget, ProgramMethod context) {
+    if (appView.options().canHaveIssueWithInlinedMonitors()) {
+      if (hasMonitorsOrIsSynchronized(singleTarget.getDefinition())) {
+        if (context.getOptimizationInfo().forceInline()
+            || hasMonitorsOrIsSynchronized(context.getDefinition())) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  public static boolean hasMonitorsOrIsSynchronized(DexEncodedMethod definition) {
+    return definition.isSynchronized() || definition.getCode().hasMonitorInstructions();
+  }
+
   public boolean satisfiesRequirementsForSimpleInlining(InvokeMethod invoke, ProgramMethod target) {
     // Code size modified by inlining, so only read for non-concurrent methods.
     boolean deterministic = !methodProcessor.isProcessedConcurrently(target);
@@ -303,6 +324,17 @@
       return null;
     }
 
+    // Ensure that we don't introduce several monitors in the same method on old device that can
+    // choke on this. If a context is forceinline, e.g., from class merging, don't ever inline
+    // monitors, since that may conflict with a similar other constructor.
+    if (appView.options().canHaveIssueWithInlinedMonitors()) {
+      if (hasMonitorsOrIsSynchronized(singleTarget.getDefinition())
+          && (context.getOptimizationInfo().forceInline()
+              || code.metadata().mayHaveMonitorInstruction())) {
+        return null;
+      }
+    }
+
     InlineAction action =
         invoke.computeInlining(
             singleTarget, reason, this, classInitializationAnalysis, whyAreYouNotInliningReporter);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/CheckCastSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/CheckCastSourceCode.java
new file mode 100644
index 0000000..32fe7c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/CheckCastSourceCode.java
@@ -0,0 +1,41 @@
+// 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.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code representing a simple call to CheckCast.
+public final class CheckCastSourceCode extends SyntheticCfCodeProvider {
+
+  private final DexType checkCastType;
+
+  private CheckCastSourceCode(AppView<?> appView, DexType holder, DexType checkCastType) {
+    super(appView, holder);
+    this.checkCastType = checkCastType;
+  }
+
+  public static CheckCastSourceCode create(
+      AppView<?> appView, DexType holder, DexType checkCastType) {
+    return new CheckCastSourceCode(appView, holder, checkCastType);
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfCheckCast(checkCastType));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
new file mode 100644
index 0000000..57257ff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
@@ -0,0 +1,41 @@
+// 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.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code representing a simple call to CheckCast.
+public final class InstanceOfSourceCode extends SyntheticCfCodeProvider {
+
+  private final DexType instanceOfType;
+
+  private InstanceOfSourceCode(AppView<?> appView, DexType holder, DexType instanceOfType) {
+    super(appView, holder);
+    this.instanceOfType = instanceOfType;
+  }
+
+  public static InstanceOfSourceCode create(
+      AppView<?> appView, DexType holder, DexType checkCastType) {
+    return new InstanceOfSourceCode(appView, holder, checkCastType);
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfInstanceOf(instanceOfType));
+    instructions.add(new CfReturn(ValueType.INT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
index fcd894d..662f9f4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -118,12 +118,12 @@
 
   @Override
   public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
-    // CheckCast are not causing soft verification issues
+    setMaxApiReferenceLevel(type);
   }
 
   @Override
   public void registerSafeCheckCast(DexType type) {
-    // CheckCast are not causing soft verification issues
+    setMaxApiReferenceLevel(type);
   }
 
   @Override
@@ -133,7 +133,7 @@
 
   @Override
   public void registerInstanceOf(DexType type) {
-    // InstanceOf are not causing soft verification issues
+    setMaxApiReferenceLevel(type);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 59c8d20..2aa20c2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -423,7 +423,7 @@
     return factory.createType(createDescriptor(separator, kind, externalSyntheticTypePrefix, id));
   }
 
-  private static String createDescriptor(
+  public static String createDescriptor(
       String separator, SyntheticKind kind, String externalSyntheticTypePrefix, String id) {
     return DescriptorUtils.getDescriptorFromClassBinaryName(
         externalSyntheticTypePrefix + separator + kind.descriptor + id);
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 b4099ee..3b87985 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2824,4 +2824,10 @@
     // TODO(b/246679983): Turned off while diagnosing b/246679983.
     return false && isGeneratingDex() && minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L);
   }
+
+  // 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);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
index f946a84..75e1da7 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
@@ -51,14 +51,20 @@
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
-                  if (Reference.methodFromMethod(readApiField).equals(method)
-                      || Reference.methodFromMethod(setApiField).equals(method)) {
+                  if (Reference.methodFromMethod(readApiField).equals(method)) {
                     if (parameters.isCfRuntime()) {
                       assertEquals(AndroidApiLevel.B, apiLevel);
                     } else {
                       assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
                     }
                   }
+                  if (Reference.methodFromMethod(setApiField).equals(method)) {
+                    if (parameters.isCfRuntime()) {
+                      assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+                    } else {
+                      assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+                    }
+                  }
                 }))
         .compile()
         .addRunClasspathClasses(Api.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
index 73132ae..2a0d763 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -38,7 +38,6 @@
   @Test
   public void testR8() throws Exception {
     Method apiCallerApiLevel22 = ApiCaller.class.getDeclaredMethod("apiLevel22");
-    Method apiCallerCallerApiLevel22 = ApiCallerCaller.class.getDeclaredMethod("apiLevel22");
     testForR8(parameters.getBackend())
         .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, OtherCaller.class, Main.class)
         .addLibraryClasses(ApiType.class)
@@ -55,8 +54,14 @@
         .assertSuccessWithOutputLines(ApiType.class.getName())
         .inspect(
             inspector -> {
-              verifyThat(inspector, parameters, apiCallerApiLevel22)
-                  .inlinedInto(apiCallerCallerApiLevel22);
+              assertThat(
+                  inspector.method(apiCallerApiLevel22),
+                  notIf(
+                      isPresent(),
+                      parameters.isDexRuntime()
+                          && parameters
+                              .getApiLevel()
+                              .isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)));
               assertThat(inspector.clazz(OtherCaller.class), not(isPresent()));
             });
   }
@@ -68,7 +73,7 @@
 
     public static ApiType apiLevel22() throws Exception {
       // The reflective call here is to ensure that the setting of A's api level is not based on
-      // a method reference to `Api` and only because of the type reference in the field `api`.
+      // a method reference to `Api` and only because of the type reference in the checkcast.
       Class<?> reflectiveCall =
           Class.forName(
               "com.android.tools.r8.apimodel.ApiModelInlineMethodWithApiTypeTest_ApiType"
@@ -82,7 +87,7 @@
 
     public static void apiLevel22() throws Exception {
       // This is referencing the proto of ApiCaller.foo and thus have a reference to ApiType. It is
-      // therefore OK to inline ApiCaller.foo() into ApiCallerCaller.bar().
+      // therefore OK to inline ApiCaller.apiLevel22() into ApiCallerCaller.apiLevel22().
       System.out.println(ApiCaller.apiLevel22().getClass().getName());
     }
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
index 53b4942..5df420d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
@@ -51,14 +51,20 @@
         .apply(
             addTracedApiReferenceLevelCallBack(
                 (method, apiLevel) -> {
-                  if (Reference.methodFromMethod(readApi).equals(method)
-                      || Reference.methodFromMethod(setApi).equals(method)) {
+                  if (Reference.methodFromMethod(readApi).equals(method)) {
                     if (parameters.isCfRuntime()) {
                       assertEquals(AndroidApiLevel.B, apiLevel);
                     } else {
                       assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
                     }
                   }
+                  if (Reference.methodFromMethod(setApi).equals(method)) {
+                    if (parameters.isCfRuntime()) {
+                      assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+                    } else {
+                      assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+                    }
+                  }
                 }))
         .compile()
         .addRunClasspathClasses(Api.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
index 22a7321..dec0a00 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.apimodel;
 
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 
@@ -48,6 +49,7 @@
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
         .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         // We are testing that we do not inline/merge higher api-levels
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
new file mode 100644
index 0000000..663a649
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
@@ -0,0 +1,171 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 ApiModelOutlineCheckCastTest extends TestBase {
+
+  private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+
+  private static final String[] EXPECTED = new String[] {"LibraryClass::foo"};
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addLibraryClasses(LibraryClass.class, LibraryProvider.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion(getApiLevelForRuntime())
+        .apply(setMockApiLevelForClass(LibraryProvider.class, AndroidApiLevel.B))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryProvider.class.getDeclaredMethod("getObject", boolean.class),
+                AndroidApiLevel.B))
+        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        .apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("foo"), classApiLevel))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+  }
+
+  public AndroidApiLevel getApiLevelForRuntime() {
+    return parameters.isCfRuntime()
+        ? AndroidApiLevel.B
+        : parameters.getRuntime().asDex().maxSupportedApiLevel();
+  }
+
+  public boolean addToBootClasspath() {
+    return getApiLevelForRuntime().isGreaterThanOrEqualTo(classApiLevel);
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+    testForJvm()
+        .addProgramClasses(Main.class)
+        .addAndroidBuildVersion(parameters.getApiLevel())
+        .addLibraryClasses(LibraryProvider.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+            b -> b.addBootClasspathClasses(LibraryProvider.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+            b -> b.addBootClasspathClasses(LibraryProvider.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+            b -> b.addBootClasspathClasses(LibraryProvider.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    verifyThat(inspector, parameters, LibraryClass.class)
+        .hasCheckCastOutlinedFromUntil(Main.class.getMethod("main", String[].class), classApiLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (addToBootClasspath()) {
+      runResult.assertSuccessWithOutputLines(EXPECTED);
+    } else {
+      runResult.assertSuccessWithOutputLines("Not calling foo on object");
+    }
+  }
+
+  // Only present from api 23.
+  public static class LibraryClass {
+
+    public void foo() {
+      System.out.println("LibraryClass::foo");
+    }
+  }
+
+  public static class LibraryProvider {
+
+    public static Object getObject(boolean hasApiLevel) {
+      if (hasApiLevel) {
+        return new LibraryClass();
+      } else {
+        return new Object();
+      }
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Object object = LibraryProvider.getObject(AndroidBuildVersion.VERSION >= 23);
+      if (AndroidBuildVersion.VERSION >= 23) {
+        ((LibraryClass) object).foo();
+      } else {
+        System.out.println("Not calling foo on object");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
new file mode 100644
index 0000000..63e03a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
@@ -0,0 +1,166 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 ApiModelOutlineInstanceOfTest extends TestBase {
+
+  private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+
+  private static final String[] EXPECTED = new String[] {"true"};
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    testBuilder
+        .addLibraryClasses(LibraryClass.class, LibraryProvider.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addAndroidBuildVersion(getApiLevelForRuntime())
+        .apply(setMockApiLevelForClass(LibraryProvider.class, AndroidApiLevel.B))
+        .apply(
+            setMockApiLevelForMethod(
+                LibraryProvider.class.getDeclaredMethod("getObject", boolean.class),
+                AndroidApiLevel.B))
+        .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+        .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+  }
+
+  public AndroidApiLevel getApiLevelForRuntime() {
+    return parameters.isCfRuntime()
+        ? AndroidApiLevel.B
+        : parameters.getRuntime().asDex().maxSupportedApiLevel();
+  }
+
+  public boolean addToBootClasspath() {
+    return getApiLevelForRuntime().isGreaterThanOrEqualTo(classApiLevel);
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+    testForJvm()
+        .addProgramClasses(Main.class)
+        .addAndroidBuildVersion(parameters.getApiLevel())
+        .addLibraryClasses(LibraryProvider.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+            b -> b.addBootClasspathClasses(LibraryProvider.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+            b -> b.addBootClasspathClasses(LibraryProvider.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .apply(this::setupTestBuilder)
+        .addKeepMainRule(Main.class)
+        .compile()
+        .inspect(this::inspect)
+        .applyIf(
+            addToBootClasspath(),
+            b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+            b -> b.addBootClasspathClasses(LibraryProvider.class))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    verifyThat(inspector, parameters, LibraryClass.class)
+        .hasInstanceOfOutlinedFromUntil(
+            Main.class.getMethod("main", String[].class), classApiLevel);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (addToBootClasspath()) {
+      runResult.assertSuccessWithOutputLines(EXPECTED);
+    } else {
+      runResult.assertSuccessWithOutputLines("Not checking instance of");
+    }
+  }
+
+  // Only present from api 23.
+  public static class LibraryClass {}
+
+  public static class LibraryProvider {
+
+    public static Object getObject(boolean hasApiLevel) {
+      if (hasApiLevel) {
+        return new LibraryClass();
+      } else {
+        return new Object();
+      }
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Object object = LibraryProvider.getObject(AndroidBuildVersion.VERSION >= 23);
+      if (AndroidBuildVersion.VERSION >= 23) {
+        System.out.println(object instanceof LibraryClass);
+      } else {
+        System.out.println("Not checking instance of");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
index 79df3a4..8d59924 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
@@ -6,7 +6,8 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -34,8 +35,6 @@
   @Test
   public void testR8() throws Exception {
     Method apiMethod = LibraryClass.class.getDeclaredMethod("apiMethod");
-    Method apiCaller = ApiHelper.class.getDeclaredMethod("apiCaller", LibraryClass.class);
-    Method apiCallerCaller = Main.class.getDeclaredMethod("typeReference", Object.class);
     boolean libraryClassOnBoot =
         parameters.isDexRuntime()
             && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.M);
@@ -53,10 +52,7 @@
         .enableInliningAnnotations()
         .addAndroidBuildVersion()
         .compile()
-        .inspect(
-            inspector ->
-                verifyThat(inspector, parameters, apiCaller)
-                    .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.M))
+        .inspect(inspector -> assertThat(inspector.clazz(ApiHelper.class), isAbsent()))
         .addRunClasspathClasses(LibraryClass.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLinesIf(
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index 20d345a..e437943 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.apimodel;
 
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsCheckCast;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsInstanceOf;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
@@ -18,9 +20,11 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.CodeMatchers;
@@ -258,6 +262,86 @@
               parameters.isCfRuntime()
                   || parameters.getApiLevel().isGreaterThanOrEqualTo(finalApiLevel)));
     }
+
+    void hasCheckCastOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
+      if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
+        hasCheckCastOutlinedFrom(method);
+      } else {
+        hasNotCheckCastOutlinedFrom(method);
+      }
+    }
+
+    public void hasCheckCastOutlinedFrom(Method method) {
+      // Check that we call is in a synthetic class with a check cast
+      ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+      List<FoundMethodSubject> outlinedMethod =
+          inspector.allClasses().stream()
+              .filter(
+                  clazz ->
+                      clazz
+                          .getOriginalName()
+                          .startsWith(
+                              SyntheticItemsTestUtils.syntheticApiOutlineClassPrefix(
+                                  method.getDeclaringClass())))
+              .flatMap(clazz -> clazz.allMethods().stream())
+              .filter(
+                  methodSubject ->
+                      methodSubject.isSynthetic()
+                          && containsCheckCast(classOfInterestReference).matches(methodSubject))
+              .collect(Collectors.toList());
+      assertFalse(outlinedMethod.isEmpty());
+      // Assert that method invokes the outline
+      MethodSubject caller = inspector.method(method);
+      assertThat(caller, isPresent());
+      assertThat(caller, invokesMethod(outlinedMethod.get(0)));
+    }
+
+    public void hasNotCheckCastOutlinedFrom(Method method) {
+      ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+      MethodSubject caller = inspector.method(method);
+      assertThat(caller, isPresent());
+      assertThat(caller, containsCheckCast(classOfInterestReference));
+    }
+
+    void hasInstanceOfOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
+      if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
+        hasInstanceOfOutlinedFrom(method);
+      } else {
+        hasNotInstanceOfOutlinedFrom(method);
+      }
+    }
+
+    public void hasInstanceOfOutlinedFrom(Method method) {
+      // Check that we call is in a synthetic class with an instance of.
+      ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+      List<FoundMethodSubject> outlinedMethod =
+          inspector.allClasses().stream()
+              .filter(
+                  clazz ->
+                      clazz
+                          .getOriginalName()
+                          .startsWith(
+                              SyntheticItemsTestUtils.syntheticApiOutlineClassPrefix(
+                                  method.getDeclaringClass())))
+              .flatMap(clazz -> clazz.allMethods().stream())
+              .filter(
+                  methodSubject ->
+                      methodSubject.isSynthetic()
+                          && containsInstanceOf(classOfInterestReference).matches(methodSubject))
+              .collect(Collectors.toList());
+      assertFalse(outlinedMethod.isEmpty());
+      // Assert that method invokes the outline
+      MethodSubject caller = inspector.method(method);
+      assertThat(caller, isPresent());
+      assertThat(caller, invokesMethod(outlinedMethod.get(0)));
+    }
+
+    public void hasNotInstanceOfOutlinedFrom(Method method) {
+      ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+      MethodSubject caller = inspector.method(method);
+      assertThat(caller, isPresent());
+      assertThat(caller, containsInstanceOf(classOfInterestReference));
+    }
   }
 
   public static class ApiModelingFieldVerificationHelper {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineConstructorsWithMonitors.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineConstructorsWithMonitors.java
new file mode 100644
index 0000000..052398d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineConstructorsWithMonitors.java
@@ -0,0 +1,94 @@
+// 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.ir.optimize.inliner.sync;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.hasDefaultConstructor;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlineConstructorsWithMonitors extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public InlineConstructorsWithMonitors(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InlineConstructorsWithMonitors.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("monitor", "monitor2")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+    ClassSubject fooClassSubject = inspector.clazz(Foo.class);
+    ClassSubject barClassSubject = inspector.clazz(Bar.class);
+    if (parameters.isCfRuntime()
+        || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
+      // On M and below we don't want to merge constructors when both have monitors. See b/238399429
+      assertThat(fooClassSubject, hasDefaultConstructor());
+      assertThat(barClassSubject, hasDefaultConstructor());
+    } else {
+      assertTrue(
+          fooClassSubject.uniqueInstanceInitializer().isPresent()
+              || barClassSubject.uniqueInstanceInitializer().isPresent());
+      assertFalse(
+          fooClassSubject.uniqueInstanceInitializer().isPresent()
+              && barClassSubject.uniqueInstanceInitializer().isPresent());
+    }
+  }
+
+  static class Foo {
+    public Foo() {
+      Object o = new Object();
+      synchronized (o) {
+        System.out.println("monitor");
+      }
+    }
+  }
+
+  static class Bar {
+    public Bar() {
+      Object o = new String("Foo");
+      synchronized (o) {
+        System.out.println("monitor2");
+      }
+    }
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      new Foo();
+      new Bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
index 1a20851..569652a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ir.optimize.inliner.sync.InlineStaticSynchronizedMethodTest.TestClass.RunnableImpl;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.lang.Thread.State;
@@ -53,8 +54,17 @@
   private void verifySynchronizedMethodsAreInlined(CodeInspector inspector) {
     ClassSubject classSubject = inspector.clazz(RunnableImpl.class);
     assertThat(classSubject, isPresent());
-    assertThat(classSubject.uniqueMethodWithOriginalName("m1"), not(isPresent()));
-    assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
+    // On M we are seeing issues when inlining code with monitors which will trip up some art
+    // vms. See issue b/238399429 for details.
+    if (parameters.isCfRuntime()
+        || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
+      assertThat(classSubject.uniqueMethodWithOriginalName("m1"), isPresent());
+      assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
+
+    } else {
+      assertThat(classSubject.uniqueMethodWithOriginalName("m1"), not(isPresent()));
+      assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java
new file mode 100644
index 0000000..96120c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java
@@ -0,0 +1,95 @@
+// 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.ir.optimize.inliner.sync;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlineWithMonitorInConstructorInline extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public InlineWithMonitorInConstructorInline(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InlineWithMonitorInConstructorInline.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::inspect)
+        .assertSuccessWithOutputLines("foo", "monitor", "bar", "monitor2");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+    ClassSubject utilClassSubject = inspector.clazz(Util.class);
+    if (parameters.isCfRuntime()
+        || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
+      // We disallow merging methods with monitors into constructors which will be class merged
+      // on pre M devices, see b/238399429
+      assertThat(utilClassSubject, isPresent());
+    } else {
+      assertThat(utilClassSubject, not(isPresent()));
+    }
+  }
+
+  static class Foo {
+    public Foo() {
+      System.out.println("foo");
+      Util.useMonitor(this);
+    }
+  }
+
+  static class Bar {
+    public Bar() {
+      System.out.println("bar");
+      Util.useMonitor2(this);
+    }
+  }
+
+  static class Util {
+    public static void useMonitor(Object object) {
+      synchronized (object) {
+        System.out.println("monitor");
+      }
+    }
+
+    public static void useMonitor2(Object object) {
+      synchronized (object) {
+        System.out.println("monitor2");
+      }
+    }
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      new Foo();
+      new Bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
index d6c07ca..3ae6d3d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -44,9 +45,8 @@
         .addOptionsModification(
             options -> options.inlinerOptions().inliningMonitorEnterValuesAllowance = threshold)
         .setMinApi(parameters.getApiLevel())
-        .compile()
-        .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
+        .inspect(this::inspect)
         .assertSuccessWithOutputLines("Hello world!");
   }
 
@@ -54,13 +54,22 @@
     ClassSubject classSubject = inspector.clazz(TestClass.class);
     assertThat(classSubject, isPresent());
     assertThat(classSubject.mainMethod(), isPresent());
-    assertThat(classSubject.uniqueMethodWithOriginalName("m1"), not(isPresent()));
-    assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
-    if (threshold == 2) {
+    // On M we are seeing issues when inlining code with monitors which will trip up some art
+    // vms. See issue b/238399429 for details.
+    if (parameters.isCfRuntime()
+        || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
+      assertThat(classSubject.uniqueMethodWithOriginalName("m1"), isPresent());
+      assertThat(classSubject.uniqueMethodWithOriginalName("m2"), isPresent());
       assertThat(classSubject.uniqueMethodWithOriginalName("m3"), isPresent());
     } else {
-      assert threshold == 3;
-      assertThat(classSubject.uniqueMethodWithOriginalName("m3"), not(isPresent()));
+      assertThat(classSubject.uniqueMethodWithOriginalName("m1"), not(isPresent()));
+      assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
+      if (threshold == 2) {
+        assertThat(classSubject.uniqueMethodWithOriginalName("m3"), isPresent());
+      } else {
+        assert threshold == 3;
+        assertThat(classSubject.uniqueMethodWithOriginalName("m3"), not(isPresent()));
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index c5bb96f..ff769a2 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import static com.android.tools.r8.synthesis.SyntheticNaming.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting;
@@ -70,6 +71,12 @@
     return syntheticClass(classReference, naming.API_MODEL_OUTLINE, id);
   }
 
+  public static String syntheticApiOutlineClassPrefix(Class<?> clazz) {
+    return clazz.getTypeName()
+        + EXTERNAL_SYNTHETIC_CLASS_SEPARATOR
+        + naming.API_MODEL_OUTLINE.getDescriptor();
+  }
+
   public static ClassReference syntheticBackportClass(Class<?> clazz, int id) {
     return syntheticClass(clazz, naming.BACKPORT, id);
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index e4f68c1..70e7eab 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.MethodReferenceUtils;
@@ -71,6 +72,56 @@
     };
   }
 
+  public static Matcher<MethodSubject> containsCheckCast(ClassReference classReference) {
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        return subject.isPresent()
+            && subject.getMethod().hasCode()
+            && subject
+                .streamInstructions()
+                .anyMatch(
+                    instructionSubject ->
+                        instructionSubject.isCheckCast(classReference.getTypeName()));
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("contains checkcast");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
+  public static Matcher<MethodSubject> containsInstanceOf(ClassReference classReference) {
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      protected boolean matchesSafely(MethodSubject subject) {
+        return subject.isPresent()
+            && subject.getMethod().hasCode()
+            && subject
+                .streamInstructions()
+                .anyMatch(
+                    instructionSubject ->
+                        instructionSubject.isInstanceOf(classReference.getTypeName()));
+      }
+
+      @Override
+      public void describeTo(Description description) {
+        description.appendText("contains instanceof");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject subject, Description description) {
+        description.appendText("method did not");
+      }
+    };
+  }
+
   public static Matcher<MethodSubject> instantiatesClass(Class<?> clazz) {
     return instantiatesClass(clazz.getTypeName());
   }