Extend MethodConversionOptions with desired backend

This makes it possible to specify whether a piece of IR should be converted to cf or dex independent of the backend of the overall compilation.

Bug: 205810841
Change-Id: I9515706409a9e0639a945321d8f5fad0d05d157f
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 4358a27..3033fb7 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -35,6 +35,8 @@
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -503,10 +505,14 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     verifyFramesOrRemove(method, appView, getCodeLens(appView));
     return internalBuildPossiblyWithLocals(
-        method, method, appView, appView.codeLens(), null, null, origin, null);
+        method, method, appView, appView.codeLens(), null, null, origin, null, conversionOptions);
   }
 
   @Override
@@ -531,7 +537,8 @@
         valueNumberGenerator,
         callerPosition,
         origin,
-        protoChanges);
+        protoChanges,
+        new ThrowingMethodConversionOptions(appView.options()));
   }
 
   private void verifyFramesOrRemove(ProgramMethod method, AppView<?> appView, GraphLens codeLens) {
@@ -552,7 +559,8 @@
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
-      RewrittenPrototypeDescription protoChanges) {
+      RewrittenPrototypeDescription protoChanges,
+      MutableMethodConversionOptions conversionOptions) {
     if (!method.getDefinition().keepLocals(appView.options())) {
       return internalBuild(
           Collections.emptyList(),
@@ -563,7 +571,8 @@
           valueNumberGenerator,
           callerPosition,
           origin,
-          protoChanges);
+          protoChanges,
+          conversionOptions);
     } else {
       return internalBuildWithLocals(
           context,
@@ -573,7 +582,8 @@
           valueNumberGenerator,
           callerPosition,
           origin,
-          protoChanges);
+          protoChanges,
+          conversionOptions);
     }
   }
 
@@ -586,7 +596,8 @@
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
-      RewrittenPrototypeDescription protoChanges) {
+      RewrittenPrototypeDescription protoChanges,
+      MutableMethodConversionOptions conversionOptions) {
     try {
       return internalBuild(
           Collections.unmodifiableList(localVariables),
@@ -597,7 +608,8 @@
           valueNumberGenerator,
           callerPosition,
           origin,
-          protoChanges);
+          protoChanges,
+          conversionOptions);
     } catch (InvalidDebugInfoException e) {
       appView.options().warningInvalidDebugInfo(method, origin, e);
       return internalBuild(
@@ -609,7 +621,8 @@
           valueNumberGenerator,
           callerPosition,
           origin,
-          protoChanges);
+          protoChanges,
+          conversionOptions);
     }
   }
 
@@ -623,7 +636,8 @@
       NumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
-      RewrittenPrototypeDescription protoChanges) {
+      RewrittenPrototypeDescription protoChanges,
+      MutableMethodConversionOptions conversionOptions) {
     CfSourceCode source =
         new CfSourceCode(
             this,
@@ -642,7 +656,7 @@
           IRBuilder.createForInlining(
               method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges);
     }
-    return builder.build(context);
+    return builder.build(context, conversionOptions);
   }
 
   @Override
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 0f4233d..8152305 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -12,13 +12,22 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 
 public abstract class Code extends CachedHashValueDexItem {
 
-  public abstract IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin);
+  public final IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    return buildIR(method, appView, origin, new MutableMethodConversionOptions(appView.options()));
+  }
+
+  public abstract IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions);
 
   public IRCode buildInliningIR(
       ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index a544337..56a5988 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -24,6 +24,8 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
@@ -134,12 +136,16 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     DexMethod originalMethod =
         appView.graphLens().getOriginalMethodSignature(method.getReference());
     DefaultInstanceInitializerSourceCode source =
         new DefaultInstanceInitializerSourceCode(originalMethod);
-    return IRBuilder.create(method, appView, source, origin).build(method);
+    return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
   }
 
   @Override
@@ -158,7 +164,7 @@
         new DefaultInstanceInitializerSourceCode(originalMethod, callerPosition);
     return IRBuilder.createForInlining(
             method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
-        .build(context);
+        .build(context, new ThrowingMethodConversionOptions(appView.options()));
   }
 
   @Override
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 3c3ce77..6eb2f08 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -26,6 +26,8 @@
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ArrayUtils;
@@ -366,7 +368,11 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     DexSourceCode source =
         new DexSourceCode(
             this,
@@ -374,7 +380,7 @@
             appView.graphLens().getOriginalMethodSignature(method.getReference()),
             null,
             appView.dexItemFactory());
-    return IRBuilder.create(method, appView, source, origin).build(method);
+    return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
   }
 
   @Override
@@ -396,7 +402,7 @@
             appView.dexItemFactory());
     return IRBuilder.createForInlining(
             method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
-        .build(context);
+        .build(context, new ThrowingMethodConversionOptions(appView.options()));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c33d18d..3871952 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -43,12 +43,9 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
-import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.NestUtils;
@@ -57,7 +54,6 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
-import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -736,16 +732,6 @@
     code = newCode;
   }
 
-  public void setCode(
-      IRCode ir,
-      BytecodeMetadataProvider bytecodeMetadataProvider,
-      RegisterAllocator registerAllocator,
-      AppView<?> appView) {
-    checkIfObsolete();
-    DexBuilder builder = new DexBuilder(ir, bytecodeMetadataProvider, registerAllocator);
-    setCode(builder.build(), appView);
-  }
-
   public void unsetCode() {
     checkIfObsolete();
     code = null;
diff --git a/src/main/java/com/android/tools/r8/graph/InvalidCode.java b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
index 5363897..2b9de26 100644
--- a/src/main/java/com/android/tools/r8/graph/InvalidCode.java
+++ b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 
@@ -23,7 +24,11 @@
   private InvalidCode() {}
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable();
   }
 
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 b2efb1b..0922945 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -60,6 +60,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
@@ -240,8 +241,12 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
-    return asCfCode().buildIR(method, appView, origin);
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
+    return asCfCode().buildIR(method, appView, origin, conversionOptions);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 3cb1a4c..f60161d 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
@@ -27,8 +28,14 @@
   }
 
   public IRCode buildIR(AppView<?> appView) {
+    return buildIR(appView, new MutableMethodConversionOptions(appView.options()));
+  }
+
+  public IRCode buildIR(AppView<?> appView, MutableMethodConversionOptions conversionOptions) {
     DexEncodedMethod method = getDefinition();
-    return method.hasCode() ? method.getCode().buildIR(this, appView, getOrigin()) : null;
+    return method.hasCode()
+        ? method.getCode().buildIR(this, appView, getOrigin(), conversionOptions)
+        : null;
   }
 
   public IRCode buildInliningIR(
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
index 3420b1b..55e6f8c 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -48,7 +49,11 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable("Should not be called");
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
index 03b811f..5b02c12 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -19,6 +19,8 @@
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
@@ -52,9 +54,13 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     ThrowNullSourceCode source = new ThrowNullSourceCode(method);
-    return IRBuilder.create(method, appView, source, origin).build(method);
+    return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
   }
 
   @Override
@@ -70,7 +76,7 @@
     ThrowNullSourceCode source = new ThrowNullSourceCode(method, callerPosition);
     return IRBuilder.createForInlining(
             method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
-        .build(context);
+        .build(context, new ThrowingMethodConversionOptions(appView.options()));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
index d961949..c9c8e34 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteHorizontalClassMergerCode.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 
@@ -37,7 +38,11 @@
   // Implement Code.
 
   @Override
-  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index fcae6d5..9787686 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.CfVersionUtils;
@@ -193,7 +194,11 @@
     }
 
     @Override
-    public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    public IRCode buildIR(
+        ProgramMethod method,
+        AppView<?> appView,
+        Origin origin,
+        MutableMethodConversionOptions conversionOptions) {
       assert !classInitializers.isEmpty();
 
       Position callerPosition =
@@ -227,7 +232,8 @@
               valueNumberGenerator,
               blockNumberGenerator,
               metadata,
-              origin);
+              origin,
+              conversionOptions);
 
       ListIterator<BasicBlock> blockIterator = code.listIterator();
       InstructionListIterator instructionIterator = blockIterator.next().listIterator(code);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
index 4727869..4d10e4f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
@@ -32,7 +31,6 @@
   public Instruction buildIR(AppView<?> appView, IRCode code) {
     Value value =
         code.createValue(TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
-    ThrowingInfo throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
     if (appView.options().isMinifying()) {
       return new DexItemBasedConstString(value, field, FieldNameComputationInfo.forFieldName());
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index d51e318..82a2ef7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -113,7 +114,8 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
     // We cannot share ArrayGet instructions without knowledge of the type of the array input.
     // If multiple primitive array types flow to the same ArrayGet instruction the art verifier
     // gets confused.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 3835b3d..a0bf286 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -84,8 +85,9 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
-    if (super.identicalAfterRegisterAllocation(other, allocator)) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
+    if (super.identicalAfterRegisterAllocation(other, allocator, conversionOptions)) {
       // The array length instruction doesn't carry the element type. The art verifier doesn't
       // allow an array length instruction into which arrays of two different base types can
       // flow. Therefore, as a safe approximation we only consider array length instructions
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 9994dde..aa70b4f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -173,7 +174,8 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
     // We cannot share ArrayPut instructions without knowledge of the type of the array input.
     // If multiple primitive array types flow to the same ArrayPut instruction the art verifier
     // gets confused.
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index e5f15bd..b664c13 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -120,14 +120,6 @@
   public enum ThrowingInfo {
     NO_THROW,
     CAN_THROW;
-
-    public static ThrowingInfo defaultForConstString(InternalOptions options) {
-      return options.isGeneratingClassFiles() ? NO_THROW : CAN_THROW;
-    }
-
-    public static ThrowingInfo defaultForInstruction(Instruction instruction) {
-      return instruction.instructionTypeCanThrow() ? CAN_THROW : NO_THROW;
-    }
   }
 
   public enum EdgeType {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 6a237d0..5175138 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -21,6 +21,8 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -110,6 +112,7 @@
   public static final int INSTRUCTION_NUMBER_DELTA = 2;
 
   private final ProgramMethod method;
+  private final MutableMethodConversionOptions conversionOptions;
 
   public LinkedList<BasicBlock> blocks;
   public final NumberGenerator valueNumberGenerator;
@@ -135,11 +138,13 @@
       NumberGenerator valueNumberGenerator,
       NumberGenerator basicBlockNumberGenerator,
       IRMetadata metadata,
-      Origin origin) {
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     assert metadata != null;
     assert options != null;
     assert blocks.size() == basicBlockNumberGenerator.peek();
     this.options = options;
+    this.conversionOptions = conversionOptions;
     this.method = method;
     this.blocks = blocks;
     this.valueNumberGenerator = valueNumberGenerator;
@@ -167,6 +172,14 @@
     return blocks.getFirst();
   }
 
+  public MethodConversionOptions getConversionOptions() {
+    return conversionOptions;
+  }
+
+  public void mutateConversionOptions(Consumer<MutableMethodConversionOptions> mutator) {
+    mutator.accept(conversionOptions);
+  }
+
   /**
    * Compute the set of live values at the entry to each block using a backwards data-flow analysis.
    */
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index e0ca6cd..68d7ffc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -153,8 +154,9 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
-    if (!super.identicalAfterRegisterAllocation(other, allocator)) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
+    if (!super.identicalAfterRegisterAllocation(other, allocator, conversionOptions)) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index ec25297..564eaf1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -26,12 +26,14 @@
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.google.common.collect.ImmutableSet;
@@ -501,7 +503,8 @@
     return a.outType() == b.outType();
   }
 
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
     if (other.getClass() != getClass()) {
       return false;
     }
@@ -541,8 +544,10 @@
       }
     }
     // Finally check that the dex instructions for the generated code actually are the same.
-    if (allocator.options().isGeneratingDex()
-        && !DexBuilder.identicalInstructionsAfterBuildingDexCode(this, other, allocator)) {
+    InternalOptions options = allocator.options();
+    if (conversionOptions.isGeneratingDex()
+        && !DexBuilder.identicalInstructionsAfterBuildingDexCode(
+            this, other, allocator, conversionOptions)) {
       return false;
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 06173d4..2fc4aff 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -187,8 +188,9 @@
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
-    if (!super.identicalAfterRegisterAllocation(other, allocator)) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
+    if (!super.identicalAfterRegisterAllocation(other, allocator, conversionOptions)) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index dd74687..e5f3722 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -85,7 +85,7 @@
     InternalOptions options = appView.options();
     if (options.debug
         || code.context().getDefinition().getOptimizationInfo().isReachabilitySensitive()
-        || options.isGeneratingClassFiles()) {
+        || code.getConversionOptions().isGeneratingClassFiles()) {
       return DeadInstructionResult.notDead();
     }
     return DeadInstructionResult.deadIfOutValueIsDead();
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index b32a651..b28f281 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -149,8 +150,9 @@
   }
 
   @Override
-  public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
-    if (!super.identicalAfterRegisterAllocation(other, allocator)) {
+  public boolean identicalAfterRegisterAllocation(
+      Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
+    if (!super.identicalAfterRegisterAllocation(other, allocator, conversionOptions)) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index e95d612..fa3c642 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -144,7 +144,8 @@
     this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider);
   }
 
-  public CfCode build(DeadCodeRemover deadCodeRemover, MethodConversionOptions conversionOptions) {
+  public CfCode build(DeadCodeRemover deadCodeRemover) {
+    code.traceBlocks();
     computeInitializers();
     TypeVerificationHelper typeVerificationHelper = new TypeVerificationHelper(appView, code);
     typeVerificationHelper.computeVerificationTypes();
@@ -178,7 +179,7 @@
 
     loadStoreHelper.insertPhiMoves(registerAllocator);
 
-    if (conversionOptions.isPeepholeOptimizationsEnabled()) {
+    if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) {
       for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
         CodeRewriter.collapseTrivialGotos(code);
         PeepholeOptimizer.removeIdenticalPredecessorBlocks(code, registerAllocator);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index c4ac3ff..2bd4cfa 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -93,6 +93,7 @@
   private final RegisterAllocator registerAllocator;
 
   private final InternalOptions options;
+  private final MethodConversionOptions conversionOptions;
 
   // List of information about switch payloads that have to be created at the end of the
   // dex code.
@@ -128,20 +129,23 @@
   public DexBuilder(
       IRCode ir,
       BytecodeMetadataProvider bytecodeMetadataProvider,
-      RegisterAllocator registerAllocator) {
-    this(ir, bytecodeMetadataProvider, registerAllocator, registerAllocator.options());
-    assert ir != null;
+      RegisterAllocator registerAllocator,
+      InternalOptions options) {
+    this(ir, bytecodeMetadataProvider, registerAllocator, options, ir.getConversionOptions());
   }
 
-  private DexBuilder(
+  public DexBuilder(
       IRCode ir,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       RegisterAllocator registerAllocator,
-      InternalOptions options) {
+      InternalOptions options,
+      MethodConversionOptions conversionOptions) {
+    assert ir == null || conversionOptions == ir.getConversionOptions();
     this.ir = ir;
     this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider);
     this.registerAllocator = registerAllocator;
     this.options = options;
+    this.conversionOptions = conversionOptions;
     if (isBuildingForComparison()) {
       instructionToInfo = new Info[1];
     }
@@ -150,9 +154,15 @@
   public static boolean identicalInstructionsAfterBuildingDexCode(
       com.android.tools.r8.ir.code.Instruction a,
       com.android.tools.r8.ir.code.Instruction b,
-      RegisterAllocator allocator) {
+      RegisterAllocator allocator,
+      MethodConversionOptions conversionOptions) {
     DexBuilder builder =
-        new DexBuilder(null, BytecodeMetadataProvider.empty(), allocator, allocator.options());
+        new DexBuilder(
+            null,
+            BytecodeMetadataProvider.empty(),
+            allocator,
+            allocator.options(),
+            conversionOptions);
     Info infoA = buildInfoForComparison(a, builder);
     Info infoB = buildInfoForComparison(b, builder);
     return infoA.identicalInstructions(infoB, builder);
@@ -647,7 +657,8 @@
 
   public void addReturn(Return ret, Instruction dex) {
     if (nextBlock != null
-        && ret.identicalAfterRegisterAllocation(nextBlock.entry(), registerAllocator)) {
+        && ret.identicalAfterRegisterAllocation(
+            nextBlock.entry(), registerAllocator, conversionOptions)) {
       addNothing(ret);
     } else {
       add(ret, dex);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 4d1cfc2..301de1a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -118,6 +118,7 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.code.Xor;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -606,7 +607,7 @@
    * @param context Under what context this IRCode is built. Either the current method or caller.
    * @return The list of basic blocks. First block is the main entry.
    */
-  public IRCode build(ProgramMethod context) {
+  public IRCode build(ProgramMethod context, MutableMethodConversionOptions conversionOptions) {
     assert source != null;
     source.setUp();
 
@@ -706,7 +707,8 @@
             valueNumberGenerator,
             basicBlockNumberGenerator,
             metadata,
-            origin);
+            origin,
+            conversionOptions);
 
     // Verify critical edges are split so we have a place to insert phi moves if necessary.
     assert ir.verifySplitCriticalEdges();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 3da2a7e..b51a072 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -11,16 +11,13 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
@@ -33,18 +30,8 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
-import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.DefaultMethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
@@ -75,7 +62,6 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.MemberValuePropagation;
 import com.android.tools.r8.ir.optimize.NaturalIntLoopRemover;
-import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
@@ -92,8 +78,6 @@
 import com.android.tools.r8.ir.optimize.outliner.Outliner;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
-import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
@@ -123,13 +107,10 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 public class IRConverter {
 
-  private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
-
   public final AppView<?> appView;
 
   private final Timing timing;
@@ -915,11 +896,10 @@
     assert code.isConsistentSSA();
     Timing timing = Timing.empty();
     deadCodeRemover.run(code, timing);
-    code.traceBlocks();
-    RegisterAllocator registerAllocator =
-        performRegisterAllocation(
-            code, method, DefaultMethodConversionOptions.getInstance(), timing);
-    method.setCode(code, BytecodeMetadataProvider.empty(), registerAllocator, appView);
+    method.setCode(
+        new IRToDexFinalizer(appView, codeRewriter, deadCodeRemover)
+            .finalizeCode(code, BytecodeMetadataProvider.empty(), timing),
+        appView);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
           method.toSourceString(), logCode(options, method));
@@ -1094,8 +1074,6 @@
     ProgramMethod context = code.context();
     DexEncodedMethod method = context.getDefinition();
     DexProgramClass holder = context.getHolder();
-    MutableMethodConversionOptions conversionOptions =
-        new MutableMethodConversionOptions(methodProcessor);
     assert holder != null;
 
     Timing timing = Timing.create(context.toSourceString(), options);
@@ -1178,7 +1156,6 @@
           ClassInitializerDefaultsResult.empty(),
           feedback,
           methodProcessor,
-          conversionOptions,
           BytecodeMetadataProvider.builder(),
           timing);
       timing.end();
@@ -1493,7 +1470,6 @@
           classInitializerDefaultsResult,
           feedback,
           methodProcessor,
-          conversionOptions,
           bytecodeMetadataProviderBuilder,
           timing);
       timing.end();
@@ -1517,19 +1493,11 @@
     previous =
         printMethod(code, "IR after computation of optimization info summary (SSA)", previous);
 
-    if (options.canHaveNumberConversionRegisterAllocationBug()) {
-      timing.begin("Check number conversion issue");
-      codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
-      timing.end();
-    }
-
     printMethod(code, "Optimized IR (SSA)", previous);
     timing.begin("Finalize IR");
     finalizeIR(
-        context,
         code,
         feedback,
-        conversionOptions,
         bytecodeMetadataProviderBuilder.build(),
         timing);
     timing.end();
@@ -1553,14 +1521,13 @@
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MutableMethodConversionOptions conversionOptions,
       BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder,
       Timing timing) {
     appView.withArgumentPropagator(
         argumentPropagator -> argumentPropagator.scan(method, code, methodProcessor, timing));
 
     if (methodProcessor.isPrimaryMethodProcessor()) {
-      enumUnboxer.analyzeEnums(code, conversionOptions);
+      enumUnboxer.analyzeEnums(code, methodProcessor);
     }
 
     if (inliner != null) {
@@ -1624,69 +1591,50 @@
     }
     deadCodeRemover.run(code, timing);
     finalizeIR(
-        code.context(),
         code,
         feedback,
-        DefaultMethodConversionOptions.getInstance(),
         BytecodeMetadataProvider.empty(),
         timing);
   }
 
   public void finalizeIR(
-      ProgramMethod method,
       IRCode code,
       OptimizationFeedback feedback,
-      MethodConversionOptions conversionOptions,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
-    code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
-      finalizeToCf(code, feedback, conversionOptions, bytecodeMetadataProvider);
+      finalizeToCf(code, feedback, bytecodeMetadataProvider, timing);
     } else {
       assert options.isGeneratingDex();
-      finalizeToDex(code, feedback, conversionOptions, bytecodeMetadataProvider, timing);
+      finalizeToDex(code, feedback, bytecodeMetadataProvider, timing);
     }
   }
 
   private void finalizeToCf(
       IRCode code,
       OptimizationFeedback feedback,
-      MethodConversionOptions conversionOptions,
-      BytecodeMetadataProvider bytecodeMetadataProvider) {
-    ProgramMethod method = code.context();
-    assert !method.getDefinition().getCode().isDexCode();
-    CfBuilder builder = new CfBuilder(appView, method, code, bytecodeMetadataProvider);
-    CfCode result = builder.build(deadCodeRemover, conversionOptions);
-    method.getDefinition().setCode(result, appView);
+      BytecodeMetadataProvider bytecodeMetadataProvider,
+      Timing timing) {
+    DexEncodedMethod method = code.method();
+    method.setCode(
+        new IRToCfFinalizer(appView, deadCodeRemover)
+            .finalizeCode(code, bytecodeMetadataProvider, timing),
+        appView);
     markProcessed(code, feedback);
   }
 
   private void finalizeToDex(
       IRCode code,
       OptimizationFeedback feedback,
-      MethodConversionOptions conversionOptions,
       BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
     DexEncodedMethod method = code.method();
-    // Workaround massive dex2oat memory use for self-recursive methods.
-    CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(appView, code);
-    // Workaround MAX_INT switch issue.
-    codeRewriter.rewriteSwitchForMaxInt(code);
-    // Perform register allocation.
-    RegisterAllocator registerAllocator =
-        performRegisterAllocation(code, method, conversionOptions, timing);
-    timing.begin("Build DEX code");
-    method.setCode(code, bytecodeMetadataProvider, registerAllocator, appView);
-    timing.end();
-    updateHighestSortingStrings(method);
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Resulting dex code for %s:\n%s",
-          method.toSourceString(), logCode(options, method));
-    }
-    printMethod(code, "Final IR (non-SSA)", null);
-    timing.begin("Marking processed");
+    method.setCode(
+        new IRToDexFinalizer(appView, codeRewriter, deadCodeRemover)
+            .finalizeCode(code, bytecodeMetadataProvider, timing),
+        appView);
     markProcessed(code, feedback);
-    timing.end();
+    updateHighestSortingStrings(method);
   }
 
   public void markProcessed(IRCode code, OptimizationFeedback feedback) {
@@ -1727,209 +1675,6 @@
     }
   }
 
-  private RegisterAllocator performRegisterAllocation(
-      IRCode code,
-      DexEncodedMethod method,
-      MethodConversionOptions conversionOptions,
-      Timing timing) {
-    // Always perform dead code elimination before register allocation. The register allocator
-    // does not allow dead code (to make sure that we do not waste registers for unneeded values).
-    assert deadCodeRemover.verifyNoDeadCode(code);
-    materializeInstructionBeforeLongOperationsWorkaround(code);
-    workaroundForwardingInitializerBug(code);
-    timing.begin("Allocate registers");
-    LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
-    registerAllocator.allocateRegisters();
-    timing.end();
-    if (options.canHaveExceptionTargetingLoopHeaderBug()) {
-      codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
-    }
-    printMethod(code, "After register allocation (non-SSA)", null);
-    if (conversionOptions.isPeepholeOptimizationsEnabled()) {
-      timing.begin("Peephole optimize");
-      for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
-        CodeRewriter.collapseTrivialGotos(code);
-        PeepholeOptimizer.optimize(code, registerAllocator);
-      }
-      timing.end();
-    }
-    timing.begin("Clean up");
-    CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
-    CodeRewriter.collapseTrivialGotos(code);
-    timing.end();
-    if (Log.ENABLED) {
-      Log.debug(getClass(), "Final (non-SSA) flow graph for %s:\n%s",
-          method.toSourceString(), code);
-    }
-    return registerAllocator;
-  }
-
-  private void workaroundForwardingInitializerBug(IRCode code) {
-    if (!options.canHaveForwardingInitInliningBug()) {
-      return;
-    }
-    // Only constructors.
-    if (!code.method().isInstanceInitializer()) {
-      return;
-    }
-    // Only constructors with certain signatures.
-    DexTypeList paramTypes = code.method().getReference().proto.parameters;
-    if (paramTypes.size() != 3 ||
-        paramTypes.values[0] != options.itemFactory.doubleType ||
-        paramTypes.values[1] != options.itemFactory.doubleType ||
-        !paramTypes.values[2].isClassType()) {
-      return;
-    }
-    // Only if the constructor contains a super constructor call taking only parameters as
-    // inputs.
-    for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator(code);
-      Instruction superConstructorCall =
-          it.nextUntil(
-              (i) ->
-                  i.isInvokeDirect()
-                      && i.asInvokeDirect().getInvokedMethod().name
-                          == options.itemFactory.constructorMethodName
-                      && i.asInvokeDirect().arguments().size() == 4
-                      && i.asInvokeDirect().arguments().stream().allMatch(Value::isArgument));
-      if (superConstructorCall != null) {
-        // We force a materializing const instruction in front of the super call to make
-        // sure that there is at least one temporary register in the method. That disables
-        // the inlining that is crashing on these devices.
-        ensureInstructionBefore(code, superConstructorCall, it);
-        break;
-      }
-    }
-  }
-
-  /**
-   * For each block, we look to see if the header matches:
-   *
-   * <pre>
-   *   pseudo-instructions*
-   *   v2 <- long-{mul,div} v0 v1
-   *   pseudo-instructions*
-   *   v5 <- long-{add,sub} v3 v4
-   * </pre>
-   *
-   * where v2 ~=~ v3 or v2 ~=~ v4 (with ~=~ being equal or an alias of) and the block is not a
-   * fallthrough target.
-   */
-  private void materializeInstructionBeforeLongOperationsWorkaround(IRCode code) {
-    if (!options.canHaveDex2OatLinkedListBug()) {
-      return;
-    }
-    DexItemFactory factory = options.itemFactory;
-    final Supplier<DexMethod> javaLangLangSignum =
-        Suppliers.memoize(
-            () ->
-                factory.createMethod(
-                    factory.createString("Ljava/lang/Long;"),
-                    factory.createString("signum"),
-                    factory.intDescriptor,
-                    new DexString[] {factory.longDescriptor}));
-    for (BasicBlock block : code.blocks) {
-      InstructionListIterator it = block.listIterator(code);
-      Instruction firstMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
-      if (!isLongMul(firstMaterializing)) {
-        continue;
-      }
-      Instruction secondMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
-      if (!isLongAddOrSub(secondMaterializing)) {
-        continue;
-      }
-      if (isFallthoughTarget(block)) {
-        continue;
-      }
-      Value outOfMul = firstMaterializing.outValue();
-      for (Value inOfAddOrSub : secondMaterializing.inValues()) {
-        if (isAliasOf(inOfAddOrSub, outOfMul)) {
-          it = block.listIterator(code);
-          it.nextUntil(i -> i == firstMaterializing);
-          Value longValue = firstMaterializing.inValues().get(0);
-          InvokeStatic invokeLongSignum =
-              new InvokeStatic(
-                  javaLangLangSignum.get(), null, Collections.singletonList(longValue));
-          ensureThrowingInstructionBefore(code, firstMaterializing, it, invokeLongSignum);
-          return;
-        }
-      }
-    }
-  }
-
-  private static boolean isAliasOf(Value usedValue, Value definingValue) {
-    while (true) {
-      if (usedValue == definingValue) {
-        return true;
-      }
-      Instruction definition = usedValue.definition;
-      if (definition == null || !definition.isMove()) {
-        return false;
-      }
-      usedValue = definition.asMove().src();
-    }
-  }
-
-  private static boolean isNotPseudoInstruction(Instruction instruction) {
-    return !(instruction.isDebugInstruction() || instruction.isMove());
-  }
-
-  private static boolean isLongMul(Instruction instruction) {
-    return instruction != null
-        && instruction.isMul()
-        && instruction.asBinop().getNumericType() == NumericType.LONG
-        && instruction.outValue() != null;
-  }
-
-  private static boolean isLongAddOrSub(Instruction instruction) {
-    return instruction != null
-        && (instruction.isAdd() || instruction.isSub())
-        && instruction.asBinop().getNumericType() == NumericType.LONG;
-  }
-
-  private static boolean isFallthoughTarget(BasicBlock block) {
-    for (BasicBlock pred : block.getPredecessors()) {
-      if (pred.exit().fallthroughBlock() == block) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private void ensureThrowingInstructionBefore(
-      IRCode code, Instruction addBefore, InstructionListIterator it, Instruction instruction) {
-    Instruction check = it.previous();
-    assert addBefore == check;
-    BasicBlock block = check.getBlock();
-    if (block.hasCatchHandlers()) {
-      // Split so the existing instructions retain their handlers and the new instruction has none.
-      BasicBlock split = it.split(code);
-      assert split.hasCatchHandlers();
-      assert !block.hasCatchHandlers();
-      it = block.listIterator(code, block.getInstructions().size() - 1);
-    }
-    instruction.setPosition(addBefore.getPosition());
-    it.add(instruction);
-  }
-
-  private static void ensureInstructionBefore(
-      IRCode code, Instruction addBefore, InstructionListIterator it) {
-    // Force materialize a constant-zero before the long operation.
-    Instruction check = it.previous();
-    assert addBefore == check;
-    // Forced definition of const-zero
-    Value fixitValue = code.createValue(TypeElement.getInt());
-    Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue);
-    fixitDefinition.setBlock(addBefore.getBlock());
-    fixitDefinition.setPosition(addBefore.getPosition());
-    it.add(fixitDefinition);
-    // Forced user of the forced definition to ensure it has a user and thus live range.
-    Instruction fixitUser = new AlwaysMaterializingUser(fixitValue);
-    fixitUser.setBlock(addBefore.getBlock());
-    fixitUser.setPosition(addBefore.getPosition());
-    it.add(fixitUser);
-  }
-
   private void printC1VisualizerHeader(DexEncodedMethod method) {
     if (printer != null) {
       printer.begin("compilation");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRFinalizer.java
new file mode 100644
index 0000000..c0c6808
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRFinalizer.java
@@ -0,0 +1,26 @@
+// 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.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.utils.Timing;
+
+public abstract class IRFinalizer<C extends Code> {
+
+  protected final AppView<?> appView;
+  protected final DeadCodeRemover deadCodeRemover;
+
+  public IRFinalizer(AppView<?> appView, DeadCodeRemover deadCodeRemover) {
+    this.appView = appView;
+    this.deadCodeRemover = deadCodeRemover;
+  }
+
+  public abstract C finalizeCode(
+      IRCode code, BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java
new file mode 100644
index 0000000..74d0376
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToCfFinalizer.java
@@ -0,0 +1,27 @@
+// 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.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.utils.Timing;
+
+public class IRToCfFinalizer extends IRFinalizer<CfCode> {
+
+  public IRToCfFinalizer(AppView<?> appView, DeadCodeRemover deadCodeRemover) {
+    super(appView, deadCodeRemover);
+  }
+
+  @Override
+  public CfCode finalizeCode(
+      IRCode code, BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing) {
+    ProgramMethod method = code.context();
+    return new CfBuilder(appView, method, code, bytecodeMetadataProvider).build(deadCodeRemover);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
new file mode 100644
index 0000000..c709bf5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -0,0 +1,81 @@
+// 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.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.RuntimeWorkaroundCodeRewriter;
+import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
+import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+
+public class IRToDexFinalizer extends IRFinalizer<DexCode> {
+
+  private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
+
+  private final CodeRewriter codeRewriter;
+  private final InternalOptions options;
+
+  public IRToDexFinalizer(
+      AppView<?> appView, CodeRewriter codeRewriter, DeadCodeRemover deadCodeRemover) {
+    super(appView, deadCodeRemover);
+    this.codeRewriter = codeRewriter;
+    this.options = appView.options();
+  }
+
+  @Override
+  public DexCode finalizeCode(
+      IRCode code, BytecodeMetadataProvider bytecodeMetadataProvider, Timing timing) {
+    DexEncodedMethod method = code.method();
+    code.traceBlocks();
+    RuntimeWorkaroundCodeRewriter.workaroundNumberConversionRegisterAllocationBug(code, options);
+    // Workaround massive dex2oat memory use for self-recursive methods.
+    RuntimeWorkaroundCodeRewriter.workaroundDex2OatInliningIssue(appView, code);
+    // Workaround MAX_INT switch issue.
+    RuntimeWorkaroundCodeRewriter.workaroundSwitchMaxIntBug(code, codeRewriter, options);
+    RuntimeWorkaroundCodeRewriter.workaroundDex2OatLinkedListBug(code, options);
+    RuntimeWorkaroundCodeRewriter.workaroundForwardingInitializerBug(code, options);
+    RuntimeWorkaroundCodeRewriter.workaroundExceptionTargetingLoopHeaderBug(code, options);
+    // Perform register allocation.
+    RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
+    return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build();
+  }
+
+  private RegisterAllocator performRegisterAllocation(
+      IRCode code, DexEncodedMethod method, Timing timing) {
+    // Always perform dead code elimination before register allocation. The register allocator
+    // does not allow dead code (to make sure that we do not waste registers for unneeded values).
+    assert deadCodeRemover.verifyNoDeadCode(code);
+    timing.begin("Allocate registers");
+    LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
+    registerAllocator.allocateRegisters();
+    timing.end();
+    if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) {
+      timing.begin("Peephole optimize");
+      for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
+        CodeRewriter.collapseTrivialGotos(code);
+        PeepholeOptimizer.optimize(code, registerAllocator);
+      }
+      timing.end();
+    }
+    timing.begin("Clean up");
+    CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
+    CodeRewriter.collapseTrivialGotos(code);
+    timing.end();
+    if (Log.ENABLED) {
+      Log.debug(
+          getClass(), "Final (non-SSA) flow graph for %s:\n%s", method.toSourceString(), code);
+    }
+    return registerAllocator;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
index 1dd9510..472f838 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
@@ -4,45 +4,64 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.InternalOptions;
+
 public abstract class MethodConversionOptions {
 
+  public abstract boolean isGeneratingClassFiles();
+
+  public final boolean isGeneratingDex() {
+    return !isGeneratingClassFiles();
+  }
+
   public abstract boolean isPeepholeOptimizationsEnabled();
 
   public static class MutableMethodConversionOptions extends MethodConversionOptions {
 
-    private final MethodProcessor methodProcessor;
     private boolean enablePeepholeOptimizations = true;
+    private boolean isGeneratingClassFiles;
 
-    public MutableMethodConversionOptions(MethodProcessor methodProcessor) {
-      this.methodProcessor = methodProcessor;
+    public MutableMethodConversionOptions(InternalOptions options) {
+      this.isGeneratingClassFiles = options.isGeneratingClassFiles();
     }
 
-    public void disablePeepholeOptimizations() {
+    public void disablePeepholeOptimizations(MethodProcessor methodProcessor) {
       assert methodProcessor.isPrimaryMethodProcessor();
       enablePeepholeOptimizations = false;
     }
 
+    public MutableMethodConversionOptions setIsGeneratingClassFiles(
+        boolean isGeneratingClassFiles) {
+      this.isGeneratingClassFiles = isGeneratingClassFiles;
+      return this;
+    }
+
+    @Override
+    public boolean isGeneratingClassFiles() {
+      return isGeneratingClassFiles;
+    }
+
     @Override
     public boolean isPeepholeOptimizationsEnabled() {
-      assert enablePeepholeOptimizations || methodProcessor.isPrimaryMethodProcessor();
       return enablePeepholeOptimizations;
     }
   }
 
-  public static class DefaultMethodConversionOptions extends MethodConversionOptions {
+  public static class ThrowingMethodConversionOptions extends MutableMethodConversionOptions {
 
-    private static final DefaultMethodConversionOptions INSTANCE =
-        new DefaultMethodConversionOptions();
+    public ThrowingMethodConversionOptions(InternalOptions options) {
+      super(options);
+    }
 
-    private DefaultMethodConversionOptions() {}
-
-    public static DefaultMethodConversionOptions getInstance() {
-      return INSTANCE;
+    @Override
+    public boolean isGeneratingClassFiles() {
+      throw new Unreachable();
     }
 
     @Override
     public boolean isPeepholeOptimizationsEnabled() {
-      return true;
+      throw new Unreachable();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index 845793f..6c4a72d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -50,13 +50,17 @@
   private final IdentifierNameStringMarker identifierNameStringMarker;
   private final ClassTypeElement stringType;
 
+  public StringSwitchRemover(AppView<?> appView) {
+    this(appView, null);
+  }
+
   StringSwitchRemover(AppView<?> appView, IdentifierNameStringMarker identifierNameStringMarker) {
     this.appView = appView;
     this.identifierNameStringMarker = identifierNameStringMarker;
     this.stringType = TypeElement.stringClassType(appView, definitelyNotNull());
   }
 
-  void run(IRCode code) {
+  public void run(IRCode code) {
     if (!code.metadata().mayHaveStringSwitch()) {
       assert Streams.stream(code.instructions()).noneMatch(Instruction::isStringSwitch);
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index b7909df..5373a30 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence;
 import java.util.Arrays;
@@ -17,10 +18,12 @@
   private static final int UNKNOW_HASH = -1;
   private static final int MAX_HASH_INSTRUCTIONS = 5;
   private final RegisterAllocator allocator;
+  private final MethodConversionOptions conversionOptions;
   private final int[] hashes;
 
   BasicBlockInstructionsEquivalence(IRCode code, RegisterAllocator allocator) {
     this.allocator = allocator;
+    this.conversionOptions = code.getConversionOptions();
     hashes = new int[code.getCurrentBlockNumber() + 1];
     Arrays.fill(hashes, UNKNOW_HASH);
   }
@@ -34,7 +37,7 @@
     for (int i = 0; i < instructions0.size(); i++) {
       Instruction i0 = instructions0.get(i);
       Instruction i1 = instructions1.get(i);
-      if (!i0.identicalAfterRegisterAllocation(i1, allocator)) {
+      if (!i0.identicalAfterRegisterAllocation(i1, allocator, conversionOptions)) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 204b1a6..a0aec41 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -39,7 +39,6 @@
 import com.android.tools.r8.ir.analysis.value.ConstantOrNonConstantNumberValue;
 import com.android.tools.r8.ir.analysis.value.SingleConstClassValue;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
-import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
 import com.android.tools.r8.ir.code.ArrayLength;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.Assume;
@@ -158,7 +157,6 @@
   private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE;
   // This constant was determined by experimentation.
   private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
-  private static final int SELF_RECURSION_LIMIT = 4;
 
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
@@ -489,41 +487,6 @@
     }
   }
 
-  // For method with many self-recursive calls, insert a try-catch to disable inlining.
-  // Marshmallow dex2oat aggressively inlines and eats up all the memory on devices.
-  public static void disableDex2OatInliningForSelfRecursiveMethods(
-      AppView<?> appView, IRCode code) {
-    if (!appView.options().canHaveDex2OatInliningIssue() || code.hasCatchHandlers()) {
-      // Catch handlers disables inlining, so if the method already has catch handlers
-      // there is nothing to do.
-      return;
-    }
-    int selfRecursionFanOut = 0;
-    Instruction lastSelfRecursiveCall = null;
-    for (Instruction i : code.instructions()) {
-      if (i.isInvokeMethod()
-          && i.asInvokeMethod().getInvokedMethod() == code.method().getReference()) {
-        selfRecursionFanOut++;
-        lastSelfRecursiveCall = i;
-      }
-    }
-    if (selfRecursionFanOut > SELF_RECURSION_LIMIT) {
-      assert lastSelfRecursiveCall != null;
-      // Split out the last recursive call in its own block.
-      InstructionListIterator splitIterator =
-          lastSelfRecursiveCall.getBlock().listIterator(code, lastSelfRecursiveCall);
-      splitIterator.previous();
-      BasicBlock newBlock = splitIterator.split(code, 1);
-      // Generate rethrow block.
-      DexType guard = appView.dexItemFactory().throwableType;
-      BasicBlock rethrowBlock =
-          BasicBlock.createRethrowBlock(code, lastSelfRecursiveCall.getPosition(), guard, appView);
-      code.blocks.add(rethrowBlock);
-      // Add catch handler to the block containing the last recursive call.
-      newBlock.appendCatchHandler(rethrowBlock, guard);
-    }
-  }
-
   // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
   public abstract static class InstructionBuilder<T> {
     protected int blockNumber;
@@ -663,7 +626,7 @@
    * Covert the switch instruction to a sequence of if instructions checking for a specified set of
    * keys, followed by a new switch with the remaining keys.
    */
-  private void convertSwitchToSwitchAndIfs(
+  void convertSwitchToSwitchAndIfs(
       IRCode code,
       ListIterator<BasicBlock> blocksIterator,
       BasicBlock originalBlock,
@@ -945,57 +908,6 @@
     return rewriteSwitchFull(code, switchCaseAnalyzer);
   }
 
-  public void rewriteSwitchForMaxInt(IRCode code) {
-    if (options.canHaveSwitchMaxIntBug() && code.metadata().mayHaveSwitch()) {
-      // Always rewrite for workaround switch bug.
-      rewriteSwitchForMaxIntOnly(code);
-    }
-  }
-
-  private void rewriteSwitchForMaxIntOnly(IRCode code) {
-    boolean needToSplitCriticalEdges = false;
-    ListIterator<BasicBlock> blocksIterator = code.listIterator();
-    while (blocksIterator.hasNext()) {
-      BasicBlock block = blocksIterator.next();
-      InstructionListIterator iterator = block.listIterator(code);
-      while (iterator.hasNext()) {
-        Instruction instruction = iterator.next();
-        assert !instruction.isStringSwitch();
-        if (instruction.isIntSwitch()) {
-          IntSwitch intSwitch = instruction.asIntSwitch();
-          if (intSwitch.getKey(intSwitch.numberOfKeys() - 1) == Integer.MAX_VALUE) {
-            if (intSwitch.numberOfKeys() == 1) {
-              rewriteSingleKeySwitchToIf(code, block, iterator, intSwitch);
-            } else {
-              IntList newSwitchSequences = new IntArrayList(intSwitch.numberOfKeys() - 1);
-              for (int i = 0; i < intSwitch.numberOfKeys() - 1; i++) {
-                newSwitchSequences.add(intSwitch.getKey(i));
-              }
-              IntList outliers = new IntArrayList(1);
-              outliers.add(Integer.MAX_VALUE);
-              convertSwitchToSwitchAndIfs(
-                  code,
-                  blocksIterator,
-                  block,
-                  iterator,
-                  intSwitch,
-                  ImmutableList.of(newSwitchSequences),
-                  outliers);
-            }
-            needToSplitCriticalEdges = true;
-          }
-        }
-      }
-    }
-
-    // Rewriting of switches introduces new branching structure. It relies on critical edges
-    // being split on the way in but does not maintain this property. We therefore split
-    // critical edges at exit.
-    if (needToSplitCriticalEdges) {
-      code.splitCriticalEdges();
-    }
-  }
-
   private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
     boolean needToRemoveUnreachableBlocks = false;
     ListIterator<BasicBlock> blocksIterator = code.listIterator();
@@ -1045,7 +957,7 @@
     return !affectedValues.isEmpty();
   }
 
-  private void rewriteSingleKeySwitchToIf(
+  void rewriteSingleKeySwitchToIf(
       IRCode code, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) {
     // Rewrite the switch to an if.
     int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
@@ -3870,78 +3782,4 @@
       }
     }
   }
-
-  // See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug().
-  public void workaroundNumberConversionRegisterAllocationBug(IRCode code) {
-    ListIterator<BasicBlock> blocks = code.listIterator();
-    while (blocks.hasNext()) {
-      BasicBlock block = blocks.next();
-      InstructionListIterator it = block.listIterator(code);
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        if (instruction.isArithmeticBinop() || instruction.isNeg()) {
-          for (Value value : instruction.inValues()) {
-            // Insert a call to Double.isNaN on each value which come from a number conversion
-            // to double and flows into an arithmetic instruction. This seems to break the traces
-            // in the Dalvik JIT and avoid the bug where the generated ARM code can clobber float
-            // values in a single-precision registers with double values written to
-            // double-precision registers. See b/77496850 for examples.
-            if (!value.isPhi()
-                && value.definition.isNumberConversion()
-                && value.definition.asNumberConversion().to == NumericType.DOUBLE) {
-              InvokeStatic invokeIsNaN =
-                  new InvokeStatic(
-                      dexItemFactory.doubleMembers.isNaN, null, ImmutableList.of(value));
-              invokeIsNaN.setPosition(instruction.getPosition());
-
-              // Insert the invoke before the current instruction.
-              it.previous();
-              BasicBlock blockWithInvokeNaN =
-                  block.hasCatchHandlers() ? it.split(code, blocks) : block;
-              if (blockWithInvokeNaN != block) {
-                // If we split, add the invoke at the end of the original block.
-                it = block.listIterator(code, block.getInstructions().size());
-                it.previous();
-                it.add(invokeIsNaN);
-                // Continue iteration in the split block.
-                block = blockWithInvokeNaN;
-                it = block.listIterator(code);
-              } else {
-                // Otherwise, add it to the current block.
-                it.add(invokeIsNaN);
-              }
-              // Skip over the instruction causing the invoke to be inserted.
-              Instruction temp = it.next();
-              assert temp == instruction;
-            }
-          }
-        }
-      }
-    }
-  }
-
-  // If an exceptional edge could target a conditional-loop header ensure that we have a
-  // materializing instruction on that path to work around a bug in some L x86_64 non-emulator VMs.
-  // See b/111337896.
-  public void workaroundExceptionTargetingLoopHeaderBug(IRCode code) {
-    for (BasicBlock block : code.blocks) {
-      if (block.hasCatchHandlers()) {
-        for (BasicBlock handler : block.getCatchHandlers().getUniqueTargets()) {
-          // We conservatively assume that a block with at least two normal predecessors is a loop
-          // header. If we ever end up computing exact loop headers, use that here instead.
-          // The loop is conditional if it has at least two normal successors.
-          BasicBlock target = handler.endOfGotoChain();
-          if (target != null
-              && target.getPredecessors().size() > 1
-              && target.getNormalPredecessors().size() > 1
-              && target.getNormalSuccessors().size() > 1) {
-            Instruction fixit = new AlwaysMaterializingNop();
-            fixit.setBlock(handler);
-            fixit.setPosition(handler.getPosition());
-            handler.getInstructions().addFirst(fixit);
-          }
-        }
-      }
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java
index 2f2523d..68207cd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InstructionEquivalence.java
@@ -3,21 +3,25 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence;
 
 public class InstructionEquivalence extends Equivalence<Instruction> {
   private final RegisterAllocator allocator;
+  private final MethodConversionOptions conversionOptions;
 
-  InstructionEquivalence(RegisterAllocator allocator) {
+  InstructionEquivalence(RegisterAllocator allocator, IRCode code) {
     this.allocator = allocator;
+    this.conversionOptions = code.getConversionOptions();
   }
 
   @Override
   protected boolean doEquivalent(Instruction a, Instruction b) {
-    return a.identicalAfterRegisterAllocation(b, allocator)
+    return a.identicalAfterRegisterAllocation(b, allocator, conversionOptions)
         && a.getBlock().getCatchHandlers().equals(b.getBlock().getCatchHandlers());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index f04e328..fa902a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -59,6 +59,7 @@
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -1794,9 +1795,13 @@
     }
 
     @Override
-    public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    public IRCode buildIR(
+        ProgramMethod method,
+        AppView<?> appView,
+        Origin origin,
+        MutableMethodConversionOptions conversionOptions) {
       OutlineSourceCode source = new OutlineSourceCode(outline, method.getReference());
-      return IRBuilder.create(method, appView, source, origin).build(method);
+      return IRBuilder.create(method, appView, source, origin).build(method, conversionOptions);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index a8cd620..ffda2f3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -46,7 +46,7 @@
 
   /** Identify common prefixes in successor blocks and share them. */
   private static void shareIdenticalBlockPrefix(IRCode code, RegisterAllocator allocator) {
-    InstructionEquivalence equivalence = new InstructionEquivalence(allocator);
+    InstructionEquivalence equivalence = new InstructionEquivalence(allocator, code);
     Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
     for (BasicBlock block : code.blocks) {
       shareIdenticalBlockPrefixFromNormalSuccessors(
@@ -244,7 +244,7 @@
     do {
       Map<BasicBlock, BasicBlock> newBlocks = new IdentityHashMap<>();
       for (BasicBlock block : blocks) {
-        InstructionEquivalence equivalence = new InstructionEquivalence(allocator);
+        InstructionEquivalence equivalence = new InstructionEquivalence(allocator, code);
         // Group interesting predecessor blocks by their last instruction.
         Map<Wrapper<Instruction>, List<BasicBlock>> lastInstructionToBlocks = new HashMap<>();
         for (BasicBlock pred : block.getPredecessors()) {
@@ -284,7 +284,7 @@
             BasicBlock pred = predsWithSameLastInstruction.get(i);
             assert pred.exit().isGoto() || pred.exit().isReturn();
             commonSuffixSize =
-                Math.min(commonSuffixSize, sharedSuffixSize(firstPred, pred, allocator));
+                Math.min(commonSuffixSize, sharedSuffixSize(firstPred, pred, allocator, code));
           }
 
           int sizeDelta = overhead - (predsWithSameLastInstruction.size() - 1) * commonSuffixSize;
@@ -403,7 +403,7 @@
   }
 
   private static int sharedSuffixSize(
-      BasicBlock block0, BasicBlock block1, RegisterAllocator allocator) {
+      BasicBlock block0, BasicBlock block1, RegisterAllocator allocator, IRCode code) {
     assert block0.exit().isGoto() || block0.exit().isReturn();
     // If the blocks do not agree on locals at exit then they don't have any shared suffix.
     if (!Objects.equals(localsAtBlockExit(block0), localsAtBlockExit(block1))) {
@@ -415,7 +415,7 @@
     while (it0.hasPrevious() && it1.hasPrevious()) {
       Instruction i0 = it0.previous();
       Instruction i1 = it1.previous();
-      if (!i0.identicalAfterRegisterAllocation(i1, allocator)) {
+      if (!i0.identicalAfterRegisterAllocation(i1, allocator, code.getConversionOptions())) {
         return suffixSize;
       }
       suffixSize++;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
new file mode 100644
index 0000000..20b26b0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
@@ -0,0 +1,375 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
+import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
+import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.Collections;
+import java.util.ListIterator;
+import java.util.function.Supplier;
+
+public class RuntimeWorkaroundCodeRewriter {
+
+  private static final int SELF_RECURSION_LIMIT = 4;
+
+  // For method with many self-recursive calls, insert a try-catch to disable inlining.
+  // Marshmallow dex2oat aggressively inlines and eats up all the memory on devices.
+  public static void workaroundDex2OatInliningIssue(AppView<?> appView, IRCode code) {
+    if (!appView.options().canHaveDex2OatInliningIssue() || code.hasCatchHandlers()) {
+      // Catch handlers disables inlining, so if the method already has catch handlers
+      // there is nothing to do.
+      return;
+    }
+    int selfRecursionFanOut = 0;
+    Instruction lastSelfRecursiveCall = null;
+    for (Instruction i : code.instructions()) {
+      if (i.isInvokeMethod()
+          && i.asInvokeMethod().getInvokedMethod() == code.method().getReference()) {
+        selfRecursionFanOut++;
+        lastSelfRecursiveCall = i;
+      }
+    }
+    if (selfRecursionFanOut > SELF_RECURSION_LIMIT) {
+      assert lastSelfRecursiveCall != null;
+      // Split out the last recursive call in its own block.
+      InstructionListIterator splitIterator =
+          lastSelfRecursiveCall.getBlock().listIterator(code, lastSelfRecursiveCall);
+      splitIterator.previous();
+      BasicBlock newBlock = splitIterator.split(code, 1);
+      // Generate rethrow block.
+      DexType guard = appView.dexItemFactory().throwableType;
+      BasicBlock rethrowBlock =
+          BasicBlock.createRethrowBlock(code, lastSelfRecursiveCall.getPosition(), guard, appView);
+      code.blocks.add(rethrowBlock);
+      // Add catch handler to the block containing the last recursive call.
+      newBlock.appendCatchHandler(rethrowBlock, guard);
+    }
+  }
+
+  /**
+   * For each block, we look to see if the header matches:
+   *
+   * <pre>
+   *   pseudo-instructions*
+   *   v2 <- long-{mul,div} v0 v1
+   *   pseudo-instructions*
+   *   v5 <- long-{add,sub} v3 v4
+   * </pre>
+   *
+   * where v2 ~=~ v3 or v2 ~=~ v4 (with ~=~ being equal or an alias of) and the block is not a
+   * fallthrough target.
+   */
+  public static void workaroundDex2OatLinkedListBug(IRCode code, InternalOptions options) {
+    if (!options.canHaveDex2OatLinkedListBug()) {
+      return;
+    }
+    DexItemFactory factory = options.itemFactory;
+    final Supplier<DexMethod> javaLangLangSignum =
+        Suppliers.memoize(
+            () ->
+                factory.createMethod(
+                    factory.createString("Ljava/lang/Long;"),
+                    factory.createString("signum"),
+                    factory.intDescriptor,
+                    new DexString[] {factory.longDescriptor}));
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator it = block.listIterator(code);
+      Instruction firstMaterializing =
+          it.nextUntil(RuntimeWorkaroundCodeRewriter::isNotPseudoInstruction);
+      if (!isLongMul(firstMaterializing)) {
+        continue;
+      }
+      Instruction secondMaterializing =
+          it.nextUntil(RuntimeWorkaroundCodeRewriter::isNotPseudoInstruction);
+      if (!isLongAddOrSub(secondMaterializing)) {
+        continue;
+      }
+      if (isFallthoughTarget(block)) {
+        continue;
+      }
+      Value outOfMul = firstMaterializing.outValue();
+      for (Value inOfAddOrSub : secondMaterializing.inValues()) {
+        if (isAliasOf(inOfAddOrSub, outOfMul)) {
+          it = block.listIterator(code);
+          it.nextUntil(i -> i == firstMaterializing);
+          Value longValue = firstMaterializing.inValues().get(0);
+          InvokeStatic invokeLongSignum =
+              new InvokeStatic(
+                  javaLangLangSignum.get(), null, Collections.singletonList(longValue));
+          ensureThrowingInstructionBefore(code, firstMaterializing, it, invokeLongSignum);
+          return;
+        }
+      }
+    }
+  }
+
+  // If an exceptional edge could target a conditional-loop header ensure that we have a
+  // materializing instruction on that path to work around a bug in some L x86_64 non-emulator VMs.
+  // See b/111337896.
+  public static void workaroundExceptionTargetingLoopHeaderBug(
+      IRCode code, InternalOptions options) {
+    if (!options.canHaveExceptionTargetingLoopHeaderBug()) {
+      return;
+    }
+    for (BasicBlock block : code.blocks) {
+      if (block.hasCatchHandlers()) {
+        for (BasicBlock handler : block.getCatchHandlers().getUniqueTargets()) {
+          // We conservatively assume that a block with at least two normal predecessors is a loop
+          // header. If we ever end up computing exact loop headers, use that here instead.
+          // The loop is conditional if it has at least two normal successors.
+          BasicBlock target = handler.endOfGotoChain();
+          if (target != null
+              && target.getPredecessors().size() > 1
+              && target.getNormalPredecessors().size() > 1
+              && target.getNormalSuccessors().size() > 1) {
+            Instruction fixit = new AlwaysMaterializingNop();
+            fixit.setBlock(handler);
+            fixit.setPosition(handler.getPosition());
+            handler.getInstructions().addFirst(fixit);
+          }
+        }
+      }
+    }
+  }
+
+  public static void workaroundForwardingInitializerBug(IRCode code, InternalOptions options) {
+    if (!options.canHaveForwardingInitInliningBug()) {
+      return;
+    }
+    // Only constructors.
+    if (!code.method().isInstanceInitializer()) {
+      return;
+    }
+    // Only constructors with certain signatures.
+    DexTypeList paramTypes = code.method().getReference().proto.parameters;
+    if (paramTypes.size() != 3
+        || paramTypes.values[0] != options.itemFactory.doubleType
+        || paramTypes.values[1] != options.itemFactory.doubleType
+        || !paramTypes.values[2].isClassType()) {
+      return;
+    }
+    // Only if the constructor contains a super constructor call taking only parameters as
+    // inputs.
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator it = block.listIterator(code);
+      Instruction superConstructorCall =
+          it.nextUntil(
+              (i) ->
+                  i.isInvokeDirect()
+                      && i.asInvokeDirect().getInvokedMethod().name
+                          == options.itemFactory.constructorMethodName
+                      && i.asInvokeDirect().arguments().size() == 4
+                      && i.asInvokeDirect().arguments().stream().allMatch(Value::isArgument));
+      if (superConstructorCall != null) {
+        // We force a materializing const instruction in front of the super call to make
+        // sure that there is at least one temporary register in the method. That disables
+        // the inlining that is crashing on these devices.
+        ensureInstructionBefore(code, superConstructorCall, it);
+        break;
+      }
+    }
+  }
+
+  public static void workaroundSwitchMaxIntBug(
+      IRCode code, CodeRewriter codeRewriter, InternalOptions options) {
+    if (options.canHaveSwitchMaxIntBug() && code.metadata().mayHaveSwitch()) {
+      // Always rewrite for workaround switch bug.
+      rewriteSwitchForMaxIntOnly(code, codeRewriter);
+    }
+  }
+
+  private static void rewriteSwitchForMaxIntOnly(IRCode code, CodeRewriter codeRewriter) {
+    boolean needToSplitCriticalEdges = false;
+    ListIterator<BasicBlock> blocksIterator = code.listIterator();
+    while (blocksIterator.hasNext()) {
+      BasicBlock block = blocksIterator.next();
+      InstructionListIterator iterator = block.listIterator(code);
+      while (iterator.hasNext()) {
+        Instruction instruction = iterator.next();
+        assert !instruction.isStringSwitch();
+        if (instruction.isIntSwitch()) {
+          IntSwitch intSwitch = instruction.asIntSwitch();
+          if (intSwitch.getKey(intSwitch.numberOfKeys() - 1) == Integer.MAX_VALUE) {
+            if (intSwitch.numberOfKeys() == 1) {
+              codeRewriter.rewriteSingleKeySwitchToIf(code, block, iterator, intSwitch);
+            } else {
+              IntList newSwitchSequences = new IntArrayList(intSwitch.numberOfKeys() - 1);
+              for (int i = 0; i < intSwitch.numberOfKeys() - 1; i++) {
+                newSwitchSequences.add(intSwitch.getKey(i));
+              }
+              IntList outliers = new IntArrayList(1);
+              outliers.add(Integer.MAX_VALUE);
+              codeRewriter.convertSwitchToSwitchAndIfs(
+                  code,
+                  blocksIterator,
+                  block,
+                  iterator,
+                  intSwitch,
+                  ImmutableList.of(newSwitchSequences),
+                  outliers);
+            }
+            needToSplitCriticalEdges = true;
+          }
+        }
+      }
+    }
+
+    // Rewriting of switches introduces new branching structure. It relies on critical edges
+    // being split on the way in but does not maintain this property. We therefore split
+    // critical edges at exit.
+    if (needToSplitCriticalEdges) {
+      code.splitCriticalEdges();
+    }
+  }
+
+  // See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug().
+  public static void workaroundNumberConversionRegisterAllocationBug(
+      IRCode code, InternalOptions options) {
+    if (!options.canHaveNumberConversionRegisterAllocationBug()) {
+      return;
+    }
+
+    DexItemFactory dexItemFactory = options.dexItemFactory();
+    ListIterator<BasicBlock> blocks = code.listIterator();
+    while (blocks.hasNext()) {
+      BasicBlock block = blocks.next();
+      InstructionListIterator it = block.listIterator(code);
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        if (instruction.isArithmeticBinop() || instruction.isNeg()) {
+          for (Value value : instruction.inValues()) {
+            // Insert a call to Double.isNaN on each value which come from a number conversion
+            // to double and flows into an arithmetic instruction. This seems to break the traces
+            // in the Dalvik JIT and avoid the bug where the generated ARM code can clobber float
+            // values in a single-precision registers with double values written to
+            // double-precision registers. See b/77496850 for examples.
+            if (!value.isPhi()
+                && value.definition.isNumberConversion()
+                && value.definition.asNumberConversion().to == NumericType.DOUBLE) {
+              InvokeStatic invokeIsNaN =
+                  new InvokeStatic(
+                      dexItemFactory.doubleMembers.isNaN, null, ImmutableList.of(value));
+              invokeIsNaN.setPosition(instruction.getPosition());
+
+              // Insert the invoke before the current instruction.
+              it.previous();
+              BasicBlock blockWithInvokeNaN =
+                  block.hasCatchHandlers() ? it.split(code, blocks) : block;
+              if (blockWithInvokeNaN != block) {
+                // If we split, add the invoke at the end of the original block.
+                it = block.listIterator(code, block.getInstructions().size());
+                it.previous();
+                it.add(invokeIsNaN);
+                // Continue iteration in the split block.
+                block = blockWithInvokeNaN;
+                it = block.listIterator(code);
+              } else {
+                // Otherwise, add it to the current block.
+                it.add(invokeIsNaN);
+              }
+              // Skip over the instruction causing the invoke to be inserted.
+              Instruction temp = it.next();
+              assert temp == instruction;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  private static void ensureInstructionBefore(
+      IRCode code, Instruction addBefore, InstructionListIterator it) {
+    // Force materialize a constant-zero before the long operation.
+    Instruction check = it.previous();
+    assert addBefore == check;
+    // Forced definition of const-zero
+    Value fixitValue = code.createValue(TypeElement.getInt());
+    Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue);
+    fixitDefinition.setBlock(addBefore.getBlock());
+    fixitDefinition.setPosition(addBefore.getPosition());
+    it.add(fixitDefinition);
+    // Forced user of the forced definition to ensure it has a user and thus live range.
+    Instruction fixitUser = new AlwaysMaterializingUser(fixitValue);
+    fixitUser.setBlock(addBefore.getBlock());
+    fixitUser.setPosition(addBefore.getPosition());
+    it.add(fixitUser);
+  }
+
+  private static void ensureThrowingInstructionBefore(
+      IRCode code, Instruction addBefore, InstructionListIterator it, Instruction instruction) {
+    Instruction check = it.previous();
+    assert addBefore == check;
+    BasicBlock block = check.getBlock();
+    if (block.hasCatchHandlers()) {
+      // Split so the existing instructions retain their handlers and the new instruction has none.
+      BasicBlock split = it.split(code);
+      assert split.hasCatchHandlers();
+      assert !block.hasCatchHandlers();
+      it = block.listIterator(code, block.getInstructions().size() - 1);
+    }
+    instruction.setPosition(addBefore.getPosition());
+    it.add(instruction);
+  }
+
+  private static boolean isNotPseudoInstruction(Instruction instruction) {
+    return !(instruction.isDebugInstruction() || instruction.isMove());
+  }
+
+  private static boolean isAliasOf(Value usedValue, Value definingValue) {
+    while (true) {
+      if (usedValue == definingValue) {
+        return true;
+      }
+      Instruction definition = usedValue.definition;
+      if (definition == null || !definition.isMove()) {
+        return false;
+      }
+      usedValue = definition.asMove().src();
+    }
+  }
+
+  private static boolean isLongMul(Instruction instruction) {
+    return instruction != null
+        && instruction.isMul()
+        && instruction.asBinop().getNumericType() == NumericType.LONG
+        && instruction.outValue() != null;
+  }
+
+  private static boolean isLongAddOrSub(Instruction instruction) {
+    return instruction != null
+        && (instruction.isAdd() || instruction.isSub())
+        && instruction.asBinop().getNumericType() == NumericType.LONG;
+  }
+
+  private static boolean isFallthoughTarget(BasicBlock block) {
+    for (BasicBlock pred : block.getPredecessors()) {
+      if (pred.exit().fallthroughBlock() == block) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
index 23ae546..bde3a44 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EmptyEnumUnboxer.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -38,7 +37,7 @@
   }
 
   @Override
-  public void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions) {
+  public void analyzeEnums(IRCode code, MethodProcessor methodProcessor) {
     // Intentionally empty.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index ae119f6..659d802 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -35,7 +34,7 @@
   public abstract void prepareForPrimaryOptimizationPass(
       GraphLens graphLensForPrimaryOptimizationPass);
 
-  public abstract void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions);
+  public abstract void analyzeEnums(IRCode code, MethodProcessor methodProcessor);
 
   public abstract void onMethodPruned(ProgramMethod method);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 700c1d7..d926360 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -75,7 +75,6 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor.Builder;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -236,7 +235,7 @@
   }
 
   @Override
-  public void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions) {
+  public void analyzeEnums(IRCode code, MethodProcessor methodProcessor) {
     Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
     for (BasicBlock block : code.blocks) {
       for (Instruction instruction : block.getInstructions()) {
@@ -307,7 +306,8 @@
       }
     }
     if (methodsDependingOnLibraryModelisation.contains(code.context(), appView.graphLens())) {
-      conversionOptions.disablePeepholeOptimizations();
+      code.mutateConversionOptions(
+          conversionOptions -> conversionOptions.disablePeepholeOptimizations(methodProcessor));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
index c2cbec9..dd73d7e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxerImpl;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -41,7 +42,11 @@
   }
 
   @Override
-  public IRCode buildIR(ProgramMethod checkNotZeroMethod, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(
+      ProgramMethod checkNotZeroMethod,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     // Build IR from the checkNotNull() method.
     IRCode code = checkNotNullMethod.buildIR(appView);
     InstructionListIterator instructionIterator = code.instructionListIterator();
@@ -82,7 +87,8 @@
         code.valueNumberGenerator,
         code.basicBlockNumberGenerator,
         code.metadata(),
-        checkNotZeroMethod.getOrigin());
+        checkNotZeroMethod.getOrigin(),
+        conversionOptions);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index a2ecba8..efd09bc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DominatorTree;
@@ -85,7 +84,6 @@
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
-  private final ThrowingInfo throwingInfo;
   @VisibleForTesting
   StringConcatenationAnalysis analysis;
   final StringBuilderOptimizationConfiguration optimizationConfiguration;
@@ -108,7 +106,6 @@
   public StringBuilderOptimizer(AppView<? extends AppInfo> appView) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
-    this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
     this.optimizationConfiguration = new DefaultStringBuilderOptimizationConfiguration();
     if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
       histogramOfLengthOfAppendChains = new Object2IntArrayMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 827f316..54de6c0 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -2508,11 +2508,7 @@
   }
 
   private static void addLiveRange(
-      Value value,
-      BasicBlock block,
-      int end,
-      List<LiveIntervals> liveIntervals,
-      InternalOptions options) {
+      Value value, BasicBlock block, int end, List<LiveIntervals> liveIntervals, IRCode code) {
     int firstInstructionInBlock = block.entry().getNumber();
     int instructionsSize = block.getInstructions().size() * INSTRUCTION_NUMBER_DELTA;
     int lastInstructionInBlock =
@@ -2548,8 +2544,8 @@
         instructionNumber--;
       }
       intervals.addRange(new LiveRange(instructionNumber, end));
-      assert unconstrainedForCf(intervals.getRegisterLimit(), options);
-      if (options.isGeneratingDex() && !value.isPhi()) {
+      assert unconstrainedForCf(intervals.getRegisterLimit(), code);
+      if (code.getConversionOptions().isGeneratingDex() && !value.isPhi()) {
         int constraint = value.definition.maxOutValueRegister();
         intervals.addUse(new LiveIntervalsUse(instructionNumber, constraint));
       }
@@ -2622,7 +2618,7 @@
         if (phiOperands.contains(value)) {
           end--;
         }
-        addLiveRange(value, block, end, liveIntervals, options);
+        addLiveRange(value, block, end, liveIntervals, code);
       }
       InstructionIterator iterator = block.iterator(block.getInstructions().size());
       while (iterator.hasPrevious()) {
@@ -2642,20 +2638,20 @@
                 block,
                 instruction.getNumber() + INSTRUCTION_NUMBER_DELTA - 1,
                 liveIntervals,
-                options);
-            assert !options.isGeneratingClassFiles() || instruction.isArgument()
+                code);
+            assert !code.getConversionOptions().isGeneratingClassFiles() || instruction.isArgument()
                 : "Arguments should be the only potentially unused local in CF";
           }
           live.remove(definition);
         }
         for (Value use : instruction.inValues()) {
           if (use.needsRegister()) {
-            assert unconstrainedForCf(instruction.maxInValueRegister(), options);
+            assert unconstrainedForCf(instruction.maxInValueRegister(), code);
             if (!live.contains(use)) {
               live.add(use);
-              addLiveRange(use, block, instruction.getNumber(), liveIntervals, options);
+              addLiveRange(use, block, instruction.getNumber(), liveIntervals, code);
             }
-            if (options.isGeneratingDex()) {
+            if (code.getConversionOptions().isGeneratingDex()) {
               int inConstraint = instruction.maxInValueRegister();
               LiveIntervals useIntervals = use.getLiveIntervals();
               // Arguments are always kept in their original, incoming register. For every
@@ -2693,7 +2689,7 @@
                   block,
                   getLiveRangeEndOnExceptionalFlow(instruction, use),
                   liveIntervals,
-                  options);
+                  code);
             }
           }
         }
@@ -2707,7 +2703,7 @@
             assert use.needsRegister();
             if (!live.contains(use)) {
               live.add(use);
-              addLiveRange(use, block, number, liveIntervals, options);
+              addLiveRange(use, block, number, liveIntervals, code);
             }
           }
         }
@@ -2723,8 +2719,8 @@
     return end;
   }
 
-  private static boolean unconstrainedForCf(int constraint, InternalOptions options) {
-    return !options.isGeneratingClassFiles() || constraint == Constants.U16BIT_MAX;
+  private static boolean unconstrainedForCf(int constraint, IRCode code) {
+    return code.getConversionOptions().isGeneratingDex() || constraint == Constants.U16BIT_MAX;
   }
 
   private void clearUserInfo() {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index acf4d56..8c28b9e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -18,6 +18,8 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -39,9 +41,13 @@
   }
 
   @Override
-  public final IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+  public final IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
     return IRBuilder.create(method, appView, getSourceCodeProvider().get(method, null), origin)
-        .build(method);
+        .build(method, conversionOptions);
   }
 
   @Override
@@ -62,7 +68,7 @@
             origin,
             valueNumberGenerator,
             protoChanges)
-        .build(context);
+        .build(context, new ThrowingMethodConversionOptions(appView.options()));
   }
 
   @Override
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 dcf8dbd..b4fd639 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2077,8 +2077,7 @@
   // and the first register of the result could lead to the wrong exception
   // being thrown on out of bounds.
   public boolean canUseSameArrayAndResultRegisterInArrayGetWide() {
-    assert isGeneratingDex();
-    return getMinApiLevel().isGreaterThan(AndroidApiLevel.O_MR1);
+    return isGeneratingClassFiles() || getMinApiLevel().isGreaterThan(AndroidApiLevel.O_MR1);
   }
 
   // Some Lollipop versions of Art found in the wild perform invalid bounds
@@ -2309,8 +2308,7 @@
   //
   // Fixed in Android Q, see b/120985556.
   public boolean canHaveArtInstanceOfVerifierBug() {
-    assert isGeneratingDex();
-    return getMinApiLevel().isLessThan(AndroidApiLevel.Q);
+    return isGeneratingDex() && getMinApiLevel().isLessThan(AndroidApiLevel.Q);
   }
 
   // Some Art Lollipop version do not deal correctly with long-to-int conversions.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index e4ee9bc..198139e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
@@ -145,7 +146,8 @@
             new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
-            Origin.unknown());
+            Origin.unknown(),
+            new MutableMethodConversionOptions(options));
     PeepholeOptimizer.optimize(code, new MockLinearScanRegisterAllocator(appView, code));
 
     // Check that all four constant number instructions remain.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 868036c..c0cc06d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -102,7 +103,8 @@
             new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
-            Origin.unknown());
+            Origin.unknown(),
+            new MutableMethodConversionOptions(options));
     CodeRewriter.collapseTrivialGotos(code);
     assertTrue(code.entryBlock().isTrivialGoto());
     assertTrue(blocks.contains(block0));
@@ -189,7 +191,8 @@
             new NumberGenerator(),
             basicBlockNumberGenerator,
             IRMetadata.unknown(),
-            Origin.unknown());
+            Origin.unknown(),
+            new MutableMethodConversionOptions(options));
     CodeRewriter.collapseTrivialGotos(code);
     assertTrue(block0.getInstructions().get(1).isIf());
     assertEquals(block1, block0.getInstructions().get(1).asIf().fallthroughBlock());
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 9e9b1c4..4319780 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
 import org.junit.Test;
@@ -86,6 +87,8 @@
     assertTrue(value3.needsRegister());
     // value1 and value2 represent different constants and the additions are therefore
     // not equivalent.
-    assertFalse(add0.identicalAfterRegisterAllocation(add1, allocator));
+    assertFalse(
+        add0.identicalAfterRegisterAllocation(
+            add1, allocator, new MutableMethodConversionOptions(allocator.options())));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 249e78d..8e7929e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -59,7 +59,9 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
+import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -868,9 +870,16 @@
                 .disableAndroidApiLevelCheck()
                 .build();
         ProgramMethod programMethod = new ProgramMethod(programClass, method);
-        IRCode ir = code.buildIR(programMethod, appView, Origin.unknown());
+        IRCode ir =
+            code.buildIR(
+                programMethod,
+                appView,
+                Origin.unknown(),
+                new MutableMethodConversionOptions(options));
         RegisterAllocator allocator = new LinearScanRegisterAllocator(appView, ir);
-        method.setCode(ir, BytecodeMetadataProvider.empty(), allocator, appView);
+        method.setCode(
+            new DexBuilder(ir, BytecodeMetadataProvider.empty(), allocator, options).build(),
+            appView);
         directMethods[i] = method;
       }
       programClass.getMethodCollection().addDirectMethods(Arrays.asList(directMethods));