Extend the use of LIR to application writer

Change-Id: Ia48dced965b3c0f665e81bbb37e90e7314d8dd5d
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ecb29de..cf5b2c1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -63,10 +63,8 @@
 import com.android.tools.r8.kotlin.KotlinMetadataUtils;
 import com.android.tools.r8.naming.IdentifierMinifier;
 import com.android.tools.r8.naming.Minifier;
-import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
 import com.android.tools.r8.naming.ProguardMapMinifier;
-import com.android.tools.r8.naming.RecordInvokeDynamicInvokeCustomRewriter;
 import com.android.tools.r8.naming.RecordRewritingNamingLens;
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.optimize.BridgeHoistingToSharedSyntheticSuperClass;
@@ -765,10 +763,6 @@
         assert appView.dexItemFactory().verifyNoCachedTypeElements();
       }
 
-      // TODO(b/225838009): Move further down.
-      LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
-      assert appView.dexItemFactory().verifyNoCachedTypeElements();
-
       // Perform minification.
       if (options.getProguardConfiguration().hasApplyMappingFile()) {
         timing.begin("apply-mapping");
@@ -781,17 +775,13 @@
         timing.begin("Minification");
         new Minifier(appView.withLiveness()).run(executorService, timing);
         timing.end();
-      } else {
-        timing.begin("MinifyIdentifiers");
-        new IdentifierMinifier(appView, NamingLens.getIdentityLens()).run(executorService);
-        timing.end();
-        timing.begin("RecordInvokeDynamicRewrite");
-        new RecordInvokeDynamicInvokeCustomRewriter(appView, NamingLens.getIdentityLens())
-            .run(executorService);
-        timing.end();
       }
       appView.appInfo().notifyMinifierFinished();
 
+      timing.begin("MinifyIdentifiers");
+      new IdentifierMinifier(appView).run(executorService);
+      timing.end();
+
       // If a method filter is present don't produce output since the application is likely partial.
       if (options.hasMethodsFilter()) {
         System.out.println("Finished compilation with method filter: ");
@@ -870,6 +860,9 @@
       assert appView.verifyMovedMethodsHaveOriginalMethodPosition();
       timing.end();
 
+      LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
+      assert appView.dexItemFactory().verifyNoCachedTypeElements();
+
       // Generate the resulting application resources.
       writeApplication(appView, inputApp, executorService);
 
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 40079b6..fa97f59 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -23,6 +23,8 @@
 import com.android.tools.r8.graph.LibraryClass;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ThrowExceptionCode;
+import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirCode.TryCatchTable;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
@@ -135,14 +137,24 @@
     clazz.forEachProgramMethodMatching(
         DexEncodedMethod::hasCode,
         method -> {
-          Code code = method.getDefinition().getCode();
-          if (!code.isDexCode()) {
-            return;
-          }
-          for (TryHandler handler : code.asDexCode().getHandlers()) {
-            for (TypeAddrPair pair : handler.pairs) {
-              DexType rewrittenType = appView.graphLens().lookupType(pair.getType());
-              findReferencedLibraryClasses(rewrittenType, clazz);
+          if (appView.enableWholeProgramOptimizations()) {
+            LirCode<Integer> code = method.getDefinition().getCode().asLirCode();
+            if (code != null && code.hasTryCatchTable()) {
+              TryCatchTable tryCatchTable = code.getTryCatchTable();
+              tryCatchTable.forEachHandler(
+                  (blockIndex, handlers) ->
+                      handlers
+                          .getGuards()
+                          .forEach(guard -> findReferencedLibraryClasses(guard, clazz)));
+            }
+          } else {
+            DexCode code = method.getDefinition().getCode().asDexCode();
+            if (code != null) {
+              for (TryHandler handler : code.getHandlers()) {
+                for (TypeAddrPair pair : handler.pairs) {
+                  findReferencedLibraryClasses(pair.getType(), clazz);
+                }
+              }
             }
           }
         });
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
index 63f24c2..903796b 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -195,7 +195,9 @@
 
   @Override
   public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
-    throw new Unreachable("Should not be called");
+    registry.registerNewInstance(exceptionType);
+    registry.registerInvokeDirect(
+        registry.dexItemFactory().createInstanceInitializer(exceptionType));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index 56f38c3..f384ef5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -170,6 +170,10 @@
     return get(Opcodes.INT_SWITCH);
   }
 
+  public boolean mayHaveInvokeCustom() {
+    return get(Opcodes.INVOKE_CUSTOM);
+  }
+
   public boolean mayHaveInvokeDirect() {
     return get(Opcodes.INVOKE_DIRECT);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
index 5eb58db..5dcd034 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -12,14 +12,18 @@
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.passes.AdaptClassStringsRewriter;
+import com.android.tools.r8.ir.conversion.passes.CodeRewriterPassCollection;
 import com.android.tools.r8.ir.conversion.passes.ConstResourceNumberRemover;
 import com.android.tools.r8.ir.conversion.passes.ConstResourceNumberRewriter;
+import com.android.tools.r8.ir.conversion.passes.DexItemBasedConstStringRemover;
 import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
 import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.lightir.IR2LirConverter;
 import com.android.tools.r8.lightir.LirCode;
 import com.android.tools.r8.lightir.LirStrategy;
+import com.android.tools.r8.naming.RecordInvokeDynamicInvokeCustomRewriter;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
 import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -152,11 +156,20 @@
     DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
     String output = appView.options().isGeneratingClassFiles() ? "CF" : "DEX";
     timing.begin("LIR->IR->" + output);
+    CodeRewriterPassCollection codeRewriterPassCollection =
+        new CodeRewriterPassCollection(
+            new AdaptClassStringsRewriter(appView),
+            new ConstResourceNumberRemover(appView),
+            new DexItemBasedConstStringRemover(appView),
+            new RecordInvokeDynamicInvokeCustomRewriter(appView),
+            new FilledNewArrayRewriter(appView));
     ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz ->
             clazz.forEachProgramMethod(
-                m -> finalizeLirMethodToOutputFormat(m, deadCodeRemover, appView)),
+                m ->
+                    finalizeLirMethodToOutputFormat(
+                        m, deadCodeRemover, appView, codeRewriterPassCollection)),
         appView.options().getThreadingModule(),
         executorService);
     timing.end();
@@ -169,7 +182,8 @@
   private static void finalizeLirMethodToOutputFormat(
       ProgramMethod method,
       DeadCodeRemover deadCodeRemover,
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      CodeRewriterPassCollection codeRewriterPassCollection) {
     Code code = method.getDefinition().getCode();
     if (!(code instanceof LirCode)) {
       return;
@@ -177,10 +191,7 @@
     Timing onThreadTiming = Timing.empty();
     IRCode irCode = method.buildIR(appView, MethodConversionOptions.forPostLirPhase(appView));
     assert irCode.verifyInvokeInterface(appView);
-    ConstResourceNumberRemover constResourceNumberRemover = new ConstResourceNumberRemover(appView);
-    constResourceNumberRemover.run(irCode, onThreadTiming);
-    FilledNewArrayRewriter filledNewArrayRewriter = new FilledNewArrayRewriter(appView);
-    boolean changed = filledNewArrayRewriter.run(irCode, onThreadTiming).hasChanged().toBoolean();
+    boolean changed = codeRewriterPassCollection.run(irCode, null, null, onThreadTiming);
     if (appView.options().isGeneratingDex() && changed) {
       ConstantCanonicalizer constantCanonicalizer =
           new ConstantCanonicalizer(appView, method, irCode);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/AdaptClassStringsRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/AdaptClassStringsRewriter.java
new file mode 100644
index 0000000..dbbac32
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/AdaptClassStringsRewriter.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.naming.IdentifierMinifier;
+import com.android.tools.r8.shaking.ProguardClassFilter;
+
+public class AdaptClassStringsRewriter extends CodeRewriterPass<AppInfoWithClassHierarchy> {
+
+  private final ProguardClassFilter adaptClassStrings;
+
+  public AdaptClassStringsRewriter(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    super(appView);
+    this.adaptClassStrings = appView.options().getProguardConfiguration().getAdaptClassStrings();
+  }
+
+  @Override
+  protected String getRewriterId() {
+    return "AdaptClassStringsRewriter";
+  }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    return !adaptClassStrings.isEmpty()
+        && adaptClassStrings.matches(code.context().getHolderType())
+        && code.metadata().mayHaveConstString();
+  }
+
+  @Override
+  protected CodeRewriterResult rewriteCode(IRCode code) {
+    boolean hasChanged = false;
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      ConstString instruction = iterator.next().asConstString();
+      if (instruction != null) {
+        DexString replacement =
+            IdentifierMinifier.getRenamedStringLiteral(appView(), instruction.getValue());
+        if (replacement.isNotIdenticalTo(instruction.getValue())) {
+          iterator.replaceCurrentInstructionWithConstString(appView, code, replacement, null);
+          hasChanged = true;
+        }
+      }
+    }
+    return CodeRewriterResult.hasChanged(hasChanged);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
index bb62066..dbbf1f6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
@@ -9,17 +9,23 @@
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination;
 import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.string.StringBuilderAppendOptimizer;
 import com.android.tools.r8.utils.Timing;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 public class CodeRewriterPassCollection {
 
   private final List<CodeRewriterPass<?>> passes;
 
+  public CodeRewriterPassCollection(CodeRewriterPass<?>... passes) {
+    this(Arrays.asList(passes));
+  }
+
   public CodeRewriterPassCollection(List<CodeRewriterPass<?>> passes) {
     this.passes = passes;
   }
@@ -46,14 +52,17 @@
     return new CodeRewriterPassCollection(passes);
   }
 
-  public void run(
+  public boolean run(
       IRCode code,
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext,
       Timing timing) {
+    boolean changed = false;
     for (CodeRewriterPass<?> pass : passes) {
-      pass.run(code, methodProcessor, methodProcessingContext, timing);
       // TODO(b/286345542): Run printMethod after each run.
+      CodeRewriterResult result = pass.run(code, methodProcessor, methodProcessingContext, timing);
+      changed |= result.hasChanged().isTrue();
     }
+    return changed;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexItemBasedConstStringRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexItemBasedConstStringRemover.java
new file mode 100644
index 0000000..15a4793
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexItemBasedConstStringRemover.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.code.DexItemBasedConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+
+public class DexItemBasedConstStringRemover extends CodeRewriterPass<AppInfoWithClassHierarchy> {
+
+  public DexItemBasedConstStringRemover(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    super(appView);
+  }
+
+  @Override
+  protected String getRewriterId() {
+    return "DexItemBasedConstStringRemover";
+  }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    return code.metadata().mayHaveDexItemBasedConstString();
+  }
+
+  @Override
+  protected CodeRewriterResult rewriteCode(IRCode code) {
+    boolean hasChanged = false;
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      DexItemBasedConstString instruction = iterator.next().asDexItemBasedConstString();
+      if (instruction != null) {
+        DexString replacement =
+            instruction.getNameComputationInfo().computeNameFor(instruction.getItem(), appView());
+        iterator.replaceCurrentInstructionWithConstString(appView, code, replacement, null);
+        hasChanged = true;
+      }
+    }
+    return CodeRewriterResult.hasChanged(hasChanged);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
index 7c476ff..eb90225 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
@@ -45,11 +45,9 @@
 
 public class FilledNewArrayRewriter extends CodeRewriterPass<AppInfo> {
 
-  private final RewriteArrayOptions rewriteArrayOptions;
   private static final Set<Instruction> NOTHING = ImmutableSet.of();
 
-  private boolean mayHaveRedundantBlocks;
-  Set<Instruction> toRemove = NOTHING;
+  private final RewriteArrayOptions rewriteArrayOptions;
 
   public FilledNewArrayRewriter(AppView<?> appView) {
     super(appView);
@@ -63,67 +61,7 @@
 
   @Override
   protected CodeRewriterResult rewriteCode(IRCode code) {
-    assert !mayHaveRedundantBlocks;
-    assert toRemove == NOTHING;
-    CodeRewriterResult result = noChange();
-    BooleanBox pendingRewrites = new BooleanBox(true);
-    while (pendingRewrites.get()) {
-      pendingRewrites.set(false);
-      BasicBlockIterator blockIterator = code.listIterator();
-      while (blockIterator.hasNext()) {
-        BasicBlock block = blockIterator.next();
-        BasicBlockInstructionListIterator instructionIterator = block.listIterator(code);
-        while (instructionIterator.hasNext()) {
-          Instruction instruction = instructionIterator.next();
-          if (instruction.isNewArrayFilled()) {
-            result =
-                processInstruction(
-                    code,
-                    blockIterator,
-                    instructionIterator,
-                    instruction.asNewArrayFilled(),
-                    result,
-                    pendingRewrites);
-          }
-        }
-      }
-      if (!toRemove.isEmpty()) {
-        Set<Instruction> additionalToRemove = SetUtils.newIdentityHashSet();
-        InstructionListIterator it = code.instructionListIterator();
-        while (it.hasNext()) {
-          Instruction next = it.next();
-          if (toRemove.contains(next)) {
-            // Also remove constants used by the removed NewArrayFilled.
-            if (next.isNewArrayFilled()) {
-              next.inValues()
-                  .forEach(
-                      value -> {
-                        if (value.hasSingleUniqueUser()) {
-                          additionalToRemove.add(value.getDefinition());
-                        }
-                      });
-            }
-            it.remove();
-            mayHaveRedundantBlocks = true;
-          }
-        }
-        if (!additionalToRemove.isEmpty()) {
-          InstructionListIterator itAdditional = code.instructionListIterator();
-          while (itAdditional.hasNext()) {
-            Instruction next = itAdditional.next();
-            if (additionalToRemove.contains(next)) {
-              itAdditional.remove();
-              mayHaveRedundantBlocks = true;
-            }
-          }
-        }
-      }
-      toRemove = NOTHING;
-      if (mayHaveRedundantBlocks) {
-        code.removeRedundantBlocks();
-      }
-    }
-    return result;
+    return new FilledNewArrayCodeRewriter().rewriteCode(code);
   }
 
   @Override
@@ -131,293 +69,499 @@
     return code.metadata().mayHaveNewArrayFilled();
   }
 
-  private boolean isNewArrayFilledOfConstants(NewArrayFilled newArrayFilled) {
-    for (Value inValue : newArrayFilled.inValues()) {
-      if (!inValue.isConstNumber() && !inValue.isConstString() && !inValue.isConstClass()) {
-        return false;
-      }
-    }
-    return true;
-  }
+  private class FilledNewArrayCodeRewriter {
 
-  private boolean isDefinedByNewArrayFilledOfConstants(Value value) {
-    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewArrayFilled)) {
-      return false;
-    }
-    return isNewArrayFilledOfConstants(value.definition.asNewArrayFilled());
-  }
+    private boolean mayHaveRedundantBlocks = false;
+    private Set<Instruction> toRemove = NOTHING;
 
-  public NewArrayFilled copyConstantsNewArrayFilled(IRCode code, NewArrayFilled original) {
-    assert isNewArrayFilledOfConstants(original);
-    Value newValue = code.createValue(original.getOutType(), original.getLocalInfo());
-    List<Value> newArguments = new ArrayList<>(original.inValues().size());
-    for (Value value : original.inValues()) {
-      if (value.isConstNumber()) {
-        newArguments.add(
-            ConstNumber.copyOf(code, value.getDefinition().asConstNumber()).outValue());
-      } else if (value.isConstString()) {
-        newArguments.add(
-            ConstString.copyOf(code, value.getDefinition().asConstString()).outValue());
-      } else if (value.isConstClass()) {
-        newArguments.add(ConstClass.copyOf(code, value.getDefinition().asConstClass()).outValue());
-      } else {
-        assert false;
-      }
-    }
-    return new NewArrayFilled(original.getArrayType(), newValue, newArguments);
-  }
-
-  private CodeRewriterResult processInstruction(
-      IRCode code,
-      BasicBlockIterator blockIterator,
-      BasicBlockInstructionListIterator instructionIterator,
-      NewArrayFilled newArrayFilled,
-      CodeRewriterResult result,
-      BooleanBox pendingRewrites) {
-    if (canUseNewArrayFilled(newArrayFilled)) {
-      return result;
-    }
-    if (newArrayFilled.hasUnusedOutValue()) {
-      instructionIterator.removeOrReplaceByDebugLocalRead();
-    } else if (canUseNewArrayFilledData(newArrayFilled)) {
-      rewriteToNewArrayFilledData(code, blockIterator, instructionIterator, newArrayFilled);
-    } else if (newArrayFilled.outValue().hasSingleUniqueUser()
-        && newArrayFilled.outValue().singleUniqueUser().isNewArrayFilled()
-        && isNewArrayFilledOfConstants(newArrayFilled)) {
-      if (canUseNewArrayFilled(newArrayFilled.outValue().singleUniqueUser().asNewArrayFilled())) {
-        // The NewArrayFilled user is supported, so rewrite here.
-        rewriteToArrayPuts(code, blockIterator, instructionIterator, newArrayFilled);
-      } else {
-        // The NewArrayFilled user is not supported so leave for rewriting after that.
-        //
-        // The effect of this is that when the user of this NewArrayFilled is rewritten to puts,
-        // the NewArrayFilled construction is copied to the use site
-        //
-        //  Input:
-        //
-        //   v0 <-  Const X
-        //   v1 <-  NewArrayFilled(v0)
-        //   v2 <-  Const Y
-        //   v3 <-  NewArrayFilled(v2)
-        //   v4 <-  NewArrayFilled(v1, v3)
-        //
-        // After rewriting the user (v0 - v3 are unused and removed):
-        //
-        //   v4 <-  NewArrayEmpty(...)
-        //   v5 <-  Const X
-        //   v6 <-  NewArrayFilled(v5)
-        //          APut v4, <Const 0>, v6
-        //   v7 <-  Const Y
-        //   v8 <-  NewArrayFilled(v7)
-        //          APut v4, <Const 1>, v8
-        //
-        // Setting pending rewrites cause the copied NewArrayFilled to be rewritten in their new
-        // location in the fixpoint:
-        //
-        //   v4 <-  NewArrayEmpty(...)
-        //   v9 <-  NewArrayEmpty(...)
-        //   v10 <- Const X
-        //          APut v9, <Const 0>, v10
-        //          APut v4, <Const 0>, v9
-        //   v11 <- NewArrayEmpty(...)
-        //   v12 <- Const Y
-        //          APut v11, <Const 0>, v12
-        //          APut v4, <Const 1>, v11
-        //
-        // If the NewArrayFilled which gets moved is supported then the second rewriting in the
-        // fixpoint does not happen.
-        pendingRewrites.set(true);
-      }
-    } else {
-      rewriteToArrayPuts(code, blockIterator, instructionIterator, newArrayFilled);
-    }
-    return CodeRewriterResult.HAS_CHANGED;
-  }
-
-  private boolean canUseNewArrayFilled(NewArrayFilled newArrayFilled) {
-    if (!options.isGeneratingDex()) {
-      return false;
-    }
-    int size = newArrayFilled.size();
-    if (size < rewriteArrayOptions.minSizeForFilledNewArray) {
-      return false;
-    }
-    // filled-new-array is implemented only for int[] and Object[].
-    DexType arrayType = newArrayFilled.getArrayType();
-    if (arrayType.isIdenticalTo(dexItemFactory.intArrayType)) {
-      // 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 (size > rewriteArrayOptions.maxSizeForFilledNewArrayOfInts) {
-        return false;
-      }
-      if (canUseNewArrayFilledData(newArrayFilled)
-          && size
-              > rewriteArrayOptions
-                  .maxSizeForFilledNewArrayOfIntsWhenNewArrayFilledDataApplicable) {
-        return false;
-      }
-      return true;
-    }
-    if (!arrayType.isPrimitiveArrayType()) {
-      if (size > rewriteArrayOptions.maxSizeForFilledNewArrayOfReferences) {
-        return false;
-      }
-      if (arrayType.isIdenticalTo(dexItemFactory.stringArrayType)) {
-        return rewriteArrayOptions.canUseFilledNewArrayOfStrings();
-      }
-      if (!rewriteArrayOptions.canUseFilledNewArrayOfNonStringObjects()) {
-        return false;
-      }
-      if (!rewriteArrayOptions.canUseFilledNewArrayOfArrays()
-          && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
-        return false;
-      }
-      // Check that all arguments to the array is the array type or that the array is type Object[].
-      if (rewriteArrayOptions.canHaveSubTypesInFilledNewArrayBug()
-          && arrayType.isNotIdenticalTo(dexItemFactory.objectArrayType)
-          && !arrayType.isPrimitiveArrayType()) {
-        DexType arrayElementType = arrayType.toArrayElementType(dexItemFactory);
-        for (Value elementValue : newArrayFilled.inValues()) {
-          if (!canStoreElementInNewArrayFilled(elementValue.getType(), arrayElementType)) {
-            return false;
+    public CodeRewriterResult rewriteCode(IRCode code) {
+      assert !mayHaveRedundantBlocks;
+      assert toRemove == NOTHING;
+      CodeRewriterResult result = noChange();
+      BooleanBox pendingRewrites = new BooleanBox(true);
+      while (pendingRewrites.get()) {
+        pendingRewrites.set(false);
+        BasicBlockIterator blockIterator = code.listIterator();
+        while (blockIterator.hasNext()) {
+          BasicBlock block = blockIterator.next();
+          BasicBlockInstructionListIterator instructionIterator = block.listIterator(code);
+          while (instructionIterator.hasNext()) {
+            Instruction instruction = instructionIterator.next();
+            if (instruction.isNewArrayFilled()) {
+              result =
+                  processInstruction(
+                      code,
+                      blockIterator,
+                      instructionIterator,
+                      instruction.asNewArrayFilled(),
+                      result,
+                      pendingRewrites);
+            }
           }
         }
-      }
-      return true;
-    }
-    return false;
-  }
-
-  private boolean canStoreElementInNewArrayFilled(TypeElement valueType, DexType elementType) {
-    if (elementType.isIdenticalTo(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 valueType.isClassType(elementType);
-  }
-
-  private boolean canUseNewArrayFilledData(NewArrayFilled newArrayFilled) {
-    // Only convert into NewArrayFilledData when compiling to DEX.
-    if (!appView.options().isGeneratingDex()) {
-      return false;
-    }
-    // If there is only one element it is typically smaller to generate the array put instruction
-    // instead of fill array data.
-    int size = newArrayFilled.size();
-    if (size < rewriteArrayOptions.minSizeForFilledArrayData
-        || size > rewriteArrayOptions.maxSizeForFilledArrayData) {
-      return false;
-    }
-    if (!newArrayFilled.getArrayType().isPrimitiveArrayType()) {
-      return false;
-    }
-    return Iterables.all(newArrayFilled.inValues(), Value::isConstant);
-  }
-
-  private NewArrayEmpty rewriteToNewArrayEmpty(
-      IRCode code,
-      BasicBlockInstructionListIterator instructionIterator,
-      NewArrayFilled newArrayFilled) {
-    // Load the size before the NewArrayEmpty instruction.
-    ConstNumber constNumber =
-        ConstNumber.builder()
-            .setFreshOutValue(code, TypeElement.getInt())
-            .setValue(newArrayFilled.size())
-            .setPosition(options.debug ? newArrayFilled.getPosition() : Position.none())
-            .build();
-    instructionIterator.previous();
-    instructionIterator.add(constNumber);
-    Instruction next = instructionIterator.next();
-    assert next == newArrayFilled;
-
-    // Replace the InvokeNewArray instruction by a NewArrayEmpty instruction.
-    NewArrayEmpty newArrayEmpty =
-        new NewArrayEmpty(
-            newArrayFilled.outValue(), constNumber.outValue(), newArrayFilled.getArrayType());
-    instructionIterator.replaceCurrentInstruction(newArrayEmpty);
-    return newArrayEmpty;
-  }
-
-  private void rewriteToNewArrayFilledData(
-      IRCode code,
-      BasicBlockIterator blockIterator,
-      BasicBlockInstructionListIterator instructionIterator,
-      NewArrayFilled newArrayFilled) {
-    NewArrayEmpty newArrayEmpty = rewriteToNewArrayEmpty(code, instructionIterator, newArrayFilled);
-
-    // Insert a new NewArrayFilledData instruction after the NewArrayEmpty instruction.
-    short[] contents = computeArrayFilledData(newArrayFilled);
-    NewArrayFilledData newArrayFilledData =
-        new NewArrayFilledData(
-            newArrayFilled.outValue(),
-            newArrayFilled.getArrayType().elementSizeForPrimitiveArrayType(),
-            newArrayFilled.size(),
-            contents);
-    newArrayFilledData.setPosition(newArrayFilled.getPosition());
-    if (newArrayEmpty.getBlock().hasCatchHandlers()) {
-      BasicBlock splitBlock =
-          instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
-      splitBlock.listIterator(code).add(newArrayFilledData);
-    } else {
-      instructionIterator.add(newArrayFilledData);
-    }
-  }
-
-  private short[] computeArrayFilledData(NewArrayFilled newArrayFilled) {
-    int elementSize = newArrayFilled.getArrayType().elementSizeForPrimitiveArrayType();
-    int size = newArrayFilled.size();
-    if (elementSize == 1) {
-      short[] result = new short[(size + 1) / 2];
-      for (int i = 0; i < size; i += 2) {
-        ConstNumber constNumber =
-            newArrayFilled.getOperand(i).getConstInstruction().asConstNumber();
-        short value = (short) (constNumber.getIntValue() & 0xFF);
-        if (i + 1 < size) {
-          ConstNumber nextConstNumber =
-              newArrayFilled.getOperand(i + 1).getConstInstruction().asConstNumber();
-          value |= (short) ((nextConstNumber.getIntValue() & 0xFF) << 8);
+        if (!toRemove.isEmpty()) {
+          Set<Instruction> additionalToRemove = SetUtils.newIdentityHashSet();
+          InstructionListIterator it = code.instructionListIterator();
+          while (it.hasNext()) {
+            Instruction next = it.next();
+            if (toRemove.contains(next)) {
+              // Also remove constants used by the removed NewArrayFilled.
+              if (next.isNewArrayFilled()) {
+                next.inValues()
+                    .forEach(
+                        value -> {
+                          if (value.hasSingleUniqueUser()) {
+                            additionalToRemove.add(value.getDefinition());
+                          }
+                        });
+              }
+              it.remove();
+              mayHaveRedundantBlocks = true;
+            }
+          }
+          if (!additionalToRemove.isEmpty()) {
+            InstructionListIterator itAdditional = code.instructionListIterator();
+            while (itAdditional.hasNext()) {
+              Instruction next = itAdditional.next();
+              if (additionalToRemove.contains(next)) {
+                itAdditional.remove();
+                mayHaveRedundantBlocks = true;
+              }
+            }
+          }
         }
-        result[i / 2] = value;
+        toRemove = NOTHING;
+        if (mayHaveRedundantBlocks) {
+          code.removeRedundantBlocks();
+        }
       }
       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++) {
-      ConstNumber constNumber = newArrayFilled.getOperand(i).getConstInstruction().asConstNumber();
-      for (int part = 0; part < shortsPerConstant; part++) {
-        result[i * shortsPerConstant + part] =
-            (short) ((constNumber.getRawValue() >> (16 * part)) & 0xFFFFL);
+
+    private boolean isNewArrayFilledOfConstants(NewArrayFilled newArrayFilled) {
+      for (Value inValue : newArrayFilled.inValues()) {
+        if (!inValue.isConstNumber() && !inValue.isConstString() && !inValue.isConstClass()) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    private boolean isDefinedByNewArrayFilledOfConstants(Value value) {
+      if (!value.isDefinedByInstructionSatisfying(Instruction::isNewArrayFilled)) {
+        return false;
+      }
+      return isNewArrayFilledOfConstants(value.definition.asNewArrayFilled());
+    }
+
+    public NewArrayFilled copyConstantsNewArrayFilled(IRCode code, NewArrayFilled original) {
+      assert isNewArrayFilledOfConstants(original);
+      Value newValue = code.createValue(original.getOutType(), original.getLocalInfo());
+      List<Value> newArguments = new ArrayList<>(original.inValues().size());
+      for (Value value : original.inValues()) {
+        if (value.isConstNumber()) {
+          newArguments.add(
+              ConstNumber.copyOf(code, value.getDefinition().asConstNumber()).outValue());
+        } else if (value.isConstString()) {
+          newArguments.add(
+              ConstString.copyOf(code, value.getDefinition().asConstString()).outValue());
+        } else if (value.isConstClass()) {
+          newArguments.add(
+              ConstClass.copyOf(code, value.getDefinition().asConstClass()).outValue());
+        } else {
+          assert false;
+        }
+      }
+      return new NewArrayFilled(original.getArrayType(), newValue, newArguments);
+    }
+
+    private CodeRewriterResult processInstruction(
+        IRCode code,
+        BasicBlockIterator blockIterator,
+        BasicBlockInstructionListIterator instructionIterator,
+        NewArrayFilled newArrayFilled,
+        CodeRewriterResult result,
+        BooleanBox pendingRewrites) {
+      if (canUseNewArrayFilled(newArrayFilled)) {
+        return result;
+      }
+      if (newArrayFilled.hasUnusedOutValue()) {
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+      } else if (canUseNewArrayFilledData(newArrayFilled)) {
+        rewriteToNewArrayFilledData(code, blockIterator, instructionIterator, newArrayFilled);
+      } else if (newArrayFilled.outValue().hasSingleUniqueUser()
+          && newArrayFilled.outValue().singleUniqueUser().isNewArrayFilled()
+          && isNewArrayFilledOfConstants(newArrayFilled)) {
+        if (canUseNewArrayFilled(newArrayFilled.outValue().singleUniqueUser().asNewArrayFilled())) {
+          // The NewArrayFilled user is supported, so rewrite here.
+          rewriteToArrayPuts(code, blockIterator, instructionIterator, newArrayFilled);
+        } else {
+          // The NewArrayFilled user is not supported so leave for rewriting after that.
+          //
+          // The effect of this is that when the user of this NewArrayFilled is rewritten to puts,
+          // the NewArrayFilled construction is copied to the use site
+          //
+          //  Input:
+          //
+          //   v0 <-  Const X
+          //   v1 <-  NewArrayFilled(v0)
+          //   v2 <-  Const Y
+          //   v3 <-  NewArrayFilled(v2)
+          //   v4 <-  NewArrayFilled(v1, v3)
+          //
+          // After rewriting the user (v0 - v3 are unused and removed):
+          //
+          //   v4 <-  NewArrayEmpty(...)
+          //   v5 <-  Const X
+          //   v6 <-  NewArrayFilled(v5)
+          //          APut v4, <Const 0>, v6
+          //   v7 <-  Const Y
+          //   v8 <-  NewArrayFilled(v7)
+          //          APut v4, <Const 1>, v8
+          //
+          // Setting pending rewrites cause the copied NewArrayFilled to be rewritten in their new
+          // location in the fixpoint:
+          //
+          //   v4 <-  NewArrayEmpty(...)
+          //   v9 <-  NewArrayEmpty(...)
+          //   v10 <- Const X
+          //          APut v9, <Const 0>, v10
+          //          APut v4, <Const 0>, v9
+          //   v11 <- NewArrayEmpty(...)
+          //   v12 <- Const Y
+          //          APut v11, <Const 0>, v12
+          //          APut v4, <Const 1>, v11
+          //
+          // If the NewArrayFilled which gets moved is supported then the second rewriting in the
+          // fixpoint does not happen.
+          pendingRewrites.set(true);
+        }
+      } else {
+        rewriteToArrayPuts(code, blockIterator, instructionIterator, newArrayFilled);
+      }
+      return CodeRewriterResult.HAS_CHANGED;
+    }
+
+    private boolean canUseNewArrayFilled(NewArrayFilled newArrayFilled) {
+      if (!options.isGeneratingDex()) {
+        return false;
+      }
+      int size = newArrayFilled.size();
+      if (size < rewriteArrayOptions.minSizeForFilledNewArray) {
+        return false;
+      }
+      // filled-new-array is implemented only for int[] and Object[].
+      DexType arrayType = newArrayFilled.getArrayType();
+      if (arrayType.isIdenticalTo(dexItemFactory.intArrayType)) {
+        // 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 (size > rewriteArrayOptions.maxSizeForFilledNewArrayOfInts) {
+          return false;
+        }
+        if (canUseNewArrayFilledData(newArrayFilled)
+            && size
+                > rewriteArrayOptions
+                    .maxSizeForFilledNewArrayOfIntsWhenNewArrayFilledDataApplicable) {
+          return false;
+        }
+        return true;
+      }
+      if (!arrayType.isPrimitiveArrayType()) {
+        if (size > rewriteArrayOptions.maxSizeForFilledNewArrayOfReferences) {
+          return false;
+        }
+        if (arrayType.isIdenticalTo(dexItemFactory.stringArrayType)) {
+          return rewriteArrayOptions.canUseFilledNewArrayOfStrings();
+        }
+        if (!rewriteArrayOptions.canUseFilledNewArrayOfNonStringObjects()) {
+          return false;
+        }
+        if (!rewriteArrayOptions.canUseFilledNewArrayOfArrays()
+            && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
+          return false;
+        }
+        // Check that all arguments to the array is the array type or that the array is type
+        // Object[].
+        if (rewriteArrayOptions.canHaveSubTypesInFilledNewArrayBug()
+            && arrayType.isNotIdenticalTo(dexItemFactory.objectArrayType)
+            && !arrayType.isPrimitiveArrayType()) {
+          DexType arrayElementType = arrayType.toArrayElementType(dexItemFactory);
+          for (Value elementValue : newArrayFilled.inValues()) {
+            if (!canStoreElementInNewArrayFilled(elementValue.getType(), arrayElementType)) {
+              return false;
+            }
+          }
+        }
+        return true;
+      }
+      return false;
+    }
+
+    private boolean canStoreElementInNewArrayFilled(TypeElement valueType, DexType elementType) {
+      if (elementType.isIdenticalTo(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 valueType.isClassType(elementType);
+    }
+
+    private boolean canUseNewArrayFilledData(NewArrayFilled newArrayFilled) {
+      // Only convert into NewArrayFilledData when compiling to DEX.
+      if (!appView.options().isGeneratingDex()) {
+        return false;
+      }
+      // If there is only one element it is typically smaller to generate the array put instruction
+      // instead of fill array data.
+      int size = newArrayFilled.size();
+      if (size < rewriteArrayOptions.minSizeForFilledArrayData
+          || size > rewriteArrayOptions.maxSizeForFilledArrayData) {
+        return false;
+      }
+      if (!newArrayFilled.getArrayType().isPrimitiveArrayType()) {
+        return false;
+      }
+      return Iterables.all(newArrayFilled.inValues(), Value::isConstant);
+    }
+
+    private NewArrayEmpty rewriteToNewArrayEmpty(
+        IRCode code,
+        BasicBlockInstructionListIterator instructionIterator,
+        NewArrayFilled newArrayFilled) {
+      // Load the size before the NewArrayEmpty instruction.
+      ConstNumber constNumber =
+          ConstNumber.builder()
+              .setFreshOutValue(code, TypeElement.getInt())
+              .setValue(newArrayFilled.size())
+              .setPosition(options.debug ? newArrayFilled.getPosition() : Position.none())
+              .build();
+      instructionIterator.previous();
+      instructionIterator.add(constNumber);
+      Instruction next = instructionIterator.next();
+      assert next == newArrayFilled;
+
+      // Replace the InvokeNewArray instruction by a NewArrayEmpty instruction.
+      NewArrayEmpty newArrayEmpty =
+          new NewArrayEmpty(
+              newArrayFilled.outValue(), constNumber.outValue(), newArrayFilled.getArrayType());
+      instructionIterator.replaceCurrentInstruction(newArrayEmpty);
+      return newArrayEmpty;
+    }
+
+    private void rewriteToNewArrayFilledData(
+        IRCode code,
+        BasicBlockIterator blockIterator,
+        BasicBlockInstructionListIterator instructionIterator,
+        NewArrayFilled newArrayFilled) {
+      NewArrayEmpty newArrayEmpty =
+          rewriteToNewArrayEmpty(code, instructionIterator, newArrayFilled);
+
+      // Insert a new NewArrayFilledData instruction after the NewArrayEmpty instruction.
+      short[] contents = computeArrayFilledData(newArrayFilled);
+      NewArrayFilledData newArrayFilledData =
+          new NewArrayFilledData(
+              newArrayFilled.outValue(),
+              newArrayFilled.getArrayType().elementSizeForPrimitiveArrayType(),
+              newArrayFilled.size(),
+              contents);
+      newArrayFilledData.setPosition(newArrayFilled.getPosition());
+      if (newArrayEmpty.getBlock().hasCatchHandlers()) {
+        BasicBlock splitBlock =
+            instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
+        splitBlock.listIterator(code).add(newArrayFilledData);
+      } else {
+        instructionIterator.add(newArrayFilledData);
       }
     }
-    return result;
+
+    private short[] computeArrayFilledData(NewArrayFilled newArrayFilled) {
+      int elementSize = newArrayFilled.getArrayType().elementSizeForPrimitiveArrayType();
+      int size = newArrayFilled.size();
+      if (elementSize == 1) {
+        short[] result = new short[(size + 1) / 2];
+        for (int i = 0; i < size; i += 2) {
+          ConstNumber constNumber =
+              newArrayFilled.getOperand(i).getConstInstruction().asConstNumber();
+          short value = (short) (constNumber.getIntValue() & 0xFF);
+          if (i + 1 < size) {
+            ConstNumber nextConstNumber =
+                newArrayFilled.getOperand(i + 1).getConstInstruction().asConstNumber();
+            value |= (short) ((nextConstNumber.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++) {
+        ConstNumber constNumber =
+            newArrayFilled.getOperand(i).getConstInstruction().asConstNumber();
+        for (int part = 0; part < shortsPerConstant; part++) {
+          result[i * shortsPerConstant + part] =
+              (short) ((constNumber.getRawValue() >> (16 * part)) & 0xFFFFL);
+        }
+      }
+      return result;
+    }
+
+    private void rewriteToArrayPuts(
+        IRCode code,
+        BasicBlockIterator blockIterator,
+        BasicBlockInstructionListIterator instructionIterator,
+        NewArrayFilled newArrayFilled) {
+      NewArrayEmpty newArrayEmpty =
+          rewriteToNewArrayEmpty(code, instructionIterator, newArrayFilled);
+
+      ConstantMaterializingInstructionCache constantMaterializingInstructionCache =
+          new ConstantMaterializingInstructionCache(rewriteArrayOptions, newArrayFilled);
+
+      int index = 0;
+      for (Value elementValue : newArrayFilled.inValues()) {
+        if (instructionIterator.getBlock().hasCatchHandlers()) {
+          BasicBlock splitBlock =
+              instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
+          instructionIterator = splitBlock.listIterator(code);
+          Value putValue =
+              getPutValue(
+                  code,
+                  instructionIterator,
+                  newArrayEmpty,
+                  elementValue,
+                  constantMaterializingInstructionCache);
+          blockIterator.positionAfterPreviousBlock(splitBlock);
+          splitBlock = instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
+          instructionIterator = splitBlock.listIterator(code);
+          addArrayPut(code, instructionIterator, newArrayEmpty, index, putValue);
+          blockIterator.positionAfterPreviousBlock(splitBlock);
+          mayHaveRedundantBlocks = true;
+        } else {
+          Value putValue =
+              getPutValue(
+                  code,
+                  instructionIterator,
+                  newArrayEmpty,
+                  elementValue,
+                  constantMaterializingInstructionCache);
+          addArrayPut(code, instructionIterator, newArrayEmpty, index, putValue);
+        }
+        index++;
+      }
+
+      assert constantMaterializingInstructionCache.checkAllOccurrenceProcessed();
+    }
+
+    private Value getPutValue(
+        IRCode code,
+        BasicBlockInstructionListIterator instructionIterator,
+        NewArrayEmpty newArrayEmpty,
+        Value elementValue,
+        ConstantMaterializingInstructionCache constantMaterializingInstructionCache) {
+      // If the value was only used by the NewArrayFilled instruction it now has no normal users.
+      if (elementValue.hasAnyUsers()
+          || !(elementValue.isConstString()
+              || elementValue.isConstNumber()
+              || elementValue.isConstClass()
+              || elementValue.isDefinedByInstructionSatisfying(Instruction::isStaticGet)
+              || (isDefinedByNewArrayFilledOfConstants(elementValue)
+                  && !instructionIterator.getBlock().hasCatchHandlers()))) {
+        return elementValue;
+      }
+
+      Value existingValue = constantMaterializingInstructionCache.getValue(elementValue);
+      if (existingValue != null) {
+        addToRemove(elementValue.getDefinition());
+        return existingValue;
+      }
+
+      Instruction copy;
+      if (elementValue.isConstNumber()) {
+        copy = ConstNumber.copyOf(code, elementValue.getDefinition().asConstNumber());
+      } else if (elementValue.isConstString()) {
+        copy = ConstString.copyOf(code, elementValue.getDefinition().asConstString());
+        constantMaterializingInstructionCache.putNewValue(copy.asConstString().outValue());
+      } else if (elementValue.isConstClass()) {
+        copy = ConstClass.copyOf(code, elementValue.getDefinition().asConstClass());
+        constantMaterializingInstructionCache.putNewValue(copy.asConstClass().outValue());
+      } else if (elementValue.isDefinedByInstructionSatisfying(Instruction::isStaticGet)) {
+        copy = StaticGet.copyOf(code, elementValue.getDefinition().asStaticGet());
+        constantMaterializingInstructionCache.putNewValue(copy.asStaticGet().outValue());
+      } else if (isDefinedByNewArrayFilledOfConstants(elementValue)) {
+        copy = copyConstantsNewArrayFilled(code, elementValue.getDefinition().asNewArrayFilled());
+        assert !instructionIterator.getBlock().hasCatchHandlers();
+        for (Value inValue : copy.asNewArrayFilled().inValues()) {
+          instructionIterator.add(inValue.getDefinition());
+          inValue.getDefinition().setBlock(instructionIterator.getBlock());
+          inValue.getDefinition().setPosition(newArrayEmpty.getPosition());
+        }
+      } else {
+        assert false;
+        return elementValue;
+      }
+      copy.setBlock(instructionIterator.getBlock());
+      copy.setPosition(newArrayEmpty.getPosition());
+      instructionIterator.add(copy);
+      addToRemove(elementValue.getDefinition());
+      return copy.outValue();
+    }
+
+    private void addToRemove(Instruction instruction) {
+      if (toRemove == NOTHING) {
+        toRemove = SetUtils.newIdentityHashSet();
+      }
+      toRemove.add(instruction);
+    }
+
+    private void addArrayPut(
+        IRCode code,
+        BasicBlockInstructionListIterator instructionIterator,
+        NewArrayEmpty newArrayEmpty,
+        int index,
+        Value elementValue) {
+      // Load the array index before the ArrayPut instruction.
+      ConstNumber constNumber =
+          ConstNumber.builder()
+              .setFreshOutValue(code, TypeElement.getInt())
+              .setValue(index)
+              .setPosition(options.debug ? newArrayEmpty.getPosition() : Position.none())
+              .build();
+      instructionIterator.add(constNumber);
+
+      // Add the ArrayPut instruction.
+      DexType arrayElementType = newArrayEmpty.getArrayType().toArrayElementType(dexItemFactory);
+      MemberType memberType = MemberType.fromDexType(arrayElementType);
+      ArrayPut arrayPut =
+          ArrayPut.create(
+              memberType, newArrayEmpty.outValue(), constNumber.outValue(), elementValue);
+      arrayPut.setPosition(newArrayEmpty.getPosition());
+      instructionIterator.add(arrayPut);
+    }
   }
 
   private static class ConstantMaterializingInstructionCache {
@@ -573,134 +717,4 @@
       return true;
     }
   }
-
-  private void rewriteToArrayPuts(
-      IRCode code,
-      BasicBlockIterator blockIterator,
-      BasicBlockInstructionListIterator instructionIterator,
-      NewArrayFilled newArrayFilled) {
-    NewArrayEmpty newArrayEmpty = rewriteToNewArrayEmpty(code, instructionIterator, newArrayFilled);
-
-    ConstantMaterializingInstructionCache constantMaterializingInstructionCache =
-        new ConstantMaterializingInstructionCache(rewriteArrayOptions, newArrayFilled);
-
-    int index = 0;
-    for (Value elementValue : newArrayFilled.inValues()) {
-      if (instructionIterator.getBlock().hasCatchHandlers()) {
-        BasicBlock splitBlock =
-            instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
-        instructionIterator = splitBlock.listIterator(code);
-        Value putValue =
-            getPutValue(
-                code,
-                instructionIterator,
-                newArrayEmpty,
-                elementValue,
-                constantMaterializingInstructionCache);
-        blockIterator.positionAfterPreviousBlock(splitBlock);
-        splitBlock = instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
-        instructionIterator = splitBlock.listIterator(code);
-        addArrayPut(code, instructionIterator, newArrayEmpty, index, putValue);
-        blockIterator.positionAfterPreviousBlock(splitBlock);
-        mayHaveRedundantBlocks = true;
-      } else {
-        Value putValue =
-            getPutValue(
-                code,
-                instructionIterator,
-                newArrayEmpty,
-                elementValue,
-                constantMaterializingInstructionCache);
-        addArrayPut(code, instructionIterator, newArrayEmpty, index, putValue);
-      }
-      index++;
-    }
-
-    assert constantMaterializingInstructionCache.checkAllOccurrenceProcessed();
-  }
-
-  private Value getPutValue(
-      IRCode code,
-      BasicBlockInstructionListIterator instructionIterator,
-      NewArrayEmpty newArrayEmpty,
-      Value elementValue,
-      ConstantMaterializingInstructionCache constantMaterializingInstructionCache) {
-    // If the value was only used by the NewArrayFilled instruction it now has no normal users.
-    if (elementValue.hasAnyUsers()
-        || !(elementValue.isConstString()
-            || elementValue.isConstNumber()
-            || elementValue.isConstClass()
-            || elementValue.isDefinedByInstructionSatisfying(Instruction::isStaticGet)
-            || (isDefinedByNewArrayFilledOfConstants(elementValue)
-                && !instructionIterator.getBlock().hasCatchHandlers()))) {
-      return elementValue;
-    }
-
-    Value existingValue = constantMaterializingInstructionCache.getValue(elementValue);
-    if (existingValue != null) {
-      addToRemove(elementValue.getDefinition());
-      return existingValue;
-    }
-
-    Instruction copy;
-    if (elementValue.isConstNumber()) {
-      copy = ConstNumber.copyOf(code, elementValue.getDefinition().asConstNumber());
-    } else if (elementValue.isConstString()) {
-      copy = ConstString.copyOf(code, elementValue.getDefinition().asConstString());
-      constantMaterializingInstructionCache.putNewValue(copy.asConstString().outValue());
-    } else if (elementValue.isConstClass()) {
-      copy = ConstClass.copyOf(code, elementValue.getDefinition().asConstClass());
-      constantMaterializingInstructionCache.putNewValue(copy.asConstClass().outValue());
-    } else if (elementValue.isDefinedByInstructionSatisfying(Instruction::isStaticGet)) {
-      copy = StaticGet.copyOf(code, elementValue.getDefinition().asStaticGet());
-      constantMaterializingInstructionCache.putNewValue(copy.asStaticGet().outValue());
-    } else if (isDefinedByNewArrayFilledOfConstants(elementValue)) {
-      copy = copyConstantsNewArrayFilled(code, elementValue.getDefinition().asNewArrayFilled());
-      assert !instructionIterator.getBlock().hasCatchHandlers();
-      for (Value inValue : copy.asNewArrayFilled().inValues()) {
-        instructionIterator.add(inValue.getDefinition());
-        inValue.getDefinition().setBlock(instructionIterator.getBlock());
-        inValue.getDefinition().setPosition(newArrayEmpty.getPosition());
-      }
-    } else {
-      assert false;
-      return elementValue;
-    }
-    copy.setBlock(instructionIterator.getBlock());
-    copy.setPosition(newArrayEmpty.getPosition());
-    instructionIterator.add(copy);
-    addToRemove(elementValue.getDefinition());
-    return copy.outValue();
-  }
-
-  private void addToRemove(Instruction instruction) {
-    if (toRemove == NOTHING) {
-      toRemove = SetUtils.newIdentityHashSet();
-    }
-    toRemove.add(instruction);
-  }
-
-  private void addArrayPut(
-      IRCode code,
-      BasicBlockInstructionListIterator instructionIterator,
-      NewArrayEmpty newArrayEmpty,
-      int index,
-      Value elementValue) {
-    // Load the array index before the ArrayPut instruction.
-    ConstNumber constNumber =
-        ConstNumber.builder()
-            .setFreshOutValue(code, TypeElement.getInt())
-            .setValue(index)
-            .setPosition(options.debug ? newArrayEmpty.getPosition() : Position.none())
-            .build();
-    instructionIterator.add(constNumber);
-
-    // Add the ArrayPut instruction.
-    DexType arrayElementType = newArrayEmpty.getArrayType().toArrayElementType(dexItemFactory);
-    MemberType memberType = MemberType.fromDexType(arrayElementType);
-    ArrayPut arrayPut =
-        ArrayPut.create(memberType, newArrayEmpty.outValue(), constNumber.outValue(), elementValue);
-    arrayPut.setPosition(newArrayEmpty.getPosition());
-    instructionIterator.add(arrayPut);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index ff9fb1a..cb8592e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -115,7 +115,7 @@
       ProgramMethod context,
       RecordInstructionDesugaringEventConsumer eventConsumer) {
     RecordInvokeDynamic recordInvokeDynamic =
-        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
+        parseInvokeDynamicOnRecord(invokeDynamic.getCallSite(), appView);
     if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName
         || recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
       ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions, context, eventConsumer);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
index 7cd44a8..b697254 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
@@ -4,91 +4,35 @@
 
 package com.android.tools.r8.ir.desugar.records;
 
-import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeCustomOnRecord;
-import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
-import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeCustomOnRecord;
-import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
+import static com.android.tools.r8.graph.lens.GraphLens.getIdentityLens;
 
-import com.android.tools.r8.cf.code.CfInvokeDynamic;
-import com.android.tools.r8.dex.code.DexInstruction;
-import com.android.tools.r8.dex.code.DexInvokeCustom;
-import com.android.tools.r8.dex.code.DexInvokeCustomRange;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.DexValue.DexValueType;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
-import com.android.tools.r8.naming.NamingLens;
 import java.util.ArrayList;
 
 /** Used to rewrite invokedynamic/invoke-custom when shrinking and minifying records. */
 public class RecordRewriter {
 
-  private final AppView<?> appView;
-
   public static RecordRewriter create(AppView<?> appView) {
     if (appView.enableWholeProgramOptimizations()) {
-      return new RecordRewriter(appView);
+      return new RecordRewriter();
     }
     return null;
   }
 
-  private RecordRewriter(AppView<?> appView) {
-    this.appView = appView;
-  }
+  private RecordRewriter() {}
 
-  // Called after final tree shaking, prune and minify field names and field values.
-  public CfInvokeDynamic rewriteRecordInvokeDynamic(
-      CfInvokeDynamic invokeDynamic, ProgramMethod context, NamingLens namingLens) {
-    if (!isInvokeDynamicOnRecord(invokeDynamic, appView, context)) {
-      return invokeDynamic;
-    }
-    RecordInvokeDynamic recordInvokeDynamic =
-        parseInvokeDynamicOnRecord(invokeDynamic, appView, context);
-    DexString newFieldNames =
-        recordInvokeDynamic
-            .computeRecordFieldNamesComputationInfo()
-            .internalComputeNameFor(
-                recordInvokeDynamic.getRecordType(), appView, appView.graphLens(), namingLens);
-    DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
-    return writeRecordInvokeDynamic(
-        recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
-  }
-
-  public DexInstruction rewriteRecordInvokeCustom(
-      DexInstruction invokeCustom, ProgramMethod context, NamingLens namingLens) {
-    if (!isInvokeCustomOnRecord(invokeCustom, appView, context)) {
-      return invokeCustom;
-    }
-    RecordInvokeDynamic recordInvokeDynamic =
-        parseInvokeCustomOnRecord(invokeCustom, appView, context);
-    DexString newFieldNames =
-        recordInvokeDynamic
-            .computeRecordFieldNamesComputationInfo()
-            .internalComputeNameFor(
-                recordInvokeDynamic.getRecordType(), appView, appView.graphLens(), namingLens);
-    DexField[] newFields = computePresentFields(appView.graphLens(), recordInvokeDynamic);
-    return writeRecordInvokeCustom(
-        invokeCustom, recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
-  }
-
-  private DexField[] computePresentFields(
+  public DexField[] computePresentFields(
       GraphLens graphLens, RecordInvokeDynamic recordInvokeDynamic) {
     ArrayList<DexField> finalFields = new ArrayList<>();
     for (DexField field : recordInvokeDynamic.getFields()) {
       DexEncodedField dexEncodedField =
           recordInvokeDynamic
               .getRecordClass()
-              .lookupInstanceField(graphLens.getRenamedFieldSignature(field));
+              .lookupInstanceField(graphLens.getRenamedFieldSignature(field, getIdentityLens()));
       if (dexEncodedField != null) {
         finalFields.add(field);
       }
@@ -99,77 +43,4 @@
     }
     return newFields;
   }
-
-  @SuppressWarnings("ReferenceEquality")
-  private CfInvokeDynamic writeRecordInvokeDynamic(RecordInvokeDynamic recordInvokeDynamic) {
-    DexItemFactory factory = appView.dexItemFactory();
-    DexMethodHandle bootstrapMethod =
-        factory.createMethodHandle(
-            MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
-    ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
-    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordType()));
-    bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
-    for (DexField field : recordInvokeDynamic.getFields()) {
-      assert recordInvokeDynamic.getRecordCodeType() == field.getHolderType();
-      bootstrapArgs.add(
-          new DexValueMethodHandle(
-              new DexMethodHandle(MethodHandleType.INSTANCE_GET, field, false, null)));
-    }
-    return new CfInvokeDynamic(
-        factory.createCallSite(
-            recordInvokeDynamic.getMethodName(),
-            recordInvokeDynamic.getMethodProto(),
-            bootstrapMethod,
-            bootstrapArgs));
-  }
-
-  private DexInstruction writeRecordInvokeCustom(
-      DexInstruction invokeCustom, RecordInvokeDynamic recordInvokeDynamic) {
-    DexItemFactory factory = appView.dexItemFactory();
-    DexMethodHandle bootstrapMethod =
-        factory.createMethodHandle(
-            MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
-    ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
-    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordCodeType()));
-    bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
-    for (DexField field : recordInvokeDynamic.getFields()) {
-      // Rewrite using the code type of the field.
-      DexField codeField =
-          factory.createField(recordInvokeDynamic.getRecordCodeType(), field.type, field.name);
-      bootstrapArgs.add(
-          new DexValueMethodHandle(
-              factory.createMethodHandle(MethodHandleType.INSTANCE_GET, codeField, false, null)));
-    }
-    if (invokeCustom instanceof DexInvokeCustom) {
-      DexInvokeCustom current = (DexInvokeCustom) invokeCustom;
-      DexInvokeCustom rewritten =
-          new DexInvokeCustom(
-              current.A,
-              factory.createCallSite(
-                  recordInvokeDynamic.getMethodName(),
-                  recordInvokeDynamic.getMethodProto(),
-                  bootstrapMethod,
-                  bootstrapArgs),
-              current.C,
-              current.D,
-              current.E,
-              current.F,
-              current.G);
-      rewritten.setOffset(current.getOffset());
-      return rewritten;
-    } else {
-      DexInvokeCustomRange current = (DexInvokeCustomRange) invokeCustom;
-      DexInvokeCustomRange rewritten =
-          new DexInvokeCustomRange(
-              current.CCCC,
-              current.AA,
-              factory.createCallSite(
-                  recordInvokeDynamic.getMethodName(),
-                  recordInvokeDynamic.getMethodProto(),
-                  bootstrapMethod,
-                  bootstrapArgs));
-      rewritten.setOffset(current.getOffset());
-      return rewritten;
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
index f465a98..82473ea 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriterHelper.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.desugar.records;
 
+import static com.android.tools.r8.graph.lens.GraphLens.getIdentityLens;
+
 import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.dex.code.DexInstruction;
 import com.android.tools.r8.dex.code.DexInvokeCustom;
@@ -22,6 +24,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.naming.dexitembasedstring.RecordFieldNamesComputationInfo;
 
 public class RecordRewriterHelper {
@@ -37,6 +40,11 @@
     return isInvokeDynamicOnRecord(invokeCustom.getCallSite(), appView, context);
   }
 
+  public static boolean isInvokeCustomOnRecord(
+      InvokeCustom invokeCustom, AppView<?> appView, ProgramMethod context) {
+    return isInvokeDynamicOnRecord(invokeCustom.getCallSite(), appView, context);
+  }
+
   @SuppressWarnings("ReferenceEquality")
   public static boolean isInvokeDynamicOnRecord(
       DexCallSite callSite, AppView<?> appView, ProgramMethod context) {
@@ -117,17 +125,17 @@
   public static RecordInvokeDynamic parseInvokeDynamicOnRecord(
       CfInvokeDynamic invokeDynamic, AppView<?> appView, ProgramMethod context) {
     assert isInvokeDynamicOnRecord(invokeDynamic, appView, context);
-    return parseInvokeDynamicOnRecord(invokeDynamic.getCallSite(), appView, context);
+    return parseInvokeDynamicOnRecord(invokeDynamic.getCallSite(), appView);
   }
 
   public static RecordInvokeDynamic parseInvokeCustomOnRecord(
-      DexInstruction invokeCustom, AppView<?> appView, ProgramMethod context) {
+      InvokeCustom invokeCustom, AppView<?> appView, ProgramMethod context) {
     assert isInvokeCustomOnRecord(invokeCustom, appView, context);
-    return parseInvokeDynamicOnRecord(invokeCustom.getCallSite(), appView, context);
+    return parseInvokeDynamicOnRecord(invokeCustom.getCallSite(), appView);
   }
 
   public static RecordInvokeDynamic parseInvokeDynamicOnRecord(
-      DexCallSite callSite, AppView<?> appView, ProgramMethod context) {
+      DexCallSite callSite, AppView<?> appView) {
     DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType();
     DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
     DexString fieldNames = valueString.getValue();
@@ -137,13 +145,16 @@
       fields[i - 2] = handle.value.member.asDexField();
     }
     DexType recordCodeType = recordValueType.getValue();
+    // TODO(b/323336433): Is this the right choice of code lens?
     DexProgramClass recordClass =
-        appView.definitionFor(appView.graphLens().lookupType(recordCodeType)).asProgramClass();
+        appView
+            .definitionFor(appView.graphLens().lookupType(recordCodeType, getIdentityLens()))
+            .asProgramClass();
     return new RecordInvokeDynamic(
         callSite.methodName, callSite.methodProto, fieldNames, fields, recordClass, recordCodeType);
   }
 
-  static class RecordInvokeDynamic {
+  public static class RecordInvokeDynamic {
 
     private final DexString methodName;
     private final DexProto methodProto;
@@ -167,20 +178,20 @@
       this.recordCodeType = recordCodeType;
     }
 
-    RecordInvokeDynamic withFieldNamesAndFields(DexString fieldNames, DexField[] fields) {
+    public RecordInvokeDynamic withFieldNamesAndFields(DexString fieldNames, DexField[] fields) {
       return new RecordInvokeDynamic(
           methodName, methodProto, fieldNames, fields, recordClass, recordCodeType);
     }
 
-    DexField[] getFields() {
+    public DexField[] getFields() {
       return fields;
     }
 
-    DexType getRecordType() {
+    public DexType getRecordType() {
       return recordClass.getType();
     }
 
-    DexType getRecordCodeType() {
+    public DexType getRecordCodeType() {
       return recordCodeType;
     }
 
@@ -188,19 +199,19 @@
       return recordClass;
     }
 
-    DexString getFieldNames() {
+    public DexString getFieldNames() {
       return fieldNames;
     }
 
-    DexString getMethodName() {
+    public DexString getMethodName() {
       return methodName;
     }
 
-    DexProto getMethodProto() {
+    public DexProto getMethodProto() {
       return methodProto;
     }
 
-    RecordFieldNamesComputationInfo computeRecordFieldNamesComputationInfo() {
+    public RecordFieldNamesComputationInfo computeRecordFieldNamesComputationInfo() {
       return RecordFieldNamesComputationInfo.forFieldNamesAndFields(getFieldNames(), getFields());
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 3a8b545..c425c1b 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -3,151 +3,94 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.graph.lens.GraphLens.getIdentityLens;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 
-import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.dex.code.DexConstString;
-import com.android.tools.r8.dex.code.DexInstruction;
-import com.android.tools.r8.dex.code.DexItemBasedConstString;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
 import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.ProguardClassFilter;
-import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
-import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
-/**
- * Replaces all instances of DexItemBasedConstString by ConstString, and all instances of
- * DexItemBasedValueString by DexValueString.
- */
+/** Replaces all instances of DexItemBasedValueString by DexValueString. */
 public class IdentifierMinifier {
 
-  private final AppView<?> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ProguardClassFilter adaptClassStrings;
   private final NamingLens lens;
 
-  public IdentifierMinifier(AppView<?> appView, NamingLens lens) {
+  public IdentifierMinifier(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
     this.adaptClassStrings = appView.options().getProguardConfiguration().getAdaptClassStrings();
-    this.lens = lens;
+    this.lens = appView.getNamingLens();
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
-    if (!adaptClassStrings.isEmpty()) {
-      adaptClassStrings(executorService);
-    }
-    replaceDexItemBasedConstString(executorService);
+    adaptClassStringsInStaticFields(executorService);
+    replaceDexItemBasedConstStringInStaticFields(executorService);
   }
 
-  private void adaptClassStrings(ExecutorService executorService) throws ExecutionException {
+  private void adaptClassStringsInStaticFields(ExecutorService executorService)
+      throws ExecutionException {
+    if (adaptClassStrings.isEmpty()) {
+      return;
+    }
     ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz -> {
-          if (adaptClassStrings.matches(clazz.type)) {
+          if (adaptClassStrings.matches(clazz.getType())) {
             for (DexEncodedField field : clazz.staticFields()) {
               adaptClassStringsInStaticField(field);
             }
-            clazz.forEachMethod(this::adaptClassStringsInMethod);
           }
         },
         appView.options().getThreadingModule(),
         executorService);
   }
 
-  private void adaptClassStringsInStaticField(DexEncodedField encodedField) {
-    assert encodedField.accessFlags.isStatic();
-    DexValue staticValue = encodedField.getStaticValue();
-    if (staticValue.isDexValueString()) {
-      DexString original = staticValue.asDexValueString().getValue();
-      encodedField.setStaticValue(new DexValueString(getRenamedStringLiteral(original)));
+  private void adaptClassStringsInStaticField(DexEncodedField field) {
+    assert field.isStatic();
+    DexValueString staticValue = field.getStaticValue().asDexValueString();
+    if (staticValue != null) {
+      field.setStaticValue(
+          new DexValueString(getRenamedStringLiteral(appView, staticValue.getValue())));
     }
   }
 
-  private void adaptClassStringsInMethod(DexEncodedMethod encodedMethod) {
-    // Abstract methods do not have code_item.
-    if (encodedMethod.shouldNotHaveCode()) {
-      return;
-    }
-    Code code = encodedMethod.getCode();
-    if (code == null) {
-      return;
-    }
-    if (code.isDexCode()) {
-      DexInstruction[] instructions = code.asDexCode().instructions;
-      DexInstruction[] newInstructions =
-          ArrayUtils.map(
-              instructions,
-              (DexInstruction instruction) -> {
-                if (instruction.isConstString()) {
-                  DexConstString cnst = instruction.asConstString();
-                  DexString renamedStringLiteral = getRenamedStringLiteral(cnst.getString());
-                  if (!renamedStringLiteral.equals(cnst.getString())) {
-                    DexConstString dexConstString =
-                        new DexConstString(instruction.asConstString().AA, renamedStringLiteral);
-                    dexConstString.setOffset(instruction.getOffset());
-                    return dexConstString;
-                  }
-                }
-                return instruction;
-              },
-              DexInstruction.EMPTY_ARRAY);
-      if (instructions != newInstructions) {
-        encodedMethod.setCode(
-            code.asDexCode().withNewInstructions(newInstructions),
-            encodedMethod.getParameterInfo());
-      }
-    } else if (code.isCfCode()) {
-      for (CfInstruction instruction : code.asCfCode().getInstructions()) {
-        if (instruction.isConstString()) {
-          CfConstString cnst = instruction.asConstString();
-          cnst.setString(getRenamedStringLiteral(cnst.getString()));
-        }
-      }
-    } else {
-      assert code.isCfWritableCode() || code.isDexWritableCode();
-    }
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private DexString getRenamedStringLiteral(DexString originalLiteral) {
+  public static DexString getRenamedStringLiteral(
+      AppView<? extends AppInfoWithClassHierarchy> appView, DexString originalLiteral) {
     String descriptor =
         DescriptorUtils.javaTypeToDescriptorIfValidJavaType(originalLiteral.toString());
     if (descriptor == null) {
       return originalLiteral;
     }
     DexType type = appView.dexItemFactory().createType(descriptor);
-    DexType originalType = appView.graphLens().getOriginalType(type);
-    if (originalType != type) {
+    DexType originalType = appView.graphLens().getOriginalType(type, getIdentityLens());
+    if (originalType.isNotIdenticalTo(type)) {
       // The type has changed to something clashing with the string.
       return originalLiteral;
     }
-    DexType rewrittenType = appView.graphLens().lookupType(type);
+    DexType rewrittenType = appView.graphLens().lookupType(type, getIdentityLens());
     DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(rewrittenType);
     if (clazz == null || clazz.isNotProgramClass()) {
       return originalLiteral;
     }
-    DexString rewrittenString = lens.lookupClassDescriptor(rewrittenType);
+    DexString rewrittenString = appView.getNamingLens().lookupClassDescriptor(rewrittenType);
     return rewrittenString == null
         ? originalLiteral
         : appView.dexItemFactory().createString(descriptorToJavaType(rewrittenString.toString()));
   }
 
-  void replaceDexItemBasedConstString(ExecutorService executorService) throws ExecutionException {
+  private void replaceDexItemBasedConstStringInStaticFields(ExecutorService executorService)
+      throws ExecutionException {
     ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz -> {
@@ -155,67 +98,20 @@
           for (DexEncodedField field : clazz.staticFields()) {
             replaceDexItemBasedConstStringInStaticField(field);
           }
-          clazz.forEachProgramMethodMatching(
-              DexEncodedMethod::hasCode, this::replaceDexItemBasedConstStringInMethod);
         },
         appView.options().getThreadingModule(),
         executorService);
   }
 
-  private void replaceDexItemBasedConstStringInStaticField(DexEncodedField encodedField) {
-    assert encodedField.accessFlags.isStatic();
-    DexValue staticValue = encodedField.getStaticValue();
-    if (staticValue instanceof DexItemBasedValueString) {
-      DexItemBasedValueString cnst = (DexItemBasedValueString) staticValue;
+  private void replaceDexItemBasedConstStringInStaticField(DexEncodedField field) {
+    assert field.isStatic();
+    DexItemBasedValueString staticValue = field.getStaticValue().asDexItemBasedValueString();
+    if (staticValue != null) {
       DexString replacement =
-          cnst.getNameComputationInfo()
-              .computeNameFor(cnst.getValue(), appView, appView.graphLens(), lens);
-      encodedField.setStaticValue(new DexValueString(replacement));
-    }
-  }
-
-  private void replaceDexItemBasedConstStringInMethod(ProgramMethod programMethod) {
-    Code code = programMethod.getDefinition().getCode();
-    assert code != null;
-    if (code.isDexCode()) {
-      DexInstruction[] instructions = code.asDexCode().instructions;
-      DexInstruction[] newInstructions =
-          ArrayUtils.map(
-              instructions,
-              (DexInstruction instruction) -> {
-                if (instruction.isDexItemBasedConstString()) {
-                  DexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
-                  DexString replacement =
-                      cnst.getNameComputationInfo()
-                          .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens);
-                  DexConstString constString = new DexConstString(cnst.AA, replacement);
-                  constString.setOffset(instruction.getOffset());
-                  return constString;
-                }
-                return instruction;
-              },
-              DexInstruction.EMPTY_ARRAY);
-      if (newInstructions != instructions) {
-        programMethod.setCode(code.asDexCode().withNewInstructions(newInstructions), appView);
-      }
-    } else if (code.isCfCode()) {
-      List<CfInstruction> instructions = code.asCfCode().getInstructions();
-      List<CfInstruction> newInstructions =
-          ListUtils.mapOrElse(
-              instructions,
-              (int i, CfInstruction instruction) -> {
-                if (instruction.isDexItemBasedConstString()) {
-                  CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
-                  return new CfConstString(
-                      cnst.getNameComputationInfo()
-                          .computeNameFor(cnst.getItem(), appView, appView.graphLens(), lens));
-                }
-                return instruction;
-              },
-              instructions);
-      code.asCfCode().setInstructions(newInstructions);
-    } else {
-      assert code.isDefaultInstanceInitializerCode() || code.isThrowNullCode();
+          staticValue
+              .getNameComputationInfo()
+              .computeNameFor(staticValue.getValue(), appView, appView.graphLens(), lens);
+      field.setStaticValue(new DexValueString(replacement));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 6c08cef..937d6a3 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -85,14 +85,6 @@
     NamingLens lens = new MinifiedRenaming(appView, classRenaming, methodRenaming, fieldRenaming);
     assert lens.verifyNoCollisions(appView.appInfo().classes(), appView.dexItemFactory());
 
-    timing.begin("MinifyIdentifiers");
-    new IdentifierMinifier(appView, lens).run(executorService);
-    timing.end();
-
-    timing.begin("RecordInvokeDynamicRewrite");
-    new RecordInvokeDynamicInvokeCustomRewriter(appView, lens).run(executorService);
-    timing.end();
-
     appView.testing().namingLensConsumer.accept(appView.dexItemFactory(), lens);
     appView.notifyOptimizationFinishedForTesting();
     appView.setNamingLens(lens);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index ddb57d6..d5f4683 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -156,15 +156,6 @@
     NamingLens lens =
         new ProguardMapMinifiedRenaming(
             appView, classRenaming, methodRenaming, fieldRenaming, notMappedReferences);
-
-    timing.begin("MinifyIdentifiers");
-    new IdentifierMinifier(appView, lens).run(executorService);
-    timing.end();
-
-    timing.begin("RecordInvokeDynamicRewrite");
-    new RecordInvokeDynamicInvokeCustomRewriter(appView, lens).run(executorService);
-    timing.begin("MinifyIdentifiers");
-
     appView.notifyOptimizationFinishedForTesting();
     return lens;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicInvokeCustomRewriter.java b/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicInvokeCustomRewriter.java
index d3d1212..6ff9638 100644
--- a/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicInvokeCustomRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/RecordInvokeDynamicInvokeCustomRewriter.java
@@ -3,85 +3,124 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.dex.code.DexInstruction;
-import com.android.tools.r8.dex.code.DexInvokeCustom;
-import com.android.tools.r8.dex.code.DexInvokeCustomRange;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeCustomOnRecord;
+import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeCustomOnRecord;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeCustom;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
 import com.android.tools.r8.ir.desugar.records.RecordRewriter;
-import com.android.tools.r8.utils.ArrayUtils;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.ThreadUtils;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
+import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
+import java.util.ArrayList;
 
 /** Rewrites the record invokedynamic/invoke-custom in hashCode, equals and toString. */
-public class RecordInvokeDynamicInvokeCustomRewriter {
+public class RecordInvokeDynamicInvokeCustomRewriter
+    extends CodeRewriterPass<AppInfoWithClassHierarchy> {
 
-  private final AppView<?> appView;
   private final RecordRewriter recordRewriter;
-  private final NamingLens lens;
 
-  public RecordInvokeDynamicInvokeCustomRewriter(AppView<?> appView, NamingLens lens) {
-    this.appView = appView;
+  public RecordInvokeDynamicInvokeCustomRewriter(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    super(appView);
     this.recordRewriter = RecordRewriter.create(appView);
-    this.lens = lens;
   }
 
-  public void run(ExecutorService executorService) throws ExecutionException {
-    ThreadUtils.processItems(
-        appView.appInfo().classes(),
-        clazz -> {
-          clazz.forEachProgramMethodMatching(
-              DexEncodedMethod::hasCode, this::rewriteRecordInvokeDynamicInMethod);
-        },
-        appView.options().getThreadingModule(),
-        executorService);
+  @Override
+  protected String getRewriterId() {
+    return "RecordInvokeDynamicInvokeCustomRewriter";
   }
 
-  private void rewriteRecordInvokeDynamicInMethod(ProgramMethod programMethod) {
-    if (recordRewriter == null) {
-      return;
-    }
-    if (!programMethod.getHolder().isRecord()) {
-      return;
-    }
-    Code code = programMethod.getDefinition().getCode();
-    assert code != null;
-    if (code.isDexCode()) {
-      DexInstruction[] instructions = code.asDexCode().instructions;
-      DexInstruction[] newInstructions =
-          ArrayUtils.map(
-              instructions,
-              (DexInstruction instruction) -> {
-                if (instruction instanceof DexInvokeCustom
-                    || instruction instanceof DexInvokeCustomRange) {
-                  return recordRewriter.rewriteRecordInvokeCustom(instruction, programMethod, lens);
-                }
-                return instruction;
-              },
-              DexInstruction.EMPTY_ARRAY);
-      if (newInstructions != instructions) {
-        programMethod.setCode(code.asDexCode().withNewInstructions(newInstructions), appView);
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    return recordRewriter != null
+        && code.context().getHolder().isRecord()
+        && code.metadata().mayHaveInvokeCustom();
+  }
+
+  @Override
+  protected CodeRewriterResult rewriteCode(IRCode code) {
+    boolean hasChanged = false;
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      InvokeCustom instruction = iterator.next().asInvokeCustom();
+      if (instruction != null) {
+        InvokeCustom replacement = rewriteRecordInvokeCustom(code, instruction);
+        if (replacement != instruction) {
+          iterator.replaceCurrentInstruction(replacement);
+          hasChanged = true;
+        }
       }
-    } else if (code.isCfCode()) {
-      List<CfInstruction> instructions = code.asCfCode().getInstructions();
-      List<CfInstruction> newInstructions =
-          ListUtils.mapOrElse(
-              instructions,
-              (int index, CfInstruction instruction) -> {
-                if (instruction.isInvokeDynamic()) {
-                  return recordRewriter.rewriteRecordInvokeDynamic(
-                      instruction.asInvokeDynamic(), programMethod, lens);
-                }
-                return instruction;
-              },
-              instructions);
-      code.asCfCode().setInstructions(newInstructions);
     }
+    return CodeRewriterResult.hasChanged(hasChanged);
+  }
+
+  // Called after final tree shaking, prune and minify field names and field values.
+  public InvokeCustom rewriteRecordInvokeCustom(IRCode code, InvokeCustom invokeCustom) {
+    ProgramMethod context = code.context();
+    if (!isInvokeCustomOnRecord(invokeCustom, appView, context)) {
+      return invokeCustom;
+    }
+    RecordInvokeDynamic recordInvokeDynamic =
+        parseInvokeCustomOnRecord(invokeCustom, appView, context);
+    DexString newFieldNames =
+        recordInvokeDynamic
+            .computeRecordFieldNamesComputationInfo()
+            .internalComputeNameFor(
+                recordInvokeDynamic.getRecordType(),
+                appView,
+                appView.graphLens(),
+                appView.getNamingLens());
+    DexField[] newFields =
+        recordRewriter.computePresentFields(appView.graphLens(), recordInvokeDynamic);
+    return writeRecordInvokeCustom(
+        invokeCustom, recordInvokeDynamic.withFieldNamesAndFields(newFieldNames, newFields));
+  }
+
+  private InvokeCustom writeRecordInvokeCustom(
+      InvokeCustom invokeCustom, RecordInvokeDynamic recordInvokeDynamic) {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexMethodHandle bootstrapMethod =
+        factory.createMethodHandle(
+            MethodHandleType.INVOKE_STATIC, factory.objectMethodsMembers.bootstrap, false, null);
+    ArrayList<DexValue> bootstrapArgs = new ArrayList<>();
+    bootstrapArgs.add(new DexValueType(recordInvokeDynamic.getRecordCodeType()));
+    bootstrapArgs.add(new DexValueString(recordInvokeDynamic.getFieldNames()));
+    for (DexField field : recordInvokeDynamic.getFields()) {
+      // Rewrite using the code type of the field.
+      DexField codeField =
+          factory.createField(recordInvokeDynamic.getRecordCodeType(), field.type, field.name);
+      bootstrapArgs.add(
+          new DexValueMethodHandle(
+              factory.createMethodHandle(MethodHandleType.INSTANCE_GET, codeField, false, null)));
+    }
+    DexCallSite callSite =
+        factory.createCallSite(
+            recordInvokeDynamic.getMethodName(),
+            recordInvokeDynamic.getMethodProto(),
+            bootstrapMethod,
+            bootstrapArgs);
+    DexType returnType = callSite.getMethodProto().getReturnType();
+    assert returnType.isBooleanType()
+        || returnType.isIntType()
+        || returnType.isIdenticalTo(factory.stringType);
+    assert invokeCustom.outValue().getType().equals(returnType.toTypeElement(appView));
+    return new InvokeCustom(callSite, invokeCustom.outValue(), invokeCustom.arguments());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
index 02e29aa..a6fdb57 100644
--- a/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
+++ b/src/main/java/com/android/tools/r8/naming/dexitembasedstring/NameComputationInfo.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.naming.dexitembasedstring;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
@@ -22,6 +24,11 @@
   }
 
   public final DexString computeNameFor(
+      DexReference reference, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return computeNameFor(reference, appView, appView.graphLens(), appView.getNamingLens());
+  }
+
+  public final DexString computeNameFor(
       DexReference reference,
       DexDefinitionSupplier definitions,
       GraphLens graphLens,
diff --git a/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java b/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java
index 60ce942..7f81005 100644
--- a/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/androidresources/ResourceIDCannonicalizationTest.java
@@ -48,8 +48,8 @@
         .inspect(
             codeInspector -> {
               // We should canonicalize the resource numbers separately from the normal const
-              // numbers.
-              // This implies that the output have two distinct const numbers with the same value.
+              // numbers. When mapping from LIR to DEX in the backend we then reapply
+              // canonicalization after changing each resource number to a const number.
               long constNumbers =
                   codeInspector
                       .clazz(FooBar.class)
@@ -57,7 +57,7 @@
                       .streamInstructions()
                       .filter(i -> i.isConstNumber(EXPECTED_RESOURCE_NUMBER))
                       .count();
-              assertEquals(2, constNumbers);
+              assertEquals(1, constNumbers);
             })
         .inspectShrunkenResources(
             resourceTableInspector -> {
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index b31bff4..05bea2e 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -182,10 +182,11 @@
     assertThat(main, isPresent());
     verifyPresenceOfConstString(main);
 
+    // Output differs due to canonicalization which happens only on DEX.
     ClassSubject a = inspector.clazz("getmembers.A");
     Set<InstructionSubject> constStringInstructions =
         getRenamedMemberIdentifierConstStrings(a, main);
-    assertEquals(2, constStringInstructions.size());
+    assertEquals(parameters.isCfRuntime() ? 2 : 1, constStringInstructions.size());
 
     ClassSubject b = inspector.clazz("getmembers.B");
     MethodSubject inliner = b.uniqueMethodWithOriginalName("inliner");
@@ -243,10 +244,11 @@
     assertThat(main, isPresent());
     verifyPresenceOfConstString(main);
 
+    // Output differs due to canonicalization which happens only on DEX.
     ClassSubject b = inspector.clazz("identifiernamestring.B");
     Set<InstructionSubject> constStringInstructions =
         getRenamedMemberIdentifierConstStrings(b, main);
-    assertEquals(2, constStringInstructions.size());
+    assertEquals(parameters.isCfRuntime() ? 2 : 1, constStringInstructions.size());
   }
 
   private static void verifyPresenceOfConstString(MethodSubject method) {
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 4d3a3fa..28b621a 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -121,13 +121,10 @@
             DexSgetObject.class,
             DexConstString.class,
             DexInvokeVirtual.class,
-            DexConstString.class,
             DexIputObject.class,
             DexReturnVoid.class));
     DexConstString constString = (DexConstString) code.instructions[2];
     assertEquals(BOO, constString.getString().toString());
-    constString = (DexConstString) code.instructions[4];
-    assertEquals(BOO, constString.getString().toString());
   }
 
   @Test
@@ -233,13 +230,10 @@
             DexSgetObject.class,
             DexConstString.class,
             DexInvokeVirtual.class,
-            DexConstString.class,
             DexSputObject.class,
             DexReturnVoid.class));
     DexConstString constString = (DexConstString) code.instructions[1];
     assertEquals(BOO, constString.getString().toString());
-    constString = (DexConstString) code.instructions[3];
-    assertEquals(BOO, constString.getString().toString());
   }
 
   @Test
@@ -462,13 +456,10 @@
             DexSgetObject.class,
             DexConstString.class,
             DexInvokeVirtual.class,
-            DexConstString.class,
             DexInvokeStatic.class,
             DexReturnVoid.class));
     DexConstString constString = (DexConstString) code.instructions[2];
     assertEquals(BOO, constString.getString().toString());
-    constString = (DexConstString) code.instructions[4];
-    assertEquals(BOO, constString.getString().toString());
   }
 
   @Test