Merge commit 'c04eb1d97999bd1208d389ddab8534928305a141' into dev-release
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 073cc8d..3d5f387 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -325,8 +325,7 @@
       List<DexString> forcedStrings,
       Timing timing)
       throws ExecutionException {
-    TimingMerger merger =
-        timing.beginMerger("Write files", ThreadUtils.getNumberOfThreads(executorService));
+    TimingMerger merger = timing.beginMerger("Write files", executorService);
     Collection<Timing> timings =
         ThreadUtils.processItemsWithResults(
             virtualFiles,
@@ -395,8 +394,7 @@
 
       {
         // Compute offsets and rewrite jumbo strings so that code offsets are fixed.
-        TimingMerger merger =
-            timing.beginMerger("Pre-write phase", ThreadUtils.getNumberOfThreads(executorService));
+        TimingMerger merger = timing.beginMerger("Pre-write phase", executorService);
         Collection<Timing> timings =
             rewriteJumboStringsAndComputeDebugRepresentation(
                 executorService, virtualFiles, lazyDexStrings);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
index 853d01e..8324c91 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
@@ -96,8 +96,7 @@
       List<VirtualFile> virtualFiles,
       List<DexString> forcedStrings,
       Timing timing) {
-    TimingMerger merger =
-        timing.beginMerger("Write files", ThreadUtils.getNumberOfThreads(executorService));
+    TimingMerger merger = timing.beginMerger("Write files", executorService);
     Collection<Timing> timings;
     // TODO(b/249922554): Current limitations for the experimental flag.
     assert globalsSyntheticsConsumer == null;
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 d72358a..9b3ea7c 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
@@ -833,7 +833,8 @@
         .forEach(
             (key, value) -> {
               assert value == 1;
-              assert value <= basicBlockNumberGenerator.peek();
+              assert key >= 0;
+              assert key <= basicBlockNumberGenerator.peek();
             });
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Inc.java b/src/main/java/com/android/tools/r8/ir/code/Inc.java
index 72b2bf2..5d3a877 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Inc.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Inc.java
@@ -33,6 +33,10 @@
     return Opcodes.INC;
   }
 
+  public int getIncrement() {
+    return increment;
+  }
+
   @Override
   public <T> T accept(InstructionVisitor<T> visitor) {
     return visitor.visit(this);
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 81f5c14..16fa00c 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
@@ -52,7 +52,7 @@
 import com.android.tools.r8.ir.code.UninitializedThisLocalRead;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.Xor;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.conversion.passes.TrivialGotosCollapser;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
 import com.android.tools.r8.ir.optimize.PhiOptimizations;
@@ -193,10 +193,11 @@
     loadStoreHelper.insertPhiMoves(registerAllocator);
     timing.end();
 
+    TrivialGotosCollapser trivialGotosCollapser = new TrivialGotosCollapser(appView);
     timing.begin("BasicBlock peephole optimizations");
     if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) {
       for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
-        CodeRewriter.collapseTrivialGotos(appView, code);
+        trivialGotosCollapser.run(code.context(), code, timing);
         PeepholeOptimizer.removeIdenticalPredecessorBlocks(code, registerAllocator);
         PeepholeOptimizer.shareIdenticalBlockSuffix(
             code, registerAllocator, SUFFIX_SHARING_OVERHEAD);
@@ -206,7 +207,7 @@
 
     timing.time("Rewrite Iinc patterns", this::rewriteIincPatterns);
 
-    CodeRewriter.collapseTrivialGotos(appView, code);
+    trivialGotosCollapser.run(code.context(), code, timing);
     timing.begin("Remove redundant debug positions");
     DexBuilder.removeRedundantDebugPositions(code);
     timing.end();
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 cd7b5b9..1ddfeac 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
@@ -63,7 +63,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.conversion.passes.TrivialGotosCollapser;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOutputMode;
@@ -401,7 +401,7 @@
       }
       if (allMatch) {
         currentBlock.removeInstruction(debugPosition);
-        CodeRewriter.unlinkTrivialGotoBlock(currentBlock, exit.getTarget());
+        TrivialGotosCollapser.unlinkTrivialGotoBlock(currentBlock, exit.getTarget());
         code.removeBlocks(Collections.singleton(currentBlock));
         // Having removed the block at blockIndex, the previous block may now be a trivial
         // fallthrough from an if/switch. Rewind to that point and retry. This avoids iterating to
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 7636cbd..55c6a06 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
@@ -27,10 +27,14 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.passes.ArrayConstructionSimplifier;
 import com.android.tools.r8.ir.conversion.passes.BinopRewriter;
+import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
 import com.android.tools.r8.ir.conversion.passes.CommonSubexpressionElimination;
 import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter;
 import com.android.tools.r8.ir.conversion.passes.SplitBranch;
+import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover;
+import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.optimize.AssertionErrorTwoArgsConstructorRewriter;
@@ -538,7 +542,7 @@
 
     if (options.canHaveArtStringNewInitBug()) {
       timing.begin("Check for new-init issue");
-      CodeRewriter.ensureDirectStringNewToInit(code, appView.dexItemFactory());
+      TrivialPhiSimplifier.ensureDirectStringNewToInit(code, appView.dexItemFactory());
       timing.end();
     }
 
@@ -726,8 +730,8 @@
     assert code.verifyTypes(appView);
 
     timing.begin("Remove trivial type checks/casts");
-    codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
-        code, context, methodProcessor, methodProcessingContext);
+    new TrivialCheckCastAndInstanceOfRemover(appView)
+        .run(code, context, methodProcessor, methodProcessingContext);
     timing.end();
 
     if (enumValueOptimizer != null) {
@@ -750,9 +754,7 @@
       timing.end();
     }
     commonSubexpressionElimination.run(context, code, timing);
-    timing.begin("Simplify arrays");
-    codeRewriter.simplifyArrayConstruction(code);
-    timing.end();
+    new ArrayConstructionSimplifier(appView).run(context, code, timing);
     timing.begin("Rewrite move result");
     codeRewriter.rewriteMoveResult(code);
     timing.end();
@@ -771,10 +773,10 @@
     codeRewriter.optimizeAlwaysThrowingInstructions(code);
     timing.end();
     timing.begin("Simplify control flow");
-    if (codeRewriter.simplifyControlFlow(code)) {
+    if (new BranchSimplifier(appView).simplifyBranches(code)) {
       timing.begin("Remove trivial type checks/casts");
-      codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
-          code, context, methodProcessor, methodProcessingContext);
+      new TrivialCheckCastAndInstanceOfRemover(appView)
+          .run(code, context, methodProcessor, methodProcessingContext);
       timing.end();
     }
     timing.end();
@@ -829,7 +831,6 @@
       assert options.inlinerOptions().enableInlining && inliner != null;
       classInliner.processMethodCode(
           appView.withLiveness(),
-          codeRewriter,
           stringOptimizer,
           enumValueOptimizer,
           code.context(),
@@ -877,7 +878,7 @@
     if (!options.isGeneratingClassFiles()) {
       timing.begin("Canonicalize constants");
       ConstantCanonicalizer constantCanonicalizer =
-          new ConstantCanonicalizer(appView, codeRewriter, context, code);
+          new ConstantCanonicalizer(appView, context, code);
       constantCanonicalizer.canonicalize();
       timing.end();
       previous = printMethod(code, "IR after constant canonicalization (SSA)", previous);
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
index 06f3da8..ddcd9ac 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -9,6 +9,7 @@
 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.conversion.passes.TrivialGotosCollapser;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
@@ -22,13 +23,10 @@
 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, DeadCodeRemover deadCodeRemover) {
     super(appView, deadCodeRemover);
-    this.codeRewriter = deadCodeRemover.getCodeRewriter();
     this.options = appView.options();
   }
 
@@ -44,7 +42,7 @@
     // Workaround massive dex2oat memory use for self-recursive methods.
     RuntimeWorkaroundCodeRewriter.workaroundDex2OatInliningIssue(appView, code);
     // Workaround MAX_INT switch issue.
-    RuntimeWorkaroundCodeRewriter.workaroundSwitchMaxIntBug(code, codeRewriter, options);
+    RuntimeWorkaroundCodeRewriter.workaroundSwitchMaxIntBug(code, appView);
     RuntimeWorkaroundCodeRewriter.workaroundDex2OatLinkedListBug(code, options);
     RuntimeWorkaroundCodeRewriter.workaroundForwardingInitializerBug(code, options);
     RuntimeWorkaroundCodeRewriter.workaroundExceptionTargetingLoopHeaderBug(code, options);
@@ -62,17 +60,18 @@
     LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
     registerAllocator.allocateRegisters();
     timing.end();
+    TrivialGotosCollapser trivialGotosCollapser = new TrivialGotosCollapser(appView);
     if (code.getConversionOptions().isPeepholeOptimizationsEnabled()) {
       timing.begin("Peephole optimize");
       for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
-        CodeRewriter.collapseTrivialGotos(appView, code);
+        trivialGotosCollapser.run(code.context(), code, timing);
         PeepholeOptimizer.optimize(appView, code, registerAllocator);
       }
       timing.end();
     }
     timing.begin("Clean up");
     CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
-    CodeRewriter.collapseTrivialGotos(appView, code);
+    trivialGotosCollapser.run(code.context(), code, timing);
     timing.end();
     return registerAllocator;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 1efe772..4c15125 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -104,7 +104,7 @@
 import com.android.tools.r8.ir.code.UnusedArgument;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.conversion.passes.TrivialPhiSimplifier;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.optimize.argumentpropagation.lenscoderewriter.NullCheckInserter;
@@ -231,7 +231,7 @@
         if (unusedArgument.outValue().hasPhiUsers()) {
           // See b/240282988: We can end up in situations where the second round of IR processing
           // introduce phis for irreducible control flow, we need to resolve them.
-          CodeRewriter.replaceUnusedArgumentTrivialPhis(unusedArgument);
+          TrivialPhiSimplifier.replaceUnusedArgumentTrivialPhis(unusedArgument);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index fe3085a..ab073dd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -178,8 +178,7 @@
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
-    TimingMerger merger =
-        timing.beginMerger("secondary-processor", ThreadUtils.getNumberOfThreads(executorService));
+    TimingMerger merger = timing.beginMerger("secondary-processor", executorService);
     while (!waves.isEmpty()) {
       wave = waves.removeFirst();
       assert !wave.isEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 9da34d4..45450f9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -129,8 +129,7 @@
       Timing timing,
       ExecutorService executorService)
       throws ExecutionException {
-    TimingMerger merger =
-        timing.beginMerger("primary-processor", ThreadUtils.getNumberOfThreads(executorService));
+    TimingMerger merger = timing.beginMerger("primary-processor", executorService);
     while (!waves.isEmpty()) {
       processorContext = appView.createProcessorContext();
       wave = waves.removeFirst();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
index c2e13af..e412ed7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Map;
 import java.util.Set;
 
 public abstract class CallSiteInformation {
@@ -23,6 +25,14 @@
    * <p>For pinned methods (methods kept through Proguard keep rules) this will always answer <code>
    * false</code>.
    */
+  public abstract boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context);
+
+  /**
+   * Checks if the given method only has a single call without considering context.
+   *
+   * <p>For pinned methods (methods kept through Proguard keep rules) and methods that override a
+   * library method this always returns false.
+   */
   public abstract boolean hasSingleCallSite(ProgramMethod method);
 
   public abstract boolean isMultiCallerInlineCandidate(ProgramMethod method);
@@ -38,6 +48,11 @@
     private static final EmptyCallSiteInformation EMPTY_INFO = new EmptyCallSiteInformation();
 
     @Override
+    public boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context) {
+      return false;
+    }
+
+    @Override
     public boolean hasSingleCallSite(ProgramMethod method) {
       return false;
     }
@@ -55,7 +70,9 @@
 
   static class CallGraphBasedCallSiteInformation extends CallSiteInformation {
 
-    private final Set<DexMethod> singleCallerMethods = Sets.newIdentityHashSet();
+    // Single callers track their calling context to ensure that the predicate is stable after
+    // inlining of the caller.
+    private final Map<DexMethod, DexMethod> singleCallerMethods = new IdentityHashMap<>();
     private final Set<DexMethod> multiCallerInlineCandidates = Sets.newIdentityHashSet();
 
     CallGraphBasedCallSiteInformation(AppView<AppInfoWithLiveness> appView, CallGraph graph) {
@@ -94,7 +111,16 @@
 
         int numberOfCallSites = node.getNumberOfCallSites();
         if (numberOfCallSites == 1) {
-          singleCallerMethods.add(reference);
+          Set<Node> callersWithDeterministicOrder = node.getCallersWithDeterministicOrder();
+          DexMethod caller = reference;
+          // We can have recursive methods where the recursive call is the only call site. We do
+          // not track callers for these.
+          if (!callersWithDeterministicOrder.isEmpty()) {
+            assert callersWithDeterministicOrder.size() == 1;
+            caller = callersWithDeterministicOrder.iterator().next().getMethod().getReference();
+          }
+          DexMethod existing = singleCallerMethods.put(reference, caller);
+          assert existing == null;
         } else if (numberOfCallSites > 1) {
           multiCallerInlineCandidates.add(reference);
         }
@@ -102,14 +128,25 @@
     }
 
     /**
-     * Checks if the given method only has a single call site.
+     * Checks if the given method only has a single call site with the given context.
+     *
+     * <p>For pinned methods (methods kept through Proguard keep rules) and methods that override a
+     * library method this always returns false.
+     */
+    @Override
+    public boolean hasSingleCallSite(ProgramMethod method, ProgramMethod context) {
+      return singleCallerMethods.get(method.getReference()) == context.getReference();
+    }
+
+    /**
+     * Checks if the given method only has a single call without considering context.
      *
      * <p>For pinned methods (methods kept through Proguard keep rules) and methods that override a
      * library method this always returns false.
      */
     @Override
     public boolean hasSingleCallSite(ProgramMethod method) {
-      return singleCallerMethods.contains(method.getReference());
+      return singleCallerMethods.containsKey(method.getReference());
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
new file mode 100644
index 0000000..bc8f8fb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
@@ -0,0 +1,474 @@
+// Copyright (c) 2023, 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.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.ArrayPut;
+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.InvokeNewArray;
+import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.NewArrayFilledData;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Replace new-array followed by stores of constants to all entries with new-array and
+ * fill-array-data / filled-new-array.
+ *
+ * <p>The format of the new-array and its puts must be of the form:
+ *
+ * <pre>
+ *   v0 <- new-array T vSize
+ *   ...
+ *   array-put v0 vValue1 vIndex1
+ *   ...
+ *   array-put v0 vValueN vIndexN
+ * </pre>
+ *
+ * <p>The flow between the array v0 and its puts must be linear with no other uses of v0 besides the
+ * array-put instructions, thus any no intermediate instruction (... above) must use v0 and also
+ * cannot have catch handlers that would transfer out control (those could then have uses of v0).
+ *
+ * <p>The allocation of the new-array can itself have catch handlers, in which case, those are to
+ * remain active on the translated code. Translated code can have two forms.
+ *
+ * <p>The first is using the original array allocation and filling in its data if it can be encoded:
+ *
+ * <pre>
+ *   v0 <- new-array T vSize
+ *   filled-array-data v0
+ *   ...
+ *   ...
+ * </pre>
+ *
+ * The data payload is encoded directly in the instruction so no dependencies are needed for filling
+ * the data array. Thus, the fill is inserted at the point of the allocation. If the allocation has
+ * catch handlers its block must be split and the handlers put on the fill instruction too. This is
+ * correct only because there are no exceptional transfers in (...) that could observe the early
+ * initialization of the data.
+ *
+ * <p>The second is using filled-new-array and has the form:
+ *
+ * <pre>
+ * ...
+ * ...
+ * v0 <- filled-new-array T vValue1 ... vValueN
+ * </pre>
+ *
+ * Here the correctness relies on no exceptional transfers in (...) that could observe the missing
+ * allocation of the array. The late allocation ensures that the values are available at allocation
+ * time. If the original allocation has catch handlers then the new allocation needs to link those
+ * too. In general that may require splitting the block twice so that the new allocation is the
+ * single throwing instruction in its block.
+ */
+public class ArrayConstructionSimplifier extends CodeRewriterPass<AppInfo> {
+
+  private final DexItemFactory dexItemFactory;
+
+  public ArrayConstructionSimplifier(AppView<?> appView) {
+    super(appView);
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  @Override
+  String getTimingId() {
+    return "ArrayConstructionSimplifier";
+  }
+
+  @Override
+  void rewriteCode(ProgramMethod method, IRCode code) {
+    WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
+    while (worklist.hasNext()) {
+      BasicBlock block = worklist.next();
+      simplifyArrayConstructionBlock(block, worklist, code, appView.options());
+    }
+  }
+
+  @Override
+  boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
+    return appView.options().isGeneratingDex();
+  }
+
+  private void simplifyArrayConstructionBlock(
+      BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, InternalOptions options) {
+    RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
+    InstructionListIterator it = block.listIterator(code);
+    while (it.hasNext()) {
+      FilledArrayCandidate candidate = computeFilledArrayCandidate(it.next(), rewriteOptions);
+      if (candidate == null) {
+        continue;
+      }
+      FilledArrayConversionInfo info =
+          computeConversionInfo(
+              candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex()));
+      if (info == null) {
+        continue;
+      }
+
+      Instruction instructionAfterCandidate = it.peekNext();
+      NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+      DexType arrayType = newArrayEmpty.type;
+      int size = candidate.size;
+      Set<Instruction> instructionsToRemove = SetUtils.newIdentityHashSet(size + 1);
+      if (candidate.useFilledNewArray()) {
+        assert newArrayEmpty.getLocalInfo() == null;
+        Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious();
+        Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null);
+        InvokeNewArray invoke =
+            new InvokeNewArray(arrayType, invokeValue, Arrays.asList(info.values));
+        invoke.setPosition(lastArrayPut.getPosition());
+        for (Value value : newArrayEmpty.inValues()) {
+          value.removeUser(newArrayEmpty);
+        }
+        newArrayEmpty.outValue().replaceUsers(invokeValue);
+        instructionsToRemove.add(newArrayEmpty);
+
+        boolean originalAllocationPointHasHandlers = block.hasCatchHandlers();
+        boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers();
+
+        if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) {
+          info.lastArrayPutIterator.add(invoke);
+        } else {
+          BasicBlock insertionBlock = info.lastArrayPutIterator.split(code);
+          if (originalAllocationPointHasHandlers) {
+            if (!insertionBlock.isTrivialGoto()) {
+              BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code);
+              assert insertionBlock.isTrivialGoto();
+              worklist.addIfNotSeen(blockAfterInsertion);
+            }
+            insertionBlock.moveCatchHandlers(block);
+          } else {
+            worklist.addIfNotSeen(insertionBlock);
+          }
+          insertionBlock.listIterator(code).add(invoke);
+        }
+      } else {
+        assert candidate.useFilledArrayData();
+        int elementSize = arrayType.elementSizeForPrimitiveArrayType();
+        short[] contents = computeArrayFilledData(info.values, size, elementSize);
+        if (contents == null) {
+          continue;
+        }
+        // fill-array-data requires the new-array-empty instruction to remain, as it does not
+        // itself create an array.
+        NewArrayFilledData fillArray =
+            new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, size, contents);
+        fillArray.setPosition(newArrayEmpty.getPosition());
+        BasicBlock newBlock =
+            it.addThrowingInstructionToPossiblyThrowingBlock(code, null, fillArray, options);
+        if (newBlock != null) {
+          worklist.addIfNotSeen(newBlock);
+        }
+      }
+
+      instructionsToRemove.addAll(info.arrayPutsToRemove);
+      Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
+      for (Instruction instruction : instructionsToRemove) {
+        BasicBlock ownerBlock = instruction.getBlock();
+        // If owner block is null, then the instruction has been removed already. We can't rely on
+        // just having the block pointer nulled, so the visited blocks guards reprocessing.
+        if (ownerBlock != null && visitedBlocks.add(ownerBlock)) {
+          InstructionListIterator removeIt = ownerBlock.listIterator(code);
+          while (removeIt.hasNext()) {
+            if (instructionsToRemove.contains(removeIt.next())) {
+              removeIt.removeOrReplaceByDebugLocalRead();
+            }
+          }
+        }
+      }
+
+      // The above has invalidated the block iterator so reset it and continue.
+      it = block.listIterator(code, instructionAfterCandidate);
+    }
+  }
+
+  private short[] computeArrayFilledData(Value[] values, int size, int elementSize) {
+    for (Value v : values) {
+      if (!v.isConstant()) {
+        return null;
+      }
+    }
+    if (elementSize == 1) {
+      short[] result = new short[(size + 1) / 2];
+      for (int i = 0; i < size; i += 2) {
+        short value =
+            (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
+        if (i + 1 < size) {
+          value |=
+              (short)
+                  ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
+        }
+        result[i / 2] = value;
+      }
+      return result;
+    }
+    assert elementSize == 2 || elementSize == 4 || elementSize == 8;
+    int shortsPerConstant = elementSize / 2;
+    short[] result = new short[size * shortsPerConstant];
+    for (int i = 0; i < size; i++) {
+      long value = values[i].getConstInstruction().asConstNumber().getRawValue();
+      for (int part = 0; part < shortsPerConstant; part++) {
+        result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
+      }
+    }
+    return result;
+  }
+
+  private static class FilledArrayConversionInfo {
+
+    Value[] values;
+    List<ArrayPut> arrayPutsToRemove;
+    LinearFlowInstructionListIterator lastArrayPutIterator;
+
+    public FilledArrayConversionInfo(int size) {
+      values = new Value[size];
+      arrayPutsToRemove = new ArrayList<>(size);
+    }
+  }
+
+  private FilledArrayConversionInfo computeConversionInfo(
+      FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) {
+    NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+    assert it.peekPrevious() == newArrayEmpty;
+    Value arrayValue = newArrayEmpty.outValue();
+    int size = candidate.size;
+
+    // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify
+    // if types require a cast.
+    // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g.
+    //   if aput-object throws a ClassCastException if given an object that does not implement the
+    //   desired interface, then we could add check-cast instructions for arguments we're not sure
+    //   about.
+    DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory);
+    boolean needsTypeCheck =
+        !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
+
+    FilledArrayConversionInfo info = new FilledArrayConversionInfo(size);
+    Value[] values = info.values;
+    int remaining = size;
+    Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      BasicBlock block = instruction.getBlock();
+      // If we encounter an instruction that can throw an exception we need to bail out of the
+      // optimization so that we do not transform half-initialized arrays into fully initialized
+      // arrays on exceptional edges. If the block has no handlers it is not observable so
+      // we perform the rewriting.
+      if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+        return null;
+      }
+      if (!users.contains(instruction)) {
+        // If any instruction can transfer control between the new-array and the last array put
+        // then it is not safe to move the new array to the point of the last put.
+        if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
+          return null;
+        }
+        continue;
+      }
+      ArrayPut arrayPut = instruction.asArrayPut();
+      // If the initialization sequence is broken by another use we cannot use a fill-array-data
+      // instruction.
+      if (arrayPut == null || arrayPut.array() != arrayValue) {
+        return null;
+      }
+      if (!arrayPut.index().isConstNumber()) {
+        return null;
+      }
+      if (arrayPut.instructionInstanceCanThrow()) {
+        assert false;
+        return null;
+      }
+      int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
+      if (index < 0 || index >= values.length) {
+        return null;
+      }
+      if (values[index] != null) {
+        return null;
+      }
+      Value value = arrayPut.value();
+      if (needsTypeCheck && !value.isAlwaysNull(appView)) {
+        DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
+        if (elementType.isArrayType()) {
+          if (elementType != valueDexType) {
+            return null;
+          }
+        } else if (valueDexType.isArrayType()) {
+          // isSubtype asserts for this case.
+          return null;
+        } else if (valueDexType.isNullValueType()) {
+          // Assume instructions can cause value.isAlwaysNull() == false while the DexType is null.
+          // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
+          //   that hits this case.
+        } else {
+          // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
+          //   library types (which this helper does not do).
+          if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
+            return null;
+          }
+        }
+      }
+      info.arrayPutsToRemove.add(arrayPut);
+      values[index] = value;
+      --remaining;
+      if (remaining == 0) {
+        info.lastArrayPutIterator = it;
+        return info;
+      }
+    }
+    return null;
+  }
+
+  private static class FilledArrayCandidate {
+
+    final NewArrayEmpty newArrayEmpty;
+    final int size;
+    final boolean encodeAsFilledNewArray;
+
+    public FilledArrayCandidate(
+        NewArrayEmpty newArrayEmpty, int size, boolean encodeAsFilledNewArray) {
+      assert size > 0;
+      this.newArrayEmpty = newArrayEmpty;
+      this.size = size;
+      this.encodeAsFilledNewArray = encodeAsFilledNewArray;
+    }
+
+    public boolean useFilledNewArray() {
+      return encodeAsFilledNewArray;
+    }
+
+    public boolean useFilledArrayData() {
+      return !useFilledNewArray();
+    }
+  }
+
+  private FilledArrayCandidate computeFilledArrayCandidate(
+      Instruction instruction, RewriteArrayOptions options) {
+    NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
+    if (newArrayEmpty == null) {
+      return null;
+    }
+    if (instruction.getLocalInfo() != null) {
+      return null;
+    }
+    if (!newArrayEmpty.size().isConstant()) {
+      return null;
+    }
+    int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+    if (!options.isPotentialSize(size)) {
+      return null;
+    }
+    DexType arrayType = newArrayEmpty.type;
+    boolean encodeAsFilledNewArray = canUseFilledNewArray(arrayType, size, options);
+    if (!encodeAsFilledNewArray && !canUseFilledArrayData(arrayType, size, options)) {
+      return null;
+    }
+    // Check that all arguments to the array is the array type or that the array is type Object[].
+    if (!options.canUseSubTypesInFilledNewArray()
+        && arrayType != dexItemFactory.objectArrayType
+        && !arrayType.isPrimitiveArrayType()) {
+      DexType elementType = arrayType.toArrayElementType(dexItemFactory);
+      for (Instruction uniqueUser : newArrayEmpty.outValue().uniqueUsers()) {
+        if (uniqueUser.isArrayPut()
+            && uniqueUser.asArrayPut().array() == newArrayEmpty.outValue()
+            && !checkTypeOfArrayPut(uniqueUser.asArrayPut(), elementType)) {
+          return null;
+        }
+      }
+    }
+    return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
+  }
+
+  private boolean checkTypeOfArrayPut(ArrayPut arrayPut, DexType elementType) {
+    TypeElement valueType = arrayPut.value().getType();
+    if (!valueType.isPrimitiveType() && elementType == dexItemFactory.objectType) {
+      return true;
+    }
+    if (valueType.isNullType() && !elementType.isPrimitiveType()) {
+      return true;
+    }
+    if (elementType.isArrayType()) {
+      if (valueType.isNullType()) {
+        return true;
+      }
+      ArrayTypeElement arrayTypeElement = valueType.asArrayType();
+      if (arrayTypeElement == null
+          || arrayTypeElement.getNesting() != elementType.getNumberOfLeadingSquareBrackets()) {
+        return false;
+      }
+      valueType = arrayTypeElement.getBaseType();
+      elementType = elementType.toBaseType(dexItemFactory);
+    }
+    assert !valueType.isArrayType();
+    assert !elementType.isArrayType();
+    if (valueType.isPrimitiveType() && !elementType.isPrimitiveType()) {
+      return false;
+    }
+    if (valueType.isPrimitiveType()) {
+      return true;
+    }
+    DexClass clazz = appView.definitionFor(elementType);
+    if (clazz == null) {
+      return false;
+    }
+    return clazz.isInterface() || valueType.isClassType(elementType);
+  }
+
+  private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) {
+    if (size < options.minSizeForFilledNewArray) {
+      return false;
+    }
+    // filled-new-array is implemented only for int[] and Object[].
+    // For int[], using filled-new-array is usually smaller than filled-array-data.
+    // filled-new-array supports up to 5 registers before it's filled-new-array/range.
+    if (!arrayType.isPrimitiveArrayType()) {
+      if (size > options.maxSizeForFilledNewArrayOfReferences) {
+        return false;
+      }
+      if (arrayType == dexItemFactory.stringArrayType) {
+        return options.canUseFilledNewArrayOfStrings();
+      }
+      if (!options.canUseFilledNewArrayOfNonStringObjects()) {
+        return false;
+      }
+      if (!options.canUseFilledNewArrayOfArrays()
+          && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
+        return false;
+      }
+      return true;
+    }
+    if (arrayType == dexItemFactory.intArrayType) {
+      return size <= options.maxSizeForFilledNewArrayOfInts;
+    }
+    return false;
+  }
+
+  private boolean canUseFilledArrayData(DexType arrayType, int size, RewriteArrayOptions options) {
+    // If there is only one element it is typically smaller to generate the array put
+    // instruction instead of fill array data.
+    if (size < options.minSizeForFilledArrayData || size > options.maxSizeForFilledArrayData) {
+      return false;
+    }
+    return arrayType.isPrimitiveArrayType();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java
new file mode 100644
index 0000000..199df62
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java
@@ -0,0 +1,1231 @@
+// Copyright (c) 2023, 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.passes;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.FieldResolutionResult.SingleProgramFieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
+import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+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.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.Goto;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.IfType;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.code.Xor;
+import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOutputMode;
+import com.android.tools.r8.utils.LongInterval;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+public class BranchSimplifier {
+
+  private final AppView<?> appView;
+  private final InternalOptions options;
+
+  public BranchSimplifier(AppView<?> appView) {
+    this.appView = appView;
+    this.options = appView.options();
+  }
+
+  public boolean simplifyBranches(IRCode code) {
+    boolean anyAffectedValues = rewriteSwitch(code);
+    anyAffectedValues |= simplifyIf(code).anyAffectedValues();
+    return anyAffectedValues;
+  }
+
+  public ControlFlowSimplificationResult simplifyIf(IRCode code) {
+    BasicBlockBehavioralSubsumption behavioralSubsumption =
+        new BasicBlockBehavioralSubsumption(appView, code);
+    boolean simplified = false;
+    for (BasicBlock block : code.blocks) {
+      // Skip removed (= unreachable) blocks.
+      if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
+        continue;
+      }
+      if (block.exit().isIf()) {
+        flipIfBranchesIfNeeded(code, block);
+        if (rewriteIfWithConstZero(code, block)) {
+          simplified = true;
+        }
+
+        if (simplifyKnownBooleanCondition(code, block)) {
+          simplified = true;
+          if (!block.exit().isIf()) {
+            continue;
+          }
+        }
+
+        // Simplify if conditions when possible.
+        If theIf = block.exit().asIf();
+        if (theIf.isZeroTest()) {
+          if (simplifyIfZeroTest(code, block, theIf)) {
+            simplified = true;
+            continue;
+          }
+        } else {
+          if (simplifyNonIfZeroTest(code, block, theIf)) {
+            simplified = true;
+            continue;
+          }
+        }
+
+        // Unable to determine which branch will be taken. Check if the true target can safely be
+        // rewritten to the false target.
+        if (behavioralSubsumption.isSubsumedBy(
+            theIf.inValues().get(0), theIf.getTrueTarget(), theIf.fallthroughBlock())) {
+          simplifyIfWithKnownCondition(code, block, theIf, theIf.fallthroughBlock());
+          simplified = true;
+        }
+      }
+    }
+    Set<Value> affectedValues = code.removeUnreachableBlocks();
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+    assert code.isConsistentSSA(appView);
+    return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified);
+  }
+
+  public static class ControlFlowSimplificationResult {
+
+    private final boolean anyAffectedValues;
+    private final boolean anySimplifications;
+
+    private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySimplifications) {
+      assert !anyAffectedValues || anySimplifications;
+      this.anyAffectedValues = anyAffectedValues;
+      this.anySimplifications = anySimplifications;
+    }
+
+    public boolean anyAffectedValues() {
+      return anyAffectedValues;
+    }
+
+    public boolean anySimplifications() {
+      return anySimplifications;
+    }
+  }
+
+  private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) {
+    Value lhs = theIf.lhs();
+    Value lhsRoot = lhs.getAliasedValue();
+    if (lhsRoot.isConstNumber()) {
+      ConstNumber cond = lhsRoot.getConstInstruction().asConstNumber();
+      BasicBlock target = theIf.targetFromCondition(cond);
+      simplifyIfWithKnownCondition(code, block, theIf, target);
+      return true;
+    }
+
+    if (theIf.isNullTest()) {
+      assert theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE;
+
+      if (lhs.isAlwaysNull(appView)) {
+        simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNullObject());
+        return true;
+      }
+
+      if (lhs.isNeverNull()) {
+        simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNonNullObject());
+        return true;
+      }
+    }
+
+    if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) {
+      AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context());
+      if (lhsAbstractValue.isConstantOrNonConstantNumberValue()
+          && !lhsAbstractValue.asConstantOrNonConstantNumberValue().containsInt(0)) {
+        // Value doesn't contain zero at all.
+        simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1));
+        return true;
+      }
+    }
+
+    if (lhs.hasValueRange()) {
+      LongInterval interval = lhs.getValueRange();
+      if (!interval.containsValue(0)) {
+        // Interval doesn't contain zero at all.
+        int sign = Long.signum(interval.getMin());
+        simplifyIfWithKnownCondition(code, block, theIf, sign);
+        return true;
+      }
+
+      // Interval contains zero.
+      switch (theIf.getType()) {
+        case GE:
+        case LT:
+          // [a, b] >= 0 is always true if a >= 0.
+          // [a, b] < 0 is always false if a >= 0.
+          // In both cases a zero condition takes the right branch.
+          if (interval.getMin() == 0) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+          break;
+
+        case LE:
+        case GT:
+          // [a, b] <= 0 is always true if b <= 0.
+          // [a, b] > 0 is always false if b <= 0.
+          // In both cases a zero condition takes the right branch.
+          if (interval.getMax() == 0) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+          break;
+
+        case EQ:
+        case NE:
+          // Only a single element interval [0, 0] can be dealt with here.
+          // Such intervals should have been replaced by constants.
+          assert !interval.isSingleValue();
+          break;
+      }
+    }
+    return false;
+  }
+
+  private boolean simplifyNonIfZeroTest(IRCode code, BasicBlock block, If theIf) {
+    Value lhs = theIf.lhs();
+    Value lhsRoot = lhs.getAliasedValue();
+    Value rhs = theIf.rhs();
+    Value rhsRoot = rhs.getAliasedValue();
+    if (lhsRoot == rhsRoot) {
+      // Comparing the same value.
+      simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(0));
+      return true;
+    }
+
+    if (lhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)
+        && rhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)) {
+      // Comparing two newly created objects.
+      assert theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE;
+      simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1));
+      return true;
+    }
+
+    if (lhsRoot.isConstNumber() && rhsRoot.isConstNumber()) {
+      // Zero test with a constant of comparison between between two constants.
+      ConstNumber left = lhsRoot.getConstInstruction().asConstNumber();
+      ConstNumber right = rhsRoot.getConstInstruction().asConstNumber();
+      BasicBlock target = theIf.targetFromCondition(left, right);
+      simplifyIfWithKnownCondition(code, block, theIf, target);
+      return true;
+    }
+
+    if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) {
+      AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context());
+      AbstractValue rhsAbstractValue = rhs.getAbstractValue(appView, code.context());
+      if (lhsAbstractValue.isConstantOrNonConstantNumberValue()
+          && rhsAbstractValue.isConstantOrNonConstantNumberValue()) {
+        ConstantOrNonConstantNumberValue lhsNumberValue =
+            lhsAbstractValue.asConstantOrNonConstantNumberValue();
+        ConstantOrNonConstantNumberValue rhsNumberValue =
+            rhsAbstractValue.asConstantOrNonConstantNumberValue();
+        if (!lhsNumberValue.mayOverlapWith(rhsNumberValue)) {
+          // No overlap.
+          simplifyIfWithKnownCondition(code, block, theIf, 1);
+          return true;
+        }
+      }
+    }
+
+    if (lhs.hasValueRange() && rhs.hasValueRange()) {
+      // Zero test with a value range, or comparison between between two values,
+      // each with a value ranges.
+      LongInterval leftRange = lhs.getValueRange();
+      LongInterval rightRange = rhs.getValueRange();
+      // Two overlapping ranges. Check for single point overlap.
+      if (!leftRange.overlapsWith(rightRange)) {
+        // No overlap.
+        int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
+        simplifyIfWithKnownCondition(code, block, theIf, cond);
+        return true;
+      }
+
+      // The two intervals overlap. We can simplify if they overlap at the end points.
+      switch (theIf.getType()) {
+        case LT:
+        case GE:
+          // [a, b] < [c, d] is always false when a == d.
+          // [a, b] >= [c, d] is always true when a == d.
+          // In both cases 0 condition will choose the right branch.
+          if (leftRange.getMin() == rightRange.getMax()) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+          break;
+        case GT:
+        case LE:
+          // [a, b] > [c, d] is always false when b == c.
+          // [a, b] <= [c, d] is always true when b == c.
+          // In both cases 0 condition will choose the right branch.
+          if (leftRange.getMax() == rightRange.getMin()) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+          break;
+        case EQ:
+        case NE:
+          // Since there is overlap EQ and NE cannot be determined.
+          break;
+      }
+    }
+
+    if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) {
+      ProgramMethod context = code.context();
+      AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
+      if (abstractValue.isSingleConstClassValue()) {
+        AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
+        if (otherAbstractValue.isSingleConstClassValue()) {
+          SingleConstClassValue singleConstClassValue = abstractValue.asSingleConstClassValue();
+          SingleConstClassValue otherSingleConstClassValue =
+              otherAbstractValue.asSingleConstClassValue();
+          simplifyIfWithKnownCondition(
+              code,
+              block,
+              theIf,
+              BooleanUtils.intValue(
+                  singleConstClassValue.getType() != otherSingleConstClassValue.getType()));
+          return true;
+        }
+        return false;
+      }
+
+      if (abstractValue.isSingleFieldValue()) {
+        AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
+        if (otherAbstractValue.isSingleFieldValue()) {
+          SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
+          SingleFieldValue otherSingleFieldValue = otherAbstractValue.asSingleFieldValue();
+          if (singleFieldValue.getField() == otherSingleFieldValue.getField()) {
+            simplifyIfWithKnownCondition(code, block, theIf, 0);
+            return true;
+          }
+
+          DexClass holder = appView.definitionForHolder(singleFieldValue.getField());
+          DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder);
+          if (field != null && field.isEnum()) {
+            DexClass otherHolder = appView.definitionForHolder(otherSingleFieldValue.getField());
+            DexEncodedField otherField =
+                otherSingleFieldValue.getField().lookupOnClass(otherHolder);
+            if (otherField != null && otherField.isEnum()) {
+              simplifyIfWithKnownCondition(code, block, theIf, 1);
+              return true;
+            }
+          }
+        }
+      }
+    }
+
+    return false;
+  }
+
+  private void simplifyIfWithKnownCondition(
+      IRCode code, BasicBlock block, If theIf, BasicBlock target) {
+    BasicBlock deadTarget =
+        target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
+    rewriteIfToGoto(code, block, theIf, target, deadTarget);
+  }
+
+  private void simplifyIfWithKnownCondition(IRCode code, BasicBlock block, If theIf, int cond) {
+    simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(cond));
+  }
+
+  /* Identify simple diamond shapes converting boolean true/false to 1/0. We consider the forms:
+   *
+   * (1)
+   *
+   *      [dbg pos x]             [dbg pos x]
+   *   ifeqz booleanValue       ifnez booleanValue
+   *      /        \              /        \
+   * [dbg pos x][dbg pos x]  [dbg pos x][dbg pos x]
+   *  [const 0]  [const 1]    [const 1]  [const 0]
+   *    goto      goto          goto      goto
+   *      \        /              \        /
+   *      phi(0, 1)                phi(1, 0)
+   *
+   * which can be replaced by a fallthrough and the phi value can be replaced
+   * with the boolean value itself.
+   *
+   * (2)
+   *
+   *      [dbg pos x]              [dbg pos x]
+   *    ifeqz booleanValue       ifnez booleanValue
+   *      /        \              /        \
+   * [dbg pos x][dbg pos x]  [dbg pos x][dbg pos x]
+   *  [const 1]  [const 0]   [const 0]  [const 1]
+   *    goto      goto          goto      goto
+   *      \        /              \        /
+   *      phi(1, 0)                phi(0, 1)
+   *
+   * which can be replaced by a fallthrough and the phi value can be replaced
+   * by an xor instruction which is smaller.
+   */
+  private boolean simplifyKnownBooleanCondition(IRCode code, BasicBlock block) {
+    If theIf = block.exit().asIf();
+    Value testValue = theIf.inValues().get(0);
+    if (theIf.isZeroTest() && testValue.knownToBeBoolean()) {
+      BasicBlock trueBlock = theIf.getTrueTarget();
+      BasicBlock falseBlock = theIf.fallthroughBlock();
+      if (isBlockSupportedBySimplifyKnownBooleanCondition(trueBlock)
+          && isBlockSupportedBySimplifyKnownBooleanCondition(falseBlock)
+          && trueBlock.getSuccessors().get(0) == falseBlock.getSuccessors().get(0)) {
+        BasicBlock targetBlock = trueBlock.getSuccessors().get(0);
+        if (targetBlock.getPredecessors().size() == 2) {
+          int trueIndex = targetBlock.getPredecessors().indexOf(trueBlock);
+          int falseIndex = trueIndex == 0 ? 1 : 0;
+          int deadPhis = 0;
+          // Locate the phis that have the same value as the boolean and replace them
+          // by the boolean in all users.
+          for (Phi phi : targetBlock.getPhis()) {
+            Value trueValue = phi.getOperand(trueIndex);
+            Value falseValue = phi.getOperand(falseIndex);
+            if (trueValue.isConstNumber() && falseValue.isConstNumber()) {
+              ConstNumber trueNumber = trueValue.getConstInstruction().asConstNumber();
+              ConstNumber falseNumber = falseValue.getConstInstruction().asConstNumber();
+              if ((theIf.getType() == IfType.EQ
+                      && trueNumber.isIntegerZero()
+                      && falseNumber.isIntegerOne())
+                  || (theIf.getType() == IfType.NE
+                      && trueNumber.isIntegerOne()
+                      && falseNumber.isIntegerZero())) {
+                phi.replaceUsers(testValue);
+                deadPhis++;
+              } else if ((theIf.getType() == IfType.NE
+                      && trueNumber.isIntegerZero()
+                      && falseNumber.isIntegerOne())
+                  || (theIf.getType() == IfType.EQ
+                      && trueNumber.isIntegerOne()
+                      && falseNumber.isIntegerZero())) {
+                Value newOutValue = code.createValue(phi.getType(), phi.getLocalInfo());
+                ConstNumber cstToUse = trueNumber.isIntegerOne() ? trueNumber : falseNumber;
+                BasicBlock phiBlock = phi.getBlock();
+                Position phiPosition = phiBlock.getPosition();
+                int insertIndex = 0;
+                if (cstToUse.getBlock() == trueBlock || cstToUse.getBlock() == falseBlock) {
+                  // The constant belongs to the block to remove, create a new one.
+                  cstToUse = ConstNumber.copyOf(code, cstToUse);
+                  cstToUse.setBlock(phiBlock);
+                  cstToUse.setPosition(phiPosition);
+                  phiBlock.getInstructions().add(insertIndex++, cstToUse);
+                }
+                phi.replaceUsers(newOutValue);
+                Instruction newInstruction =
+                    Xor.create(NumericType.INT, newOutValue, testValue, cstToUse.outValue());
+                newInstruction.setBlock(phiBlock);
+                // The xor is replacing a phi so it does not have an actual position.
+                newInstruction.setPosition(phiPosition);
+                phiBlock.listIterator(code, insertIndex).add(newInstruction);
+                deadPhis++;
+              }
+            }
+          }
+          // If all phis were removed, there is no need for the diamond shape anymore
+          // and it can be rewritten to a goto to one of the branches.
+          if (deadPhis == targetBlock.getPhis().size()) {
+            rewriteIfToGoto(code, block, theIf, trueBlock, falseBlock);
+            return true;
+          }
+          return deadPhis > 0;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean isBlockSupportedBySimplifyKnownBooleanCondition(BasicBlock b) {
+    if (b.isTrivialGoto()) {
+      return true;
+    }
+
+    int instructionSize = b.getInstructions().size();
+    if (b.exit().isGoto() && (instructionSize == 2 || instructionSize == 3)) {
+      Instruction constInstruction = b.getInstructions().get(instructionSize - 2);
+      if (constInstruction.isConstNumber()) {
+        if (!constInstruction.asConstNumber().isIntegerOne()
+            && !constInstruction.asConstNumber().isIntegerZero()) {
+          return false;
+        }
+        if (instructionSize == 2) {
+          return true;
+        }
+        Instruction firstInstruction = b.getInstructions().getFirst();
+        if (firstInstruction.isDebugPosition()) {
+          assert b.getPredecessors().size() == 1;
+          BasicBlock predecessorBlock = b.getPredecessors().get(0);
+          InstructionIterator it = predecessorBlock.iterator(predecessorBlock.exit());
+          Instruction previousPosition = null;
+          while (it.hasPrevious() && !(previousPosition = it.previous()).isDebugPosition()) {
+            // Intentionally empty.
+          }
+          if (previousPosition != null) {
+            return previousPosition.getPosition() == firstInstruction.getPosition();
+          }
+        }
+      }
+    }
+
+    return false;
+  }
+
+  private void rewriteIfToGoto(
+      IRCode code, BasicBlock block, If theIf, BasicBlock target, BasicBlock deadTarget) {
+    deadTarget.unlinkSinglePredecessorSiblingsAllowed();
+    assert theIf == block.exit();
+    block.replaceLastInstruction(new Goto(), code);
+    assert block.exit().isGoto();
+    assert block.exit().asGoto().getTarget() == target;
+  }
+
+  private boolean rewriteIfWithConstZero(IRCode code, BasicBlock block) {
+    If theIf = block.exit().asIf();
+    if (theIf.isZeroTest()) {
+      return false;
+    }
+
+    Value leftValue = theIf.lhs();
+    Value rightValue = theIf.rhs();
+    if (leftValue.isConstNumber() || rightValue.isConstNumber()) {
+      if (leftValue.isConstNumber()) {
+        if (leftValue.getConstInstruction().asConstNumber().isZero()) {
+          If ifz = new If(theIf.getType().forSwappedOperands(), rightValue);
+          block.replaceLastInstruction(ifz, code);
+          assert block.exit() == ifz;
+          return true;
+        }
+      } else if (rightValue.getConstInstruction().asConstNumber().isZero()) {
+        If ifz = new If(theIf.getType(), leftValue);
+        block.replaceLastInstruction(ifz, code);
+        assert block.exit() == ifz;
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) {
+    If theIf = block.exit().asIf();
+    BasicBlock trueTarget = theIf.getTrueTarget();
+    BasicBlock fallthrough = theIf.fallthroughBlock();
+    assert trueTarget != fallthrough;
+
+    if (!fallthrough.isSimpleAlwaysThrowingPath() || trueTarget.isSimpleAlwaysThrowingPath()) {
+      return false;
+    }
+
+    // In case fall-through block always throws there is a good chance that it
+    // is created for error checks and 'trueTarget' represents most more common
+    // non-error case. Flipping the if in this case may result in faster code
+    // on older Android versions.
+    List<Value> inValues = theIf.inValues();
+    If newIf = new If(theIf.getType().inverted(), inValues);
+    block.replaceLastInstruction(newIf, code);
+    block.swapSuccessors(trueTarget, fallthrough);
+    return true;
+  }
+
+  private boolean rewriteSwitch(IRCode code) {
+    return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance());
+  }
+
+  private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
+    if (!options.isSwitchRewritingEnabled()) {
+      return false;
+    }
+    if (!code.metadata().mayHaveSwitch()) {
+      return false;
+    }
+    return rewriteSwitchFull(code, switchCaseAnalyzer);
+  }
+
+  private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
+    boolean needToRemoveUnreachableBlocks = 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();
+        if (instruction.isSwitch()) {
+          Switch theSwitch = instruction.asSwitch();
+          if (options.testing.enableDeadSwitchCaseElimination) {
+            SwitchCaseEliminator eliminator =
+                removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer);
+            if (eliminator.mayHaveIntroducedUnreachableBlocks()) {
+              needToRemoveUnreachableBlocks = true;
+            }
+
+            iterator.previous();
+            instruction = iterator.next();
+            if (instruction.isGoto()) {
+              continue;
+            }
+
+            assert instruction.isSwitch();
+            theSwitch = instruction.asSwitch();
+          }
+          if (theSwitch.isIntSwitch()) {
+            rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch());
+          }
+        }
+      }
+    }
+
+    // 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.
+    code.splitCriticalEdges();
+
+    Set<Value> affectedValues =
+        needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of();
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+    assert code.isConsistentSSA(appView);
+    return !affectedValues.isEmpty();
+  }
+
+  public void rewriteSingleKeySwitchToIf(
+      IRCode code, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) {
+    // Rewrite the switch to an if.
+    int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
+    int caseBlockIndex = theSwitch.targetBlockIndices()[0];
+    if (fallthroughBlockIndex < caseBlockIndex) {
+      block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
+    }
+    If replacement;
+    if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) {
+      replacement = new If(IfType.EQ, theSwitch.value());
+    } else {
+      Instruction labelConst = theSwitch.materializeFirstKey(appView, code);
+      labelConst.setPosition(theSwitch.getPosition());
+      iterator.previous();
+      iterator.add(labelConst);
+      Instruction dummy = iterator.next();
+      assert dummy == theSwitch;
+      replacement = new If(IfType.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue()));
+    }
+    iterator.replaceCurrentInstruction(replacement);
+  }
+
+  private void rewriteIntSwitch(
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      BasicBlock block,
+      InstructionListIterator iterator,
+      IntSwitch theSwitch) {
+    if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) {
+      return;
+    }
+
+    if (theSwitch.numberOfKeys() == 1) {
+      rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch);
+      return;
+    }
+
+    // If there are more than 1 key, we use the following algorithm to find keys to combine.
+    // First, scan through the keys forward and combine each packed interval with the
+    // previous interval if it gives a net saving.
+    // Secondly, go through all created intervals and combine the ones without a saving into
+    // a single interval and keep a max number of packed switches.
+    // Finally, go through all intervals and check if the switch or part of the switch
+    // should be transformed to ifs.
+
+    // Phase 1: Combine packed intervals.
+    InternalOutputMode mode = options.getInternalOutputMode();
+    int[] keys = theSwitch.getKeys();
+    int maxNumberOfIfsOrSwitches = 10;
+    PriorityQueue<Interval> biggestPackedSavings =
+        new PriorityQueue<>((x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode)));
+    Set<Interval> biggestPackedSet = new HashSet<>();
+    List<Interval> intervals = new ArrayList<>();
+    int previousKey = keys[0];
+    IntList currentKeys = new IntArrayList();
+    currentKeys.add(previousKey);
+    Interval previousInterval = null;
+    for (int i = 1; i < keys.length; i++) {
+      int key = keys[i];
+      if (((long) key - (long) previousKey) > 1) {
+        Interval current = new Interval(currentKeys);
+        Interval added = combineOrAddInterval(intervals, previousInterval, current);
+        if (added != current && biggestPackedSet.contains(previousInterval)) {
+          biggestPackedSet.remove(previousInterval);
+          biggestPackedSavings.remove(previousInterval);
+        }
+        tryAddToBiggestSavings(
+            biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+        previousInterval = added;
+        currentKeys = new IntArrayList();
+      }
+      currentKeys.add(key);
+      previousKey = key;
+    }
+    Interval current = new Interval(currentKeys);
+    Interval added = combineOrAddInterval(intervals, previousInterval, current);
+    if (added != current && biggestPackedSet.contains(previousInterval)) {
+      biggestPackedSet.remove(previousInterval);
+      biggestPackedSavings.remove(previousInterval);
+    }
+    tryAddToBiggestSavings(biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
+
+    // Phase 2: combine sparse intervals into a single bin.
+    // Check if we should save a space for a sparse switch, if so, remove the switch with
+    // the smallest savings.
+    if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches
+        && maxNumberOfIfsOrSwitches < intervals.size()) {
+      biggestPackedSet.remove(biggestPackedSavings.poll());
+    }
+    Interval sparse = null;
+    List<Interval> newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches);
+    for (int i = 0; i < intervals.size(); i++) {
+      Interval interval = intervals.get(i);
+      if (biggestPackedSet.contains(interval)) {
+        newSwitches.add(interval);
+      } else if (sparse == null) {
+        sparse = interval;
+        newSwitches.add(sparse);
+      } else {
+        sparse.addInterval(interval);
+      }
+    }
+
+    // Phase 3: at this point we are guaranteed to have the biggest saving switches
+    // in newIntervals, potentially with a switch combining the remaining intervals.
+    // Now we check to see if we can create any if's to reduce size.
+    IntList outliers = new IntArrayList();
+    int outliersAsIfSize =
+        options.testing.enableSwitchToIfRewriting
+            ? findIfsForCandidates(newSwitches, theSwitch, outliers)
+            : 0;
+
+    long newSwitchesSize = 0;
+    List<IntList> newSwitchSequences = new ArrayList<>(newSwitches.size());
+    for (Interval interval : newSwitches) {
+      newSwitchesSize += interval.estimatedSize(mode);
+      newSwitchSequences.add(interval.keys);
+    }
+
+    long currentSize = IntSwitch.estimatedSize(mode, theSwitch.getKeys());
+    if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) {
+      convertSwitchToSwitchAndIfs(
+          code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers);
+    }
+  }
+
+  // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId
+  //  field values (from horizontal class merging. See bug for more details.
+  private boolean disableSwitchToIfRewritingForClassIdComparisons(IntSwitch theSwitch) {
+    Value switchValue = theSwitch.value().getAliasedValue();
+    if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) {
+      return false;
+    }
+    AppInfoWithLiveness appInfo = appView.appInfoWithLiveness();
+    if (appInfo == null) {
+      return false;
+    }
+    InstanceGet instanceGet = switchValue.getDefinition().asInstanceGet();
+    SingleProgramFieldResolutionResult resolutionResult =
+        appInfo.resolveField(instanceGet.getField()).asSingleProgramFieldResolutionResult();
+    if (resolutionResult == null) {
+      return false;
+    }
+    DexEncodedField resolvedField = resolutionResult.getResolvedField();
+    return HorizontalClassMergerUtils.isClassIdField(appView, resolvedField);
+  }
+
+  private SwitchCaseEliminator removeUnnecessarySwitchCases(
+      IRCode code,
+      Switch theSwitch,
+      InstructionListIterator iterator,
+      SwitchCaseAnalyzer switchCaseAnalyzer) {
+    BasicBlock defaultTarget = theSwitch.fallthroughBlock();
+    SwitchCaseEliminator eliminator = new SwitchCaseEliminator(theSwitch, iterator);
+    BasicBlockBehavioralSubsumption behavioralSubsumption =
+        new BasicBlockBehavioralSubsumption(appView, code);
+
+    // Compute the set of switch cases that can be removed.
+    boolean hasSwitchCaseToDefaultRewrite = false;
+    AbstractValue switchAbstractValue = theSwitch.value().getAbstractValue(appView, code.context());
+    for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
+      BasicBlock targetBlock = theSwitch.targetBlock(i);
+
+      if (switchCaseAnalyzer.switchCaseIsAlwaysHit(theSwitch, i)) {
+        eliminator.markSwitchCaseAsAlwaysHit(i);
+        break;
+      }
+
+      // This switch case can be removed if the behavior of the target block is equivalent to the
+      // behavior of the default block, or if the switch case is unreachable.
+      if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)) {
+        eliminator.markSwitchCaseForRemoval(i);
+      } else if (behavioralSubsumption.isSubsumedBy(
+          theSwitch.value(), targetBlock, defaultTarget)) {
+        eliminator.markSwitchCaseForRemoval(i);
+        hasSwitchCaseToDefaultRewrite = true;
+      }
+    }
+
+    if (eliminator.isFallthroughLive()
+        && !hasSwitchCaseToDefaultRewrite
+        && switchCaseAnalyzer.switchFallthroughIsNeverHit(theSwitch, switchAbstractValue)) {
+      eliminator.markSwitchFallthroughAsNeverHit();
+    }
+
+    eliminator.optimize();
+    return eliminator;
+  }
+
+  private static class Interval {
+
+    private final IntList keys = new IntArrayList();
+
+    public Interval(IntList... allKeys) {
+      assert allKeys.length > 0;
+      for (IntList keys : allKeys) {
+        assert keys.size() > 0;
+        this.keys.addAll(keys);
+      }
+    }
+
+    public int getMin() {
+      return keys.getInt(0);
+    }
+
+    public int getMax() {
+      return keys.getInt(keys.size() - 1);
+    }
+
+    public void addInterval(Interval other) {
+      assert getMax() < other.getMin();
+      keys.addAll(other.keys);
+    }
+
+    public long packedSavings(InternalOutputMode mode) {
+      long packedTargets = (long) getMax() - (long) getMin() + 1;
+      if (!IntSwitch.canBePacked(mode, packedTargets)) {
+        return Long.MIN_VALUE + 1;
+      }
+      long sparseCost =
+          IntSwitch.baseSparseSize(mode) + IntSwitch.sparsePayloadSize(mode, keys.size());
+      long packedCost =
+          IntSwitch.basePackedSize(mode) + IntSwitch.packedPayloadSize(mode, packedTargets);
+      return sparseCost - packedCost;
+    }
+
+    public long estimatedSize(InternalOutputMode mode) {
+      return IntSwitch.estimatedSize(mode, keys.toIntArray());
+    }
+  }
+
+  private Interval combineOrAddInterval(
+      List<Interval> intervals, Interval previous, Interval current) {
+    // As a first iteration, we only combine intervals if their packed size is less than their
+    // sparse counterpart. In CF we will have to add a load and a jump which add to the
+    // stack map table (1 is the size of a same entry).
+    InternalOutputMode mode = options.getInternalOutputMode();
+    int penalty = mode.isGeneratingClassFiles() ? 3 + 1 : 0;
+    if (previous == null) {
+      intervals.add(current);
+      return current;
+    }
+    Interval combined = new Interval(previous.keys, current.keys);
+    long packedSavings = combined.packedSavings(mode);
+    if (packedSavings <= 0
+        || packedSavings < previous.estimatedSize(mode) + current.estimatedSize(mode) - penalty) {
+      intervals.add(current);
+      return current;
+    } else {
+      intervals.set(intervals.size() - 1, combined);
+      return combined;
+    }
+  }
+
+  private void tryAddToBiggestSavings(
+      Set<Interval> biggestPackedSet,
+      PriorityQueue<Interval> intervals,
+      Interval toAdd,
+      int maximumNumberOfSwitches) {
+    assert !biggestPackedSet.contains(toAdd);
+    long savings = toAdd.packedSavings(options.getInternalOutputMode());
+    if (savings <= 0) {
+      return;
+    }
+    if (intervals.size() < maximumNumberOfSwitches) {
+      intervals.add(toAdd);
+      biggestPackedSet.add(toAdd);
+    } else if (savings > intervals.peek().packedSavings(options.getInternalOutputMode())) {
+      intervals.add(toAdd);
+      biggestPackedSet.add(toAdd);
+      biggestPackedSet.remove(intervals.poll());
+    }
+  }
+
+  private int codeUnitMargin() {
+    return options.getInternalOutputMode().isGeneratingClassFiles() ? 3 : 1;
+  }
+
+  private int findIfsForCandidates(
+      List<Interval> newSwitches, IntSwitch theSwitch, IntList outliers) {
+    Set<Interval> switchesToRemove = new HashSet<>();
+    InternalOutputMode mode = options.getInternalOutputMode();
+    int outliersAsIfSize = 0;
+    // The candidateForIfs is either an index to a switch that can be eliminated totally or a sparse
+    // where removing a key may produce a greater saving. It is only if keys are small in the packed
+    // switch that removing the keys makes sense (size wise).
+    for (Interval candidate : newSwitches) {
+      int maxIfBudget = 10;
+      long switchSize = candidate.estimatedSize(mode);
+      int sizeOfAllKeysAsIf = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), candidate.keys);
+      if (candidate.keys.size() <= maxIfBudget
+          && sizeOfAllKeysAsIf < switchSize - codeUnitMargin()) {
+        outliersAsIfSize += sizeOfAllKeysAsIf;
+        switchesToRemove.add(candidate);
+        outliers.addAll(candidate.keys);
+        continue;
+      }
+      // One could do something clever here, but we use a simple algorithm that use the fact that
+      // all keys are sorted in ascending order and that the smallest absolute value will give the
+      // best saving.
+      IntList candidateKeys = candidate.keys;
+      int smallestPosition = -1;
+      long smallest = Long.MAX_VALUE;
+      for (int i = 0; i < candidateKeys.size(); i++) {
+        long current = Math.abs((long) candidateKeys.getInt(i));
+        if (current < smallest) {
+          smallestPosition = i;
+          smallest = current;
+        }
+      }
+      // Add as many keys forward and backward as we have budget and we decrease in size.
+      IntList ifKeys = new IntArrayList();
+      ifKeys.add(candidateKeys.getInt(smallestPosition));
+      long previousSavings = 0;
+      long currentSavings =
+          switchSize
+              - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys)
+              - IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
+      int minIndex = smallestPosition - 1;
+      int maxIndex = smallestPosition + 1;
+      while (ifKeys.size() < maxIfBudget && currentSavings > previousSavings) {
+        if (minIndex >= 0 && maxIndex < candidateKeys.size()) {
+          long valMin = Math.abs((long) candidateKeys.getInt(minIndex));
+          int valMax = Math.abs(candidateKeys.getInt(maxIndex));
+          if (valMax <= valMin) {
+            ifKeys.add(candidateKeys.getInt(maxIndex++));
+          } else {
+            ifKeys.add(candidateKeys.getInt(minIndex--));
+          }
+        } else if (minIndex >= 0) {
+          ifKeys.add(candidateKeys.getInt(minIndex--));
+        } else if (maxIndex < candidateKeys.size()) {
+          ifKeys.add(candidateKeys.getInt(maxIndex++));
+        } else {
+          // No more elements to add as if's.
+          break;
+        }
+        previousSavings = currentSavings;
+        currentSavings =
+            switchSize
+                - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys)
+                - IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
+      }
+      if (previousSavings >= currentSavings) {
+        // Remove the last added key since it did not contribute to savings.
+        int lastKey = ifKeys.getInt(ifKeys.size() - 1);
+        ifKeys.removeInt(ifKeys.size() - 1);
+        if (lastKey == candidateKeys.getInt(minIndex + 1)) {
+          minIndex++;
+        } else {
+          maxIndex--;
+        }
+      }
+      // Adjust pointers into the candidate keys.
+      minIndex++;
+      maxIndex--;
+      if (ifKeys.size() > 0) {
+        int ifsSize = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys);
+        long newSwitchSize =
+            IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
+        if (newSwitchSize + ifsSize + codeUnitMargin() < switchSize) {
+          candidateKeys.removeElements(minIndex, maxIndex);
+          outliers.addAll(ifKeys);
+          outliersAsIfSize += ifsSize;
+        }
+      }
+    }
+    newSwitches.removeAll(switchesToRemove);
+    return outliersAsIfSize;
+  }
+
+  private int sizeForKeysWrittenAsIfs(ValueType type, Collection<Integer> keys) {
+    int ifsSize = If.estimatedSize(options.getInternalOutputMode()) * keys.size();
+    // In Cf we also require a load as well (and a stack map entry)
+    if (options.getInternalOutputMode().isGeneratingClassFiles()) {
+      ifsSize += keys.size() * (3 + 1);
+    }
+    for (int k : keys) {
+      if (k != 0) {
+        ifsSize += ConstNumber.estimatedSize(options.getInternalOutputMode(), type, k);
+      }
+    }
+    return ifsSize;
+  }
+
+  /**
+   * 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.
+   */
+  // TODO(b/270398965): Replace LinkedList.
+  @SuppressWarnings("JdkObsolete")
+  public void convertSwitchToSwitchAndIfs(
+      IRCode code,
+      ListIterator<BasicBlock> blocksIterator,
+      BasicBlock originalBlock,
+      InstructionListIterator iterator,
+      IntSwitch theSwitch,
+      List<IntList> switches,
+      IntList keysToRemove) {
+
+    Position position = theSwitch.getPosition();
+
+    // Extract the information from the switch before removing it.
+    Int2ReferenceSortedMap<BasicBlock> keyToTarget = theSwitch.getKeyToTargetMap();
+
+    // Keep track of the current fallthrough, starting with the original.
+    BasicBlock fallthroughBlock = theSwitch.fallthroughBlock();
+
+    // Split the switch instruction into its own block and remove it.
+    iterator.previous();
+    BasicBlock originalSwitchBlock = iterator.split(code, blocksIterator);
+    assert !originalSwitchBlock.hasCatchHandlers();
+    assert originalSwitchBlock.getInstructions().size() == 1;
+    assert originalBlock.exit().isGoto();
+    theSwitch.moveDebugValues(originalBlock.exit());
+    blocksIterator.remove();
+    theSwitch.getBlock().detachAllSuccessors();
+    BasicBlock block = theSwitch.getBlock().unlinkSinglePredecessor();
+    assert theSwitch.getBlock().getPredecessors().size() == 0;
+    assert theSwitch.getBlock().getSuccessors().size() == 0;
+    assert block == originalBlock;
+
+    // Collect the new blocks for adding to the block list.
+    LinkedList<BasicBlock> newBlocks = new LinkedList<>();
+
+    // Build the switch-blocks backwards, to always have the fallthrough block in hand.
+    for (int i = switches.size() - 1; i >= 0; i--) {
+      SwitchBuilder switchBuilder = new SwitchBuilder(position);
+      switchBuilder.setValue(theSwitch.value());
+      IntList keys = switches.get(i);
+      for (int j = 0; j < keys.size(); j++) {
+        int key = keys.getInt(j);
+        switchBuilder.addKeyAndTarget(key, keyToTarget.get(key));
+      }
+      switchBuilder.setFallthrough(fallthroughBlock).setBlockNumber(code.getNextBlockNumber());
+      BasicBlock newSwitchBlock = switchBuilder.build(code.metadata());
+      newBlocks.addFirst(newSwitchBlock);
+      fallthroughBlock = newSwitchBlock;
+    }
+
+    // Build the if-blocks backwards, to always have the fallthrough block in hand.
+    for (int i = keysToRemove.size() - 1; i >= 0; i--) {
+      int key = keysToRemove.getInt(i);
+      BasicBlock peeledOffTarget = keyToTarget.get(key);
+      IfBuilder ifBuilder = new IfBuilder(position, code);
+      ifBuilder
+          .setLeft(theSwitch.value())
+          .setRight(key)
+          .setTarget(peeledOffTarget)
+          .setFallthrough(fallthroughBlock)
+          .setBlockNumber(code.getNextBlockNumber());
+      BasicBlock ifBlock = ifBuilder.build();
+      newBlocks.addFirst(ifBlock);
+      fallthroughBlock = ifBlock;
+    }
+
+    // Finally link the block before the original switch to the new block sequence.
+    originalBlock.link(fallthroughBlock);
+
+    // Finally add the blocks.
+    newBlocks.forEach(blocksIterator::add);
+  }
+
+  // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
+  private abstract static class InstructionBuilder<T> {
+
+    int blockNumber;
+    final Position position;
+
+    InstructionBuilder(Position position) {
+      this.position = position;
+    }
+
+    abstract T self();
+
+    T setBlockNumber(int blockNumber) {
+      this.blockNumber = blockNumber;
+      return self();
+    }
+  }
+
+  private static class SwitchBuilder extends InstructionBuilder<SwitchBuilder> {
+
+    private Value value;
+    private final Int2ReferenceSortedMap<BasicBlock> keyToTarget = new Int2ReferenceAVLTreeMap<>();
+    private BasicBlock fallthrough;
+
+    SwitchBuilder(Position position) {
+      super(position);
+    }
+
+    @Override
+    SwitchBuilder self() {
+      return this;
+    }
+
+    SwitchBuilder setValue(Value value) {
+      this.value = value;
+      return this;
+    }
+
+    SwitchBuilder addKeyAndTarget(int key, BasicBlock target) {
+      keyToTarget.put(key, target);
+      return this;
+    }
+
+    SwitchBuilder setFallthrough(BasicBlock fallthrough) {
+      this.fallthrough = fallthrough;
+      return this;
+    }
+
+    BasicBlock build(IRMetadata metadata) {
+      final int NOT_FOUND = -1;
+      Object2IntMap<BasicBlock> targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>();
+      targetToSuccessorIndex.defaultReturnValue(NOT_FOUND);
+
+      int[] keys = new int[keyToTarget.size()];
+      int[] targetBlockIndices = new int[keyToTarget.size()];
+      // Sort keys descending.
+      int count = 0;
+      IntIterator iter = keyToTarget.keySet().iterator();
+      while (iter.hasNext()) {
+        int key = iter.nextInt();
+        BasicBlock target = keyToTarget.get(key);
+        Integer targetIndex =
+            targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size());
+        keys[count] = key;
+        targetBlockIndices[count] = targetIndex;
+        count++;
+      }
+      Integer fallthroughIndex =
+          targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size());
+      IntSwitch newSwitch = new IntSwitch(value, keys, targetBlockIndices, fallthroughIndex);
+      newSwitch.setPosition(position);
+      BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch, metadata);
+      for (BasicBlock successor : targetToSuccessorIndex.keySet()) {
+        newSwitchBlock.link(successor);
+      }
+      return newSwitchBlock;
+    }
+  }
+
+  private static class IfBuilder extends InstructionBuilder<IfBuilder> {
+
+    private final IRCode code;
+    private Value left;
+    private int right;
+    private BasicBlock target;
+    private BasicBlock fallthrough;
+
+    IfBuilder(Position position, IRCode code) {
+      super(position);
+      this.code = code;
+    }
+
+    @Override
+    IfBuilder self() {
+      return this;
+    }
+
+    IfBuilder setLeft(Value left) {
+      this.left = left;
+      return this;
+    }
+
+    IfBuilder setRight(int right) {
+      this.right = right;
+      return this;
+    }
+
+    IfBuilder setTarget(BasicBlock target) {
+      this.target = target;
+      return this;
+    }
+
+    IfBuilder setFallthrough(BasicBlock fallthrough) {
+      this.fallthrough = fallthrough;
+      return this;
+    }
+
+    BasicBlock build() {
+      assert target != null;
+      assert fallthrough != null;
+      If newIf;
+      BasicBlock ifBlock;
+      if (right != 0) {
+        ConstNumber rightConst = code.createIntConstant(right);
+        rightConst.setPosition(position);
+        newIf = new If(IfType.EQ, ImmutableList.of(left, rightConst.dest()));
+        ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata(), rightConst);
+      } else {
+        newIf = new If(IfType.EQ, left);
+        ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata());
+      }
+      newIf.setPosition(position);
+      ifBlock.link(target);
+      ifBlock.link(fallthrough);
+      return ifBlock;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java
similarity index 99%
rename from src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
rename to src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java
index 11e0b51..cbda630 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchCaseEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SwitchCaseEliminator.java
@@ -2,7 +2,7 @@
 // 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;
+package com.android.tools.r8.ir.conversion.passes;
 
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.code.BasicBlock;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
new file mode 100644
index 0000000..e19b963
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
@@ -0,0 +1,384 @@
+// Copyright (c) 2023, 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.passes;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeUtils;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.InstanceOf;
+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.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class TrivialCheckCastAndInstanceOfRemover {
+
+  private final AppView<?> appView;
+  private final InternalOptions options;
+  private final DexItemFactory dexItemFactory;
+
+  public TrivialCheckCastAndInstanceOfRemover(AppView<?> appView) {
+    this.appView = appView;
+    this.options = appView.options();
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  public void run(
+      IRCode code,
+      ProgramMethod context,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    if (!appView.enableWholeProgramOptimizations()) {
+      return;
+    }
+
+    assert appView.appInfo().hasLiveness();
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
+    if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) {
+      return;
+    }
+
+    IRMetadata metadata = code.metadata();
+    if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) {
+      return;
+    }
+
+    // If we can remove a CheckCast it is due to us having at least as much information about the
+    // type as the CheckCast gives. We then need to propagate that information to the users of
+    // the CheckCast to ensure further optimizations and removals of CheckCast:
+    //
+    //    : 1: NewArrayEmpty        v2 <- v1(1) java.lang.String[]  <-- v2 = String[]
+    // ...
+    //    : 2: CheckCast            v5 <- v2; java.lang.Object[]    <-- v5 = Object[]
+    // ...
+    //    : 3: ArrayGet             v7 <- v5, v6(0)                 <-- v7 = Object
+    //    : 4: CheckCast            v8 <- v7; java.lang.String      <-- v8 = String
+    // ...
+    //
+    // When looking at line 2 we can conclude that the CheckCast is trivial because v2 is String[]
+    // and remove it. However, v7 is still only known to be Object and we cannot remove the
+    // CheckCast at line 4 unless we update v7 with the most precise information by narrowing the
+    // affected values of v5. We therefore have to run the type analysis after each CheckCast
+    // removal.
+    TypeAnalysis typeAnalysis = new TypeAnalysis(appView);
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    InstructionListIterator it = code.instructionListIterator();
+    boolean needToRemoveTrivialPhis = false;
+    while (it.hasNext()) {
+      Instruction current = it.next();
+      if (current.isCheckCast()) {
+        boolean hasPhiUsers = current.outValue().hasPhiUsers();
+        RemoveCheckCastInstructionIfTrivialResult removeResult =
+            removeCheckCastInstructionIfTrivial(
+                appViewWithLiveness,
+                current.asCheckCast(),
+                it,
+                code,
+                context,
+                affectedValues,
+                methodProcessor,
+                methodProcessingContext);
+        if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) {
+          assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
+          needToRemoveTrivialPhis |= hasPhiUsers;
+          typeAnalysis.narrowing(affectedValues);
+          affectedValues.clear();
+        }
+      } else if (current.isInstanceOf()) {
+        boolean hasPhiUsers = current.outValue().hasPhiUsers();
+        if (removeInstanceOfInstructionIfTrivial(
+            appViewWithLiveness, current.asInstanceOf(), it, code)) {
+          needToRemoveTrivialPhis |= hasPhiUsers;
+        }
+      }
+    }
+    // ... v1
+    // ...
+    // v2 <- check-cast v1, T
+    // v3 <- phi(v1, v2)
+    // Removing check-cast may result in a trivial phi:
+    // v3 <- phi(v1, v1)
+    if (needToRemoveTrivialPhis) {
+      code.removeAllDeadAndTrivialPhis(affectedValues);
+      if (!affectedValues.isEmpty()) {
+        typeAnalysis.narrowing(affectedValues);
+      }
+    }
+    assert code.isConsistentSSA(appView);
+  }
+
+  enum RemoveCheckCastInstructionIfTrivialResult {
+    NO_REMOVALS,
+    REMOVED_CAST_DO_NARROW
+  }
+
+  private enum InstanceOfResult {
+    UNKNOWN,
+    TRUE,
+    FALSE
+  }
+
+  // Returns true if the given check-cast instruction was removed.
+  private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial(
+      AppView<AppInfoWithLiveness> appViewWithLiveness,
+      CheckCast checkCast,
+      InstructionListIterator it,
+      IRCode code,
+      ProgramMethod context,
+      Set<Value> affectedValues,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    Value inValue = checkCast.object();
+    Value outValue = checkCast.outValue();
+    DexType castType = checkCast.getType();
+    DexType baseCastType = castType.toBaseType(dexItemFactory);
+
+    // If the cast type is not accessible in the current context, we should not remove the cast
+    // in order to preserve runtime errors. Note that JVM and ART behave differently: see
+    // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}.
+    if (baseCastType.isClassType()) {
+      DexClass baseCastClass = appView.definitionFor(baseCastType);
+      if (baseCastClass == null
+          || AccessControl.isClassAccessible(baseCastClass, code.context(), appViewWithLiveness)
+              .isPossiblyFalse()) {
+        return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+      }
+    }
+
+    if (!appView
+        .getOpenClosedInterfacesCollection()
+        .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) {
+      return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+    }
+
+    // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
+    // elimination may lead to verification errors. See b/123269162.
+    if (options.canHaveArtCheckCastVerifierBug()) {
+      if (inValue.getType().isNullType()
+          && castType.isArrayType()
+          && castType.toBaseType(dexItemFactory).isFloatType()) {
+        return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+      }
+    }
+
+    // If casting to an array of an interface type elimination may lead to verification errors.
+    // See b/132420510 and b/223424356.
+    if (options.canHaveIncorrectJoinForArrayOfInterfacesBug()) {
+      if (castType.isArrayType()) {
+        DexType baseType = castType.toBaseType(dexItemFactory);
+        if (baseType.isClassType() && baseType.isInterface(appViewWithLiveness)) {
+          return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+        }
+      }
+    }
+
+    TypeElement inTypeLattice = inValue.getType();
+    TypeElement outTypeLattice = outValue.getType();
+    TypeElement castTypeLattice = castType.toTypeElement(appView, inTypeLattice.nullability());
+
+    assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability());
+
+    if (inTypeLattice.lessThanOrEqual(castTypeLattice, appView)) {
+      // 1) Trivial cast.
+      //   A a = ...
+      //   A a' = (A) a;
+      // 2) Up-cast: we already have finer type info.
+      //   A < B
+      //   A a = ...
+      //   B b = (B) a;
+      assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView);
+      // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast
+      // to the users of the CheckCast's out value.
+      //
+      // v2 = CheckCast A v1  ~~>  DebugLocalWrite $v0 <- v1
+      //
+      // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the
+      // CheckCast invalue users that includes the potential DebugLocalWrite.
+      CodeRewriter.removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
+      affectedValues.addAll(inValue.affectedValues());
+      return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
+    }
+
+    // If values of cast type are guaranteed to be null, then the out-value must be null if the cast
+    // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced
+    // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast
+    // type.
+    if (castType.isClassType()
+        && castType.isAlwaysNull(appViewWithLiveness)
+        && !outValue.hasDebugUsers()) {
+      // Replace all usages of the out-value by null.
+      it.previous();
+      Value nullValue = it.insertConstNullInstruction(code, options);
+      it.next();
+      checkCast.outValue().replaceUsers(nullValue);
+      affectedValues.addAll(nullValue.affectedValues());
+
+      // Replace the check-cast instruction by throwClassCastExceptionIfNotNull().
+      UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod =
+          UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod(
+              appView, methodProcessor.getEventConsumer(), methodProcessingContext);
+      throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
+      InvokeStatic replacement =
+          InvokeStatic.builder()
+              .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod())
+              .setSingleArgument(checkCast.object())
+              .setPosition(checkCast)
+              .build();
+      it.replaceCurrentInstruction(replacement);
+      assert replacement.lookupSingleTarget(appView, context) != null;
+      return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
+    }
+
+    // If the cast is guaranteed to succeed and only there to ensure the program type checks, then
+    // check if the program would still type check after removing the cast.
+    if (checkCast.isSafeCheckCast()
+        || checkCast
+            .getFirstOperand()
+            .getDynamicType(appViewWithLiveness)
+            .getDynamicUpperBoundType()
+            .lessThanOrEqualUpToNullability(castTypeLattice, appView)) {
+      TypeElement useType =
+          TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue());
+      if (inTypeLattice.lessThanOrEqualUpToNullability(useType, appView)) {
+        return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
+      }
+    }
+
+    // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
+    // A < B < C
+    // c = ...        // Even though we know c is of type A,
+    // a' = (B) c;    // (this could be removed, since chained below.)
+    // a'' = (A) a';  // this should remain for runtime verification.
+    assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType());
+    assert outTypeLattice.equalUpToNullability(castTypeLattice);
+    return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+  }
+
+  // Returns true if the given instance-of instruction was removed.
+  private boolean removeInstanceOfInstructionIfTrivial(
+      AppView<AppInfoWithLiveness> appViewWithLiveness,
+      InstanceOf instanceOf,
+      InstructionListIterator it,
+      IRCode code) {
+    ProgramMethod context = code.context();
+
+    // If the instance-of type is not accessible in the current context, we should not remove the
+    // instance-of instruction in order to preserve IllegalAccessError.
+    DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory);
+    if (instanceOfBaseType.isClassType()) {
+      DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType);
+      if (instanceOfClass == null
+          || AccessControl.isClassAccessible(instanceOfClass, context, appViewWithLiveness)
+              .isPossiblyFalse()) {
+        return false;
+      }
+    }
+
+    Value inValue = instanceOf.value();
+    if (!appView
+        .getOpenClosedInterfacesCollection()
+        .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) {
+      return false;
+    }
+
+    TypeElement inType = inValue.getType();
+    TypeElement instanceOfType =
+        TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView);
+    Value aliasValue = inValue.getAliasedValue();
+
+    InstanceOfResult result = InstanceOfResult.UNKNOWN;
+    if (inType.isDefinitelyNull()) {
+      result = InstanceOfResult.FALSE;
+    } else if (inType.lessThanOrEqual(instanceOfType, appView) && !inType.isNullable()) {
+      result = InstanceOfResult.TRUE;
+    } else if (!aliasValue.isPhi()
+        && aliasValue.definition.isCreatingInstanceOrArray()
+        && instanceOfType.strictlyLessThan(inType, appView)) {
+      result = InstanceOfResult.FALSE;
+    } else if (appView.appInfo().hasLiveness()) {
+      if (instanceOf.type().isClassType()
+          && isNeverInstantiatedDirectlyOrIndirectly(instanceOf.type())) {
+        // The type of the instance-of instruction is a program class, and is never instantiated
+        // directly or indirectly. Thus, the in-value must be null, meaning that the instance-of
+        // instruction will always evaluate to false.
+        result = InstanceOfResult.FALSE;
+      }
+
+      if (result == InstanceOfResult.UNKNOWN) {
+        if (inType.isClassType()
+            && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) {
+          // The type of the in-value is a program class, and is never instantiated directly or
+          // indirectly. This, the in-value must be null, meaning that the instance-of instruction
+          // will always evaluate to false.
+          result = InstanceOfResult.FALSE;
+        }
+      }
+
+      if (result == InstanceOfResult.UNKNOWN) {
+        Value aliasedValue =
+            inValue.getSpecificAliasedValue(
+                value ->
+                    value.isDefinedByInstructionSatisfying(
+                        Instruction::isAssumeWithDynamicTypeAssumption));
+        if (aliasedValue != null) {
+          DynamicTypeWithUpperBound dynamicType =
+              aliasedValue.getDefinition().asAssume().getDynamicTypeAssumption().getDynamicType();
+          Nullability nullability = dynamicType.getNullability();
+          if (nullability.isDefinitelyNull()) {
+            result = InstanceOfResult.FALSE;
+          } else if (dynamicType.getDynamicUpperBoundType().lessThanOrEqual(instanceOfType, appView)
+              && (!inType.isNullable() || !nullability.isNullable())) {
+            result = InstanceOfResult.TRUE;
+          }
+        }
+      }
+    }
+    if (result != InstanceOfResult.UNKNOWN) {
+      ConstNumber newInstruction =
+          new ConstNumber(
+              new Value(
+                  code.valueNumberGenerator.next(),
+                  TypeElement.getInt(),
+                  instanceOf.outValue().getLocalInfo()),
+              result == InstanceOfResult.TRUE ? 1 : 0);
+      it.replaceCurrentInstruction(newInstruction);
+      return true;
+    }
+    return false;
+  }
+
+  private boolean isNeverInstantiatedDirectlyOrIndirectly(DexType type) {
+    assert appView.appInfo().hasLiveness();
+    assert type.isClassType();
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+    return clazz != null
+        && !appView.appInfo().withLiveness().isInstantiatedDirectlyOrIndirectly(clazz);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java
new file mode 100644
index 0000000..7c9b4d0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java
@@ -0,0 +1,204 @@
+// Copyright (c) 2023, 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.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Switch;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+/**
+ * Rewrite all branch targets to the destination of trivial goto chains when possible. Does not
+ * rewrite fallthrough targets as that would require block reordering and the transformation only
+ * makes sense after SSA destruction where there are no phis.
+ */
+public class TrivialGotosCollapser extends CodeRewriterPass<AppInfo> {
+
+  public TrivialGotosCollapser(AppView<?> appView) {
+    super(appView);
+  }
+
+  @Override
+  String getTimingId() {
+    return "TrivialGotosCollapser";
+  }
+
+  @Override
+  void rewriteCode(ProgramMethod method, IRCode code) {
+    assert code.isConsistentGraph(appView);
+    List<BasicBlock> blocksToRemove = new ArrayList<>();
+    // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove
+    // first round of trivial goto blocks.
+    ListIterator<BasicBlock> iterator = code.listIterator();
+    assert iterator.hasNext();
+    BasicBlock block = iterator.next();
+    BasicBlock nextBlock;
+
+    do {
+      nextBlock = iterator.hasNext() ? iterator.next() : null;
+      if (block.isTrivialGoto()) {
+        collapseTrivialGoto(code, block, nextBlock, blocksToRemove);
+      }
+      if (block.exit().isIf()) {
+        collapseIfTrueTarget(block);
+      }
+      if (block.exit().isSwitch()) {
+        collapseNonFallthroughSwitchTargets(block);
+      }
+      block = nextBlock;
+    } while (nextBlock != null);
+    code.removeBlocks(blocksToRemove);
+    // Get rid of gotos to the next block.
+    while (!blocksToRemove.isEmpty()) {
+      blocksToRemove = new ArrayList<>();
+      iterator = code.listIterator();
+      block = iterator.next();
+      do {
+        nextBlock = iterator.hasNext() ? iterator.next() : null;
+        if (block.isTrivialGoto()) {
+          collapseTrivialGoto(code, block, nextBlock, blocksToRemove);
+        }
+        block = nextBlock;
+      } while (block != null);
+      code.removeBlocks(blocksToRemove);
+    }
+    assert removedTrivialGotos(code);
+    assert code.isConsistentGraph(appView);
+  }
+
+  @Override
+  boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
+    return true;
+  }
+
+  public static void unlinkTrivialGotoBlock(BasicBlock block, BasicBlock target) {
+    assert block.isTrivialGoto();
+    for (BasicBlock pred : block.getPredecessors()) {
+      pred.replaceSuccessor(block, target);
+    }
+    for (BasicBlock succ : block.getSuccessors()) {
+      succ.getMutablePredecessors().remove(block);
+    }
+    for (BasicBlock pred : block.getPredecessors()) {
+      if (!target.getPredecessors().contains(pred)) {
+        target.getMutablePredecessors().add(pred);
+      }
+    }
+  }
+
+  private boolean isFallthroughBlock(BasicBlock block) {
+    for (BasicBlock pred : block.getPredecessors()) {
+      if (pred.exit().fallthroughBlock() == block) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean removedTrivialGotos(IRCode code) {
+    ListIterator<BasicBlock> iterator = code.listIterator();
+    assert iterator.hasNext();
+    BasicBlock block = iterator.next();
+    BasicBlock nextBlock;
+    do {
+      nextBlock = iterator.hasNext() ? iterator.next() : null;
+      // Trivial goto block are only kept if they are self-targeting or are targeted by
+      // fallthroughs.
+      BasicBlock blk = block; // Additional local for lambda below.
+      assert !block.isTrivialGoto()
+          || block.exit().asGoto().getTarget() == block
+          || code.entryBlock() == block
+          || block.getPredecessors().stream().anyMatch((b) -> b.exit().fallthroughBlock() == blk);
+      // Trivial goto blocks never target the next block (in that case there should just be a
+      // fallthrough).
+      assert !block.isTrivialGoto() || block.exit().asGoto().getTarget() != nextBlock;
+      block = nextBlock;
+    } while (block != null);
+    return true;
+  }
+
+  private void collapseTrivialGoto(
+      IRCode code, BasicBlock block, BasicBlock nextBlock, List<BasicBlock> blocksToRemove) {
+
+    // This is the base case for GOTO loops.
+    if (block.exit().asGoto().getTarget() == block) {
+      return;
+    }
+
+    BasicBlock target = block.endOfGotoChain();
+
+    boolean needed = false;
+
+    if (target == null) {
+      // This implies we are in a loop of GOTOs. In that case, we will iteratively remove each
+      // trivial GOTO one-by-one until the above base case (one block targeting itself) is left.
+      target = block.exit().asGoto().getTarget();
+    }
+
+    if (target != nextBlock) {
+      // Not targeting the fallthrough block, determine if we need this goto. We need it if
+      // a fallthrough can hit this block. That is the case if the block is the entry block
+      // or if one of the predecessors fall through to the block.
+      needed = code.entryBlock() == block || isFallthroughBlock(block);
+    }
+
+    if (!needed) {
+      blocksToRemove.add(block);
+      unlinkTrivialGotoBlock(block, target);
+    }
+  }
+
+  private void collapseIfTrueTarget(BasicBlock block) {
+    If insn = block.exit().asIf();
+    BasicBlock target = insn.getTrueTarget();
+    BasicBlock newTarget = target.endOfGotoChain();
+    BasicBlock fallthrough = insn.fallthroughBlock();
+    BasicBlock newFallthrough = fallthrough.endOfGotoChain();
+    if (newTarget != null && target != newTarget) {
+      insn.getBlock().replaceSuccessor(target, newTarget);
+      target.getMutablePredecessors().remove(block);
+      if (!newTarget.getPredecessors().contains(block)) {
+        newTarget.getMutablePredecessors().add(block);
+      }
+    }
+    if (block.exit().isIf()) {
+      insn = block.exit().asIf();
+      if (insn.getTrueTarget() == newFallthrough) {
+        // Replace if with the same true and fallthrough target with a goto to the fallthrough.
+        block.replaceSuccessor(insn.getTrueTarget(), fallthrough);
+        assert block.exit().isGoto();
+        assert block.exit().asGoto().getTarget() == fallthrough;
+      }
+    }
+  }
+
+  private void collapseNonFallthroughSwitchTargets(BasicBlock block) {
+    Switch insn = block.exit().asSwitch();
+    BasicBlock fallthroughBlock = insn.fallthroughBlock();
+    Set<BasicBlock> replacedBlocks = new HashSet<>();
+    for (int j = 0; j < insn.targetBlockIndices().length; j++) {
+      BasicBlock target = insn.targetBlock(j);
+      if (target != fallthroughBlock) {
+        BasicBlock newTarget = target.endOfGotoChain();
+        if (newTarget != null && target != newTarget && !replacedBlocks.contains(target)) {
+          insn.getBlock().replaceSuccessor(target, newTarget);
+          target.getMutablePredecessors().remove(block);
+          if (!newTarget.getPredecessors().contains(block)) {
+            newTarget.getMutablePredecessors().add(block);
+          }
+          replacedBlocks.add(target);
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialPhiSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialPhiSimplifier.java
new file mode 100644
index 0000000..6c4c66f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialPhiSimplifier.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2023, 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.passes;
+
+import com.android.tools.r8.algorithms.scc.SCC;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.UnusedArgument;
+import com.android.tools.r8.ir.code.Value;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Set;
+
+public class TrivialPhiSimplifier {
+
+  public static void replaceUnusedArgumentTrivialPhis(UnusedArgument unusedArgument) {
+    replaceTrivialPhis(unusedArgument.outValue());
+    for (Phi phiUser : unusedArgument.outValue().uniquePhiUsers()) {
+      phiUser.removeTrivialPhi();
+    }
+    assert !unusedArgument.outValue().hasPhiUsers();
+  }
+
+  public static void ensureDirectStringNewToInit(IRCode code, DexItemFactory dexItemFactory) {
+    for (Instruction instruction : code.instructions()) {
+      if (instruction.isInvokeDirect()) {
+        InvokeDirect invoke = instruction.asInvokeDirect();
+        DexMethod method = invoke.getInvokedMethod();
+        if (dexItemFactory.isConstructor(method)
+            && method.holder == dexItemFactory.stringType
+            && invoke.getReceiver().isPhi()) {
+          NewInstance newInstance = findNewInstance(invoke.getReceiver().asPhi());
+          replaceTrivialPhis(newInstance.outValue());
+          if (invoke.getReceiver().isPhi()) {
+            throw new CompilationError(
+                "Failed to remove trivial phis between new-instance and <init>");
+          }
+          newInstance.markNoSpilling();
+        }
+      }
+    }
+  }
+
+  private static NewInstance findNewInstance(Phi phi) {
+    Set<Phi> seen = Sets.newIdentityHashSet();
+    Set<Value> values = Sets.newIdentityHashSet();
+    recursiveAddOperands(phi, seen, values);
+    if (values.size() != 1) {
+      throw new CompilationError("Failed to identify unique new-instance for <init>");
+    }
+    Value newInstanceValue = values.iterator().next();
+    if (newInstanceValue.definition == null || !newInstanceValue.definition.isNewInstance()) {
+      throw new CompilationError("Invalid defining value for call to <init>");
+    }
+    return newInstanceValue.definition.asNewInstance();
+  }
+
+  private static void recursiveAddOperands(Phi phi, Set<Phi> seen, Set<Value> values) {
+    for (Value operand : phi.getOperands()) {
+      if (!operand.isPhi()) {
+        values.add(operand);
+      } else {
+        Phi phiOp = operand.asPhi();
+        if (seen.add(phiOp)) {
+          recursiveAddOperands(phiOp, seen, values);
+        }
+      }
+    }
+  }
+
+  // We compute the set of strongly connected phis making use of the out value and replace all
+  // trivial ones by the out value.
+  // This is a simplified variant of the removeRedundantPhis algorithm in Section 3.2 of:
+  // http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf
+  private static void replaceTrivialPhis(Value outValue) {
+    List<Set<Value>> components = new SCC<Value>(Value::uniquePhiUsers).computeSCC(outValue);
+    for (int i = components.size() - 1; i >= 0; i--) {
+      Set<Value> component = components.get(i);
+      if (component.size() == 1 && component.iterator().next() == outValue) {
+        continue;
+      }
+      Set<Phi> trivialPhis = Sets.newIdentityHashSet();
+      for (Value value : component) {
+        boolean isTrivial = true;
+        Phi p = value.asPhi();
+        for (Value op : p.getOperands()) {
+          if (op != outValue && !component.contains(op)) {
+            isTrivial = false;
+            break;
+          }
+        }
+        if (isTrivial) {
+          trivialPhis.add(p);
+        }
+      }
+      for (Phi trivialPhi : trivialPhis) {
+        for (Value op : trivialPhi.getOperands()) {
+          op.removePhiUser(trivialPhi);
+        }
+        trivialPhi.replaceUsers(outValue);
+        trivialPhi.getBlock().removePhi(trivialPhi);
+      }
+    }
+  }
+}
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 26da1f1..4ad6534 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
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
@@ -14,47 +13,29 @@
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
 import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
 
-import com.android.tools.r8.algorithms.scc.SCC;
-import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
 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.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.FieldResolutionResult.SingleProgramFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerUtils;
-import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
-import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
-import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.type.TypeUtils;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-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.ArrayLength;
-import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.Binop;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
-import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
@@ -64,85 +45,51 @@
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.IfType;
 import com.android.tools.r8.ir.code.InstanceFieldInstruction;
 import com.android.tools.r8.ir.code.InstanceGet;
-import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InstructionOrPhi;
-import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.InvokeNewArray;
-import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
 import com.android.tools.r8.ir.code.Move;
-import com.android.tools.r8.ir.code.NewArrayEmpty;
-import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Throw;
-import com.android.tools.r8.ir.code.UnusedArgument;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.code.Xor;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
-import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
-import com.android.tools.r8.utils.InternalOutputMode;
 import com.android.tools.r8.utils.LazyBox;
-import com.android.tools.r8.utils.LongInterval;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
-import it.unimi.dsi.fastutil.ints.IntArrayList;
-import it.unimi.dsi.fastutil.ints.IntIterator;
-import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 import it.unimi.dsi.fastutil.ints.IntSet;
 import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
 import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.BitSet;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -150,18 +97,11 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
-import java.util.PriorityQueue;
 import java.util.Set;
 import java.util.function.Predicate;
 
 public class CodeRewriter {
 
-  private enum InstanceOfResult {
-    UNKNOWN,
-    TRUE,
-    FALSE
-  }
-
   // This constant was determined by experimentation.
   private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
 
@@ -233,28 +173,6 @@
     assert Streams.stream(code.instructions()).noneMatch(Instruction::isAssume);
   }
 
-  private static boolean removedTrivialGotos(IRCode code) {
-    ListIterator<BasicBlock> iterator = code.listIterator();
-    assert iterator.hasNext();
-    BasicBlock block = iterator.next();
-    BasicBlock nextBlock;
-    do {
-      nextBlock = iterator.hasNext() ? iterator.next() : null;
-      // Trivial goto block are only kept if they are self-targeting or are targeted by
-      // fallthroughs.
-      BasicBlock blk = block;  // Additional local for lambda below.
-      assert !block.isTrivialGoto()
-          || block.exit().asGoto().getTarget() == block
-          || code.entryBlock() == block
-          || block.getPredecessors().stream().anyMatch((b) -> b.exit().fallthroughBlock() == blk);
-      // Trivial goto blocks never target the next block (in that case there should just be a
-      // fallthrough).
-      assert !block.isTrivialGoto() || block.exit().asGoto().getTarget() != nextBlock;
-      block = nextBlock;
-    } while (block != null);
-    return true;
-  }
-
   // Rewrite 'throw new NullPointerException()' to 'throw null'.
   public void rewriteThrowNullPointerException(IRCode code) {
     boolean shouldRemoveUnreachableBlocks = false;
@@ -395,815 +313,6 @@
     assert code.isConsistentSSA(appView);
   }
 
-  public static boolean isFallthroughBlock(BasicBlock block) {
-    for (BasicBlock pred : block.getPredecessors()) {
-      if (pred.exit().fallthroughBlock() == block) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private static void collapseTrivialGoto(
-      IRCode code, BasicBlock block, BasicBlock nextBlock, List<BasicBlock> blocksToRemove) {
-
-    // This is the base case for GOTO loops.
-    if (block.exit().asGoto().getTarget() == block) {
-      return;
-    }
-
-    BasicBlock target = block.endOfGotoChain();
-
-    boolean needed = false;
-
-    if (target == null) {
-      // This implies we are in a loop of GOTOs. In that case, we will iteratively remove each
-      // trivial GOTO one-by-one until the above base case (one block targeting itself) is left.
-      target = block.exit().asGoto().getTarget();
-    }
-
-    if (target != nextBlock) {
-      // Not targeting the fallthrough block, determine if we need this goto. We need it if
-      // a fallthrough can hit this block. That is the case if the block is the entry block
-      // or if one of the predecessors fall through to the block.
-      needed = code.entryBlock() == block || isFallthroughBlock(block);
-    }
-
-    if (!needed) {
-      blocksToRemove.add(block);
-      unlinkTrivialGotoBlock(block, target);
-    }
-  }
-
-  public static void unlinkTrivialGotoBlock(BasicBlock block, BasicBlock target) {
-    assert block.isTrivialGoto();
-    for (BasicBlock pred : block.getPredecessors()) {
-      pred.replaceSuccessor(block, target);
-    }
-    for (BasicBlock succ : block.getSuccessors()) {
-      succ.getMutablePredecessors().remove(block);
-    }
-    for (BasicBlock pred : block.getPredecessors()) {
-      if (!target.getPredecessors().contains(pred)) {
-        target.getMutablePredecessors().add(pred);
-      }
-    }
-  }
-
-  private static void collapseIfTrueTarget(BasicBlock block) {
-    If insn = block.exit().asIf();
-    BasicBlock target = insn.getTrueTarget();
-    BasicBlock newTarget = target.endOfGotoChain();
-    BasicBlock fallthrough = insn.fallthroughBlock();
-    BasicBlock newFallthrough = fallthrough.endOfGotoChain();
-    if (newTarget != null && target != newTarget) {
-      insn.getBlock().replaceSuccessor(target, newTarget);
-      target.getMutablePredecessors().remove(block);
-      if (!newTarget.getPredecessors().contains(block)) {
-        newTarget.getMutablePredecessors().add(block);
-      }
-    }
-    if (block.exit().isIf()) {
-      insn = block.exit().asIf();
-      if (insn.getTrueTarget() == newFallthrough) {
-        // Replace if with the same true and fallthrough target with a goto to the fallthrough.
-        block.replaceSuccessor(insn.getTrueTarget(), fallthrough);
-        assert block.exit().isGoto();
-        assert block.exit().asGoto().getTarget() == fallthrough;
-      }
-    }
-  }
-
-  private static void collapseNonFallthroughSwitchTargets(BasicBlock block) {
-    Switch insn = block.exit().asSwitch();
-    BasicBlock fallthroughBlock = insn.fallthroughBlock();
-    Set<BasicBlock> replacedBlocks = new HashSet<>();
-    for (int j = 0; j < insn.targetBlockIndices().length; j++) {
-      BasicBlock target = insn.targetBlock(j);
-      if (target != fallthroughBlock) {
-        BasicBlock newTarget = target.endOfGotoChain();
-        if (newTarget != null && target != newTarget && !replacedBlocks.contains(target)) {
-          insn.getBlock().replaceSuccessor(target, newTarget);
-          target.getMutablePredecessors().remove(block);
-          if (!newTarget.getPredecessors().contains(block)) {
-            newTarget.getMutablePredecessors().add(block);
-          }
-          replacedBlocks.add(target);
-        }
-      }
-    }
-  }
-
-  // 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;
-    protected final Position position;
-
-    protected InstructionBuilder(Position position) {
-      this.position = position;
-    }
-
-    public abstract T self();
-
-    public T setBlockNumber(int blockNumber) {
-      this.blockNumber = blockNumber;
-      return self();
-    }
-  }
-
-  public static class SwitchBuilder extends InstructionBuilder<SwitchBuilder> {
-
-    private Value value;
-    private final Int2ReferenceSortedMap<BasicBlock> keyToTarget = new Int2ReferenceAVLTreeMap<>();
-    private BasicBlock fallthrough;
-
-    public SwitchBuilder(Position position) {
-      super(position);
-    }
-
-    @Override
-    public SwitchBuilder self() {
-      return this;
-    }
-
-    public SwitchBuilder setValue(Value value) {
-      this.value = value;
-      return this;
-    }
-
-    public SwitchBuilder addKeyAndTarget(int key, BasicBlock target) {
-      keyToTarget.put(key, target);
-      return this;
-    }
-
-    public SwitchBuilder setFallthrough(BasicBlock fallthrough) {
-      this.fallthrough = fallthrough;
-      return this;
-    }
-
-    public BasicBlock build(IRMetadata metadata) {
-      final int NOT_FOUND = -1;
-      Object2IntMap<BasicBlock> targetToSuccessorIndex = new Object2IntLinkedOpenHashMap<>();
-      targetToSuccessorIndex.defaultReturnValue(NOT_FOUND);
-
-      int[] keys = new int[keyToTarget.size()];
-      int[] targetBlockIndices = new int[keyToTarget.size()];
-      // Sort keys descending.
-      int count = 0;
-      IntIterator iter = keyToTarget.keySet().iterator();
-      while (iter.hasNext()) {
-        int key = iter.nextInt();
-        BasicBlock target = keyToTarget.get(key);
-        Integer targetIndex =
-            targetToSuccessorIndex.computeIfAbsent(target, b -> targetToSuccessorIndex.size());
-        keys[count] = key;
-        targetBlockIndices[count] = targetIndex;
-        count++;
-      }
-      Integer fallthroughIndex =
-          targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size());
-      IntSwitch newSwitch = new IntSwitch(value, keys, targetBlockIndices, fallthroughIndex);
-      newSwitch.setPosition(position);
-      BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch, metadata);
-      for (BasicBlock successor : targetToSuccessorIndex.keySet()) {
-        newSwitchBlock.link(successor);
-      }
-      return newSwitchBlock;
-    }
-  }
-
-  public static class IfBuilder extends InstructionBuilder<IfBuilder> {
-
-    private final IRCode code;
-    private Value left;
-    private int right;
-    private BasicBlock target;
-    private BasicBlock fallthrough;
-
-    public IfBuilder(Position position, IRCode code) {
-      super(position);
-      this.code = code;
-    }
-
-    @Override
-    public IfBuilder self() {
-      return this;
-    }
-
-    public IfBuilder setLeft(Value left) {
-      this.left = left;
-      return this;
-    }
-
-    public IfBuilder setRight(int right) {
-      this.right = right;
-      return this;
-    }
-
-    public IfBuilder setTarget(BasicBlock target) {
-      this.target = target;
-      return this;
-    }
-
-    public IfBuilder setFallthrough(BasicBlock fallthrough) {
-      this.fallthrough = fallthrough;
-      return this;
-    }
-
-    public BasicBlock build() {
-      assert target != null;
-      assert fallthrough != null;
-      If newIf;
-      BasicBlock ifBlock;
-      if (right != 0) {
-        ConstNumber rightConst = code.createIntConstant(right);
-        rightConst.setPosition(position);
-        newIf = new If(IfType.EQ, ImmutableList.of(left, rightConst.dest()));
-        ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata(), rightConst);
-      } else {
-        newIf = new If(IfType.EQ, left);
-        ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, code.metadata());
-      }
-      newIf.setPosition(position);
-      ifBlock.link(target);
-      ifBlock.link(fallthrough);
-      return ifBlock;
-    }
-  }
-
-  /**
-   * 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.
-   */
-  // TODO(b/270398965): Replace LinkedList.
-  @SuppressWarnings("JdkObsolete")
-  void convertSwitchToSwitchAndIfs(
-      IRCode code,
-      ListIterator<BasicBlock> blocksIterator,
-      BasicBlock originalBlock,
-      InstructionListIterator iterator,
-      IntSwitch theSwitch,
-      List<IntList> switches,
-      IntList keysToRemove) {
-
-    Position position = theSwitch.getPosition();
-
-    // Extract the information from the switch before removing it.
-    Int2ReferenceSortedMap<BasicBlock> keyToTarget = theSwitch.getKeyToTargetMap();
-
-    // Keep track of the current fallthrough, starting with the original.
-    BasicBlock fallthroughBlock = theSwitch.fallthroughBlock();
-
-    // Split the switch instruction into its own block and remove it.
-    iterator.previous();
-    BasicBlock originalSwitchBlock = iterator.split(code, blocksIterator);
-    assert !originalSwitchBlock.hasCatchHandlers();
-    assert originalSwitchBlock.getInstructions().size() == 1;
-    assert originalBlock.exit().isGoto();
-    theSwitch.moveDebugValues(originalBlock.exit());
-    blocksIterator.remove();
-    theSwitch.getBlock().detachAllSuccessors();
-    BasicBlock block = theSwitch.getBlock().unlinkSinglePredecessor();
-    assert theSwitch.getBlock().getPredecessors().size() == 0;
-    assert theSwitch.getBlock().getSuccessors().size() == 0;
-    assert block == originalBlock;
-
-    // Collect the new blocks for adding to the block list.
-    LinkedList<BasicBlock> newBlocks = new LinkedList<>();
-
-    // Build the switch-blocks backwards, to always have the fallthrough block in hand.
-    for (int i = switches.size() - 1; i >= 0; i--) {
-      SwitchBuilder switchBuilder = new SwitchBuilder(position);
-      switchBuilder.setValue(theSwitch.value());
-      IntList keys = switches.get(i);
-      for (int j = 0; j < keys.size(); j++) {
-        int key = keys.getInt(j);
-        switchBuilder.addKeyAndTarget(key, keyToTarget.get(key));
-      }
-      switchBuilder.setFallthrough(fallthroughBlock).setBlockNumber(code.getNextBlockNumber());
-      BasicBlock newSwitchBlock = switchBuilder.build(code.metadata());
-      newBlocks.addFirst(newSwitchBlock);
-      fallthroughBlock = newSwitchBlock;
-    }
-
-    // Build the if-blocks backwards, to always have the fallthrough block in hand.
-    for (int i = keysToRemove.size() - 1; i >= 0; i--) {
-      int key = keysToRemove.getInt(i);
-      BasicBlock peeledOffTarget = keyToTarget.get(key);
-      IfBuilder ifBuilder = new IfBuilder(position, code);
-      ifBuilder
-          .setLeft(theSwitch.value())
-          .setRight(key)
-          .setTarget(peeledOffTarget)
-          .setFallthrough(fallthroughBlock)
-          .setBlockNumber(code.getNextBlockNumber());
-      BasicBlock ifBlock = ifBuilder.build();
-      newBlocks.addFirst(ifBlock);
-      fallthroughBlock = ifBlock;
-    }
-
-    // Finally link the block before the original switch to the new block sequence.
-    originalBlock.link(fallthroughBlock);
-
-    // Finally add the blocks.
-    newBlocks.forEach(blocksIterator::add);
-  }
-
-  private static class Interval {
-
-    private final IntList keys = new IntArrayList();
-
-    public Interval(IntList... allKeys) {
-      assert allKeys.length > 0;
-      for (IntList keys : allKeys) {
-        assert keys.size() > 0;
-        this.keys.addAll(keys);
-      }
-    }
-
-    public int getMin() {
-      return keys.getInt(0);
-    }
-
-    public int getMax() {
-      return keys.getInt(keys.size() - 1);
-    }
-
-    public void addInterval(Interval other) {
-      assert getMax() < other.getMin();
-      keys.addAll(other.keys);
-    }
-
-    public long packedSavings(InternalOutputMode mode) {
-      long packedTargets = (long) getMax() - (long) getMin() + 1;
-      if (!IntSwitch.canBePacked(mode, packedTargets)) {
-        return Long.MIN_VALUE + 1;
-      }
-      long sparseCost =
-          IntSwitch.baseSparseSize(mode) + IntSwitch.sparsePayloadSize(mode, keys.size());
-      long packedCost =
-          IntSwitch.basePackedSize(mode) + IntSwitch.packedPayloadSize(mode, packedTargets);
-      return sparseCost - packedCost;
-    }
-
-    public long estimatedSize(InternalOutputMode mode) {
-      return IntSwitch.estimatedSize(mode, keys.toIntArray());
-    }
-  }
-
-  private Interval combineOrAddInterval(
-      List<Interval> intervals, Interval previous, Interval current) {
-    // As a first iteration, we only combine intervals if their packed size is less than their
-    // sparse counterpart. In CF we will have to add a load and a jump which add to the
-    // stack map table (1 is the size of a same entry).
-    InternalOutputMode mode = options.getInternalOutputMode();
-    int penalty = mode.isGeneratingClassFiles() ? 3 + 1 : 0;
-    if (previous == null) {
-      intervals.add(current);
-      return current;
-    }
-    Interval combined = new Interval(previous.keys, current.keys);
-    long packedSavings = combined.packedSavings(mode);
-    if (packedSavings <= 0
-        || packedSavings < previous.estimatedSize(mode) + current.estimatedSize(mode) - penalty) {
-      intervals.add(current);
-      return current;
-    } else {
-      intervals.set(intervals.size() - 1, combined);
-      return combined;
-    }
-  }
-
-  private void tryAddToBiggestSavings(
-      Set<Interval> biggestPackedSet,
-      PriorityQueue<Interval> intervals,
-      Interval toAdd,
-      int maximumNumberOfSwitches) {
-    assert !biggestPackedSet.contains(toAdd);
-    long savings = toAdd.packedSavings(options.getInternalOutputMode());
-    if (savings <= 0) {
-      return;
-    }
-    if (intervals.size() < maximumNumberOfSwitches) {
-      intervals.add(toAdd);
-      biggestPackedSet.add(toAdd);
-    } else if (savings > intervals.peek().packedSavings(options.getInternalOutputMode())) {
-      intervals.add(toAdd);
-      biggestPackedSet.add(toAdd);
-      biggestPackedSet.remove(intervals.poll());
-    }
-  }
-
-  private int sizeForKeysWrittenAsIfs(ValueType type, Collection<Integer> keys) {
-    int ifsSize = If.estimatedSize(options.getInternalOutputMode()) * keys.size();
-    // In Cf we also require a load as well (and a stack map entry)
-    if (options.getInternalOutputMode().isGeneratingClassFiles()) {
-      ifsSize += keys.size() * (3 + 1);
-    }
-    for (int k : keys) {
-      if (k != 0) {
-        ifsSize += ConstNumber.estimatedSize(options.getInternalOutputMode(), type, k);
-      }
-    }
-    return ifsSize;
-  }
-
-  private int codeUnitMargin() {
-    return options.getInternalOutputMode().isGeneratingClassFiles() ? 3 : 1;
-  }
-
-  private int findIfsForCandidates(
-      List<Interval> newSwitches, IntSwitch theSwitch, IntList outliers) {
-    Set<Interval> switchesToRemove = new HashSet<>();
-    InternalOutputMode mode = options.getInternalOutputMode();
-    int outliersAsIfSize = 0;
-    // The candidateForIfs is either an index to a switch that can be eliminated totally or a sparse
-    // where removing a key may produce a greater saving. It is only if keys are small in the packed
-    // switch that removing the keys makes sense (size wise).
-    for (Interval candidate : newSwitches) {
-      int maxIfBudget = 10;
-      long switchSize = candidate.estimatedSize(mode);
-      int sizeOfAllKeysAsIf = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), candidate.keys);
-      if (candidate.keys.size() <= maxIfBudget
-          && sizeOfAllKeysAsIf < switchSize - codeUnitMargin()) {
-        outliersAsIfSize += sizeOfAllKeysAsIf;
-        switchesToRemove.add(candidate);
-        outliers.addAll(candidate.keys);
-        continue;
-      }
-      // One could do something clever here, but we use a simple algorithm that use the fact that
-      // all keys are sorted in ascending order and that the smallest absolute value will give the
-      // best saving.
-      IntList candidateKeys = candidate.keys;
-      int smallestPosition = -1;
-      long smallest = Long.MAX_VALUE;
-      for (int i = 0; i < candidateKeys.size(); i++) {
-        long current = Math.abs((long) candidateKeys.getInt(i));
-        if (current < smallest) {
-          smallestPosition = i;
-          smallest = current;
-        }
-      }
-      // Add as many keys forward and backward as we have budget and we decrease in size.
-      IntList ifKeys = new IntArrayList();
-      ifKeys.add(candidateKeys.getInt(smallestPosition));
-      long previousSavings = 0;
-      long currentSavings =
-          switchSize
-              - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys)
-              - IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
-      int minIndex = smallestPosition - 1;
-      int maxIndex = smallestPosition + 1;
-      while (ifKeys.size() < maxIfBudget && currentSavings > previousSavings) {
-        if (minIndex >= 0 && maxIndex < candidateKeys.size()) {
-          long valMin = Math.abs((long) candidateKeys.getInt(minIndex));
-          int valMax = Math.abs(candidateKeys.getInt(maxIndex));
-          if (valMax <= valMin) {
-            ifKeys.add(candidateKeys.getInt(maxIndex++));
-          } else {
-            ifKeys.add(candidateKeys.getInt(minIndex--));
-          }
-        } else if (minIndex >= 0) {
-          ifKeys.add(candidateKeys.getInt(minIndex--));
-        } else if (maxIndex < candidateKeys.size()) {
-          ifKeys.add(candidateKeys.getInt(maxIndex++));
-        } else {
-          // No more elements to add as if's.
-          break;
-        }
-        previousSavings = currentSavings;
-        currentSavings =
-            switchSize
-                - sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys)
-                - IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
-      }
-      if (previousSavings >= currentSavings) {
-        // Remove the last added key since it did not contribute to savings.
-        int lastKey = ifKeys.getInt(ifKeys.size() - 1);
-        ifKeys.removeInt(ifKeys.size() - 1);
-        if (lastKey == candidateKeys.getInt(minIndex + 1)) {
-          minIndex++;
-        } else {
-          maxIndex--;
-        }
-      }
-      // Adjust pointers into the candidate keys.
-      minIndex++;
-      maxIndex--;
-      if (ifKeys.size() > 0) {
-        int ifsSize = sizeForKeysWrittenAsIfs(theSwitch.value().outType(), ifKeys);
-        long newSwitchSize =
-            IntSwitch.estimatedSparseSize(mode, candidateKeys.size() - ifKeys.size());
-        if (newSwitchSize + ifsSize + codeUnitMargin() < switchSize) {
-          candidateKeys.removeElements(minIndex, maxIndex);
-          outliers.addAll(ifKeys);
-          outliersAsIfSize += ifsSize;
-        }
-      }
-    }
-    newSwitches.removeAll(switchesToRemove);
-    return outliersAsIfSize;
-  }
-
-  public boolean rewriteSwitch(IRCode code) {
-    return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance());
-  }
-
-  private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
-    if (!options.isSwitchRewritingEnabled()) {
-      return false;
-    }
-    if (!code.metadata().mayHaveSwitch()) {
-      return false;
-    }
-    return rewriteSwitchFull(code, switchCaseAnalyzer);
-  }
-
-  private boolean rewriteSwitchFull(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
-    boolean needToRemoveUnreachableBlocks = 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();
-        if (instruction.isSwitch()) {
-          Switch theSwitch = instruction.asSwitch();
-          if (options.testing.enableDeadSwitchCaseElimination) {
-            SwitchCaseEliminator eliminator =
-                removeUnnecessarySwitchCases(code, theSwitch, iterator, switchCaseAnalyzer);
-            if (eliminator.mayHaveIntroducedUnreachableBlocks()) {
-              needToRemoveUnreachableBlocks = true;
-            }
-
-            iterator.previous();
-            instruction = iterator.next();
-            if (instruction.isGoto()) {
-              continue;
-            }
-
-            assert instruction.isSwitch();
-            theSwitch = instruction.asSwitch();
-          }
-          if (theSwitch.isIntSwitch()) {
-            rewriteIntSwitch(code, blocksIterator, block, iterator, theSwitch.asIntSwitch());
-          }
-        }
-      }
-    }
-
-    // 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.
-    code.splitCriticalEdges();
-
-    Set<Value> affectedValues =
-        needToRemoveUnreachableBlocks ? code.removeUnreachableBlocks() : ImmutableSet.of();
-    if (!affectedValues.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(affectedValues);
-    }
-    assert code.isConsistentSSA(appView);
-    return !affectedValues.isEmpty();
-  }
-
-  void rewriteSingleKeySwitchToIf(
-      IRCode code, BasicBlock block, InstructionListIterator iterator, IntSwitch theSwitch) {
-    // Rewrite the switch to an if.
-    int fallthroughBlockIndex = theSwitch.getFallthroughBlockIndex();
-    int caseBlockIndex = theSwitch.targetBlockIndices()[0];
-    if (fallthroughBlockIndex < caseBlockIndex) {
-      block.swapSuccessorsByIndex(fallthroughBlockIndex, caseBlockIndex);
-    }
-    If replacement;
-    if (theSwitch.isIntSwitch() && theSwitch.asIntSwitch().getFirstKey() == 0) {
-      replacement = new If(IfType.EQ, theSwitch.value());
-    } else {
-      Instruction labelConst = theSwitch.materializeFirstKey(appView, code);
-      labelConst.setPosition(theSwitch.getPosition());
-      iterator.previous();
-      iterator.add(labelConst);
-      Instruction dummy = iterator.next();
-      assert dummy == theSwitch;
-      replacement = new If(IfType.EQ, ImmutableList.of(theSwitch.value(), labelConst.outValue()));
-    }
-    iterator.replaceCurrentInstruction(replacement);
-  }
-
-  private void rewriteIntSwitch(
-      IRCode code,
-      ListIterator<BasicBlock> blockIterator,
-      BasicBlock block,
-      InstructionListIterator iterator,
-      IntSwitch theSwitch) {
-    if (disableSwitchToIfRewritingForClassIdComparisons(theSwitch)) {
-      return;
-    }
-
-    if (theSwitch.numberOfKeys() == 1) {
-      rewriteSingleKeySwitchToIf(code, block, iterator, theSwitch);
-      return;
-    }
-
-    // If there are more than 1 key, we use the following algorithm to find keys to combine.
-    // First, scan through the keys forward and combine each packed interval with the
-    // previous interval if it gives a net saving.
-    // Secondly, go through all created intervals and combine the ones without a saving into
-    // a single interval and keep a max number of packed switches.
-    // Finally, go through all intervals and check if the switch or part of the switch
-    // should be transformed to ifs.
-
-    // Phase 1: Combine packed intervals.
-    InternalOutputMode mode = options.getInternalOutputMode();
-    int[] keys = theSwitch.getKeys();
-    int maxNumberOfIfsOrSwitches = 10;
-    PriorityQueue<Interval> biggestPackedSavings =
-        new PriorityQueue<>((x, y) -> Long.compare(y.packedSavings(mode), x.packedSavings(mode)));
-    Set<Interval> biggestPackedSet = new HashSet<>();
-    List<Interval> intervals = new ArrayList<>();
-    int previousKey = keys[0];
-    IntList currentKeys = new IntArrayList();
-    currentKeys.add(previousKey);
-    Interval previousInterval = null;
-    for (int i = 1; i < keys.length; i++) {
-      int key = keys[i];
-      if (((long) key - (long) previousKey) > 1) {
-        Interval current = new Interval(currentKeys);
-        Interval added = combineOrAddInterval(intervals, previousInterval, current);
-        if (added != current && biggestPackedSet.contains(previousInterval)) {
-          biggestPackedSet.remove(previousInterval);
-          biggestPackedSavings.remove(previousInterval);
-        }
-        tryAddToBiggestSavings(
-            biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
-        previousInterval = added;
-        currentKeys = new IntArrayList();
-      }
-      currentKeys.add(key);
-      previousKey = key;
-    }
-    Interval current = new Interval(currentKeys);
-    Interval added = combineOrAddInterval(intervals, previousInterval, current);
-    if (added != current && biggestPackedSet.contains(previousInterval)) {
-      biggestPackedSet.remove(previousInterval);
-      biggestPackedSavings.remove(previousInterval);
-    }
-    tryAddToBiggestSavings(biggestPackedSet, biggestPackedSavings, added, maxNumberOfIfsOrSwitches);
-
-    // Phase 2: combine sparse intervals into a single bin.
-    // Check if we should save a space for a sparse switch, if so, remove the switch with
-    // the smallest savings.
-    if (biggestPackedSet.size() == maxNumberOfIfsOrSwitches
-        && maxNumberOfIfsOrSwitches < intervals.size()) {
-      biggestPackedSet.remove(biggestPackedSavings.poll());
-    }
-    Interval sparse = null;
-    List<Interval> newSwitches = new ArrayList<>(maxNumberOfIfsOrSwitches);
-    for (int i = 0; i < intervals.size(); i++) {
-      Interval interval = intervals.get(i);
-      if (biggestPackedSet.contains(interval)) {
-        newSwitches.add(interval);
-      } else if (sparse == null) {
-        sparse = interval;
-        newSwitches.add(sparse);
-      } else {
-        sparse.addInterval(interval);
-      }
-    }
-
-    // Phase 3: at this point we are guaranteed to have the biggest saving switches
-    // in newIntervals, potentially with a switch combining the remaining intervals.
-    // Now we check to see if we can create any if's to reduce size.
-    IntList outliers = new IntArrayList();
-    int outliersAsIfSize =
-        appView.options().testing.enableSwitchToIfRewriting
-            ? findIfsForCandidates(newSwitches, theSwitch, outliers)
-            : 0;
-
-    long newSwitchesSize = 0;
-    List<IntList> newSwitchSequences = new ArrayList<>(newSwitches.size());
-    for (Interval interval : newSwitches) {
-      newSwitchesSize += interval.estimatedSize(mode);
-      newSwitchSequences.add(interval.keys);
-    }
-
-    long currentSize = IntSwitch.estimatedSize(mode, theSwitch.getKeys());
-    if (newSwitchesSize + outliersAsIfSize + codeUnitMargin() < currentSize) {
-      convertSwitchToSwitchAndIfs(
-          code, blockIterator, block, iterator, theSwitch, newSwitchSequences, outliers);
-    }
-  }
-
-  // TODO(b/181732463): We currently disable switch-to-if rewritings for switches on $r8$classId
-  //  field values (from horizontal class merging. See bug for more details.
-  private boolean disableSwitchToIfRewritingForClassIdComparisons(IntSwitch theSwitch) {
-    Value switchValue = theSwitch.value().getAliasedValue();
-    if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInstanceGet)) {
-      return false;
-    }
-    AppInfoWithLiveness appInfo = appView.appInfoWithLiveness();
-    if (appInfo == null) {
-      return false;
-    }
-    InstanceGet instanceGet = switchValue.getDefinition().asInstanceGet();
-    SingleProgramFieldResolutionResult resolutionResult =
-        appInfo.resolveField(instanceGet.getField()).asSingleProgramFieldResolutionResult();
-    if (resolutionResult == null) {
-      return false;
-    }
-    DexEncodedField resolvedField = resolutionResult.getResolvedField();
-    return HorizontalClassMergerUtils.isClassIdField(appView, resolvedField);
-  }
-
-  private SwitchCaseEliminator removeUnnecessarySwitchCases(
-      IRCode code,
-      Switch theSwitch,
-      InstructionListIterator iterator,
-      SwitchCaseAnalyzer switchCaseAnalyzer) {
-    BasicBlock defaultTarget = theSwitch.fallthroughBlock();
-    SwitchCaseEliminator eliminator = new SwitchCaseEliminator(theSwitch, iterator);
-    BasicBlockBehavioralSubsumption behavioralSubsumption =
-        new BasicBlockBehavioralSubsumption(appView, code);
-
-    // Compute the set of switch cases that can be removed.
-    boolean hasSwitchCaseToDefaultRewrite = false;
-    AbstractValue switchAbstractValue = theSwitch.value().getAbstractValue(appView, code.context());
-    for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
-      BasicBlock targetBlock = theSwitch.targetBlock(i);
-
-      if (switchCaseAnalyzer.switchCaseIsAlwaysHit(theSwitch, i)) {
-        eliminator.markSwitchCaseAsAlwaysHit(i);
-        break;
-      }
-
-      // This switch case can be removed if the behavior of the target block is equivalent to the
-      // behavior of the default block, or if the switch case is unreachable.
-      if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)) {
-        eliminator.markSwitchCaseForRemoval(i);
-      } else if (behavioralSubsumption.isSubsumedBy(
-          theSwitch.value(), targetBlock, defaultTarget)) {
-        eliminator.markSwitchCaseForRemoval(i);
-        hasSwitchCaseToDefaultRewrite = true;
-      }
-    }
-
-    if (eliminator.isFallthroughLive()
-        && !hasSwitchCaseToDefaultRewrite
-        && switchCaseAnalyzer.switchFallthroughIsNeverHit(theSwitch, switchAbstractValue)) {
-      eliminator.markSwitchFallthroughAsNeverHit();
-    }
-
-    eliminator.optimize();
-    return eliminator;
-  }
-
-  /**
-   * Rewrite all branch targets to the destination of trivial goto chains when possible. Does not
-   * rewrite fallthrough targets as that would require block reordering and the transformation only
-   * makes sense after SSA destruction where there are no phis.
-   */
-  public static void collapseTrivialGotos(AppView<?> appView, IRCode code) {
-    assert code.isConsistentGraph(appView);
-    List<BasicBlock> blocksToRemove = new ArrayList<>();
-    // Rewrite all non-fallthrough targets to the end of trivial goto chains and remove
-    // first round of trivial goto blocks.
-    ListIterator<BasicBlock> iterator = code.listIterator();
-    assert iterator.hasNext();
-    BasicBlock block = iterator.next();
-    BasicBlock nextBlock;
-
-    do {
-      nextBlock = iterator.hasNext() ? iterator.next() : null;
-      if (block.isTrivialGoto()) {
-        collapseTrivialGoto(code, block, nextBlock, blocksToRemove);
-      }
-      if (block.exit().isIf()) {
-        collapseIfTrueTarget(block);
-      }
-      if (block.exit().isSwitch()) {
-        collapseNonFallthroughSwitchTargets(block);
-      }
-      block = nextBlock;
-    } while (nextBlock != null);
-    code.removeBlocks(blocksToRemove);
-    // Get rid of gotos to the next block.
-    while (!blocksToRemove.isEmpty()) {
-      blocksToRemove = new ArrayList<>();
-      iterator = code.listIterator();
-      block = iterator.next();
-      do {
-        nextBlock = iterator.hasNext() ? iterator.next() : null;
-        if (block.isTrivialGoto()) {
-          collapseTrivialGoto(code, block, nextBlock, blocksToRemove);
-        }
-        block = nextBlock;
-      } while (block != null);
-      code.removeBlocks(blocksToRemove);
-    }
-    assert removedTrivialGotos(code);
-    assert code.isConsistentGraph(appView);
-  }
-
   private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) {
     // TODO(sgjesse): Insert cast if required.
     TypeElement returnType =
@@ -1309,333 +418,6 @@
     return changed;
   }
 
-  enum RemoveCheckCastInstructionIfTrivialResult {
-    NO_REMOVALS,
-    REMOVED_CAST_DO_NARROW
-  }
-
-  public void removeTrivialCheckCastAndInstanceOfInstructions(
-      IRCode code,
-      ProgramMethod context,
-      MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext) {
-    if (!appView.enableWholeProgramOptimizations()) {
-      return;
-    }
-
-    assert appView.appInfo().hasLiveness();
-    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-
-    if (!appView.options().testing.enableCheckCastAndInstanceOfRemoval) {
-      return;
-    }
-
-    IRMetadata metadata = code.metadata();
-    if (!metadata.mayHaveCheckCast() && !metadata.mayHaveInstanceOf()) {
-      return;
-    }
-
-    // If we can remove a CheckCast it is due to us having at least as much information about the
-    // type as the CheckCast gives. We then need to propagate that information to the users of
-    // the CheckCast to ensure further optimizations and removals of CheckCast:
-    //
-    //    : 1: NewArrayEmpty        v2 <- v1(1) java.lang.String[]  <-- v2 = String[]
-    // ...
-    //    : 2: CheckCast            v5 <- v2; java.lang.Object[]    <-- v5 = Object[]
-    // ...
-    //    : 3: ArrayGet             v7 <- v5, v6(0)                 <-- v7 = Object
-    //    : 4: CheckCast            v8 <- v7; java.lang.String      <-- v8 = String
-    // ...
-    //
-    // When looking at line 2 we can conclude that the CheckCast is trivial because v2 is String[]
-    // and remove it. However, v7 is still only known to be Object and we cannot remove the
-    // CheckCast at line 4 unless we update v7 with the most precise information by narrowing the
-    // affected values of v5. We therefore have to run the type analysis after each CheckCast
-    // removal.
-    TypeAnalysis typeAnalysis = new TypeAnalysis(appView);
-    Set<Value> affectedValues = Sets.newIdentityHashSet();
-    InstructionListIterator it = code.instructionListIterator();
-    boolean needToRemoveTrivialPhis = false;
-    while (it.hasNext()) {
-      Instruction current = it.next();
-      if (current.isCheckCast()) {
-        boolean hasPhiUsers = current.outValue().hasPhiUsers();
-        RemoveCheckCastInstructionIfTrivialResult removeResult =
-            removeCheckCastInstructionIfTrivial(
-                appViewWithLiveness,
-                current.asCheckCast(),
-                it,
-                code,
-                context,
-                affectedValues,
-                methodProcessor,
-                methodProcessingContext);
-        if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) {
-          assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
-          needToRemoveTrivialPhis |= hasPhiUsers;
-          typeAnalysis.narrowing(affectedValues);
-          affectedValues.clear();
-        }
-      } else if (current.isInstanceOf()) {
-        boolean hasPhiUsers = current.outValue().hasPhiUsers();
-        if (removeInstanceOfInstructionIfTrivial(
-            appViewWithLiveness, current.asInstanceOf(), it, code)) {
-          needToRemoveTrivialPhis |= hasPhiUsers;
-        }
-      }
-    }
-    // ... v1
-    // ...
-    // v2 <- check-cast v1, T
-    // v3 <- phi(v1, v2)
-    // Removing check-cast may result in a trivial phi:
-    // v3 <- phi(v1, v1)
-    if (needToRemoveTrivialPhis) {
-      code.removeAllDeadAndTrivialPhis(affectedValues);
-      if (!affectedValues.isEmpty()) {
-        typeAnalysis.narrowing(affectedValues);
-      }
-    }
-    assert code.isConsistentSSA(appView);
-  }
-
-  // Returns true if the given check-cast instruction was removed.
-  private RemoveCheckCastInstructionIfTrivialResult removeCheckCastInstructionIfTrivial(
-      AppView<AppInfoWithLiveness> appViewWithLiveness,
-      CheckCast checkCast,
-      InstructionListIterator it,
-      IRCode code,
-      ProgramMethod context,
-      Set<Value> affectedValues,
-      MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext) {
-    Value inValue = checkCast.object();
-    Value outValue = checkCast.outValue();
-    DexType castType = checkCast.getType();
-    DexType baseCastType = castType.toBaseType(dexItemFactory);
-
-    // If the cast type is not accessible in the current context, we should not remove the cast
-    // in order to preserve runtime errors. Note that JVM and ART behave differently: see
-    // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}.
-    if (baseCastType.isClassType()) {
-      DexClass baseCastClass = appView.definitionFor(baseCastType);
-      if (baseCastClass == null
-          || AccessControl.isClassAccessible(baseCastClass, code.context(), appViewWithLiveness)
-              .isPossiblyFalse()) {
-        return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
-      }
-    }
-
-    if (!appView
-        .getOpenClosedInterfacesCollection()
-        .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) {
-      return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
-    }
-
-    // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
-    // elimination may lead to verification errors. See b/123269162.
-    if (options.canHaveArtCheckCastVerifierBug()) {
-      if (inValue.getType().isNullType()
-          && castType.isArrayType()
-          && castType.toBaseType(dexItemFactory).isFloatType()) {
-        return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
-      }
-    }
-
-    // If casting to an array of an interface type elimination may lead to verification errors.
-    // See b/132420510 and b/223424356.
-    if (options.canHaveIncorrectJoinForArrayOfInterfacesBug()) {
-      if (castType.isArrayType()) {
-        DexType baseType = castType.toBaseType(dexItemFactory);
-        if (baseType.isClassType() && baseType.isInterface(appViewWithLiveness)) {
-          return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
-        }
-      }
-    }
-
-    TypeElement inTypeLattice = inValue.getType();
-    TypeElement outTypeLattice = outValue.getType();
-    TypeElement castTypeLattice = castType.toTypeElement(appView, inTypeLattice.nullability());
-
-    assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability());
-
-    if (inTypeLattice.lessThanOrEqual(castTypeLattice, appView)) {
-      // 1) Trivial cast.
-      //   A a = ...
-      //   A a' = (A) a;
-      // 2) Up-cast: we already have finer type info.
-      //   A < B
-      //   A a = ...
-      //   B b = (B) a;
-      assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView);
-      // The removeOrReplaceByDebugLocalWrite will propagate the incoming value for the CheckCast
-      // to the users of the CheckCast's out value.
-      //
-      // v2 = CheckCast A v1  ~~>  DebugLocalWrite $v0 <- v1
-      //
-      // The DebugLocalWrite is not a user of the outvalue, we therefore have to wait and take the
-      // CheckCast invalue users that includes the potential DebugLocalWrite.
-      removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
-      affectedValues.addAll(inValue.affectedValues());
-      return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
-    }
-
-    // If values of cast type are guaranteed to be null, then the out-value must be null if the cast
-    // succeeds. After removing all usages of the out-value, the check-cast instruction is replaced
-    // by a call to throwClassCastExceptionIfNotNull() to allow dead code elimination of the cast
-    // type.
-    if (castType.isClassType()
-        && castType.isAlwaysNull(appViewWithLiveness)
-        && !outValue.hasDebugUsers()) {
-      // Replace all usages of the out-value by null.
-      it.previous();
-      Value nullValue = it.insertConstNullInstruction(code, options);
-      it.next();
-      checkCast.outValue().replaceUsers(nullValue);
-      affectedValues.addAll(nullValue.affectedValues());
-
-      // Replace the check-cast instruction by throwClassCastExceptionIfNotNull().
-      UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod =
-          UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod(
-              appView, methodProcessor.getEventConsumer(), methodProcessingContext);
-      throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
-      InvokeStatic replacement =
-          InvokeStatic.builder()
-              .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod())
-              .setSingleArgument(checkCast.object())
-              .setPosition(checkCast)
-              .build();
-      it.replaceCurrentInstruction(replacement);
-      assert replacement.lookupSingleTarget(appView, context) != null;
-      return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
-    }
-
-    // If the cast is guaranteed to succeed and only there to ensure the program type checks, then
-    // check if the program would still type check after removing the cast.
-    if (checkCast.isSafeCheckCast()
-        || checkCast
-            .getFirstOperand()
-            .getDynamicType(appViewWithLiveness)
-            .getDynamicUpperBoundType()
-            .lessThanOrEqualUpToNullability(castTypeLattice, appView)) {
-      TypeElement useType =
-          TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue());
-      if (inTypeLattice.lessThanOrEqualUpToNullability(useType, appView)) {
-        return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
-      }
-    }
-
-    // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
-    // A < B < C
-    // c = ...        // Even though we know c is of type A,
-    // a' = (B) c;    // (this could be removed, since chained below.)
-    // a'' = (A) a';  // this should remain for runtime verification.
-    assert !inTypeLattice.isDefinitelyNull() || (inValue.isPhi() && !inTypeLattice.isNullType());
-    assert outTypeLattice.equalUpToNullability(castTypeLattice);
-    return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
-  }
-
-  // Returns true if the given instance-of instruction was removed.
-  private boolean removeInstanceOfInstructionIfTrivial(
-      AppView<AppInfoWithLiveness> appViewWithLiveness,
-      InstanceOf instanceOf,
-      InstructionListIterator it,
-      IRCode code) {
-    ProgramMethod context = code.context();
-
-    // If the instance-of type is not accessible in the current context, we should not remove the
-    // instance-of instruction in order to preserve IllegalAccessError.
-    DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory);
-    if (instanceOfBaseType.isClassType()) {
-      DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType);
-      if (instanceOfClass == null
-          || AccessControl.isClassAccessible(instanceOfClass, context, appViewWithLiveness)
-              .isPossiblyFalse()) {
-        return false;
-      }
-    }
-
-    Value inValue = instanceOf.value();
-    if (!appView
-        .getOpenClosedInterfacesCollection()
-        .isDefinitelyInstanceOfStaticType(appViewWithLiveness, inValue)) {
-      return false;
-    }
-
-    TypeElement inType = inValue.getType();
-    TypeElement instanceOfType =
-        TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView);
-    Value aliasValue = inValue.getAliasedValue();
-
-    InstanceOfResult result = InstanceOfResult.UNKNOWN;
-    if (inType.isDefinitelyNull()) {
-      result = InstanceOfResult.FALSE;
-    } else if (inType.lessThanOrEqual(instanceOfType, appView) && !inType.isNullable()) {
-      result = InstanceOfResult.TRUE;
-    } else if (!aliasValue.isPhi()
-        && aliasValue.definition.isCreatingInstanceOrArray()
-        && instanceOfType.strictlyLessThan(inType, appView)) {
-      result = InstanceOfResult.FALSE;
-    } else if (appView.appInfo().hasLiveness()) {
-      if (instanceOf.type().isClassType()
-          && isNeverInstantiatedDirectlyOrIndirectly(instanceOf.type())) {
-        // The type of the instance-of instruction is a program class, and is never instantiated
-        // directly or indirectly. Thus, the in-value must be null, meaning that the instance-of
-        // instruction will always evaluate to false.
-        result = InstanceOfResult.FALSE;
-      }
-
-      if (result == InstanceOfResult.UNKNOWN) {
-        if (inType.isClassType()
-            && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) {
-          // The type of the in-value is a program class, and is never instantiated directly or
-          // indirectly. This, the in-value must be null, meaning that the instance-of instruction
-          // will always evaluate to false.
-          result = InstanceOfResult.FALSE;
-        }
-      }
-
-      if (result == InstanceOfResult.UNKNOWN) {
-        Value aliasedValue =
-            inValue.getSpecificAliasedValue(
-                value ->
-                    value.isDefinedByInstructionSatisfying(
-                        Instruction::isAssumeWithDynamicTypeAssumption));
-        if (aliasedValue != null) {
-          DynamicTypeWithUpperBound dynamicType =
-              aliasedValue.getDefinition().asAssume().getDynamicTypeAssumption().getDynamicType();
-          Nullability nullability = dynamicType.getNullability();
-          if (nullability.isDefinitelyNull()) {
-            result = InstanceOfResult.FALSE;
-          } else if (dynamicType.getDynamicUpperBoundType().lessThanOrEqual(instanceOfType, appView)
-              && (!inType.isNullable() || !nullability.isNullable())) {
-            result = InstanceOfResult.TRUE;
-          }
-        }
-      }
-    }
-    if (result != InstanceOfResult.UNKNOWN) {
-      ConstNumber newInstruction =
-          new ConstNumber(
-              new Value(
-                  code.valueNumberGenerator.next(),
-                  TypeElement.getInt(),
-                  instanceOf.outValue().getLocalInfo()),
-              result == InstanceOfResult.TRUE ? 1 : 0);
-      it.replaceCurrentInstruction(newInstruction);
-      return true;
-    }
-    return false;
-  }
-
-  private boolean isNeverInstantiatedDirectlyOrIndirectly(DexType type) {
-    assert appView.appInfo().hasLiveness();
-    assert type.isClassType();
-    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
-    return clazz != null
-        && !appView.appInfo().withLiveness().isInstantiatedDirectlyOrIndirectly(clazz);
-  }
-
   public static void removeOrReplaceByDebugLocalWrite(
       Instruction currentInstruction, InstructionListIterator it, Value inValue, Value outValue) {
     if (outValue.hasLocalInfo() && outValue.getLocalInfo() != inValue.getLocalInfo()) {
@@ -1688,6 +470,53 @@
     assert code.isConsistentSSA(appView);
   }
 
+  public void rewriteKnownArrayLengthCalls(IRCode code) {
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction current = iterator.next();
+      if (!current.isArrayLength()) {
+        continue;
+      }
+
+      ArrayLength arrayLength = current.asArrayLength();
+      if (arrayLength.hasOutValue() && arrayLength.outValue().hasLocalInfo()) {
+        continue;
+      }
+
+      Value array = arrayLength.array().getAliasedValue();
+      if (array.isPhi() || !arrayLength.array().isNeverNull() || array.hasLocalInfo()) {
+        continue;
+      }
+
+      AbstractValue abstractValue = array.getAbstractValue(appView, code.context());
+      if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) {
+        continue;
+      }
+      Instruction arrayDefinition = array.getDefinition();
+      assert arrayDefinition != null;
+
+      Set<Phi> phiUsers = arrayLength.outValue().uniquePhiUsers();
+      if (arrayDefinition.isNewArrayEmpty()) {
+        Value size = arrayDefinition.asNewArrayEmpty().size();
+        arrayLength.outValue().replaceUsers(size);
+        iterator.removeOrReplaceByDebugLocalRead();
+      } else if (arrayDefinition.isNewArrayFilledData()) {
+        long size = arrayDefinition.asNewArrayFilledData().size;
+        if (size > Integer.MAX_VALUE) {
+          continue;
+        }
+        iterator.replaceCurrentInstructionWithConstInt(code, (int) size);
+      } else if (abstractValue.hasKnownArrayLength()) {
+        iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength());
+      } else {
+        continue;
+      }
+
+      phiUsers.forEach(Phi::removeTrivialPhi);
+    }
+    assert code.isConsistentSSA(appView);
+  }
+
   /**
    * If an instruction is known to be a binop/lit8 or binop//lit16 instruction, update the
    * instruction to use its own constant that will be defined just before the instruction. This
@@ -2105,431 +934,6 @@
     }
   }
 
-  private short[] computeArrayFilledData(Value[] values, int size, int elementSize) {
-    for (Value v : values) {
-      if (!v.isConstant()) {
-        return null;
-      }
-    }
-    if (elementSize == 1) {
-      short[] result = new short[(size + 1) / 2];
-      for (int i = 0; i < size; i += 2) {
-        short value =
-            (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
-        if (i + 1 < size) {
-          value |=
-              (short)
-                  ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
-        }
-        result[i / 2] = value;
-      }
-      return result;
-    }
-    assert elementSize == 2 || elementSize == 4 || elementSize == 8;
-    int shortsPerConstant = elementSize / 2;
-    short[] result = new short[size * shortsPerConstant];
-    for (int i = 0; i < size; i++) {
-      long value = values[i].getConstInstruction().asConstNumber().getRawValue();
-      for (int part = 0; part < shortsPerConstant; part++) {
-        result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
-      }
-    }
-    return result;
-  }
-
-  private static class FilledArrayConversionInfo {
-
-    Value[] values;
-    List<ArrayPut> arrayPutsToRemove;
-    LinearFlowInstructionListIterator lastArrayPutIterator;
-
-    public FilledArrayConversionInfo(int size) {
-      values = new Value[size];
-      arrayPutsToRemove = new ArrayList<>(size);
-    }
-  }
-
-  private FilledArrayConversionInfo computeConversionInfo(
-      FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) {
-    NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
-    assert it.peekPrevious() == newArrayEmpty;
-    Value arrayValue = newArrayEmpty.outValue();
-    int size = candidate.size;
-
-    // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify
-    // if types require a cast.
-    // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g.
-    //   if aput-object throws a ClassCastException if given an object that does not implement the
-    //   desired interface, then we could add check-cast instructions for arguments we're not sure
-    //   about.
-    DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory);
-    boolean needsTypeCheck =
-        !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
-
-    FilledArrayConversionInfo info = new FilledArrayConversionInfo(size);
-    Value[] values = info.values;
-    int remaining = size;
-    Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
-    while (it.hasNext()) {
-      Instruction instruction = it.next();
-      BasicBlock block = instruction.getBlock();
-      // If we encounter an instruction that can throw an exception we need to bail out of the
-      // optimization so that we do not transform half-initialized arrays into fully initialized
-      // arrays on exceptional edges. If the block has no handlers it is not observable so
-      // we perform the rewriting.
-      if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
-        return null;
-      }
-      if (!users.contains(instruction)) {
-        // If any instruction can transfer control between the new-array and the last array put
-        // then it is not safe to move the new array to the point of the last put.
-        if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
-          return null;
-        }
-        continue;
-      }
-      ArrayPut arrayPut = instruction.asArrayPut();
-      // If the initialization sequence is broken by another use we cannot use a fill-array-data
-      // instruction.
-      if (arrayPut == null || arrayPut.array() != arrayValue) {
-        return null;
-      }
-      if (!arrayPut.index().isConstNumber()) {
-        return null;
-      }
-      if (arrayPut.instructionInstanceCanThrow()) {
-        assert false;
-        return null;
-      }
-      int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
-      if (index < 0 || index >= values.length) {
-        return null;
-      }
-      if (values[index] != null) {
-        return null;
-      }
-      Value value = arrayPut.value();
-      if (needsTypeCheck && !value.isAlwaysNull(appView)) {
-        DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
-        if (elementType.isArrayType()) {
-          if (elementType != valueDexType) {
-            return null;
-          }
-        } else if (valueDexType.isArrayType()) {
-          // isSubtype asserts for this case.
-          return null;
-        } else if (valueDexType.isNullValueType()) {
-          // Assume instructions can cause value.isAlwaysNull() == false while the DexType is null.
-          // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
-          //   that hits this case.
-        } else {
-          // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
-          //   library types (which this helper does not do).
-          if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
-            return null;
-          }
-        }
-      }
-      info.arrayPutsToRemove.add(arrayPut);
-      values[index] = value;
-      --remaining;
-      if (remaining == 0) {
-        info.lastArrayPutIterator = it;
-        return info;
-      }
-    }
-    return null;
-  }
-
-  private static class FilledArrayCandidate {
-
-    final NewArrayEmpty newArrayEmpty;
-    final int size;
-    final boolean encodeAsFilledNewArray;
-
-    public FilledArrayCandidate(
-        NewArrayEmpty newArrayEmpty, int size, boolean encodeAsFilledNewArray) {
-      assert size > 0;
-      this.newArrayEmpty = newArrayEmpty;
-      this.size = size;
-      this.encodeAsFilledNewArray = encodeAsFilledNewArray;
-    }
-
-    public boolean useFilledNewArray() {
-      return encodeAsFilledNewArray;
-    }
-
-    public boolean useFilledArrayData() {
-      return !useFilledNewArray();
-    }
-  }
-
-  private FilledArrayCandidate computeFilledArrayCandidate(
-      Instruction instruction, RewriteArrayOptions options) {
-    NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
-    if (newArrayEmpty == null) {
-      return null;
-    }
-    if (instruction.getLocalInfo() != null) {
-      return null;
-    }
-    if (!newArrayEmpty.size().isConstant()) {
-      return null;
-    }
-    int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
-    if (!options.isPotentialSize(size)) {
-      return null;
-    }
-    DexType arrayType = newArrayEmpty.type;
-    boolean encodeAsFilledNewArray = canUseFilledNewArray(arrayType, size, options);
-    if (!encodeAsFilledNewArray && !canUseFilledArrayData(arrayType, size, options)) {
-      return null;
-    }
-    // Check that all arguments to the array is the array type or that the array is type Object[].
-    if (!options.canUseSubTypesInFilledNewArray()
-        && arrayType != dexItemFactory.objectArrayType
-        && !arrayType.isPrimitiveArrayType()) {
-      DexType elementType = arrayType.toArrayElementType(dexItemFactory);
-      for (Instruction uniqueUser : newArrayEmpty.outValue().uniqueUsers()) {
-        if (uniqueUser.isArrayPut()
-            && uniqueUser.asArrayPut().array() == newArrayEmpty.outValue()
-            && !checkTypeOfArrayPut(uniqueUser.asArrayPut(), elementType)) {
-          return null;
-        }
-      }
-    }
-    return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
-  }
-
-  private boolean checkTypeOfArrayPut(ArrayPut arrayPut, DexType elementType) {
-    TypeElement valueType = arrayPut.value().getType();
-    if (!valueType.isPrimitiveType() && elementType == dexItemFactory.objectType) {
-      return true;
-    }
-    if (valueType.isNullType() && !elementType.isPrimitiveType()) {
-      return true;
-    }
-    if (elementType.isArrayType()) {
-      if (valueType.isNullType()) {
-        return true;
-      }
-      ArrayTypeElement arrayTypeElement = valueType.asArrayType();
-      if (arrayTypeElement == null
-          || arrayTypeElement.getNesting() != elementType.getNumberOfLeadingSquareBrackets()) {
-        return false;
-      }
-      valueType = arrayTypeElement.getBaseType();
-      elementType = elementType.toBaseType(dexItemFactory);
-    }
-    assert !valueType.isArrayType();
-    assert !elementType.isArrayType();
-    if (valueType.isPrimitiveType() && !elementType.isPrimitiveType()) {
-      return false;
-    }
-    if (valueType.isPrimitiveType()) {
-      return true;
-    }
-    DexClass clazz = appView.definitionFor(elementType);
-    if (clazz == null) {
-      return false;
-    }
-    return clazz.isInterface() || valueType.isClassType(elementType);
-  }
-
-  private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) {
-    if (size < options.minSizeForFilledNewArray) {
-      return false;
-    }
-    // filled-new-array is implemented only for int[] and Object[].
-    // For int[], using filled-new-array is usually smaller than filled-array-data.
-    // filled-new-array supports up to 5 registers before it's filled-new-array/range.
-    if (!arrayType.isPrimitiveArrayType()) {
-      if (size > options.maxSizeForFilledNewArrayOfReferences) {
-        return false;
-      }
-      if (arrayType == dexItemFactory.stringArrayType) {
-        return options.canUseFilledNewArrayOfStrings();
-      }
-      if (!options.canUseFilledNewArrayOfNonStringObjects()) {
-        return false;
-      }
-      if (!options.canUseFilledNewArrayOfArrays()
-          && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
-        return false;
-      }
-      return true;
-    }
-    if (arrayType == dexItemFactory.intArrayType) {
-      return size <= options.maxSizeForFilledNewArrayOfInts;
-    }
-    return false;
-  }
-
-  private boolean canUseFilledArrayData(DexType arrayType, int size, RewriteArrayOptions options) {
-    // If there is only one element it is typically smaller to generate the array put
-    // instruction instead of fill array data.
-    if (size < options.minSizeForFilledArrayData || size > options.maxSizeForFilledArrayData) {
-      return false;
-    }
-    return arrayType.isPrimitiveArrayType();
-  }
-
-  /**
-   * Replace new-array followed by stores of constants to all entries with new-array and
-   * fill-array-data / filled-new-array.
-   *
-   * <p>The format of the new-array and its puts must be of the form:
-   *
-   * <pre>
-   *   v0 <- new-array T vSize
-   *   ...
-   *   array-put v0 vValue1 vIndex1
-   *   ...
-   *   array-put v0 vValueN vIndexN
-   * </pre>
-   *
-   * <p>The flow between the array v0 and its puts must be linear with no other uses of v0 besides
-   * the array-put instructions, thus any no intermediate instruction (... above) must use v0 and
-   * also cannot have catch handlers that would transfer out control (those could then have uses of
-   * v0).
-   *
-   * <p>The allocation of the new-array can itself have catch handlers, in which case, those are to
-   * remain active on the translated code. Translated code can have two forms.
-   *
-   * <p>The first is using the original array allocation and filling in its data if it can be
-   * encoded:
-   *
-   * <pre>
-   *   v0 <- new-array T vSize
-   *   filled-array-data v0
-   *   ...
-   *   ...
-   * </pre>
-   *
-   * The data payload is encoded directly in the instruction so no dependencies are needed for
-   * filling the data array. Thus, the fill is inserted at the point of the allocation. If the
-   * allocation has catch handlers its block must be split and the handlers put on the fill
-   * instruction too. This is correct only because there are no exceptional transfers in (...) that
-   * could observe the early initialization of the data.
-   *
-   * <p>The second is using filled-new-array and has the form:
-   *
-   * <pre>
-   * ...
-   * ...
-   * v0 <- filled-new-array T vValue1 ... vValueN
-   * </pre>
-   *
-   * Here the correctness relies on no exceptional transfers in (...) that could observe the missing
-   * allocation of the array. The late allocation ensures that the values are available at
-   * allocation time. If the original allocation has catch handlers then the new allocation needs to
-   * link those too. In general that may require splitting the block twice so that the new
-   * allocation is the single throwing instruction in its block.
-   */
-  public void simplifyArrayConstruction(IRCode code) {
-    if (options.isGeneratingClassFiles()) {
-      return;
-    }
-    WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
-    while (worklist.hasNext()) {
-      BasicBlock block = worklist.next();
-      simplifyArrayConstructionBlock(block, worklist, code, options);
-    }
-  }
-
-  private void simplifyArrayConstructionBlock(
-      BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, InternalOptions options) {
-    RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
-    InstructionListIterator it = block.listIterator(code);
-    while (it.hasNext()) {
-      FilledArrayCandidate candidate = computeFilledArrayCandidate(it.next(), rewriteOptions);
-      if (candidate == null) {
-        continue;
-      }
-      FilledArrayConversionInfo info =
-          computeConversionInfo(
-              candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex()));
-      if (info == null) {
-        continue;
-      }
-
-      Instruction instructionAfterCandidate = it.peekNext();
-      NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
-      DexType arrayType = newArrayEmpty.type;
-      int size = candidate.size;
-      Set<Instruction> instructionsToRemove = SetUtils.newIdentityHashSet(size + 1);
-      if (candidate.useFilledNewArray()) {
-        assert newArrayEmpty.getLocalInfo() == null;
-        Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious();
-        Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null);
-        InvokeNewArray invoke =
-            new InvokeNewArray(arrayType, invokeValue, Arrays.asList(info.values));
-        invoke.setPosition(lastArrayPut.getPosition());
-        for (Value value : newArrayEmpty.inValues()) {
-          value.removeUser(newArrayEmpty);
-        }
-        newArrayEmpty.outValue().replaceUsers(invokeValue);
-        instructionsToRemove.add(newArrayEmpty);
-
-        boolean originalAllocationPointHasHandlers = block.hasCatchHandlers();
-        boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers();
-
-        if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) {
-          info.lastArrayPutIterator.add(invoke);
-        } else {
-          BasicBlock insertionBlock = info.lastArrayPutIterator.split(code);
-          if (originalAllocationPointHasHandlers) {
-            if (!insertionBlock.isTrivialGoto()) {
-              BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code);
-              assert insertionBlock.isTrivialGoto();
-              worklist.addIfNotSeen(blockAfterInsertion);
-            }
-            insertionBlock.moveCatchHandlers(block);
-          } else {
-            worklist.addIfNotSeen(insertionBlock);
-          }
-          insertionBlock.listIterator(code).add(invoke);
-        }
-      } else {
-        assert candidate.useFilledArrayData();
-        int elementSize = arrayType.elementSizeForPrimitiveArrayType();
-        short[] contents = computeArrayFilledData(info.values, size, elementSize);
-        if (contents == null) {
-          continue;
-        }
-        // fill-array-data requires the new-array-empty instruction to remain, as it does not
-        // itself create an array.
-        NewArrayFilledData fillArray =
-            new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, size, contents);
-        fillArray.setPosition(newArrayEmpty.getPosition());
-        BasicBlock newBlock =
-            it.addThrowingInstructionToPossiblyThrowingBlock(code, null, fillArray, options);
-        if (newBlock != null) {
-          worklist.addIfNotSeen(newBlock);
-        }
-      }
-
-      instructionsToRemove.addAll(info.arrayPutsToRemove);
-      Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
-      for (Instruction instruction : instructionsToRemove) {
-        BasicBlock ownerBlock = instruction.getBlock();
-        // If owner block is null, then the instruction has been removed already. We can't rely on
-        // just having the block pointer nulled, so the visited blocks guards reprocessing.
-        if (ownerBlock != null && visitedBlocks.add(ownerBlock)) {
-          InstructionListIterator removeIt = ownerBlock.listIterator(code);
-          while (removeIt.hasNext()) {
-            if (instructionsToRemove.contains(removeIt.next())) {
-              removeIt.removeOrReplaceByDebugLocalRead();
-            }
-          }
-        }
-      }
-
-      // The above has invalidated the block iterator so reset it and continue.
-      it = block.listIterator(code, instructionAfterCandidate);
-    }
-  }
-
   // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
   private static boolean hasLocalOrLineChangeBetween(
       Instruction from, Instruction to, DexString localVar) {
@@ -2597,309 +1001,6 @@
     }
   }
 
-  static class ControlFlowSimplificationResult {
-
-    private boolean anyAffectedValues;
-    private boolean anySimplifications;
-
-    private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySimplifications) {
-      assert !anyAffectedValues || anySimplifications;
-      this.anyAffectedValues = anyAffectedValues;
-      this.anySimplifications = anySimplifications;
-    }
-
-    public boolean anyAffectedValues() {
-      return anyAffectedValues;
-    }
-
-    public boolean anySimplifications() {
-      return anySimplifications;
-    }
-  }
-
-  public boolean simplifyControlFlow(IRCode code) {
-    boolean anyAffectedValues = rewriteSwitch(code);
-    anyAffectedValues |= simplifyIf(code).anyAffectedValues();
-    return anyAffectedValues;
-  }
-
-  public ControlFlowSimplificationResult simplifyIf(IRCode code) {
-    BasicBlockBehavioralSubsumption behavioralSubsumption =
-        new BasicBlockBehavioralSubsumption(appView, code);
-    boolean simplified = false;
-    for (BasicBlock block : code.blocks) {
-      // Skip removed (= unreachable) blocks.
-      if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
-        continue;
-      }
-      if (block.exit().isIf()) {
-        flipIfBranchesIfNeeded(code, block);
-        if (rewriteIfWithConstZero(code, block)) {
-          simplified = true;
-        }
-
-        if (simplifyKnownBooleanCondition(code, block)) {
-          simplified = true;
-          if (!block.exit().isIf()) {
-            continue;
-          }
-        }
-
-        // Simplify if conditions when possible.
-        If theIf = block.exit().asIf();
-        if (theIf.isZeroTest()) {
-          if (simplifyIfZeroTest(code, block, theIf)) {
-            simplified = true;
-            continue;
-          }
-        } else {
-          if (simplifyNonIfZeroTest(code, block, theIf)) {
-            simplified = true;
-            continue;
-          }
-        }
-
-        // Unable to determine which branch will be taken. Check if the true target can safely be
-        // rewritten to the false target.
-        if (behavioralSubsumption.isSubsumedBy(
-            theIf.inValues().get(0), theIf.getTrueTarget(), theIf.fallthroughBlock())) {
-          simplifyIfWithKnownCondition(code, block, theIf, theIf.fallthroughBlock());
-          simplified = true;
-        }
-      }
-    }
-    Set<Value> affectedValues = code.removeUnreachableBlocks();
-    if (!affectedValues.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(affectedValues);
-    }
-    assert code.isConsistentSSA(appView);
-    return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified);
-  }
-
-  private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) {
-    Value lhs = theIf.lhs();
-    Value lhsRoot = lhs.getAliasedValue();
-    if (lhsRoot.isConstNumber()) {
-      ConstNumber cond = lhsRoot.getConstInstruction().asConstNumber();
-      BasicBlock target = theIf.targetFromCondition(cond);
-      simplifyIfWithKnownCondition(code, block, theIf, target);
-      return true;
-    }
-
-    if (theIf.isNullTest()) {
-      assert theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE;
-
-      if (lhs.isAlwaysNull(appView)) {
-        simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNullObject());
-        return true;
-      }
-
-      if (lhs.isNeverNull()) {
-        simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNonNullObject());
-        return true;
-      }
-    }
-
-    if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) {
-      AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context());
-      if (lhsAbstractValue.isConstantOrNonConstantNumberValue()
-          && !lhsAbstractValue.asConstantOrNonConstantNumberValue().containsInt(0)) {
-        // Value doesn't contain zero at all.
-        simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1));
-        return true;
-      }
-    }
-
-    if (lhs.hasValueRange()) {
-      LongInterval interval = lhs.getValueRange();
-      if (!interval.containsValue(0)) {
-        // Interval doesn't contain zero at all.
-        int sign = Long.signum(interval.getMin());
-        simplifyIfWithKnownCondition(code, block, theIf, sign);
-        return true;
-      }
-
-      // Interval contains zero.
-      switch (theIf.getType()) {
-        case GE:
-        case LT:
-          // [a, b] >= 0 is always true if a >= 0.
-          // [a, b] < 0 is always false if a >= 0.
-          // In both cases a zero condition takes the right branch.
-          if (interval.getMin() == 0) {
-            simplifyIfWithKnownCondition(code, block, theIf, 0);
-            return true;
-          }
-          break;
-
-        case LE:
-        case GT:
-          // [a, b] <= 0 is always true if b <= 0.
-          // [a, b] > 0 is always false if b <= 0.
-          // In both cases a zero condition takes the right branch.
-          if (interval.getMax() == 0) {
-            simplifyIfWithKnownCondition(code, block, theIf, 0);
-            return true;
-          }
-          break;
-
-        case EQ:
-        case NE:
-          // Only a single element interval [0, 0] can be dealt with here.
-          // Such intervals should have been replaced by constants.
-          assert !interval.isSingleValue();
-          break;
-      }
-    }
-    return false;
-  }
-
-  private boolean simplifyNonIfZeroTest(IRCode code, BasicBlock block, If theIf) {
-    Value lhs = theIf.lhs();
-    Value lhsRoot = lhs.getAliasedValue();
-    Value rhs = theIf.rhs();
-    Value rhsRoot = rhs.getAliasedValue();
-    if (lhsRoot == rhsRoot) {
-      // Comparing the same value.
-      simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(0));
-      return true;
-    }
-
-    if (lhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)
-        && rhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)) {
-      // Comparing two newly created objects.
-      assert theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE;
-      simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1));
-      return true;
-    }
-
-    if (lhsRoot.isConstNumber() && rhsRoot.isConstNumber()) {
-      // Zero test with a constant of comparison between between two constants.
-      ConstNumber left = lhsRoot.getConstInstruction().asConstNumber();
-      ConstNumber right = rhsRoot.getConstInstruction().asConstNumber();
-      BasicBlock target = theIf.targetFromCondition(left, right);
-      simplifyIfWithKnownCondition(code, block, theIf, target);
-      return true;
-    }
-
-    if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) {
-      AbstractValue lhsAbstractValue = lhs.getAbstractValue(appView, code.context());
-      AbstractValue rhsAbstractValue = rhs.getAbstractValue(appView, code.context());
-      if (lhsAbstractValue.isConstantOrNonConstantNumberValue()
-          && rhsAbstractValue.isConstantOrNonConstantNumberValue()) {
-        ConstantOrNonConstantNumberValue lhsNumberValue =
-            lhsAbstractValue.asConstantOrNonConstantNumberValue();
-        ConstantOrNonConstantNumberValue rhsNumberValue =
-            rhsAbstractValue.asConstantOrNonConstantNumberValue();
-        if (!lhsNumberValue.mayOverlapWith(rhsNumberValue)) {
-          // No overlap.
-          simplifyIfWithKnownCondition(code, block, theIf, 1);
-          return true;
-        }
-      }
-    }
-
-    if (lhs.hasValueRange() && rhs.hasValueRange()) {
-      // Zero test with a value range, or comparison between between two values,
-      // each with a value ranges.
-      LongInterval leftRange = lhs.getValueRange();
-      LongInterval rightRange = rhs.getValueRange();
-      // Two overlapping ranges. Check for single point overlap.
-      if (!leftRange.overlapsWith(rightRange)) {
-        // No overlap.
-        int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
-        simplifyIfWithKnownCondition(code, block, theIf, cond);
-        return true;
-      }
-
-      // The two intervals overlap. We can simplify if they overlap at the end points.
-      switch (theIf.getType()) {
-        case LT:
-        case GE:
-          // [a, b] < [c, d] is always false when a == d.
-          // [a, b] >= [c, d] is always true when a == d.
-          // In both cases 0 condition will choose the right branch.
-          if (leftRange.getMin() == rightRange.getMax()) {
-            simplifyIfWithKnownCondition(code, block, theIf, 0);
-            return true;
-          }
-          break;
-        case GT:
-        case LE:
-          // [a, b] > [c, d] is always false when b == c.
-          // [a, b] <= [c, d] is always true when b == c.
-          // In both cases 0 condition will choose the right branch.
-          if (leftRange.getMax() == rightRange.getMin()) {
-            simplifyIfWithKnownCondition(code, block, theIf, 0);
-            return true;
-          }
-          break;
-        case EQ:
-        case NE:
-          // Since there is overlap EQ and NE cannot be determined.
-          break;
-      }
-    }
-
-    if (theIf.getType() == IfType.EQ || theIf.getType() == IfType.NE) {
-      ProgramMethod context = code.context();
-      AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
-      if (abstractValue.isSingleConstClassValue()) {
-        AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
-        if (otherAbstractValue.isSingleConstClassValue()) {
-          SingleConstClassValue singleConstClassValue = abstractValue.asSingleConstClassValue();
-          SingleConstClassValue otherSingleConstClassValue =
-              otherAbstractValue.asSingleConstClassValue();
-          simplifyIfWithKnownCondition(
-              code,
-              block,
-              theIf,
-              BooleanUtils.intValue(
-                  singleConstClassValue.getType() != otherSingleConstClassValue.getType()));
-          return true;
-        }
-        return false;
-      }
-
-      if (abstractValue.isSingleFieldValue()) {
-        AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
-        if (otherAbstractValue.isSingleFieldValue()) {
-          SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
-          SingleFieldValue otherSingleFieldValue = otherAbstractValue.asSingleFieldValue();
-          if (singleFieldValue.getField() == otherSingleFieldValue.getField()) {
-            simplifyIfWithKnownCondition(code, block, theIf, 0);
-            return true;
-          }
-
-          DexClass holder = appView.definitionForHolder(singleFieldValue.getField());
-          DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder);
-          if (field != null && field.isEnum()) {
-            DexClass otherHolder = appView.definitionForHolder(otherSingleFieldValue.getField());
-            DexEncodedField otherField =
-                otherSingleFieldValue.getField().lookupOnClass(otherHolder);
-            if (otherField != null && otherField.isEnum()) {
-              simplifyIfWithKnownCondition(code, block, theIf, 1);
-              return true;
-            }
-          }
-        }
-      }
-    }
-
-    return false;
-  }
-
-  private void simplifyIfWithKnownCondition(
-      IRCode code, BasicBlock block, If theIf, BasicBlock target) {
-    BasicBlock deadTarget =
-        target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
-    rewriteIfToGoto(code, block, theIf, target, deadTarget);
-  }
-
-  private void simplifyIfWithKnownCondition(IRCode code, BasicBlock block, If theIf, int cond) {
-    simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(cond));
-  }
-
   /**
    * This optimization exploits that we can sometimes learn the constant value of an SSA value that
    * flows into an if-eq of if-neq instruction.
@@ -3380,152 +1481,6 @@
     assert code.isConsistentSSA(appView);
   }
 
-  /* Identify simple diamond shapes converting boolean true/false to 1/0. We consider the forms:
-   *
-   * (1)
-   *
-   *      [dbg pos x]             [dbg pos x]
-   *   ifeqz booleanValue       ifnez booleanValue
-   *      /        \              /        \
-   * [dbg pos x][dbg pos x]  [dbg pos x][dbg pos x]
-   *  [const 0]  [const 1]    [const 1]  [const 0]
-   *    goto      goto          goto      goto
-   *      \        /              \        /
-   *      phi(0, 1)                phi(1, 0)
-   *
-   * which can be replaced by a fallthrough and the phi value can be replaced
-   * with the boolean value itself.
-   *
-   * (2)
-   *
-   *      [dbg pos x]              [dbg pos x]
-   *    ifeqz booleanValue       ifnez booleanValue
-   *      /        \              /        \
-   * [dbg pos x][dbg pos x]  [dbg pos x][dbg pos x]
-   *  [const 1]  [const 0]   [const 0]  [const 1]
-   *    goto      goto          goto      goto
-   *      \        /              \        /
-   *      phi(1, 0)                phi(0, 1)
-   *
-   * which can be replaced by a fallthrough and the phi value can be replaced
-   * by an xor instruction which is smaller.
-   */
-  private boolean simplifyKnownBooleanCondition(IRCode code, BasicBlock block) {
-    If theIf = block.exit().asIf();
-    Value testValue = theIf.inValues().get(0);
-    if (theIf.isZeroTest() && testValue.knownToBeBoolean()) {
-      BasicBlock trueBlock = theIf.getTrueTarget();
-      BasicBlock falseBlock = theIf.fallthroughBlock();
-      if (isBlockSupportedBySimplifyKnownBooleanCondition(trueBlock) &&
-          isBlockSupportedBySimplifyKnownBooleanCondition(falseBlock) &&
-          trueBlock.getSuccessors().get(0) == falseBlock.getSuccessors().get(0)) {
-        BasicBlock targetBlock = trueBlock.getSuccessors().get(0);
-        if (targetBlock.getPredecessors().size() == 2) {
-          int trueIndex = targetBlock.getPredecessors().indexOf(trueBlock);
-          int falseIndex = trueIndex == 0 ? 1 : 0;
-          int deadPhis = 0;
-          // Locate the phis that have the same value as the boolean and replace them
-          // by the boolean in all users.
-          for (Phi phi : targetBlock.getPhis()) {
-            Value trueValue = phi.getOperand(trueIndex);
-            Value falseValue = phi.getOperand(falseIndex);
-            if (trueValue.isConstNumber() && falseValue.isConstNumber()) {
-              ConstNumber trueNumber = trueValue.getConstInstruction().asConstNumber();
-              ConstNumber falseNumber = falseValue.getConstInstruction().asConstNumber();
-              if ((theIf.getType() == IfType.EQ
-                      && trueNumber.isIntegerZero()
-                      && falseNumber.isIntegerOne())
-                  || (theIf.getType() == IfType.NE
-                      && trueNumber.isIntegerOne()
-                      && falseNumber.isIntegerZero())) {
-                phi.replaceUsers(testValue);
-                deadPhis++;
-              } else if ((theIf.getType() == IfType.NE
-                      && trueNumber.isIntegerZero()
-                      && falseNumber.isIntegerOne())
-                  || (theIf.getType() == IfType.EQ
-                      && trueNumber.isIntegerOne()
-                      && falseNumber.isIntegerZero())) {
-                Value newOutValue = code.createValue(phi.getType(), phi.getLocalInfo());
-                ConstNumber cstToUse = trueNumber.isIntegerOne() ? trueNumber : falseNumber;
-                BasicBlock phiBlock = phi.getBlock();
-                Position phiPosition = phiBlock.getPosition();
-                int insertIndex = 0;
-                if (cstToUse.getBlock() == trueBlock || cstToUse.getBlock() == falseBlock) {
-                  // The constant belongs to the block to remove, create a new one.
-                  cstToUse = ConstNumber.copyOf(code, cstToUse);
-                  cstToUse.setBlock(phiBlock);
-                  cstToUse.setPosition(phiPosition);
-                  phiBlock.getInstructions().add(insertIndex++, cstToUse);
-                }
-                phi.replaceUsers(newOutValue);
-                Instruction newInstruction =
-                    Xor.create(NumericType.INT, newOutValue, testValue, cstToUse.outValue());
-                newInstruction.setBlock(phiBlock);
-                // The xor is replacing a phi so it does not have an actual position.
-                newInstruction.setPosition(phiPosition);
-                phiBlock.listIterator(code, insertIndex).add(newInstruction);
-                deadPhis++;
-              }
-            }
-          }
-          // If all phis were removed, there is no need for the diamond shape anymore
-          // and it can be rewritten to a goto to one of the branches.
-          if (deadPhis == targetBlock.getPhis().size()) {
-            rewriteIfToGoto(code, block, theIf, trueBlock, falseBlock);
-            return true;
-          }
-          return deadPhis > 0;
-        }
-      }
-    }
-    return false;
-  }
-
-  private boolean isBlockSupportedBySimplifyKnownBooleanCondition(BasicBlock b) {
-    if (b.isTrivialGoto()) {
-      return true;
-    }
-
-    int instructionSize = b.getInstructions().size();
-    if (b.exit().isGoto() && (instructionSize == 2 || instructionSize == 3)) {
-      Instruction constInstruction = b.getInstructions().get(instructionSize - 2);
-      if (constInstruction.isConstNumber()) {
-        if (!constInstruction.asConstNumber().isIntegerOne() &&
-            !constInstruction.asConstNumber().isIntegerZero()) {
-          return false;
-        }
-        if (instructionSize == 2) {
-          return true;
-        }
-        Instruction firstInstruction = b.getInstructions().getFirst();
-        if (firstInstruction.isDebugPosition()) {
-          assert b.getPredecessors().size() == 1;
-          BasicBlock predecessorBlock = b.getPredecessors().get(0);
-          InstructionIterator it = predecessorBlock.iterator(predecessorBlock.exit());
-          Instruction previousPosition = null;
-          while (it.hasPrevious() && !(previousPosition = it.previous()).isDebugPosition()) {
-            // Intentionally empty.
-          }
-          if (previousPosition != null) {
-            return previousPosition.getPosition() == firstInstruction.getPosition();
-          }
-        }
-      }
-    }
-
-    return false;
-  }
-
-  private void rewriteIfToGoto(
-      IRCode code, BasicBlock block, If theIf, BasicBlock target, BasicBlock deadTarget) {
-    deadTarget.unlinkSinglePredecessorSiblingsAllowed();
-    assert theIf == block.exit();
-    block.replaceLastInstruction(new Goto(), code);
-    assert block.exit().isGoto();
-    assert block.exit().asGoto().getTarget() == target;
-  }
-
   private void insertNotNullCheck(
       BasicBlock block,
       InstructionListIterator iterator,
@@ -3547,101 +1502,6 @@
     assert block.exit().asGoto().getTarget() == target;
   }
 
-  private boolean rewriteIfWithConstZero(IRCode code, BasicBlock block) {
-    If theIf = block.exit().asIf();
-    if (theIf.isZeroTest()) {
-      return false;
-    }
-
-    Value leftValue = theIf.lhs();
-    Value rightValue = theIf.rhs();
-    if (leftValue.isConstNumber() || rightValue.isConstNumber()) {
-      if (leftValue.isConstNumber()) {
-        if (leftValue.getConstInstruction().asConstNumber().isZero()) {
-          If ifz = new If(theIf.getType().forSwappedOperands(), rightValue);
-          block.replaceLastInstruction(ifz, code);
-          assert block.exit() == ifz;
-          return true;
-        }
-      } else if (rightValue.getConstInstruction().asConstNumber().isZero()) {
-        If ifz = new If(theIf.getType(), leftValue);
-        block.replaceLastInstruction(ifz, code);
-        assert block.exit() == ifz;
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) {
-    If theIf = block.exit().asIf();
-    BasicBlock trueTarget = theIf.getTrueTarget();
-    BasicBlock fallthrough = theIf.fallthroughBlock();
-    assert trueTarget != fallthrough;
-
-    if (!fallthrough.isSimpleAlwaysThrowingPath() || trueTarget.isSimpleAlwaysThrowingPath()) {
-      return false;
-    }
-
-    // In case fall-through block always throws there is a good chance that it
-    // is created for error checks and 'trueTarget' represents most more common
-    // non-error case. Flipping the if in this case may result in faster code
-    // on older Android versions.
-    List<Value> inValues = theIf.inValues();
-    If newIf = new If(theIf.getType().inverted(), inValues);
-    block.replaceLastInstruction(newIf, code);
-    block.swapSuccessors(trueTarget, fallthrough);
-    return true;
-  }
-
-  public void rewriteKnownArrayLengthCalls(IRCode code) {
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Instruction current = iterator.next();
-      if (!current.isArrayLength()) {
-        continue;
-      }
-
-      ArrayLength arrayLength = current.asArrayLength();
-      if (arrayLength.hasOutValue() && arrayLength.outValue().hasLocalInfo()) {
-        continue;
-      }
-
-      Value array = arrayLength.array().getAliasedValue();
-      if (array.isPhi() || !arrayLength.array().isNeverNull() || array.hasLocalInfo()) {
-        continue;
-      }
-
-      AbstractValue abstractValue = array.getAbstractValue(appView, code.context());
-      if (!abstractValue.hasKnownArrayLength() && !array.isNeverNull()) {
-        continue;
-      }
-      Instruction arrayDefinition = array.getDefinition();
-      assert arrayDefinition != null;
-
-      Set<Phi> phiUsers = arrayLength.outValue().uniquePhiUsers();
-      if (arrayDefinition.isNewArrayEmpty()) {
-        Value size = arrayDefinition.asNewArrayEmpty().size();
-        arrayLength.outValue().replaceUsers(size);
-        iterator.removeOrReplaceByDebugLocalRead();
-      } else if (arrayDefinition.isNewArrayFilledData()) {
-        long size = arrayDefinition.asNewArrayFilledData().size;
-        if (size > Integer.MAX_VALUE) {
-          continue;
-        }
-        iterator.replaceCurrentInstructionWithConstInt(code, (int) size);
-      } else if (abstractValue.hasKnownArrayLength()) {
-        iterator.replaceCurrentInstructionWithConstInt(code, abstractValue.getKnownArrayLength());
-      } else {
-        continue;
-      }
-
-      phiUsers.forEach(Phi::removeTrivialPhi);
-    }
-    assert code.isConsistentSSA(appView);
-  }
-
   /**
    * Remove moves that are not actually used by instructions in exiting paths. These moves can arise
    * due to debug local info needing a particular value and the live-interval for it then moves it
@@ -3891,34 +1751,6 @@
     iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, empty)));
   }
 
-  public static void replaceUnusedArgumentTrivialPhis(UnusedArgument unusedArgument) {
-    replaceTrivialPhis(unusedArgument.outValue());
-    for (Phi phiUser : unusedArgument.outValue().uniquePhiUsers()) {
-      phiUser.removeTrivialPhi();
-    }
-    assert !unusedArgument.outValue().hasPhiUsers();
-  }
-
-  public static void ensureDirectStringNewToInit(IRCode code, DexItemFactory dexItemFactory) {
-    for (Instruction instruction : code.instructions()) {
-      if (instruction.isInvokeDirect()) {
-        InvokeDirect invoke = instruction.asInvokeDirect();
-        DexMethod method = invoke.getInvokedMethod();
-        if (dexItemFactory.isConstructor(method)
-            && method.holder == dexItemFactory.stringType
-            && invoke.getReceiver().isPhi()) {
-          NewInstance newInstance = findNewInstance(invoke.getReceiver().asPhi());
-          replaceTrivialPhis(newInstance.outValue());
-          if (invoke.getReceiver().isPhi()) {
-            throw new CompilationError(
-                "Failed to remove trivial phis between new-instance and <init>");
-          }
-          newInstance.markNoSpilling();
-        }
-      }
-    }
-  }
-
   // The javac fix for JDK-8272564 has to be rewritten back to invoke-virtual on Object if the
   // method with an Object signature is not defined on the interface. See
   // https://bugs.openjdk.java.net/browse/JDK-8272564
@@ -3943,66 +1775,4 @@
       }
     }
   }
-
-  private static NewInstance findNewInstance(Phi phi) {
-    Set<Phi> seen = Sets.newIdentityHashSet();
-    Set<Value> values = Sets.newIdentityHashSet();
-    recursiveAddOperands(phi, seen, values);
-    if (values.size() != 1) {
-      throw new CompilationError("Failed to identify unique new-instance for <init>");
-    }
-    Value newInstanceValue = values.iterator().next();
-    if (newInstanceValue.definition == null || !newInstanceValue.definition.isNewInstance()) {
-      throw new CompilationError("Invalid defining value for call to <init>");
-    }
-    return newInstanceValue.definition.asNewInstance();
-  }
-
-  private static void recursiveAddOperands(Phi phi, Set<Phi> seen, Set<Value> values) {
-    for (Value operand : phi.getOperands()) {
-      if (!operand.isPhi()) {
-        values.add(operand);
-      } else {
-        Phi phiOp = operand.asPhi();
-        if (seen.add(phiOp)) {
-          recursiveAddOperands(phiOp, seen, values);
-        }
-      }
-    }
-  }
-
-  // We compute the set of strongly connected phis making use of the out value and replace all
-  // trivial ones by the out value.
-  // This is a simplified variant of the removeRedundantPhis algorithm in Section 3.2 of:
-  // http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf
-  private static void replaceTrivialPhis(Value outValue) {
-    List<Set<Value>> components = new SCC<Value>(Value::uniquePhiUsers).computeSCC(outValue);
-    for (int i = components.size() - 1; i >= 0; i--) {
-      Set<Value> component = components.get(i);
-      if (component.size() == 1 && component.iterator().next() == outValue) {
-        continue;
-      }
-      Set<Phi> trivialPhis = Sets.newIdentityHashSet();
-      for (Value value : component) {
-        boolean isTrivial = true;
-        Phi p = value.asPhi();
-        for (Value op : p.getOperands()) {
-          if (op != outValue && !component.contains(op)) {
-            isTrivial = false;
-            break;
-          }
-        }
-        if (isTrivial) {
-          trivialPhis.add(p);
-        }
-      }
-      for (Phi trivialPhi : trivialPhis) {
-        for (Value op : trivialPhi.getOperands()) {
-          op.removePhiUser(trivialPhi);
-        }
-        trivialPhi.replaceUsers(outValue);
-        trivialPhi.getBlock().removePhi(trivialPhi);
-      }
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 677da9c..b4fa5a6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -41,6 +41,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
@@ -65,17 +66,16 @@
   private static final int MAX_CANONICALIZED_CONSTANT = 22;
 
   private final AppView<?> appView;
-  private final CodeRewriter codeRewriter;
+  private final BranchSimplifier branchSimplifier;
   private final ProgramMethod context;
   private final IRCode code;
 
   private OptionalBool isAccessingVolatileField = OptionalBool.unknown();
   private Set<InstanceGet> ineligibleInstanceGetInstructions;
 
-  public ConstantCanonicalizer(
-      AppView<?> appView, CodeRewriter codeRewriter, ProgramMethod context, IRCode code) {
+  public ConstantCanonicalizer(AppView<?> appView, ProgramMethod context, IRCode code) {
     this.appView = appView;
-    this.codeRewriter = codeRewriter;
+    this.branchSimplifier = new BranchSimplifier(appView);
     this.context = context;
     this.code = code;
   }
@@ -288,7 +288,7 @@
     shouldSimplifyIfs |= code.removeAllDeadAndTrivialPhis();
 
     if (shouldSimplifyIfs) {
-      codeRewriter.simplifyIf(code);
+      branchSimplifier.simplifyIf(code);
     }
 
     assert code.isConsistentSSA(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index b7d7b10..fcefb79 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueIsDeadAnalysis;
+import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.IterableUtils;
@@ -50,6 +51,8 @@
 
     codeRewriter.rewriteMoveResult(code);
 
+    BranchSimplifier branchSimplifier = new BranchSimplifier(appView);
+
     // We may encounter unneeded catch handlers after each iteration, e.g., if a dead instruction
     // is the only throwing instruction in a block. Removing unneeded catch handlers can lead to
     // more dead instructions.
@@ -62,7 +65,7 @@
         removeDeadInstructions(worklist, code, block, valueIsDeadAnalysis);
         removeDeadPhis(worklist, block, valueIsDeadAnalysis);
       }
-    } while (codeRewriter.simplifyIf(code).anySimplifications()
+    } while (branchSimplifier.simplifyIf(code).anySimplifications()
         || removeUnneededCatchHandlers(code));
     assert code.isConsistentSSA(appView);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
index 54c8f61..4b5f0c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
@@ -147,12 +147,11 @@
     // We track up to n call sites, where n is the size of multiCallerInliningInstructionLimits.
     if (callers.size() > multiCallerInliningInstructionLimits.length) {
       stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining(
-          method, singleTarget, methodProcessor, callers);
+          singleTarget, methodProcessor, callers);
     }
   }
 
   private void stopTrackingCallSitesForMethodIfDefinitelyIneligibleForMultiCallerInlining(
-      ProgramMethod method,
       ProgramMethod singleTarget,
       MethodProcessor methodProcessor,
       ProgramMethodMultiset callers) {
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
index afed4c2..370a5d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RuntimeWorkaroundCodeRewriter.java
@@ -22,6 +22,7 @@
 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.passes.BranchSimplifier;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LazyBox;
 import com.google.common.collect.ImmutableList;
@@ -68,6 +69,58 @@
     }
   }
 
+  public static void workaroundSwitchMaxIntBug(IRCode code, AppView<?> appView) {
+    if (appView.options().canHaveSwitchMaxIntBug() && code.metadata().mayHaveSwitch()) {
+      // Always rewrite for workaround switch bug.
+      rewriteSwitchForMaxIntOnly(code, appView);
+    }
+  }
+
+  private static void rewriteSwitchForMaxIntOnly(IRCode code, AppView<?> appView) {
+    boolean needToSplitCriticalEdges = false;
+    BranchSimplifier branchSimplifier = new BranchSimplifier(appView);
+    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) {
+              branchSimplifier.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);
+              branchSimplifier.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();
+    }
+  }
+
   /**
    * For each block, we look to see if the header matches:
    *
@@ -192,58 +245,6 @@
     }
   }
 
-  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) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 7607596..889a434 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -19,8 +19,9 @@
 import com.android.tools.r8.ir.code.InstructionOrPhi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
+import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover;
 import com.android.tools.r8.ir.optimize.AssumeRemover;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
@@ -126,7 +127,6 @@
   //
   public final void processMethodCode(
       AppView<AppInfoWithLiveness> appView,
-      CodeRewriter codeRewriter,
       StringOptimizer stringOptimizer,
       EnumValueOptimizer enumValueOptimizer,
       ProgramMethod method,
@@ -247,10 +247,10 @@
       // If a method was inlined we may be able to remove check-cast instructions because we may
       // have more information about the types of the arguments at the call site. This is
       // particularly important for bridge methods.
-      codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
-          code, method, methodProcessor, methodProcessingContext);
+      new TrivialCheckCastAndInstanceOfRemover(appView)
+          .run(code, method, methodProcessor, methodProcessingContext);
       // If a method was inlined we may be able to prune additional branches.
-      codeRewriter.simplifyControlFlow(code);
+      new BranchSimplifier(appView).simplifyBranches(code);
       // If a method was inlined we may see more trivial computation/conversion of String.
       boolean isDebugMode =
           appView.options().debug || method.getOrComputeReachabilitySensitive(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index 537150e..02d13dd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -53,7 +53,7 @@
       // program.
       return Reason.SIMPLE;
     }
-    if (isSingleCallerInliningTarget(target)) {
+    if (isSingleCallerInliningTarget(target, context)) {
       return Reason.SINGLE_CALLER;
     }
     if (isMultiCallerInlineCandidate(invoke, target, oracle, methodProcessor)) {
@@ -64,8 +64,8 @@
     return Reason.SIMPLE;
   }
 
-  private boolean isSingleCallerInliningTarget(ProgramMethod method) {
-    if (!callSiteInformation.hasSingleCallSite(method)) {
+  private boolean isSingleCallerInliningTarget(ProgramMethod method, ProgramMethod context) {
+    if (!callSiteInformation.hasSingleCallSite(method, context)) {
       return false;
     }
     if (appView.appInfo().isNeverInlineDueToSingleCallerMethod(method)) {
diff --git a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
index d87e036..502f120 100644
--- a/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
+++ b/src/main/java/com/android/tools/r8/naming/ComposingBuilder.java
@@ -1306,7 +1306,7 @@
       }
       newMappingInformation.forEach(
           info -> {
-            if (!nonCompasableNewInfos.contains(info)) {
+            if (!nonCompasableNewInfos.contains(info) && !info.isFileNameInformation()) {
               consumer.accept(info);
             }
           });
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
index 3af7dff..4fdc94a 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
@@ -273,7 +273,7 @@
       extends ProguardMapPartitionerBuilderImpl {
 
     private MappingPartitionKeyStrategy mappingPartitionKeyStrategy =
-        MappingPartitionKeyStrategy.OBFUSCATED_TYPE_NAME_AS_KEY_WITH_PARTITIONS;
+        MappingPartitionKeyStrategy.getDefaultStrategy();
 
     public ProguardMapPartitionerBuilderImplInternal(DiagnosticsHandler diagnosticsHandler) {
       super(diagnosticsHandler);
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 2c6f535..19b4d38 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -456,8 +456,8 @@
    * @return java package name i.e. "java.lang"
    */
   public static String getPackageNameFromTypeName(String typeName) {
-    return getPackageNameFromBinaryName(
-        getClassBinaryNameFromDescriptor(javaTypeToDescriptor(typeName)));
+    int packageEndIndex = typeName.lastIndexOf(JAVA_PACKAGE_SEPARATOR);
+    return (packageEndIndex < 0) ? "" : typeName.substring(0, packageEndIndex);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java
index 1e61b88..143bc2f 100644
--- a/src/main/java/com/android/tools/r8/utils/Timing.java
+++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -19,6 +19,7 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.concurrent.ExecutorService;
 
 public class Timing {
 
@@ -373,6 +374,10 @@
     }
   }
 
+  public final TimingMerger beginMerger(String title, ExecutorService executorService) {
+    return beginMerger(title, ThreadUtils.getNumberOfThreads(executorService));
+  }
+
   public TimingMerger beginMerger(String title, int numberOfThreads) {
     return new TimingMerger(title, numberOfThreads, this);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java
index 5822d2f..9ff2346 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java
@@ -115,12 +115,17 @@
   public static final String[] SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7 =
       new String[] {
         // Require the virtual method isDefault() in class java/lang/reflect/Method.
-        "org/openjdk/tests/java/util/stream/WhileOpTest.java",
         "org/openjdk/tests/java/util/stream/WhileOpStatefulTest.java",
         // Require a Random method not present before Android 7 and not desugared.
         "org/openjdk/tests/java/util/stream/IntPrimitiveOpsTests.java"
       };
 
+  public static final String[] LONG_RUNNING_SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7 =
+      new String[] {
+        // Require the virtual method isDefault() in class java/lang/reflect/Method.
+        "org/openjdk/tests/java/util/stream/WhileOpTest.java",
+      };
+
   // Disabled because time to run > 1 min for each test.
   // Can be used for experimentation/testing purposes.
   private static String[] LONG_RUNNING_TESTS =
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java
index f4fec42..d743010 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java
@@ -7,117 +7,132 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
-import org.junit.Ignore;
+import java.io.IOException;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
 public class InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest extends TestBase {
 
+  private final TestParameters parameters;
   private final boolean includeInterfaceMethodOnJ;
 
-  @Parameterized.Parameters(name = "Include interface method on J: {0}")
-  public static Boolean[] data() {
-    return BooleanUtils.values();
+  // Note that the expected output is independent of the presence of J.m().
+  private static final String EXPECTED = StringUtils.lines("I.m()", "JImpl.m()");
+
+  @Parameterized.Parameters(name = "{0}, J.m(): {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        TestParameters.builder()
+            .withCfRuntimes()
+            .enableApiLevelsForCf()
+            .withDexRuntimes()
+            .withApiLevel(AndroidApiLevel.B)
+            .build(),
+        BooleanUtils.values());
   }
 
   public InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest(
-      boolean includeInterfaceMethodOnJ) {
+      TestParameters parameters, boolean includeInterfaceMethodOnJ) {
+    this.parameters = parameters;
     this.includeInterfaceMethodOnJ = includeInterfaceMethodOnJ;
   }
 
   @Test
-  @Ignore("b/197494749 and b/120130831")
-  // TODO(b/197494749): Update this test now that desugaring is moved up. In particular this must
-  //  be rewritten with CF based transformers as R8 does not support interface desugaring on DEX.
-  // TODO(b/120130831): With the move of desugaring this issue should be resolved. The bug indicates
-  //  that a workaround for issues related to the IR implementation can now be reverted.
-  public void test() throws Exception {
-    // Note that the expected output is independent of the presence of J.m().
-    String expectedOutput = StringUtils.lines("I.m()", "JImpl.m()");
-
-    byte[] dex = buildProgramDexFileData();
-    if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V6_0_1)) {
-      AndroidApp app =
-          AndroidApp.builder()
-              .addDexProgramData(buildProgramDexFileData(), Origin.unknown())
-              .build();
-      assertEquals(expectedOutput, runOnArt(app, "TestClass"));
-    }
-
-    testForR8(Backend.DEX)
-        .addProgramDexFileData(dex)
-        .addKeepMainRule("TestClass")
-        .setMinApi(AndroidApiLevel.M)
-        .run("TestClass")
-        .assertSuccessWithOutput(expectedOutput);
+  public void testJvm() throws Exception {
+    // The rewritten input is invalid on JVM.
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(VerifyError.class);
   }
 
-  // Using Smali instead of Jasmin because interfaces are broken in Jasmin.
-  private byte[] buildProgramDexFileData() throws Exception {
-    SmaliBuilder smaliBuilder = new SmaliBuilder();
-    smaliBuilder.setMinApi(AndroidApiLevel.N);
+  @Test
+  public void testD8() throws Exception {
+    // Notice that desugaring will map out of the invalid invoke.
+    testForD8(parameters.getBackend())
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
 
-    smaliBuilder.addClass("TestClass");
+  @Test
+  public void testR8() throws Exception {
+    // Notice that desugaring will map out of the invalid invoke.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
 
-    // public void main(String[] args) { new JImpl().m(); }
-    smaliBuilder.addMainMethod(
-        1,
-        "new-instance v0, LJImpl;",
-        "invoke-direct {v0}, LJImpl;-><init>()V",
-        "invoke-virtual {v0}, LJImpl;->m()V",
-        "return-void");
+  private List<Class<?>> getClasses() {
+    return ImmutableList.of(TestClass.class, I.class);
+  }
 
-    smaliBuilder.addInterface("I");
+  private List<byte[]> getTransformedClasses() throws IOException {
+    return ImmutableList.of(
+        transformer(JImpl.class)
+            .transformMethodInsnInMethod(
+                "m",
+                (opcode, owner, name, descriptor, isInterface, visitor) -> {
+                  if (opcode == Opcodes.INVOKESPECIAL) {
+                    assertEquals(owner, binaryName(J.class));
+                    owner = binaryName(I.class);
+                  }
+                  visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                })
+            .transform(),
+        transformer(J.class)
+            .removeMethods(
+                (access, name, descriptor, signature, exceptions) ->
+                    !includeInterfaceMethodOnJ && name.equals("m"))
+            .transform());
+  }
 
-    // default void m() { System.out.println("In I.m()"); }
-    smaliBuilder.addInstanceMethod(
-        "void",
-        "m",
-        2,
-        "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "const-string v1, \"I.m()\"",
-        "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
-        "return-void");
+  static class TestClass {
 
-    smaliBuilder.addInterface("J", "java.lang.Object", ImmutableList.of("I"));
-    if (includeInterfaceMethodOnJ) {
-      smaliBuilder.addInstanceMethod(
-          "void",
-          "m",
-          2,
-          "invoke-super {p0}, LI;->m()V",
-          "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-          "const-string v1, \"J.m()\"",
-          "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
-          "return-void");
+    public static void main(String[] args) {
+      new JImpl().m();
     }
+  }
 
-    smaliBuilder.addClass("JImpl", "java.lang.Object", ImmutableList.of("J"));
-    smaliBuilder.addDefaultConstructor();
+  interface I {
 
-    // default void m() { I.super.m(); System.out.println("In JImpl.m()"); }
-    smaliBuilder.addInstanceMethod(
-        "void",
-        "m",
-        2,
-        // Note: invoke-super instruction to the non-immediate interface I.
-        "invoke-super {p0}, LI;->m()V",
-        "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "const-string v1, \"JImpl.m()\"",
-        "invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
-        "return-void");
+    default void m() {
+      System.out.println("I.m()");
+    }
+  }
 
-    return smaliBuilder.compile();
+  interface J extends I {
+
+    @Override
+    default void m() {
+      I.super.m();
+      System.out.println("J.m()");
+    }
+  }
+
+  static class JImpl implements J {
+
+    @Override
+    public void m() {
+      J.super.m(); // Will be rewritten to non-immediate interface: I.super.m();
+      System.out.println("JImpl.m()");
+    }
   }
 }
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 019431d..8a8ab51 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
@@ -29,6 +29,7 @@
 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.ir.conversion.passes.TrivialGotosCollapser;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -107,7 +108,7 @@
             IRMetadata.unknown(),
             Origin.unknown(),
             new MutableMethodConversionOptions(options));
-    CodeRewriter.collapseTrivialGotos(appView, code);
+    new TrivialGotosCollapser(appView).run(code.context(), code);
     assertTrue(code.entryBlock().isTrivialGoto());
     assertTrue(blocks.contains(block0));
     assertTrue(blocks.contains(block1));
@@ -196,7 +197,7 @@
             IRMetadata.unknown(),
             Origin.unknown(),
             new MutableMethodConversionOptions(options));
-    CodeRewriter.collapseTrivialGotos(appView, code);
+    new TrivialGotosCollapser(appView).run(code.context(), code);
     assertTrue(block0.getInstructions().get(1).isIf());
     assertEquals(block1, block0.getInstructions().get(1).asIf().fallthroughBlock());
     assertTrue(blocks.containsAll(ImmutableList.of(block0, block1, block2, block3)));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
index f5b5a47..706f8f0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetAfterInliningTest.java
@@ -75,13 +75,15 @@
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
 
-    // B.foo() should be absent if the max inlining depth is 1, because indirection() has been
-    // inlined into main(), which makes B.foo() eligible for inlining into main().
+    // B.foo() could potentially be absent if the max inlining depth is 1, because indirection() has
+    // been inlined into main(), which makes B.foo() eligible for inlining into main() since we can
+    // compute a single target. However, because indirection() can be inlined into multiple callers
+    // we cannot trust the single caller predicate. If indirection() also has a singler caller we
+    // could propagate the single call site property to B.foo().
     ClassSubject bClassSubject = inspector.clazz(B.class);
     assertThat(bClassSubject, isPresent());
-    assertThat(
-        bClassSubject.uniqueMethodWithOriginalName("foo"),
-        notIf(isPresent(), maxInliningDepth == 1));
+    // TODO(b/286058449): We could inline this.
+    assertThat(bClassSubject.uniqueMethodWithOriginalName("foo"), isPresent());
 
     // B.bar() should always be inlined because it is marked as @AlwaysInline.
     assertThat(
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java
index b07c55c..434c494 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSourceFileTest.java
@@ -31,22 +31,32 @@
 
   private static final String mappingFoo =
       StringUtils.unixLines(
-          "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
-          "com.foo -> a:",
-          "    # {'id':'sourceFile','fileName':'Foo.kt'}");
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "com.foo -> A:",
+          "    # {'id':'sourceFile','fileName':'Foo.kt'}",
+          "com.bar -> B:",
+          "    # {'id':'sourceFile','fileName':'Bar.kt'}",
+          "com.baz -> C:");
   private static final String mappingBar =
       StringUtils.unixLines(
-          "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
-          "com.bar -> c:",
-          "    # {'id':'sourceFile','fileName':'Bar.kt'}",
-          "a -> b:");
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "A -> a:",
+          "    # {'id':'sourceFile','fileName':'some-hash-inserted-into-source-file'}",
+          "B -> b:",
+          "C -> c:",
+          "    # {'id':'sourceFile','fileName':'some-other-hash-inserted-into-source-file'}",
+          "com.qux -> d:",
+          "    # {'id':'sourceFile','fileName':'Qux.kt'}");
   private static final String mappingResult =
       StringUtils.unixLines(
-          "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
-          "com.bar -> c:",
+          "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+          "com.bar -> b:",
           "# {'id':'sourceFile','fileName':'Bar.kt'}",
-          "com.foo -> b:",
-          "# {'id':'sourceFile','fileName':'Foo.kt'}");
+          "com.baz -> c:",
+          "com.foo -> a:",
+          "# {'id':'sourceFile','fileName':'Foo.kt'}",
+          "com.qux -> d:",
+          "# {'id':'sourceFile','fileName':'Qux.kt'}");
 
   @Test
   public void testCompose() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java
new file mode 100644
index 0000000..e848753
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/partition/RetracePartitionWithPrimitiveNameTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2023, 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.retrace.partition;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.retrace.PartitionMappingSupplier;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceOptions;
+import com.android.tools.r8.retrace.RetraceStackFrameAmbiguousResultWithContext;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.StringRetrace;
+import com.android.tools.r8.retrace.internal.MappingPartitionKeyStrategy;
+import com.android.tools.r8.retrace.internal.ProguardMapPartitionerOnClassNameToText.ProguardMapPartitionerBuilderImplInternal;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Regression test for b/286001537. */
+@RunWith(Parameterized.class)
+public class RetracePartitionWithPrimitiveNameTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RetracePartitionWithPrimitiveNameTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  public final String mapping =
+      StringUtils.unixLines(
+          "# { id: 'com.android.tools.r8.mapping', version: '2.0' }",
+          "some.class1 -> int:",
+          "  void method() -> a");
+
+  @Test
+  public void testPartitionAndRetrace() throws Exception {
+    ProguardMapProducer proguardMapProducer = ProguardMapProducer.fromString(mapping);
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    Map<String, byte[]> partitions = new HashMap<>();
+    MappingPartitionMetadata metadata =
+        new ProguardMapPartitionerBuilderImplInternal(diagnosticsHandler)
+            .setMappingPartitionKeyStrategy(MappingPartitionKeyStrategy.getDefaultStrategy())
+            .setProguardMapProducer(proguardMapProducer)
+            .setPartitionConsumer(
+                partition -> partitions.put(partition.getKey(), partition.getPayload()))
+            .build()
+            .run();
+
+    PartitionMappingSupplier mappingSupplier =
+        PartitionMappingSupplier.builder()
+            .setMetadata(metadata.getBytes())
+            .setMappingPartitionFromKeySupplier(partitions::get)
+            .build();
+
+    StringRetrace retracer =
+        StringRetrace.create(RetraceOptions.builder().setMappingSupplier(mappingSupplier).build());
+    RetraceStackFrameAmbiguousResultWithContext<String> result =
+        retracer.retraceFrame("  at int.a()", RetraceStackTraceContext.empty());
+    StringBuilder sb = new StringBuilder();
+    result.forEach(frames -> frames.forEach(sb::append));
+    assertEquals("  at some.class1.method(class1.java)", sb.toString());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
index bcacbe7..a09de37 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.conversion.passes.ArrayConstructionSimplifier;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -64,7 +64,7 @@
   }
 
   private void transformArray(IRCode irCode, AppView<?> appView) {
-      new CodeRewriter(appView).simplifyArrayConstruction(irCode);
+    new ArrayConstructionSimplifier(appView).run(irCode.context(), irCode);
     String name = irCode.context().getReference().getName().toString();
     if (name.contains("filledArrayData")) {
       assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));
diff --git a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
index 4a27e0f..1cb68de 100644
--- a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
+++ b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.startup;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -12,6 +15,7 @@
 import com.android.tools.r8.startup.profile.ExternalStartupItem;
 import com.android.tools.r8.startup.profile.ExternalStartupMethod;
 import com.android.tools.r8.startup.utils.StartupTestingUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Test;
@@ -55,9 +59,16 @@
                     (appView, inlinee, inliningDepth) ->
                         inlinee.getMethodReference().equals(barMethod))
         .setMinApi(parameters)
+        .compile()
+        .inspect(
+            inspector -> {
+              // Assert that foo is not inlined.
+              ClassSubject A = inspector.clazz(A.class);
+              assertThat(A, isPresent());
+              assertThat(A.uniqueMethodWithOriginalName("foo"), isPresent());
+            })
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/285021603): We should not fail here.
-        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+        .assertSuccessWithOutputLines("A::foo", "A::foo");
   }
 
   static class Main {
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index ee68e1e..1ca239e 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -124,6 +124,12 @@
                       help='Build desugar library configuration (original JDK-8)')
   group.add_argument('--desugar-configuration-jdk11-legacy', action='store_true',
                       help='Build desugar library configuration (JDK-11 legacy)')
+  group.add_argument('--desugar-configuration-jdk11-minimal', action='store_true',
+                      help='Build desugar library configuration (JDK-11 minimal)')
+  group.add_argument('--desugar-configuration-jdk11', action='store_true',
+                      help='Build desugar library configuration (JDK-11)')
+  group.add_argument('--desugar-configuration-jdk11-nio', action='store_true',
+                      help='Build desugar library configuration (JDK-11 nio)')
   return result.parse_args(argv)
 
 def determine_version():
@@ -369,7 +375,7 @@
     with zipfile.ZipFile(conversions, 'r') as conversions_zip:
       conversions_zip.extractall(tmp_dir)
 
-    # Add configuration
+    # Add configuration.
     configuration_dir = join(tmp_dir, 'META-INF', 'desugar', 'd8')
     makedirs(configuration_dir)
     copyfile(configuration, join(configuration_dir, 'desugar.json'))
@@ -388,6 +394,9 @@
     utils.PrintCmd(cmd)
     subprocess.check_call(cmd)
 
+    # Add LICENSE file.
+    copyfile(join(utils.REPO_ROOT, 'LICENSE'), join(tmp_dir, 'LICENSE'))
+
     make_archive(destination, 'zip', tmp_dir)
     move(destination + '.zip', destination)
 
@@ -435,11 +444,34 @@
         'Need to supply output zip with --out.')
   if options.desugar_configuration or options.desugar_configuration_jdk8:
     generate_desugar_configuration_maven_zip(
-      options.out, utils.DESUGAR_CONFIGURATION, utils.DESUGAR_IMPLEMENTATION)
+      options.out,
+      utils.DESUGAR_CONFIGURATION,
+      utils.DESUGAR_IMPLEMENTATION,
+      utils.LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP)
   elif options.desugar_configuration_jdk11_legacy:
     generate_desugar_configuration_maven_zip(
-      options.out, utils.DESUGAR_CONFIGURATION_JDK11_LEGACY,
-      utils.DESUGAR_IMPLEMENTATION_JDK11)
+      options.out,
+      utils.DESUGAR_CONFIGURATION_JDK11_LEGACY,
+      utils.DESUGAR_IMPLEMENTATION_JDK11,
+      utils.LIBRARY_DESUGAR_CONVERSIONS_LEGACY_ZIP)
+  elif options.desugar_configuration_jdk11_minimal:
+    generate_desugar_configuration_maven_zip(
+      options.out,
+      utils.DESUGAR_CONFIGURATION_JDK11_MINIMAL,
+      utils.DESUGAR_IMPLEMENTATION_JDK11,
+      utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP)
+  elif options.desugar_configuration_jdk11:
+    generate_desugar_configuration_maven_zip(
+      options.out,
+      utils.DESUGAR_CONFIGURATION_JDK11,
+      utils.DESUGAR_IMPLEMENTATION_JDK11,
+      utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP)
+  elif options.desugar_configuration_jdk11_nio:
+    generate_desugar_configuration_maven_zip(
+      options.out,
+      utils.DESUGAR_CONFIGURATION_JDK11_NIO,
+      utils.DESUGAR_IMPLEMENTATION_JDK11,
+      utils.LIBRARY_DESUGAR_CONVERSIONS_ZIP)
   else:
     generate_r8_maven_zip(options.out, options.r8lib)