Merge commit '24659d5eae579cd1db20ffe88f6a634a333e9bfe' into dev-release

Change-Id: I8edd1d5d428182ef95072c9061ff21a7c69414a4
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 3b8ba07..1deb035 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -138,6 +138,19 @@
   private final com.android.tools.r8.position.Position diagnosticPosition;
   private final BytecodeMetadata<CfInstruction> metadata;
 
+  public CfCode(CfCode code) {
+    this(
+        code.originalHolder,
+        code.maxStack,
+        code.maxLocals,
+        code.instructions,
+        code.tryCatchRanges,
+        code.localVariables,
+        code.diagnosticPosition,
+        code.metadata);
+    this.stackMapStatus = code.stackMapStatus;
+  }
+
   public CfCode(
       DexType originalHolder, int maxStack, int maxLocals, List<CfInstruction> instructions) {
     this(
@@ -896,7 +909,7 @@
   }
 
   @Override
-  public Code getCodeAsInlining(
+  public CfCode getCodeAsInlining(
       DexMethod caller,
       boolean isCallerD8R8Synthesized,
       DexMethod callee,
diff --git a/src/main/java/com/android/tools/r8/graph/CfCodeWithLens.java b/src/main/java/com/android/tools/r8/graph/CfCodeWithLens.java
new file mode 100644
index 0000000..9ccdfa6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/CfCodeWithLens.java
@@ -0,0 +1,52 @@
+// 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.graph;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.lens.GraphLens;
+import java.util.List;
+
+public class CfCodeWithLens extends CfCode {
+
+  private GraphLens codeLens;
+
+  public CfCodeWithLens(GraphLens codeLens, CfCode code) {
+    super(code);
+    this.codeLens = codeLens;
+  }
+
+  public CfCodeWithLens(
+      GraphLens codeLens,
+      DexType originalHolder,
+      int maxStack,
+      int maxLocals,
+      List<CfInstruction> instructions) {
+    super(originalHolder, maxStack, maxLocals, instructions);
+    this.codeLens = codeLens;
+  }
+
+  @Override
+  public GraphLens getCodeLens(AppView<?> appView) {
+    assert codeLens != null;
+    assert !codeLens.isIdentityLens();
+    return codeLens;
+  }
+
+  public void setCodeLens(GraphLens codeLens) {
+    this.codeLens = codeLens;
+  }
+
+  @Override
+  public CfCodeWithLens getCodeAsInlining(
+      DexMethod caller,
+      boolean isCallerD8R8Synthesized,
+      DexMethod callee,
+      boolean isCalleeD8R8Synthesized,
+      DexItemFactory factory) {
+    CfCode code =
+        super.getCodeAsInlining(
+            caller, isCallerD8R8Synthesized, callee, isCalleeD8R8Synthesized, factory);
+    return new CfCodeWithLens(codeLens, code);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
index e09f988..95a4920 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteMergedInstanceInitializerCode.java
@@ -20,11 +20,11 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCodeWithLens;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.MethodLookupResult;
 import com.android.tools.r8.ir.analysis.value.SingleConstValue;
 import com.android.tools.r8.ir.analysis.value.SingleDexItemBasedStringValue;
@@ -166,17 +166,12 @@
     // Return.
     instructionBuilder.add(new CfReturnVoid());
 
-    return new CfCode(
+    return new CfCodeWithLens(
+        lens,
         originalMethodReference.getHolderType(),
         maxStack.get(),
         maxLocals,
-        instructionBuilder.build()) {
-
-      @Override
-      public GraphLens getCodeLens(AppView<?> appView) {
-        return lens;
-      }
-    };
+        instructionBuilder.build());
   }
 
   private static void addCfInstructionsForInstanceFieldAssignments(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java
index 6698a71..378b433 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IncompleteVirtuallyMergedMethodCode.java
@@ -18,11 +18,11 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCodeWithLens;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.horizontalclassmerging.VirtualMethodMerger.SuperMethodReference;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -153,13 +153,8 @@
     } else {
       instructions.add(new CfReturn(ValueType.fromDexType(method.getReturnType())));
     }
-    return new CfCode(originalMethod.getHolderType(), maxStack, maxLocals, instructions) {
-
-      @Override
-      public GraphLens getCodeLens(AppView<?> appView) {
-        return lens;
-      }
-    };
+    return new CfCodeWithLens(
+        lens, originalMethod.getHolderType(), maxStack, maxLocals, instructions);
   }
 
   private static CfFrame createCfFrameForSwitchCase(ProgramMethod representative, int localsSize) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
index a8e46f9..0c0f059 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ConstructorEntryPointSynthesizedCode.java
@@ -4,16 +4,33 @@
 
 package com.android.tools.r8.horizontalclassmerging.code;
 
-import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathMethod;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.horizontalclassmerging.ConstructorEntryPoint;
-import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.SourceCode;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
-import java.util.function.Consumer;
 
-public class ConstructorEntryPointSynthesizedCode extends AbstractSynthesizedCode {
+public class ConstructorEntryPointSynthesizedCode extends Code {
+
   private final DexMethod newConstructor;
   private final DexField classIdField;
   private final Int2ReferenceSortedMap<DexMethod> typeConstructors;
@@ -27,18 +44,7 @@
     this.classIdField = classIdField;
   }
 
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return (ignored, position) ->
-        new ConstructorEntryPoint(typeConstructors, newConstructor, classIdField, position);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method) {
-    return this::registerReachableDefinitions;
-  }
-
-  private void registerReachableDefinitions(UseRegistry registry) {
+  private void registerReachableDefinitions(UseRegistry<?> registry) {
     assert registry.getTraversalContinuation().shouldContinue();
     for (DexMethod typeConstructor : typeConstructors.values()) {
       registry.registerInvokeDirect(typeConstructor);
@@ -52,4 +58,91 @@
   public boolean isHorizontalClassMergerCode() {
     return true;
   }
+
+  @Override
+  public final boolean isEmptyVoidMethod() {
+    return false;
+  }
+
+  @Override
+  public final IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
+    SyntheticPosition position =
+        SyntheticPosition.builder()
+            .setLine(0)
+            .setMethod(method.getReference())
+            .setIsD8R8Synthesized(true)
+            .build();
+    SourceCode sourceCode =
+        new ConstructorEntryPoint(typeConstructors, newConstructor, classIdField, position);
+    return IRBuilder.create(method, appView, sourceCode, origin).build(method, conversionOptions);
+  }
+
+  @Override
+  public final IRCode buildInliningIR(
+      ProgramMethod context,
+      ProgramMethod method,
+      AppView<?> appView,
+      GraphLens codeLens,
+      NumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin,
+      RewrittenPrototypeDescription protoChanges) {
+    SourceCode sourceCode =
+        new ConstructorEntryPoint(typeConstructors, newConstructor, classIdField, callerPosition);
+    return IRBuilder.createForInlining(
+            method, appView, codeLens, sourceCode, origin, valueNumberGenerator, protoChanges)
+        .build(context, MethodConversionOptions.nonConverting());
+  }
+
+  @Override
+  public final Code getCodeAsInlining(
+      DexMethod caller,
+      boolean isCallerD8R8Synthesized,
+      DexMethod callee,
+      boolean isCalleeD8R8Synthesized,
+      DexItemFactory factory) {
+    // This code object is synthesized so "inlining" just "strips" the callee position.
+    assert isCalleeD8R8Synthesized;
+    return this;
+  }
+
+  @Override
+  public final String toString() {
+    return toString(null, RetracerForCodePrinting.empty());
+  }
+
+  @Override
+  public final void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    registerReachableDefinitions(registry);
+  }
+
+  @Override
+  public final void registerCodeReferencesForDesugaring(
+      ClasspathMethod method, UseRegistry registry) {
+    registerReachableDefinitions(registry);
+  }
+
+  @Override
+  protected final int computeHashCode() {
+    throw new Unreachable();
+  }
+
+  @Override
+  protected final boolean computeEquals(Object other) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public final String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
+    return this.getClass().getSimpleName();
+  }
+
+  @Override
+  public final int estimatedDexCodeSizeUpperBoundInBytes() {
+    return Integer.MAX_VALUE;
+  }
 }
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 1610454..bb62066 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
@@ -39,7 +39,9 @@
     passes.add(new BranchSimplifier(appView));
     passes.add(new SplitBranch(appView));
     passes.add(new RedundantConstNumberRemover(appView));
-    passes.add(new RedundantFieldLoadAndStoreElimination(appView));
+    if (!appView.options().debug) {
+      passes.add(new RedundantFieldLoadAndStoreElimination(appView));
+    }
     passes.add(new BinopRewriter(appView));
     return new CodeRewriterPassCollection(passes);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 898bd66..064c4b2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -233,7 +233,7 @@
     private final ProgramMethod method;
     private final IRCode code;
     private final int maxCapacityPerBlock;
-    private final boolean release;
+    private final boolean release = true;
 
     // Values that may require type propagation.
     private final AffectedValues affectedValues = new AffectedValues();
@@ -254,7 +254,7 @@
       this.code = code;
       this.maxCapacityPerBlock =
           Math.max(MIN_CAPACITY_PER_BLOCK, MAX_CAPACITY / code.blocks.size());
-      this.release = !appView.options().debug;
+      assert !appView.options().debug;
     }
 
     class ExistingValue implements FieldValue {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 35e4f74..09b22db 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCodeWithLens;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedField.Builder;
@@ -62,7 +63,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
-import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider.CfCodeWithLens;
 import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider.EnumUnboxingMethodDispatchCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.ThrowCfCodeProvider;
 import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
deleted file mode 100644
index 7a9429c..0000000
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.synthetic;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClasspathMethod;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.NumberGenerator;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Position.SyntheticPosition;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
-import com.android.tools.r8.ir.conversion.SourceCode;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.RetracerForCodePrinting;
-import java.util.function.Consumer;
-
-public abstract class AbstractSynthesizedCode extends Code {
-
-  public interface SourceCodeProvider {
-    SourceCode get(ProgramMethod context, Position callerPosition);
-  }
-
-  public abstract SourceCodeProvider getSourceCodeProvider();
-
-  public abstract Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method);
-
-  @Override
-  public boolean isEmptyVoidMethod() {
-    return false;
-  }
-
-  @Override
-  public final IRCode buildIR(
-      ProgramMethod method,
-      AppView<?> appView,
-      Origin origin,
-      MutableMethodConversionOptions conversionOptions) {
-    SyntheticPosition position =
-        SyntheticPosition.builder()
-            .setLine(0)
-            .setMethod(method.getReference())
-            .setIsD8R8Synthesized(true)
-            .build();
-    return IRBuilder.create(method, appView, getSourceCodeProvider().get(method, position), origin)
-        .build(method, conversionOptions);
-  }
-
-  @Override
-  public final IRCode buildInliningIR(
-      ProgramMethod context,
-      ProgramMethod method,
-      AppView<?> appView,
-      GraphLens codeLens,
-      NumberGenerator valueNumberGenerator,
-      Position callerPosition,
-      Origin origin,
-      RewrittenPrototypeDescription protoChanges) {
-    return IRBuilder.createForInlining(
-            method,
-            appView,
-            codeLens,
-            getSourceCodeProvider().get(context, callerPosition),
-            origin,
-            valueNumberGenerator,
-            protoChanges)
-        .build(context, MethodConversionOptions.nonConverting());
-  }
-
-  @Override
-  public final Code getCodeAsInlining(
-      DexMethod caller,
-      boolean isCallerD8R8Synthesized,
-      DexMethod callee,
-      boolean isCalleeD8R8Synthesized,
-      DexItemFactory factory) {
-    // This code object is synthesized so "inlining" just "strips" the callee position.
-    assert isCalleeD8R8Synthesized;
-    return this;
-  }
-
-  @Override
-  public final String toString() {
-    return toString(null, RetracerForCodePrinting.empty());
-  }
-
-  @Override
-  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
-    internalRegisterCodeReferences(method, registry);
-  }
-
-  @Override
-  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
-    internalRegisterCodeReferences(method, registry);
-  }
-
-  private void internalRegisterCodeReferences(DexClassAndMethod method, UseRegistry registry) {
-    getRegistryCallback(method).accept(registry);
-  }
-
-  @Override
-  protected final int computeHashCode() {
-    throw new Unreachable();
-  }
-
-  @Override
-  protected final boolean computeEquals(Object other) {
-    throw new Unreachable();
-  }
-
-  @Override
-  public final String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
-    return this.getClass().getSimpleName();
-  }
-
-  @Override
-  public int estimatedDexCodeSizeUpperBoundInBytes() {
-    return Integer.MAX_VALUE;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index ca847d1..2e5b45b 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -25,11 +25,11 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCodeWithLens;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.IfType;
 import com.android.tools.r8.ir.code.ValueType;
@@ -153,7 +153,7 @@
       assert paramRegisterSize < 256;
       int maxStack = 2 * paramRegisterSize + 16;
       int maxLocals = paramRegisterSize + 16;
-      return new CfCodeWithLens(getHolder(), maxStack, maxLocals, instructions);
+      return new CfCodeWithLens(null, getHolder(), maxStack, maxLocals, instructions);
     }
 
     private void addReturnInvoke(List<CfInstruction> instructions, DexMethod method) {
@@ -302,24 +302,4 @@
       return standardCfCodeFromInstructions(instructions);
     }
   }
-
-  public static class CfCodeWithLens extends CfCode {
-
-    private GraphLens codeLens;
-
-    public void setCodeLens(GraphLens codeLens) {
-      this.codeLens = codeLens;
-    }
-
-    public CfCodeWithLens(
-        DexType originalHolder, int maxStack, int maxLocals, List<CfInstruction> instructions) {
-      super(originalHolder, maxStack, maxLocals, instructions);
-    }
-
-    @Override
-    public GraphLens getCodeLens(AppView<?> appView) {
-      assert codeLens != null;
-      return codeLens;
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index 71172bb..0718a90 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -14,13 +14,14 @@
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
-import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCodeWithLens;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
@@ -43,6 +44,8 @@
 
   private final DexItemFactory factory;
 
+  private GraphLens codeLens = null;
+
   private DexMethod sourceMethod = null;
   private DexMethod targetMethod = null;
 
@@ -81,6 +84,11 @@
     return this;
   }
 
+  public ForwardMethodBuilder setCodeLens(GraphLens codeLens) {
+    this.codeLens = codeLens;
+    return this;
+  }
+
   public ForwardMethodBuilder setNonStaticSource(DexMethod method) {
     sourceMethod = method;
     staticSource = false;
@@ -205,15 +213,11 @@
       }
       instructions.add(new CfReturn(getSourceReturnType()));
     }
-    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
-    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
-    return new CfCode(
-        sourceMethod.holder,
-        maxStack,
-        maxLocals,
-        instructions.build(),
-        tryCatchRanges,
-        localVariables);
+    if (codeLens != null) {
+      return new CfCodeWithLens(
+          codeLens, sourceMethod.holder, maxStack, maxLocals, instructions.build());
+    }
+    return new CfCode(sourceMethod.holder, maxStack, maxLocals, instructions.build());
   }
 
   @SuppressWarnings({"BadImport", "ReferenceEquality"})
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
deleted file mode 100644
index c1fbd37..0000000
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.synthetic;
-
-import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.InvokeType;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.google.common.collect.Lists;
-import java.util.ArrayList;
-import java.util.List;
-
-// Source code representing simple forwarding method.
-public final class ForwardMethodSourceCode extends SyntheticSourceCode {
-
-  public static Builder builder(DexMethod method) {
-    return new Builder(method);
-  }
-
-  public static class Builder {
-
-    private DexType receiver;
-    private DexMethod method;
-    private DexType targetReceiver;
-    private DexMethod target;
-    private InvokeType invokeType;
-    private boolean castResult;
-    private boolean isInterface;
-    private boolean extraNullParameter;
-
-    public Builder(DexMethod method) {
-      this.method = method;
-    }
-
-    public Builder setReceiver(DexType receiver) {
-      this.receiver = receiver;
-      return this;
-    }
-
-    public Builder setMethod(DexMethod method) {
-      this.method = method;
-      return this;
-    }
-
-    public Builder setTargetReceiver(DexType targetReceiver) {
-      this.targetReceiver = targetReceiver;
-      return this;
-    }
-
-    public Builder setTarget(DexMethod target) {
-      this.target = target;
-      return this;
-    }
-
-    public Builder setInvokeType(InvokeType invokeType) {
-      this.invokeType = invokeType;
-      return this;
-    }
-
-    public Builder setCastResult() {
-      this.castResult = true;
-      return this;
-    }
-
-    public Builder setIsInterface(boolean isInterface) {
-      this.isInterface = isInterface;
-      return this;
-    }
-
-    public Builder setExtraNullParameter() {
-      this.extraNullParameter = true;
-      return this;
-    }
-
-    public ForwardMethodSourceCode build(ProgramMethod context, Position position) {
-      return new ForwardMethodSourceCode(
-          receiver,
-          method,
-          targetReceiver,
-          target,
-          invokeType,
-          position,
-          isInterface,
-          castResult,
-          extraNullParameter);
-    }
-  }
-
-  private final DexType targetReceiver;
-  private final DexMethod target;
-  private final InvokeType invokeType;
-  private final boolean castResult;
-  private final boolean isInterface;
-  private final boolean extraNullParameter;
-
-  ForwardMethodSourceCode(
-      DexType receiver,
-      DexMethod method,
-      DexType targetReceiver,
-      DexMethod target,
-      InvokeType invokeType,
-      Position position,
-      boolean isInterface,
-      boolean castResult,
-      boolean extraNullParameter) {
-    super(receiver, method, position);
-    assert (targetReceiver == null) == (invokeType == InvokeType.STATIC);
-
-    this.target = target;
-    this.targetReceiver = targetReceiver;
-    this.invokeType = invokeType;
-    this.isInterface = isInterface;
-    this.castResult = castResult;
-    this.extraNullParameter = extraNullParameter;
-    assert checkSignatures();
-
-    switch (invokeType) {
-      case DIRECT:
-      case STATIC:
-      case SUPER:
-      case INTERFACE:
-      case VIRTUAL:
-        break;
-      default:
-        throw new Unimplemented("Invoke type " + invokeType + " is not yet supported.");
-    }
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private boolean checkSignatures() {
-    List<DexType> sourceParams = new ArrayList<>();
-    if (receiver != null) {
-      sourceParams.add(receiver);
-    }
-    sourceParams.addAll(Lists.newArrayList(proto.parameters.values));
-    if (extraNullParameter) {
-      sourceParams.remove(sourceParams.size() - 1);
-    }
-
-    List<DexType> targetParams = new ArrayList<>();
-    if (targetReceiver != null) {
-      targetParams.add(targetReceiver);
-    }
-    targetParams.addAll(Lists.newArrayList(target.proto.parameters.values));
-
-    assert sourceParams.size() == targetParams.size();
-    for (int i = 0; i < sourceParams.size(); i++) {
-      DexType source = sourceParams.get(i);
-      DexType target = targetParams.get(i);
-
-      // We assume source is compatible with target if they both are classes.
-      // This check takes care of receiver widening conversion but does not
-      // many others, like conversion from an array to Object.
-      assert (source.isClassType() && target.isClassType()) || source == target;
-    }
-
-    assert this.proto.returnType == target.proto.returnType || castResult;
-    return true;
-  }
-
-  @Override
-  @SuppressWarnings("ReferenceEquality")
-  protected void prepareInstructions() {
-    // Prepare call arguments.
-    List<ValueType> argValueTypes = new ArrayList<>();
-    List<Integer> argRegisters = new ArrayList<>();
-
-    if (receiver != null) {
-      argValueTypes.add(ValueType.OBJECT);
-      argRegisters.add(getReceiverRegister());
-    }
-
-    DexType[] accessorParams = proto.parameters.values;
-    for (int i = 0; i < accessorParams.length - BooleanUtils.intValue(extraNullParameter); i++) {
-      argValueTypes.add(ValueType.fromDexType(accessorParams[i]));
-      argRegisters.add(getParamRegister(i));
-    }
-
-    // Method call to the target method.
-    add(
-        builder ->
-            builder.addInvoke(
-                this.invokeType,
-                this.target,
-                this.target.proto,
-                argValueTypes,
-                argRegisters,
-                this.isInterface));
-
-    // Does the method return value?
-    if (proto.returnType.isVoidType()) {
-      add(IRBuilder::addReturn);
-    } else {
-      ValueType valueType = ValueType.fromDexType(proto.returnType);
-      int tempValue = nextRegister(valueType);
-      add(builder -> builder.addMoveResult(tempValue));
-      if (this.proto.returnType != target.proto.returnType) {
-        add(builder -> builder.addCheckCast(tempValue, this.proto.returnType));
-      }
-      add(builder -> builder.addReturn(tempValue));
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
index 86b9848..bf712ad 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.dex.Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.code.InvokeType.DIRECT;
 import static com.android.tools.r8.ir.code.InvokeType.STATIC;
 import static com.android.tools.r8.ir.code.InvokeType.VIRTUAL;
 import static java.util.function.Predicate.not;
@@ -13,6 +12,7 @@
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
+import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -76,12 +76,12 @@
   private final DexProgramClass source;
   private final DexProgramClass target;
 
-  private final List<SynthesizedBridgeCode> synthesizedBridges;
+  private final List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges;
 
   ClassMerger(
       AppView<AppInfoWithLiveness> appView,
       VerticalClassMergerGraphLens.Builder outerLensBuilder,
-      List<SynthesizedBridgeCode> synthesizedBridges,
+      List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges,
       VerticallyMergedClasses.Builder verticallyMergedClassesBuilder,
       VerticalMergeGroup group) {
     this.appView = appView;
@@ -252,8 +252,12 @@
         assert availableMethodSignatures.test(resultingMethodReference);
         resultingMethod =
             virtualMethod.toTypeSubstitutedMethodAsInlining(
-                resultingMethodReference, dexItemFactory);
-        makeStatic(resultingMethod);
+                resultingMethodReference,
+                dexItemFactory,
+                builder ->
+                    builder
+                        .modifyAccessFlags(AccessFlags::setStatic)
+                        .unsetIsLibraryMethodOverride());
       } else {
         // This virtual method could be called directly from a sub class via an invoke-super
         // instruction. Therefore, we translate this virtual method into an instance method with a
@@ -550,15 +554,11 @@
     accessFlags.setSynthetic();
     accessFlags.unsetAbstract();
 
-    assert invocationTarget.isStatic()
-        || invocationTarget.isNonPrivateVirtualMethod()
-        || invocationTarget.isNonStaticPrivateMethod();
-    SynthesizedBridgeCode code =
-        new SynthesizedBridgeCode(
+    assert invocationTarget.isStatic() || invocationTarget.isNonPrivateVirtualMethod();
+    IncompleteVerticalClassMergerBridgeCode code =
+        new IncompleteVerticalClassMergerBridgeCode(
             method.getReference(),
-            invocationTarget.isStatic()
-                ? STATIC
-                : (invocationTarget.isNonPrivateVirtualMethod() ? VIRTUAL : DIRECT),
+            invocationTarget.isStatic() ? STATIC : VIRTUAL,
             target.isInterface());
 
     // Add the bridge to the list of synthesized bridges such that the method signatures will
@@ -754,9 +754,4 @@
     accessFlags.setPublic();
     accessFlags.setFinal();
   }
-
-  private void makeStatic(DexEncodedMethod method) {
-    method.getAccessFlags().setStatic();
-    assert method.getCode().isCfCode();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
index dc82f99..cc04384 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
@@ -20,7 +20,8 @@
   private final VerticalClassMergerGraphLens.Builder lensBuilder;
 
   // All the bridge methods that have been synthesized during vertical class merging.
-  private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
+  private final List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges =
+      new ArrayList<>();
 
   private final VerticallyMergedClasses.Builder verticallyMergedClassesBuilder =
       VerticallyMergedClasses.builder();
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java b/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
new file mode 100644
index 0000000..60694ae
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
@@ -0,0 +1,139 @@
+// 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.verticalclassmerging;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ClasspathMethod;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeType;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
+
+/**
+ * A short-lived piece of code that will be converted into {@link CfCode} using the method {@link
+ * IncompleteVerticalClassMergerBridgeCode#toCfCode(DexItemFactory, VerticalClassMergerGraphLens)}.
+ */
+public class IncompleteVerticalClassMergerBridgeCode extends Code {
+
+  private DexMethod method;
+  private DexMethod invocationTarget;
+  private final InvokeType type;
+  private final boolean isInterface;
+
+  public IncompleteVerticalClassMergerBridgeCode(
+      DexMethod method, InvokeType type, boolean isInterface) {
+    this.method = method;
+    this.invocationTarget = null;
+    this.type = type;
+    this.isInterface = isInterface;
+  }
+
+  @Override
+  public Code getCodeAsInlining(
+      DexMethod caller,
+      boolean isCallerD8R8Synthesized,
+      DexMethod callee,
+      boolean isCalleeD8R8Synthesized,
+      DexItemFactory factory) {
+    // This code object is synthesized so "inlining" just "strips" the callee position.
+    assert isCalleeD8R8Synthesized;
+    return this;
+  }
+
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public DexMethod getTarget() {
+    return invocationTarget;
+  }
+
+  // By the time the synthesized code object is created, vertical class merging still has not
+  // finished. Therefore it is possible that the method signatures `method` and `invocationTarget`
+  // will change as a result of additional class merging operations. To deal with this, the
+  // vertical class merger explicitly invokes this method to update `method` and `invocation-
+  // Target` when vertical class merging has finished.
+  //
+  // Note that, without this step, these method signatures might refer to intermediate signatures
+  // that are only present in the middle of vertical class merging, which means that the graph
+  // lens will not work properly (since the graph lens generated by vertical class merging only
+  // expects to be applied to method signatures from *before* vertical class merging or *after*
+  // vertical class merging).
+  public void updateMethodSignatures(VerticalClassMergerGraphLens lens) {
+    invocationTarget = lens.getNextImplementationMethodSignature(method);
+    method = lens.getNextBridgeMethodSignature(method);
+  }
+
+  public CfCode toCfCode(DexItemFactory dexItemFactory, VerticalClassMergerGraphLens lens) {
+    return ForwardMethodBuilder.builder(dexItemFactory)
+        .applyIf(
+            type.isStatic(),
+            builder -> builder.setStaticTarget(invocationTarget, isInterface),
+            builder -> builder.setVirtualTarget(invocationTarget, isInterface))
+        .setNonStaticSource(method)
+        .setCodeLens(lens)
+        .build();
+  }
+
+  // Implement Code.
+
+  @Override
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
+    throw new Unreachable();
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    throw new Unreachable();
+  }
+
+  @Override
+  protected int computeHashCode() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int estimatedDexCodeSizeUpperBoundInBytes() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public String toString() {
+    return "IncompleteVerticalClassMergerBridgeCode";
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
+    return toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java b/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java
deleted file mode 100644
index edffcd5..0000000
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/SynthesizedBridgeCode.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package com.android.tools.r8.verticalclassmerging;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.ir.code.InvokeType;
-import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
-import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
-import java.util.function.Consumer;
-
-public class SynthesizedBridgeCode extends AbstractSynthesizedCode {
-
-  private DexMethod method;
-  private DexMethod invocationTarget;
-  private final InvokeType type;
-  private final boolean isInterface;
-  private VerticalClassMergerGraphLens codeLens;
-
-  public SynthesizedBridgeCode(DexMethod method, InvokeType type, boolean isInterface) {
-    this.method = method;
-    this.invocationTarget = null;
-    this.type = type;
-    this.isInterface = isInterface;
-  }
-
-  public DexMethod getMethod() {
-    return method;
-  }
-
-  public DexMethod getTarget() {
-    return invocationTarget;
-  }
-
-  // By the time the synthesized code object is created, vertical class merging still has not
-  // finished. Therefore it is possible that the method signatures `method` and `invocationTarget`
-  // will change as a result of additional class merging operations. To deal with this, the
-  // vertical class merger explicitly invokes this method to update `method` and `invocation-
-  // Target` when vertical class merging has finished.
-  //
-  // Note that, without this step, these method signatures might refer to intermediate signatures
-  // that are only present in the middle of vertical class merging, which means that the graph
-  // lens will not work properly (since the graph lens generated by vertical class merging only
-  // expects to be applied to method signatures from *before* vertical class merging or *after*
-  // vertical class merging).
-  public void updateMethodSignatures(VerticalClassMergerGraphLens lens) {
-    codeLens = lens;
-    invocationTarget = lens.getNextImplementationMethodSignature(method);
-    method = lens.getNextBridgeMethodSignature(method);
-  }
-
-  @Override
-  public GraphLens getCodeLens(AppView<?> appView) {
-    return codeLens;
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
-        ForwardMethodSourceCode.builder(method);
-    forwardSourceCodeBuilder
-        .setReceiver(method.holder)
-        .setTargetReceiver(type.isStatic() ? null : method.holder)
-        .setTarget(invocationTarget)
-        .setInvokeType(type)
-        .setIsInterface(isInterface);
-    return forwardSourceCodeBuilder::build;
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback(DexClassAndMethod method) {
-    return registry -> {
-      assert registry.getTraversalContinuation().shouldContinue();
-      switch (type) {
-        case DIRECT:
-          registry.registerInvokeDirect(invocationTarget);
-          break;
-        case STATIC:
-          registry.registerInvokeStatic(invocationTarget);
-          break;
-        case VIRTUAL:
-          registry.registerInvokeVirtual(invocationTarget);
-          break;
-        default:
-          throw new Unreachable("Unexpected invocation type: " + type);
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
index f94208d..d791b84 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -117,7 +117,7 @@
     assert verifyGraphLens(lens, verticalClassMergerResult);
     updateArtProfiles(profileCollectionAdditions, lens, verticalClassMergerResult);
     appView.rewriteWithLens(lens, executorService, timing);
-    updateKeepInfoForSynthesizedBridges(verticalClassMergerResult);
+    finalizeSynthesizedBridges(verticalClassMergerResult.getSynthesizedBridges(), lens);
     appView.notifyOptimizationFinishedForTesting();
   }
 
@@ -223,9 +223,9 @@
       VerticalClassMergerResult verticalClassMergerResult) {
     // Include bridges in art profiles.
     if (!profileCollectionAdditions.isNop()) {
-      List<SynthesizedBridgeCode> synthesizedBridges =
+      List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges =
           verticalClassMergerResult.getSynthesizedBridges();
-      for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
+      for (IncompleteVerticalClassMergerBridgeCode synthesizedBridge : synthesizedBridges) {
         profileCollectionAdditions.applyIfContextIsInProfile(
             verticalClassMergerLens.getPreviousMethodSignature(synthesizedBridge.getMethod()),
             additionsBuilder -> additionsBuilder.addRule(synthesizedBridge.getMethod()));
@@ -247,26 +247,24 @@
         });
   }
 
-  private void updateKeepInfoForSynthesizedBridges(
-      VerticalClassMergerResult verticalClassMergerResult) {
-    // Copy keep info to newly synthesized methods.
+  private void finalizeSynthesizedBridges(
+      List<IncompleteVerticalClassMergerBridgeCode> bridges, VerticalClassMergerGraphLens lens) {
     KeepInfoCollection keepInfo = appView.getKeepInfo();
-    keepInfo.mutate(
-        mutator -> {
-          List<SynthesizedBridgeCode> synthesizedBridges =
-              verticalClassMergerResult.getSynthesizedBridges();
-          for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
-            ProgramMethod bridge =
-                asProgramMethodOrNull(appView.definitionFor(synthesizedBridge.getMethod()));
-            ProgramMethod target =
-                asProgramMethodOrNull(appView.definitionFor(synthesizedBridge.getTarget()));
-            if (bridge != null && target != null) {
-              mutator.joinMethod(bridge, info -> info.merge(appView.getKeepInfo(target).joiner()));
-              continue;
-            }
-            assert false;
-          }
-        });
+    for (IncompleteVerticalClassMergerBridgeCode code : bridges) {
+      ProgramMethod bridge = asProgramMethodOrNull(appView.definitionFor(code.getMethod()));
+      assert bridge != null;
+
+      ProgramMethod target = asProgramMethodOrNull(appView.definitionFor(code.getTarget()));
+      assert target != null;
+
+      // Finalize code.
+      bridge.setCode(code.toCfCode(dexItemFactory, lens), appView);
+
+      // Copy keep info to newly synthesized methods.
+      keepInfo.mutate(
+          mutator ->
+              mutator.joinMethod(bridge, info -> info.merge(appView.getKeepInfo(target).joiner())));
+    }
   }
 
   private boolean verifyGraphLens(
@@ -307,14 +305,15 @@
         DexMethod renamedMethod = graphLens.getRenamedMethodSignature(originalMethod);
 
         // Must be able to map back and forth.
-        if (encodedMethod.hasCode() && encodedMethod.getCode() instanceof SynthesizedBridgeCode) {
+        if (encodedMethod.hasCode()
+            && encodedMethod.getCode() instanceof IncompleteVerticalClassMergerBridgeCode) {
           // For virtual methods, the vertical class merger creates two methods in the sub class
           // in order to deal with invoke-super instructions (one that is private and one that is
           // virtual). Therefore, it is not possible to go back and forth. Instead, we check that
           // the two methods map back to the same original method, and that the original method
           // can be mapped to the implementation method.
           DexMethod implementationMethod =
-              ((SynthesizedBridgeCode) encodedMethod.getCode()).getTarget();
+              ((IncompleteVerticalClassMergerBridgeCode) encodedMethod.getCode()).getTarget();
           DexMethod originalImplementationMethod =
               graphLens.getOriginalMethodSignature(implementationMethod);
           assert originalMethod.isIdenticalTo(originalImplementationMethod);
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerResult.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerResult.java
index 390131a..43bdbd7 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerResult.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerResult.java
@@ -11,12 +11,12 @@
 public class VerticalClassMergerResult {
 
   private final VerticalClassMergerGraphLens.Builder lensBuilder;
-  private final List<SynthesizedBridgeCode> synthesizedBridges;
+  private final List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges;
   private final VerticallyMergedClasses verticallyMergedClasses;
 
   public VerticalClassMergerResult(
       VerticalClassMergerGraphLens.Builder lensBuilder,
-      List<SynthesizedBridgeCode> synthesizedBridges,
+      List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges,
       VerticallyMergedClasses verticallyMergedClasses) {
     this.lensBuilder = lensBuilder;
     this.synthesizedBridges = synthesizedBridges;
@@ -29,7 +29,7 @@
 
   public static Builder builder(
       VerticalClassMergerGraphLens.Builder lensBuilder,
-      List<SynthesizedBridgeCode> synthesizedBridges,
+      List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges,
       VerticallyMergedClasses.Builder verticallyMergedClassesBuilder) {
     return new Builder(lensBuilder, synthesizedBridges, verticallyMergedClassesBuilder);
   }
@@ -38,7 +38,7 @@
     return lensBuilder;
   }
 
-  List<SynthesizedBridgeCode> getSynthesizedBridges() {
+  List<IncompleteVerticalClassMergerBridgeCode> getSynthesizedBridges() {
     return synthesizedBridges;
   }
 
@@ -53,7 +53,7 @@
   public static class Builder {
 
     private final VerticalClassMergerGraphLens.Builder lensBuilder;
-    private final List<SynthesizedBridgeCode> synthesizedBridges;
+    private final List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges;
     private final VerticallyMergedClasses.Builder verticallyMergedClassesBuilder;
 
     Builder(AppView<AppInfoWithLiveness> appView) {
@@ -65,7 +65,7 @@
 
     Builder(
         VerticalClassMergerGraphLens.Builder lensBuilder,
-        List<SynthesizedBridgeCode> synthesizedBridges,
+        List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges,
         VerticallyMergedClasses.Builder verticallyMergedClassesBuilder) {
       this.lensBuilder = lensBuilder;
       this.synthesizedBridges = synthesizedBridges;
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
index d814695..c6c1f4d 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
@@ -19,7 +19,7 @@
         VerticalClassMergerGraphLens,
         VerticallyMergedClasses> {
 
-  private final List<SynthesizedBridgeCode> synthesizedBridges;
+  private final List<IncompleteVerticalClassMergerBridgeCode> synthesizedBridges;
 
   VerticalClassMergerTreeFixer(
       AppView<AppInfoWithLiveness> appView,
@@ -39,7 +39,7 @@
   public VerticalClassMergerGraphLens run(ExecutorService executorService, Timing timing)
       throws ExecutionException {
     VerticalClassMergerGraphLens lens = super.run(executorService, timing);
-    for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
+    for (IncompleteVerticalClassMergerBridgeCode synthesizedBridge : synthesizedBridges) {
       synthesizedBridge.updateMethodSignatures(lens);
     }
     return lens;
diff --git a/src/test/java/com/android/tools/r8/gson/GsonDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/gson/GsonDefaultConstructorTest.java
index 5c61354..9d0b65d 100644
--- a/src/test/java/com/android/tools/r8/gson/GsonDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/gson/GsonDefaultConstructorTest.java
@@ -3,11 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.gson;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.ProguardVersion;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.gson.GsonNoDefaultConstructorTest.Data;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.annotations.SerializedName;
@@ -41,6 +49,14 @@
 
   private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
 
+  private void inspect(CodeInspector inspector) {
+    ClassSubject dataSubject = inspector.clazz(Data.class);
+    assertThat(dataSubject, isPresentAndRenamed());
+    assertThat(dataSubject.uniqueFieldWithOriginalName("s"), isPresentAndRenamed());
+    assertThat(dataSubject.init(), isPresent());
+    assertThat(dataSubject.init("java.lang.String"), isAbsent());
+  }
+
   @Test
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
@@ -51,6 +67,8 @@
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters)
         .run(parameters.getRuntime(), TestClass.class, enableUnsafe ? "enable" : "disable")
+        .inspect(this::serializedNamePresentAndRenamed)
+        .inspect(this::inspect)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
diff --git a/src/test/java/com/android/tools/r8/gson/GsonNoDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/gson/GsonNoDefaultConstructorTest.java
index 5a0244a..b536709 100644
--- a/src/test/java/com/android/tools/r8/gson/GsonNoDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/gson/GsonNoDefaultConstructorTest.java
@@ -3,13 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.gson;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ProguardVersion;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.annotations.SerializedName;
@@ -43,6 +49,13 @@
 
   private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
 
+  private void inspect(CodeInspector inspector) {
+    ClassSubject dataSubject = inspector.clazz(Data.class);
+    assertThat(dataSubject, isPresentAndRenamed());
+    assertThat(dataSubject.uniqueFieldWithOriginalName("s"), isPresentAndRenamed());
+    assertTrue(dataSubject.allMethods(FoundMethodSubject::isInstanceInitializer).isEmpty());
+  }
+
   @Test
   public void testR8() throws Exception {
     parameters.assumeR8TestParameters();
@@ -52,6 +65,9 @@
         .apply(GsonTestBase::addGsonLibraryAndKeepRules)
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters)
+        .compile()
+        .inspect(this::serializedNamePresentAndRenamed)
+        .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class, enableUnsafe ? "enable" : "disable")
         .applyIf(
             enableUnsafe,
diff --git a/src/test/java/com/android/tools/r8/gson/GsonTestBase.java b/src/test/java/com/android/tools/r8/gson/GsonTestBase.java
index 970450c..f0029f4 100644
--- a/src/test/java/com/android/tools/r8/gson/GsonTestBase.java
+++ b/src/test/java/com/android/tools/r8/gson/GsonTestBase.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.gson;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.ArchiveProgramResourceProvider;
 import com.android.tools.r8.ProguardTestBuilder;
 import com.android.tools.r8.R8FullTestBuilder;
@@ -10,9 +13,16 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 
 public class GsonTestBase extends TestBase {
 
+  public static String SERIALIZED_NAME_CLASS_NAME = "com.google.gson.annotations.SerializedName";
+
+  public void serializedNamePresentAndRenamed(CodeInspector inspector) {
+    assertThat(inspector.clazz(SERIALIZED_NAME_CLASS_NAME), isPresentAndRenamed());
+  }
+
   static void addRuntimeLibrary(TestShrinkerBuilder builder, TestParameters parameters) {
     // Gson use java.lang.ReflectiveOperationException.
     builder.addDefaultRuntimeLibraryWithReflectiveOperationException(parameters);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java
index 343a944..c719a9a 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NarrowingWithoutSubtypingTest.java
@@ -4,12 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
 
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8TestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -38,32 +33,17 @@
 
   @Test
   public void test() throws Exception {
-    D8TestBuilder d8TestBuilder =
-        testForD8()
-            .addInnerClasses(NarrowingWithoutSubtypingTest.class)
-            .addOptionsModification(
-                options -> {
-                  options.testing.readInputStackMaps = readStackMap;
-                  options.testing.enableNarrowAndWideningingChecksInD8 = true;
-                  options.testing.noLocalsTableOnInput = true;
-                })
-            .setMinApi(parameters);
-    if (readStackMap) {
-      d8TestBuilder
-          .run(parameters.getRuntime(), TestClass.class)
-          .assertSuccessWithOutputLines("Hello world!");
-    } else {
-      // TODO(b/169120386): We should not be narrowing in D8.
-      assertThrows(
-          CompilationFailedException.class,
-          () -> {
-            d8TestBuilder.compileWithExpectedDiagnostics(
-                diagnostics ->
-                    diagnostics.assertAllErrorsMatch(
-                        diagnosticMessage(
-                            containsString("java.lang.AssertionError: During NARROWING"))));
-          });
-    }
+    testForD8()
+        .addInnerClasses(NarrowingWithoutSubtypingTest.class)
+        .addOptionsModification(
+            options -> {
+              options.testing.readInputStackMaps = readStackMap;
+              options.testing.enableNarrowAndWideningingChecksInD8 = true;
+              options.testing.noLocalsTableOnInput = true;
+            })
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java
index e3e6058..2bcf2f7 100644
--- a/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java
@@ -86,8 +86,7 @@
         breakpoint(fooMethod, IN_STREAM_IS_NULL_LINE),
         breakpoint(fooMethod, NORMAL_EXIT_LINE),
         run(),
-        // TODO(b/316744331): D8 incorrectly optimizing out the code after the null check.
-        checkLine(parameters.isCfRuntime() ? IN_STREAM_IS_NULL_LINE : NORMAL_EXIT_LINE),
+        checkLine(IN_STREAM_IS_NULL_LINE),
         run());
   }
 
@@ -105,8 +104,7 @@
         breakpoint(fooMethod, OUT_STREAM_IS_NULL_LINE),
         breakpoint(fooMethod, NORMAL_EXIT_LINE),
         run(),
-        // TODO(b/316744331): D8 incorrectly optimizing out the code after the null check.
-        checkLine(parameters.isCfRuntime() ? OUT_STREAM_IS_NULL_LINE : NORMAL_EXIT_LINE),
+        checkLine(OUT_STREAM_IS_NULL_LINE),
         run());
   }
 }