Merge commit '24cf166186f71f6fba2b3b771738877477f7a7fc' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9bee3f3..e767011 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -568,7 +568,9 @@
               appView.appInfo().app().asDirect().builder();
           HorizontalClassMergerGraphLens lens = merger.run(appBuilder);
           if (lens != null) {
-            appView.rewriteWithLensAndApplication(lens, appBuilder.build());
+            DirectMappedDexApplication app = appBuilder.build();
+            appView.removePrunedClasses(app, appView.horizontallyMergedClasses().getSources());
+            appView.rewriteWithLens(lens);
           }
           timing.end();
         }
@@ -807,11 +809,9 @@
       appView.setGraphLens(memberRebindingLens);
 
       // Perform repackaging.
-      if (options.isRepackagingEnabled() && options.testing.enableExperimentalRepackaging) {
+      if (options.isRepackagingEnabled()) {
         DirectMappedDexApplication.Builder appBuilder =
             appView.appInfo().app().asDirect().builder();
-        // TODO(b/165783399): We need to deal with non-rebound member references in the writer,
-        //  possibly by adding a member rebinding lens on top of the repackaging lens.
         RepackagingLens lens =
             new Repackaging(appView.withLiveness()).run(appBuilder, executorService, timing);
         if (lens != null) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 6073b28..f39795b 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexEncodedArray;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
@@ -45,6 +46,7 @@
 import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -457,87 +459,107 @@
   private void insertAttributeAnnotations() {
     // Convert inner-class attributes to DEX annotations
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
-      List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
-      if (enclosingMethod == null
-          && innerClasses.isEmpty()
-          && clazz.getClassSignature().hasNoSignature()) {
-        continue;
-      }
-
-      // EnclosingMember translates directly to an enclosing class/method if present.
-      List<DexAnnotation> annotations = new ArrayList<>(2 + innerClasses.size());
-      if (enclosingMethod != null) {
-        if (enclosingMethod.getEnclosingMethod() != null) {
-          annotations.add(
-              DexAnnotation.createEnclosingMethodAnnotation(
-                  enclosingMethod.getEnclosingMethod(), options.itemFactory));
-        } else {
-          // At this point DEX can't distinguish between local classes and member classes based on
-          // the enclosing class annotation itself.
-          annotations.add(
-              DexAnnotation.createEnclosingClassAnnotation(
-                  enclosingMethod.getEnclosingClass(), options.itemFactory));
-        }
-      }
-
-      // Each inner-class entry becomes a inner-class (or inner-class & enclosing-class pair) if
-      // it relates to the present class. If it relates to the outer-type (and is named) it becomes
-      // part of the member-classes annotation.
-      if (!innerClasses.isEmpty()) {
-        List<DexType> memberClasses = new ArrayList<>(innerClasses.size());
-        for (InnerClassAttribute innerClass : innerClasses) {
-          if (clazz.type == innerClass.getInner()) {
-            if (enclosingMethod == null
-                && (innerClass.getOuter() == null || innerClass.isAnonymous())) {
-              options.warningMissingEnclosingMember(
-                  clazz.type, clazz.origin, clazz.getInitialClassFileVersion());
-            } else {
-              annotations.add(
-                  DexAnnotation.createInnerClassAnnotation(
-                      namingLens.lookupInnerName(innerClass, options),
-                      innerClass.getAccess(),
-                      options.itemFactory));
-              if (innerClass.getOuter() != null && innerClass.isNamed()) {
-                annotations.add(
-                    DexAnnotation.createEnclosingClassAnnotation(
-                        innerClass.getOuter(), options.itemFactory));
-              }
-            }
-          } else if (clazz.type == innerClass.getOuter() && innerClass.isNamed()) {
-            memberClasses.add(innerClass.getInner());
-          }
-        }
-        if (!memberClasses.isEmpty()) {
-          annotations.add(
-              DexAnnotation.createMemberClassesAnnotation(memberClasses, options.itemFactory));
-        }
-      }
-
-      if (clazz.getClassSignature().hasSignature()) {
-        annotations.add(
-            DexAnnotation.createSignatureAnnotation(
-                clazz.getClassSignature().toRenamedString(namingLens, isTypeMissing),
-                options.itemFactory));
-      }
-
-      if (!annotations.isEmpty()) {
-        // Append the annotations to annotations array of the class.
-        DexAnnotation[] copy =
-            ObjectArrays.concat(
-                clazz.annotations().annotations,
-                annotations.toArray(DexAnnotation.EMPTY_ARRAY),
-                DexAnnotation.class);
-        clazz.setAnnotations(new DexAnnotationSet(copy));
-      }
-
-      // Clear the attribute structures now that they are represented in annotations.
-      clazz.clearEnclosingMethodAttribute();
-      clazz.clearInnerClasses();
-      clazz.clearClassSignature();
+      insertAttributeAnnotationsForClass(clazz);
+      clazz.fields().forEach(this::insertAttributeAnnotationsForField);
     }
   }
 
+  private void insertAttributeAnnotationsForClass(DexProgramClass clazz) {
+    EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
+    List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
+    if (enclosingMethod == null
+        && innerClasses.isEmpty()
+        && clazz.getClassSignature().hasNoSignature()) {
+      return;
+    }
+
+    // EnclosingMember translates directly to an enclosing class/method if present.
+    List<DexAnnotation> annotations = new ArrayList<>(2 + innerClasses.size());
+    if (enclosingMethod != null) {
+      if (enclosingMethod.getEnclosingMethod() != null) {
+        annotations.add(
+            DexAnnotation.createEnclosingMethodAnnotation(
+                enclosingMethod.getEnclosingMethod(), options.itemFactory));
+      } else {
+        // At this point DEX can't distinguish between local classes and member classes based on
+        // the enclosing class annotation itself.
+        annotations.add(
+            DexAnnotation.createEnclosingClassAnnotation(
+                enclosingMethod.getEnclosingClass(), options.itemFactory));
+      }
+    }
+
+    // Each inner-class entry becomes a inner-class (or inner-class & enclosing-class pair) if
+    // it relates to the present class. If it relates to the outer-type (and is named) it becomes
+    // part of the member-classes annotation.
+    if (!innerClasses.isEmpty()) {
+      List<DexType> memberClasses = new ArrayList<>(innerClasses.size());
+      for (InnerClassAttribute innerClass : innerClasses) {
+        if (clazz.type == innerClass.getInner()) {
+          if (enclosingMethod == null
+              && (innerClass.getOuter() == null || innerClass.isAnonymous())) {
+            options.warningMissingEnclosingMember(
+                clazz.type, clazz.origin, clazz.getInitialClassFileVersion());
+          } else {
+            annotations.add(
+                DexAnnotation.createInnerClassAnnotation(
+                    namingLens.lookupInnerName(innerClass, options),
+                    innerClass.getAccess(),
+                    options.itemFactory));
+            if (innerClass.getOuter() != null && innerClass.isNamed()) {
+              annotations.add(
+                  DexAnnotation.createEnclosingClassAnnotation(
+                      innerClass.getOuter(), options.itemFactory));
+            }
+          }
+        } else if (clazz.type == innerClass.getOuter() && innerClass.isNamed()) {
+          memberClasses.add(innerClass.getInner());
+        }
+      }
+      if (!memberClasses.isEmpty()) {
+        annotations.add(
+            DexAnnotation.createMemberClassesAnnotation(memberClasses, options.itemFactory));
+      }
+    }
+
+    if (clazz.getClassSignature().hasSignature()) {
+      annotations.add(
+          DexAnnotation.createSignatureAnnotation(
+              clazz.getClassSignature().toRenamedString(namingLens, isTypeMissing),
+              options.itemFactory));
+    }
+
+    if (!annotations.isEmpty()) {
+      // Append the annotations to annotations array of the class.
+      DexAnnotation[] copy =
+          ObjectArrays.concat(
+              clazz.annotations().annotations,
+              annotations.toArray(DexAnnotation.EMPTY_ARRAY),
+              DexAnnotation.class);
+      clazz.setAnnotations(new DexAnnotationSet(copy));
+    }
+
+    // Clear the attribute structures now that they are represented in annotations.
+    clazz.clearEnclosingMethodAttribute();
+    clazz.clearInnerClasses();
+    clazz.clearClassSignature();
+  }
+
+  private void insertAttributeAnnotationsForField(DexEncodedField field) {
+    if (field.getFieldSignature().hasNoSignature()) {
+      return;
+    }
+    // Append the annotations to annotations array of the class.
+    field.setAnnotations(
+        new DexAnnotationSet(
+            ArrayUtils.appendSingleElement(
+                field.annotations().annotations,
+                DexAnnotation.createSignatureAnnotation(
+                    field.getFieldSignature().toRenamedString(namingLens, isTypeMissing),
+                    options.itemFactory))));
+    field.clearFieldSignature();
+  }
+
   private void setCallSiteContexts(ExecutorService executorService) throws ExecutionException {
     ThreadUtils.processItems(
         appView.appInfo().classes(), this::setCallSiteContexts, executorService);
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 711eb47..6f00265 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -629,14 +629,25 @@
       fieldIndex += dexReader.getUleb128();
       DexField field = indexedItems.getField(fieldIndex);
       FieldAccessFlags accessFlags = FieldAccessFlags.fromDexAccessFlags(dexReader.getUleb128());
-      DexAnnotationSet fieldAnnotations = annotationIterator.getNextFor(field);
       DexValue staticValue = null;
       if (accessFlags.isStatic()) {
         if (staticValues != null && i < staticValues.length) {
           staticValue = staticValues[i];
         }
       }
-      fields[i] = new DexEncodedField(field, accessFlags, fieldAnnotations, staticValue);
+      DexAnnotationSet fieldAnnotations = annotationIterator.getNextFor(field);
+      String fieldSignature = DexAnnotation.getSignature(fieldAnnotations, dexItemFactory);
+      if (fieldSignature != null) {
+        fieldAnnotations = fieldAnnotations.getWithout(dexItemFactory.annotationSignature);
+      }
+      fields[i] =
+          new DexEncodedField(
+              field,
+              accessFlags,
+              GenericSignature.parseFieldTypeSignature(
+                  field.name.toString(), fieldSignature, origin, dexItemFactory, options.reporter),
+              fieldAnnotations,
+              staticValue);
     }
     return fields;
   }
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index e4f0a0c..b808097 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
@@ -42,6 +43,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -179,12 +181,14 @@
   }
 
   private static Map<DexProgramClass, String> computeOriginalNameMapping(
-      Collection<DexProgramClass> classes, ClassNameMapper proguardMap) {
-    Map<DexProgramClass, String> originalNames = new HashMap<>(classes.size());
+      Collection<DexProgramClass> classes, GraphLens graphLens, ClassNameMapper proguardMap) {
+    Map<DexProgramClass, String> originalNames = new IdentityHashMap<>(classes.size());
     classes.forEach(
-        (DexProgramClass c) -> {
+        clazz -> {
+          DexType originalType = graphLens.getOriginalType(clazz.getType());
           originalNames.put(
-              c, DescriptorUtils.descriptorToJavaType(c.type.toDescriptorString(), proguardMap));
+              clazz,
+              DescriptorUtils.descriptorToJavaType(originalType.toDescriptorString(), proguardMap));
         });
     return originalNames;
   }
@@ -367,8 +371,10 @@
       virtualFiles.add(mainDexFile);
       addMarkers(mainDexFile);
 
-      classes = Sets.newHashSet(appView.appInfo().classes());
-      originalNames = computeOriginalNameMapping(classes, appView.appInfo().app().getProguardMap());
+      classes = SetUtils.newIdentityHashSet(appView.appInfo().classes());
+      originalNames =
+          computeOriginalNameMapping(
+              classes, appView.graphLens(), appView.appInfo().app().getProguardMap());
     }
 
     private void addMarkers(VirtualFile virtualFile) {
@@ -693,7 +699,7 @@
 
     @Override
     public DexString getRenamedDescriptor(DexType type) {
-      return namingLens.lookupDescriptor(graphLens.lookupType(type));
+      return namingLens.lookupDescriptor(type);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 282f852..6bf8757 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -35,6 +35,7 @@
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -523,6 +524,11 @@
   }
 
   public void removePrunedClasses(
+      DirectMappedDexApplication prunedApp, Set<DexType> removedClasses) {
+    removePrunedClasses(prunedApp, removedClasses, Collections.emptySet());
+  }
+
+  public void removePrunedClasses(
       DirectMappedDexApplication prunedApp,
       Set<DexType> removedClasses,
       Collection<DexMethod> methodsToKeepForConfigurationDebugging) {
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 de1c2b9..141484c 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -290,9 +290,11 @@
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    assert verifyFrames(method.getDefinition(), appView, null, false);
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
     GraphLens graphLens = appView.graphLens();
+    // TODO(b/170073151): Handle unapplied code rewritings.
+    assert graphLens.hasCodeRewritings()
+        || verifyFrames(method.getDefinition(), appView, null, false);
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     InitClassLens initClassLens = appView.initClassLens();
     InternalOptions options = appView.options();
     CfLabel parameterLabel = null;
@@ -410,7 +412,9 @@
       Origin origin,
       boolean shouldApplyCodeRewritings) {
     if (!verifyFrames(method, appView, origin, shouldApplyCodeRewritings)) {
-      instructions.removeIf(CfInstruction::isFrame);
+      ArrayList<CfInstruction> copy = new ArrayList<>(instructions);
+      copy.removeIf(CfInstruction::isFrame);
+      setInstructions(copy);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 5bdddd9..05010c8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -243,6 +243,11 @@
     return signature.toString();
   }
 
+  public static String getSignature(DexAnnotationSet signatureAnnotations, DexItemFactory factory) {
+    DexAnnotation signature = signatureAnnotations.getFirstMatching(factory.annotationSignature);
+    return signature == null ? null : getSignature(signature);
+  }
+
   public static DexAnnotation createThrowsAnnotation(DexValue[] exceptions,
       DexItemFactory factory) {
     return createSystemValueAnnotation(factory.annotationThrows, factory,
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 8068417..8e51e49 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -4,10 +4,12 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -28,6 +30,8 @@
   public final FieldAccessFlags accessFlags;
   private DexValue staticValue;
   private final boolean deprecated;
+  /** Generic signature information if the attribute is present in the input */
+  private FieldTypeSignature fieldSignature;
 
   private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
   private KotlinFieldLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
@@ -35,6 +39,7 @@
   public DexEncodedField(
       DexField field,
       FieldAccessFlags accessFlags,
+      FieldTypeSignature fieldSignature,
       DexAnnotationSet annotations,
       DexValue staticValue,
       boolean deprecated) {
@@ -43,14 +48,18 @@
     this.accessFlags = accessFlags;
     this.staticValue = staticValue;
     this.deprecated = deprecated;
+    this.fieldSignature = fieldSignature;
+    assert fieldSignature != null;
+    assert GenericSignatureUtils.verifyNoDuplicateGenericDefinitions(fieldSignature, annotations);
   }
 
   public DexEncodedField(
       DexField field,
       FieldAccessFlags accessFlags,
+      FieldTypeSignature fieldSignature,
       DexAnnotationSet annotations,
       DexValue staticValue) {
-    this(field, accessFlags, annotations, staticValue, false);
+    this(field, accessFlags, fieldSignature, annotations, staticValue, false);
   }
 
   public DexType type() {
@@ -288,7 +297,9 @@
     if (this.field == field) {
       return this;
     }
-    DexEncodedField result = new DexEncodedField(field, accessFlags, annotations(), staticValue);
+    // TODO(b/169923358): Consider removing the fieldSignature here.
+    DexEncodedField result =
+        new DexEncodedField(field, accessFlags, fieldSignature, annotations(), staticValue);
     result.optimizationInfo =
         optimizationInfo.isMutableFieldOptimizationInfo()
             ? optimizationInfo.asMutableFieldOptimizationInfo().mutableCopy()
@@ -310,4 +321,17 @@
     // TODO(b/150593449): Support non primitive DexValue (String, enum) and add assertions.
     return true;
   }
+
+  public FieldTypeSignature getFieldSignature() {
+    return fieldSignature;
+  }
+
+  public void setFieldSignature(FieldTypeSignature fieldSignature) {
+    assert fieldSignature != null;
+    this.fieldSignature = fieldSignature;
+  }
+
+  public void clearFieldSignature() {
+    this.fieldSignature = NO_FIELD_TYPE_SIGNATURE;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 43c065a..012030b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -169,6 +169,10 @@
     return dexItemFactory.createField(holder, type, name);
   }
 
+  public DexField withName(DexString name, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createField(holder, type, name);
+  }
+
   public FieldReference asFieldReference() {
     return Reference.field(
         Reference.classFromDescriptor(holder.toDescriptorString()),
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index cdf97ab..cf30eed 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1799,7 +1799,7 @@
    * @param tryString callback to check if the method name is in use.
    */
   public <T extends DexMember<?, ?>> T createFreshMember(
-      String baseName, DexType holder, Function<DexString, Optional<T>> tryString) {
+      Function<DexString, Optional<T>> tryString, String baseName, DexType holder) {
     int index = 0;
     while (true) {
       DexString name = createString(createMemberString(baseName, holder, index++));
@@ -1811,6 +1811,17 @@
   }
 
   /**
+   * Find a fresh method name that is not used by any other method. The method name takes the form
+   * "basename" or "basename$index".
+   *
+   * @param tryString callback to check if the method name is in use.
+   */
+  public <T extends DexMember<?, ?>> T createFreshMember(
+      Function<DexString, Optional<T>> tryString, String baseName) {
+    return createFreshMember(tryString, baseName, null);
+  }
+
+  /**
    * Find a fresh method name that is not in the string pool. The name takes the form
    * "basename$holdername" or "basename$holdername$index".
    */
@@ -1850,8 +1861,6 @@
       Predicate<DexMethod> isFresh) {
     DexMethod method =
         createFreshMember(
-            baseName,
-            holder,
             name -> {
               DexMethod tryMethod = createMethod(target, proto, name);
               if (isFresh.test(tryMethod)) {
@@ -1859,7 +1868,9 @@
               } else {
                 return Optional.empty();
               }
-            });
+            },
+            baseName,
+            holder);
     return method;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 5cd3f8f..bbff32c 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -322,11 +322,30 @@
       return this != GenericSignature.NO_FIELD_TYPE_SIGNATURE;
     }
 
+    public boolean hasNoSignature() {
+      return !hasSignature();
+    }
+
     public abstract FieldTypeSignature asArgument(WildcardIndicator indicator);
 
     public boolean isStar() {
       return false;
     }
+
+    public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+      if (hasNoSignature()) {
+        return null;
+      }
+      GenericSignaturePrinter genericSignaturePrinter =
+          new GenericSignaturePrinter(namingLens, isTypeMissing);
+      genericSignaturePrinter.visitTypeSignature(this);
+      return genericSignaturePrinter.toString();
+    }
+
+    @Override
+    public String toString() {
+      return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue());
+    }
   }
 
   static final class StarFieldTypeSignature extends FieldTypeSignature {
@@ -650,6 +669,9 @@
       Origin origin,
       DexItemFactory factory,
       Reporter reporter) {
+    if (signature == null || signature.isEmpty()) {
+      return NO_FIELD_TYPE_SIGNATURE;
+    }
     Parser parser = new Parser(factory);
     try {
       return parser.parseFieldTypeSignature(signature);
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
index 1b23c85..e75ee78 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -33,6 +33,11 @@
   }
 
   @Override
+  public void visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
+    printFieldTypeSignature(fieldSignature, false);
+  }
+
+  @Override
   public void visitFormalTypeParameters(List<FormalTypeParameter> formalTypeParameters) {
     if (formalTypeParameters.isEmpty()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index ddabc37..4cce25e 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -41,6 +41,13 @@
     return new ClassSignatureRewriter().run(classSignature);
   }
 
+  public FieldTypeSignature rewrite(FieldTypeSignature fieldTypeSignature) {
+    if (fieldTypeSignature.hasNoSignature() || appView.graphLens().isIdentityLens()) {
+      return fieldTypeSignature;
+    }
+    return new TypeSignatureRewriter().run(fieldTypeSignature);
+  }
+
   private class ClassSignatureRewriter implements GenericSignatureVisitor {
 
     private final List<FormalTypeParameter> rewrittenTypeParameters = new ArrayList<>();
@@ -228,10 +235,10 @@
     }
 
     private DexType getTarget(DexType type) {
-      if (appInfoWithLiveness != null && appInfoWithLiveness.wasPruned(type)) {
+      DexType rewrittenType = appView.graphLens().lookupType(type);
+      if (appInfoWithLiveness != null && appInfoWithLiveness.wasPruned(rewrittenType)) {
         return null;
       }
-      DexType rewrittenType = appView.graphLens().lookupType(type);
       if (isSuperClassOrInterface && context.type == rewrittenType) {
         return null;
       }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
index 22ddca7..6d9385e 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
 import com.android.tools.r8.graph.GenericSignature.ReturnType;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
-import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import java.util.List;
 
@@ -25,7 +24,7 @@
     throw new Unreachable("Implement if visited");
   }
 
-  default void visitFieldSignature(FieldSignature fieldSignature) {
+  default void visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
     throw new Unreachable("Implement if visited");
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 4382eaa..ccaa837 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -559,6 +560,7 @@
     private final String name;
     private final String desc;
     private final Object value;
+    private FieldTypeSignature fieldSignature;
     private List<DexAnnotation> annotations = null;
 
     public CreateFieldVisitor(CreateDexClassVisitor parent,
@@ -569,10 +571,13 @@
       this.name = name;
       this.desc = desc;
       this.value = value;
-      if (signature != null && !signature.isEmpty()) {
-        addAnnotation(
-            DexAnnotation.createSignatureAnnotation(signature, parent.application.getFactory()));
-      }
+      this.fieldSignature =
+          GenericSignature.parseFieldTypeSignature(
+              name,
+              signature,
+              parent.origin,
+              parent.application.getFactory(),
+              parent.application.options.reporter);
     }
 
     @Override
@@ -598,7 +603,12 @@
         DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
         DexEncodedField field =
             new DexEncodedField(
-                dexField, flags, annotationSet, staticValue, AsmUtils.isDeprecated(access));
+                dexField,
+                flags,
+                fieldSignature,
+                annotationSet,
+                staticValue,
+                AsmUtils.isDeprecated(access));
         if (flags.isStatic()) {
           parent.staticFields.add(field);
         } else {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 73d1e68..4a4936b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
+
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -151,6 +153,7 @@
             classIdField,
             FieldAccessFlags.fromSharedAccessFlags(
                 Constants.ACC_PUBLIC + Constants.ACC_FINAL + Constants.ACC_SYNTHETIC),
+            NO_FIELD_TYPE_SIGNATURE,
             DexAnnotationSet.empty(),
             null);
     target.appendInstanceField(encodedField);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
index b912cf7..01058e2 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
 import java.util.Collection;
 import java.util.Map;
+import java.util.Set;
 
 public class HorizontallyMergedClasses implements MergedClasses {
   private final BidirectionalManyToOneMap<DexType, DexType> mergedClasses;
@@ -24,6 +25,10 @@
     return mergedClasses.getOrDefault(type, type);
   }
 
+  public Set<DexType> getSources() {
+    return mergedClasses.keySet();
+  }
+
   public boolean hasBeenMergedIntoDifferentType(DexType type) {
     return mergedClasses.hasKey(type);
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index e33137c..7f12cc9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -32,6 +32,7 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -384,10 +385,26 @@
     if (fields == null) {
       return;
     }
+    Set<DexField> existingFields = Sets.newIdentityHashSet();
+
     for (int i = 0; i < fields.size(); i++) {
       DexEncodedField encodedField = fields.get(i);
       DexField field = encodedField.field;
       DexField newField = fixupFieldReference(field);
+
+      // Rename the field if it already exists.
+      if (!existingFields.add(newField)) {
+        DexField template = newField;
+        newField =
+            dexItemFactory.createFreshMember(
+                tryName ->
+                    Optional.of(template.withName(tryName, dexItemFactory))
+                        .filter(tryMethod -> !existingFields.contains(tryMethod)),
+                newField.name.toSourceString());
+        boolean added = existingFields.add(newField);
+        assert added;
+      }
+
       if (newField != encodedField.field) {
         lensBuilder.mapField(field, newField);
         setter.setField(i, encodedField.toTypeSubstitutedField(newField));
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index b695024..f475e89 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1135,9 +1135,13 @@
       return false;
     }
     boolean didDesugar = false;
+    Supplier<AppInfoWithClassHierarchy> lazyAppInfo =
+        Suppliers.memoize(() -> appView.appInfoForDesugaring());
     if (lambdaRewriter != null) {
-      AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
-      didDesugar |= lambdaRewriter.desugarLambdas(method, appInfo) > 0;
+      didDesugar |= lambdaRewriter.desugarLambdas(method, lazyAppInfo.get()) > 0;
+    }
+    if (backportedMethodRewriter != null) {
+      didDesugar |= backportedMethodRewriter.desugar(method, lazyAppInfo.get());
     }
     return didDesugar;
   }
@@ -1423,12 +1427,6 @@
       timing.end();
     }
 
-    if (backportedMethodRewriter != null) {
-      timing.begin("Rewrite backport methods");
-      backportedMethodRewriter.desugar(code);
-      timing.end();
-    }
-
     timing.begin("Desugar string concat");
     stringConcatRewriter.desugarStringConcats(method.method, code);
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index b283f96..d7b3d0a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -1,129 +1,47 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2020, 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.desugar;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke;
-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 java.util.ArrayList;
-import java.util.List;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 
 // Source code representing synthesized accessor method.
-final class AccessorMethodSourceCode extends SynthesizedLambdaSourceCode {
 
-  AccessorMethodSourceCode(LambdaClass lambda, Position callerPosition) {
-    super(
-        lambda, lambda.target.callTarget, callerPosition, null /* no receiver for static method */);
-    // We should never need an accessor for interface methods since
-    // they are supposed to be public.
-    assert !descriptor().implHandle.type.isInvokeInterface();
-    assert checkSignatures();
-  }
+public class AccessorMethodSourceCode {
 
-  private boolean checkSignatures() {
-    DexMethodHandle implHandle = descriptor().implHandle;
-    assert implHandle != null;
-
-    DexType[] accessorParams = proto.parameters.values;
-    DexMethod implMethod = implHandle.asMethod();
-    DexProto implProto = implMethod.proto;
-    DexType[] implParams = implProto.parameters.values;
-
-    int index = 0;
-    if (implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect()) {
-      assert accessorParams[index] == descriptor().getImplReceiverType();
-      index++;
-    }
-
-    for (DexType implParam : implParams) {
-      assert accessorParams[index] == implParam;
-      index++;
-    }
-    assert index == accessorParams.length;
-
-    assert delegatingToConstructor()
-        ? this.proto.returnType == implMethod.holder
-        : this.proto.returnType == implProto.returnType;
-    return true;
-  }
-
-  // Are we delegating to a constructor?
-  private boolean delegatingToConstructor() {
-    return descriptor().implHandle.type.isInvokeConstructor();
-  }
-
-  private Invoke.Type inferInvokeType() {
-    switch (descriptor().implHandle.type) {
+  public static CfCode build(LambdaClass lambda, DexMethod accessor) {
+    DexMethod target = lambda.descriptor.implHandle.asMethod();
+    ForwardMethodBuilder forwardMethodBuilder =
+        ForwardMethodBuilder.builder(lambda.appView.dexItemFactory()).setStaticSource(accessor);
+    switch (lambda.descriptor.implHandle.type) {
       case INVOKE_INSTANCE:
-        return Invoke.Type.VIRTUAL;
+        {
+          forwardMethodBuilder.setVirtualTarget(target, false);
+          break;
+        }
       case INVOKE_STATIC:
-        return Invoke.Type.STATIC;
+        {
+          forwardMethodBuilder.setStaticTarget(target, false);
+          break;
+        }
       case INVOKE_DIRECT:
+        {
+          forwardMethodBuilder.setDirectTarget(target, false);
+          break;
+        }
       case INVOKE_CONSTRUCTOR:
-        return Invoke.Type.DIRECT;
+        {
+          forwardMethodBuilder.setConstructorTarget(target, lambda.appView.dexItemFactory());
+          break;
+        }
       case INVOKE_INTERFACE:
         throw new Unreachable("Accessor for an interface method?");
       default:
         throw new Unreachable();
     }
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    DexMethod implMethod = descriptor().implHandle.asMethod();
-    DexType[] accessorParams = proto.parameters.values;
-
-    // Prepare call arguments.
-    List<ValueType> argValueTypes = new ArrayList<>();
-    List<Integer> argRegisters = new ArrayList<>();
-
-    // If we are delegating to a constructor, we need to create the instance
-    // first. This instance will be the first argument to the call.
-    if (delegatingToConstructor()) {
-      int instance = nextRegister(ValueType.OBJECT);
-      add(builder -> builder.addNewInstance(instance, implMethod.holder));
-      argValueTypes.add(ValueType.OBJECT);
-      argRegisters.add(instance);
-    }
-
-    for (int i = 0; i < accessorParams.length; i++) {
-      DexType param = accessorParams[i];
-      argValueTypes.add(ValueType.fromDexType(param));
-      argRegisters.add(getParamRegister(i));
-    }
-
-    // Method call to the original impl-method.
-    // Mirroring assert in constructor, we never need accessors to interfaces.
-    assert !descriptor().implHandle.type.isInvokeInterface();
-    add(
-        builder ->
-            builder.addInvoke(
-                inferInvokeType(),
-                implMethod,
-                implMethod.proto,
-                argValueTypes,
-                argRegisters,
-                false /* isInterface */));
-
-    // Does the method have return value?
-    if (proto.returnType == factory().voidType) {
-      add(IRBuilder::addReturn);
-    } else if (delegatingToConstructor()) {
-      // Return newly created instance
-      add(builder -> builder.addReturn(argRegisters.get(0)));
-    } else {
-      ValueType valueType = ValueType.fromDexType(proto.returnType);
-      int tempValue = nextRegister(valueType);
-      add(builder -> builder.addMoveResult(tempValue));
-      add(builder -> builder.addReturn(tempValue));
-    }
+    return forwardMethodBuilder.build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 6dc372e..b45fe42 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -4,27 +4,25 @@
 
 package com.android.tools.r8.ir.desugar;
 
-
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+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.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
 import com.android.tools.r8.ir.desugar.backports.BooleanMethodRewrites;
@@ -40,19 +38,19 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Queue;
-import java.util.Set;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
 
 public final class BackportedMethodRewriter {
 
@@ -107,29 +105,34 @@
     BackportedMethods.registerSynthesizedCodeReferences(options.itemFactory);
   }
 
-  public void desugar(IRCode code) {
+  public boolean desugar(ProgramMethod method, AppInfoWithClassHierarchy appInfo) {
     if (!enabled) {
-      return; // Nothing to do!
+      return false;
     }
-    Set<Value> affectedValues = Sets.newIdentityHashSet();
-    InstructionListIterator iterator = code.instructionListIterator();
+    CfCode code = method.getDefinition().getCode().asCfCode();
+    ListIterator<CfInstruction> iterator = code.getInstructions().listIterator();
+    boolean replaced = false;
     while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
-      if (!instruction.isInvokeMethod()) {
+      CfInvoke invoke = iterator.next().asInvoke();
+      if (invoke == null) {
         continue;
       }
-
-      InvokeMethod invoke = instruction.asInvokeMethod();
-      DexMethod invokedMethod = invoke.getInvokedMethod();
+      DexMethod invokedMethod = invoke.getMethod();
       MethodProvider provider = getMethodProviderOrNull(invokedMethod);
       if (provider != null) {
+        if (!replaced) {
+          // Create mutable instructions on first write.
+          ArrayList<CfInstruction> mutableInstructions = new ArrayList<>(code.getInstructions());
+          code.setInstructions(mutableInstructions);
+          iterator = mutableInstructions.listIterator(iterator.previousIndex());
+          iterator.next();
+        }
         provider.rewriteInvoke(
-            invoke, iterator, code, appView, affectedValues, synthesizedMethods::add);
+            invoke, iterator, method.getHolder(), appInfo, synthesizedMethods::add);
+        replaced = true;
       }
     }
-    if (!affectedValues.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(affectedValues);
-    }
+    return replaced;
   }
 
   public void processSynthesizedClasses(IRConverter converter, ExecutorService executor)
@@ -244,7 +247,7 @@
       name = factory.createString("compare");
       proto = factory.createProto(factory.intType, factory.longType, factory.longType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, LongMethodRewrites::rewriteCompare));
+      addProvider(new InvokeRewriter(method, LongMethodRewrites.rewriteCompare()));
 
       // Boolean
       type = factory.boxedBooleanType;
@@ -289,7 +292,7 @@
       name = factory.createString("hash");
       proto = factory.createProto(factory.intType, factory.objectArrayType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, ObjectsMethodRewrites::rewriteToArraysHashCode));
+      addProvider(new InvokeRewriter(method, ObjectsMethodRewrites.rewriteToArraysHashCode()));
 
       // int Objects.hashCode(Object o)
       name = factory.createString("hashCode");
@@ -299,7 +302,7 @@
 
       // T Objects.requireNonNull(T obj)
       method = factory.objectsMethods.requireNonNull;
-      addProvider(new InvokeRewriter(method, ObjectsMethodRewrites::rewriteRequireNonNull));
+      addProvider(new InvokeRewriter(method, ObjectsMethodRewrites.rewriteRequireNonNull()));
 
       // T Objects.requireNonNull(T obj, String message)
       name = factory.createString("requireNonNull");
@@ -356,7 +359,7 @@
       DexString name = factory.createString("hashCode");
       DexProto proto = factory.createProto(factory.intType, factory.byteType);
       DexMethod method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteAsIdentity));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteAsIdentity()));
 
       // Short
       type = factory.boxedShortType;
@@ -364,7 +367,7 @@
       name = factory.createString("hashCode");
       proto = factory.createProto(factory.intType, factory.shortType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteAsIdentity));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteAsIdentity()));
 
       // Integer
       type = factory.boxedIntType;
@@ -373,25 +376,25 @@
       name = factory.createString("hashCode");
       proto = factory.createProto(factory.intType, factory.intType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteAsIdentity));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteAsIdentity()));
 
       // int Integer.max(int a, int b)
       name = factory.createString("max");
       proto = factory.createProto(factory.intType, factory.intType, factory.intType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // int Integer.min(int a, int b)
       name = factory.createString("min");
       proto = factory.createProto(factory.intType, factory.intType, factory.intType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // int Integer.sum(int a, int b)
       name = factory.createString("sum");
       proto = factory.createProto(factory.intType, factory.intType, factory.intType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToAddInstruction));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToAddInstruction()));
 
       // Double
       type = factory.boxedDoubleType;
@@ -406,19 +409,19 @@
       name = factory.createString("max");
       proto = factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // double Double.min(double a, double b)
       name = factory.createString("min");
       proto = factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // double Double.sum(double a, double b)
       name = factory.createString("sum");
       proto = factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToAddInstruction));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToAddInstruction()));
 
       // boolean Double.isFinite(double a)
       name = factory.createString("isFinite");
@@ -433,25 +436,25 @@
       name = factory.createString("hashCode");
       proto = factory.createProto(factory.intType, factory.floatType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, FloatMethodRewrites::rewriteHashCode));
+      addProvider(new InvokeRewriter(method, FloatMethodRewrites.rewriteHashCode()));
 
       // float Float.max(float a, float b)
       name = factory.createString("max");
       proto = factory.createProto(factory.floatType, factory.floatType, factory.floatType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // float Float.min(float a, float b)
       name = factory.createString("min");
       proto = factory.createProto(factory.floatType, factory.floatType, factory.floatType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // float Float.sum(float a, float b)
       name = factory.createString("sum");
       proto = factory.createProto(factory.floatType, factory.floatType, factory.floatType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToAddInstruction));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToAddInstruction()));
 
       // boolean Float.isFinite(float a)
       name = factory.createString("isFinite");
@@ -472,19 +475,19 @@
       name = factory.createString("logicalAnd");
       proto = factory.createProto(factory.booleanType, factory.booleanType, factory.booleanType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, BooleanMethodRewrites::rewriteLogicalAnd));
+      addProvider(new InvokeRewriter(method, BooleanMethodRewrites.rewriteLogicalAnd()));
 
       // boolean Boolean.logicalOr(boolean a, boolean b)
       name = factory.createString("logicalOr");
       proto = factory.createProto(factory.booleanType, factory.booleanType, factory.booleanType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, BooleanMethodRewrites::rewriteLogicalOr));
+      addProvider(new InvokeRewriter(method, BooleanMethodRewrites.rewriteLogicalOr()));
 
       // boolean Boolean.logicalXor(boolean a, boolean b)
       name = factory.createString("logicalXor");
       proto = factory.createProto(factory.booleanType, factory.booleanType, factory.booleanType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, BooleanMethodRewrites::rewriteLogicalXor));
+      addProvider(new InvokeRewriter(method, BooleanMethodRewrites.rewriteLogicalXor()));
 
       // Long
       type = factory.boxedLongType;
@@ -499,19 +502,19 @@
       name = factory.createString("max");
       proto = factory.createProto(factory.longType, factory.longType, factory.longType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // long Long.min(long a, long b)
       name = factory.createString("min");
       proto = factory.createProto(factory.longType, factory.longType, factory.longType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToInvokeMath));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToInvokeMath()));
 
       // long Long.sum(long a, long b)
       name = factory.createString("sum");
       proto = factory.createProto(factory.longType, factory.longType, factory.longType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteToAddInstruction));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteToAddInstruction()));
 
       // Character
       type = factory.boxedCharType;
@@ -520,7 +523,7 @@
       name = factory.createString("hashCode");
       proto = factory.createProto(factory.intType, factory.charType);
       method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, NumericMethodRewrites::rewriteAsIdentity));
+      addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteAsIdentity()));
 
       // Objects
       type = factory.objectsType;
@@ -912,7 +915,7 @@
         method = factory.createMethod(type, proto, name);
         addProvider(
             i == 0
-                ? new InvokeRewriter(method, CollectionMethodRewrites::rewriteListOfEmpty)
+                ? new InvokeRewriter(method, CollectionMethodRewrites.rewriteListOfEmpty())
                 : new MethodGenerator(
                     method,
                     (options, methodArg) ->
@@ -933,7 +936,7 @@
         method = factory.createMethod(type, proto, name);
         addProvider(
             i == 0
-                ? new InvokeRewriter(method, CollectionMethodRewrites::rewriteSetOfEmpty)
+                ? new InvokeRewriter(method, CollectionMethodRewrites.rewriteSetOfEmpty())
                 : new MethodGenerator(
                     method,
                     (options, methodArg) ->
@@ -953,7 +956,7 @@
         method = factory.createMethod(type, proto, name);
         addProvider(
             i == 0
-                ? new InvokeRewriter(method, CollectionMethodRewrites::rewriteMapOfEmpty)
+                ? new InvokeRewriter(method, CollectionMethodRewrites.rewriteMapOfEmpty())
                 : new MethodGenerator(
                     method,
                     (options, methodArg) ->
@@ -1220,10 +1223,10 @@
           };
       MethodInvokeRewriter[] rewriters =
           new MethodInvokeRewriter[] {
-            OptionalMethodRewrites::rewriteOrElseGet,
-            OptionalMethodRewrites::rewriteDoubleOrElseGet,
-            OptionalMethodRewrites::rewriteLongOrElseGet,
-            OptionalMethodRewrites::rewriteIntOrElseGet,
+            OptionalMethodRewrites.rewriteOrElseGet(),
+            OptionalMethodRewrites.rewriteDoubleOrElseGet(),
+            OptionalMethodRewrites.rewriteLongOrElseGet(),
+            OptionalMethodRewrites.rewriteIntOrElseGet(),
           };
       DexString name = factory.createString("orElseThrow");
       for (int i = 0; i < optionalTypes.length; i++) {
@@ -1290,11 +1293,10 @@
     }
 
     public abstract void rewriteInvoke(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        IRCode code,
-        AppView<?> appView,
-        Set<Value> affectedValues,
+        CfInvoke invoke,
+        ListIterator<CfInstruction> iterator,
+        DexProgramClass context,
+        AppInfoWithClassHierarchy appInfo,
         Consumer<ProgramMethod> registerSynthesizedMethod);
   }
 
@@ -1309,14 +1311,12 @@
 
     @Override
     public void rewriteInvoke(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        IRCode code,
-        AppView<?> appView,
-        Set<Value> affectedValues,
+        CfInvoke invoke,
+        ListIterator<CfInstruction> iterator,
+        DexProgramClass context,
+        AppInfoWithClassHierarchy appInfo,
         Consumer<ProgramMethod> registerSynthesizedMethod) {
-      rewriter.rewrite(invoke, iterator, appView.dexItemFactory(), affectedValues);
-      assert code.isConsistentSSA();
+      rewriter.rewrite(invoke, iterator, appInfo.dexItemFactory());
     }
   }
 
@@ -1337,34 +1337,33 @@
 
     @Override
     public void rewriteInvoke(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        IRCode code,
-        AppView<?> appView,
-        Set<Value> affectedValues,
+        CfInvoke invoke,
+        ListIterator<CfInstruction> iterator,
+        DexProgramClass context,
+        AppInfoWithClassHierarchy appInfo,
         Consumer<ProgramMethod> registerSynthesizedMethod) {
-      ProgramMethod method =
-          appView
-              .getSyntheticItems()
-              .createMethod(
-                  code.context().getHolder(),
-                  appView.dexItemFactory(),
-                  builder ->
-                      builder
-                          .setProto(getProto(appView.dexItemFactory()))
-                          .setAccessFlags(
-                              MethodAccessFlags.fromSharedAccessFlags(
-                                  Constants.ACC_PUBLIC
-                                      | Constants.ACC_STATIC
-                                      | Constants.ACC_SYNTHETIC,
-                                  false))
-                          .setCode(
-                              methodSig -> generateTemplateMethod(appView.options(), methodSig)));
-
-      iterator.replaceCurrentInstruction(
-          new InvokeStatic(method.getReference(), invoke.outValue(), invoke.inValues()));
-
+      ProgramMethod method = getSyntheticMethod(context, appInfo);
       registerSynthesizedMethod.accept(method);
+      iterator.remove();
+      iterator.add(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
+    }
+
+    private ProgramMethod getSyntheticMethod(
+        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+      return appInfo
+          .getSyntheticItems()
+          .createMethod(
+              context,
+              appInfo.dexItemFactory(),
+              builder ->
+                  builder
+                      .setProto(getProto(appInfo.dexItemFactory()))
+                      .setAccessFlags(
+                          MethodAccessFlags.fromSharedAccessFlags(
+                              Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC,
+                              false))
+                      .setCode(
+                          methodSig -> generateTemplateMethod(appInfo.app().options, methodSig)));
     }
 
     public DexProto getProto(DexItemFactory itemFactory) {
@@ -1397,15 +1396,30 @@
 
   private interface TemplateMethodFactory {
 
-    Code create(InternalOptions options, DexMethod method);
+    CfCode create(InternalOptions options, DexMethod method);
   }
 
-  private interface MethodInvokeRewriter {
+  public interface MethodInvokeRewriter {
 
-    void rewrite(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        DexItemFactory factory,
-        Set<Value> affectedValues);
+    CfInstruction rewriteSingle(CfInvoke invoke, DexItemFactory factory);
+
+    // Convenience wrapper since most rewrites are to a single instruction.
+    default void rewrite(
+        CfInvoke invoke, ListIterator<CfInstruction> iterator, DexItemFactory factory) {
+      iterator.remove();
+      iterator.add(rewriteSingle(invoke, factory));
+    }
+  }
+
+  public abstract static class FullMethodInvokeRewriter implements MethodInvokeRewriter {
+
+    @Override
+    public final CfInstruction rewriteSingle(CfInvoke invoke, DexItemFactory factory) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public abstract void rewrite(
+        CfInvoke invoke, ListIterator<CfInstruction> iterator, DexItemFactory factory);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 4d5c7be..01fb238 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -22,10 +22,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.google.common.base.Predicates;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -111,12 +109,13 @@
       DexProgramClass clazz,
       List<DexEncodedMethod> methodsWithCovariantReturnTypeAnnotation,
       List<DexEncodedMethod> covariantReturnTypeMethods) {
-    for (DexEncodedMethod method : clazz.virtualMethods()) {
-      if (methodHasCovariantReturnTypeAnnotation(method)) {
-        methodsWithCovariantReturnTypeAnnotation.add(method);
-        buildCovariantReturnTypeMethodsForMethod(clazz, method, covariantReturnTypeMethods);
-      }
-    }
+    clazz.forEachProgramVirtualMethod(
+        method -> {
+          if (methodHasCovariantReturnTypeAnnotation(method.getDefinition())) {
+            methodsWithCovariantReturnTypeAnnotation.add(method.getDefinition());
+            buildCovariantReturnTypeMethodsForMethod(method, covariantReturnTypeMethods);
+          }
+        });
   }
 
   private boolean methodHasCovariantReturnTypeAnnotation(DexEncodedMethod method) {
@@ -132,13 +131,11 @@
   // variantReturnTypes annotations on the given method. Adds the newly constructed, synthetic
   // methods to the list covariantReturnTypeMethods.
   private void buildCovariantReturnTypeMethodsForMethod(
-      DexProgramClass clazz,
-      DexEncodedMethod method,
-      List<DexEncodedMethod> covariantReturnTypeMethods) {
-    assert methodHasCovariantReturnTypeAnnotation(method);
-    for (DexType covariantReturnType : getCovariantReturnTypes(clazz, method)) {
+      ProgramMethod method, List<DexEncodedMethod> covariantReturnTypeMethods) {
+    assert methodHasCovariantReturnTypeAnnotation(method.getDefinition());
+    for (DexType covariantReturnType : getCovariantReturnTypes(method)) {
       DexEncodedMethod covariantReturnTypeMethod =
-          buildCovariantReturnTypeMethod(clazz, method, covariantReturnType);
+          buildCovariantReturnTypeMethod(method, covariantReturnType);
       covariantReturnTypeMethods.add(covariantReturnTypeMethod);
     }
   }
@@ -149,32 +146,35 @@
   //
   // Note: any "synchronized" or "strictfp" modifier could be dropped safely.
   private DexEncodedMethod buildCovariantReturnTypeMethod(
-      DexProgramClass clazz, DexEncodedMethod method, DexType covariantReturnType) {
+      ProgramMethod method, DexType covariantReturnType) {
+    DexProgramClass methodHolder = method.getHolder();
+    DexMethod methodReference = method.getReference();
+    DexEncodedMethod methodDefinition = method.getDefinition();
     DexProto newProto =
         factory.createProto(
-            covariantReturnType, method.method.proto.parameters, method.method.proto.shorty);
-    MethodAccessFlags newAccessFlags = method.accessFlags.copy();
+            covariantReturnType, methodReference.proto.parameters, methodReference.proto.shorty);
+    MethodAccessFlags newAccessFlags = methodDefinition.accessFlags.copy();
     newAccessFlags.setBridge();
     newAccessFlags.setSynthetic();
-    DexMethod newMethod = factory.createMethod(method.holder(), newProto, method.method.name);
-    ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
-        ForwardMethodSourceCode.builder(newMethod);
-    forwardSourceCodeBuilder
-        .setReceiver(clazz.type)
-        .setTargetReceiver(method.holder())
-        .setTarget(method.method)
-        .setInvokeType(Invoke.Type.VIRTUAL)
-        .setCastResult();
+    DexMethod newMethod =
+        factory.createMethod(methodHolder.getType(), newProto, methodReference.getName());
+    ForwardMethodBuilder forwardMethodBuilder =
+        ForwardMethodBuilder.builder(factory)
+            .setNonStaticSource(newMethod)
+            .setVirtualTarget(methodReference, methodHolder.isInterface())
+            .setCastResult();
     DexEncodedMethod newVirtualMethod =
         new DexEncodedMethod(
             newMethod,
             newAccessFlags,
-            method.annotations().keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation)),
-            method.parameterAnnotationsList.keepIf(Predicates.alwaysTrue()),
-            new SynthesizedCode(forwardSourceCodeBuilder::build),
+            methodDefinition
+                .annotations()
+                .keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation)),
+            methodDefinition.parameterAnnotationsList.keepIf(Predicates.alwaysTrue()),
+            forwardMethodBuilder.build(),
             true);
-    // Optimize to generate DexCode instead of SynthesizedCode.
-    ProgramMethod programMethod = new ProgramMethod(clazz, newVirtualMethod);
+    // Optimize to generate DexCode instead of CfCode.
+    ProgramMethod programMethod = new ProgramMethod(methodHolder, newVirtualMethod);
     converter.optimizeSynthesizedMethod(programMethod);
     return newVirtualMethod;
   }
@@ -187,12 +187,15 @@
   //   @Override
   //   public Foo foo() { ... return new SubOfSubOfFoo(); }
   // then this method returns the set { SubOfFoo, SubOfSubOfFoo }.
-  private Set<DexType> getCovariantReturnTypes(DexClass clazz, DexEncodedMethod method) {
+  private Set<DexType> getCovariantReturnTypes(ProgramMethod method) {
     Set<DexType> covariantReturnTypes = new HashSet<>();
-    for (DexAnnotation annotation : method.annotations().annotations) {
+    for (DexAnnotation annotation : method.getDefinition().annotations().annotations) {
       if (isCovariantReturnTypeAnnotation(annotation.annotation)) {
         getCovariantReturnTypesFromAnnotation(
-            clazz, method, annotation.annotation, covariantReturnTypes);
+            method.getHolder(),
+            method.getDefinition(),
+            annotation.annotation,
+            covariantReturnTypes);
       }
     }
     return covariantReturnTypes;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index f2ea71d..88fc45a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
+
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
@@ -427,7 +429,8 @@
     // Field is package private to be accessible from convert methods without a getter.
     FieldAccessFlags fieldAccessFlags =
         FieldAccessFlags.fromCfAccessFlags(Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
-    return new DexEncodedField(field, fieldAccessFlags, DexAnnotationSet.empty(), null);
+    return new DexEncodedField(
+        field, fieldAccessFlags, NO_FIELD_TYPE_SIGNATURE, DexAnnotationSet.empty(), null);
   }
 
   private DexEncodedMethod synthesizeConstructor(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 67d389f..069f4b3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -32,8 +32,7 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.BiMap;
@@ -260,15 +259,13 @@
 
       DexMethod origMethod = direct.method;
       DexMethod newMethod = rewriter.staticAsMethodOfDispatchClass(origMethod);
-      ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
-          ForwardMethodSourceCode.builder(newMethod);
       // Create a forwarding method to the library static interface method. The method is added
       // to the dispatch class, however, the targeted method is still on the interface, so the
       // interface bit should be set to true.
-      forwardSourceCodeBuilder
-          .setTarget(origMethod)
-          .setInvokeType(Type.STATIC)
-          .setIsInterface(true);
+      ForwardMethodBuilder forwardMethodBuilder =
+          ForwardMethodBuilder.builder(appView.dexItemFactory())
+              .setStaticSource(newMethod)
+              .setStaticTarget(origMethod, true);
       DexEncodedMethod newEncodedMethod =
           new DexEncodedMethod(
               newMethod,
@@ -276,7 +273,7 @@
                   Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new SynthesizedCode(forwardSourceCodeBuilder::build),
+              forwardMethodBuilder.build(),
               true);
       newEncodedMethod.getMutableOptimizationInfo().markNeverInline();
       dispatchMethods.add(newEncodedMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java
deleted file mode 100644
index a733540..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaAccessorMethodWithSynthesizedCode.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2020, 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.desugar;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexMethodHandle;
-import com.android.tools.r8.graph.UseRegistry;
-import java.util.function.Consumer;
-
-public class LambdaAccessorMethodWithSynthesizedCode extends LambdaSynthesizedCode {
-
-  public LambdaAccessorMethodWithSynthesizedCode(LambdaClass lambda) {
-    super(lambda);
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition -> new AccessorMethodSourceCode(lambda, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      DexMethodHandle handle = lambda.descriptor.implHandle;
-      DexMethod target = handle.asMethod();
-      switch (handle.type) {
-        case INVOKE_STATIC:
-          registry.registerInvokeStatic(target);
-          break;
-        case INVOKE_INSTANCE:
-          registry.registerInvokeVirtual(target);
-          break;
-        case INVOKE_CONSTRUCTOR:
-        case INVOKE_DIRECT:
-          registry.registerInvokeDirect(target);
-          break;
-        default:
-          throw new Unreachable("Unexpected handle type: " + handle.type);
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
index f0eb914..ddcdc6e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
@@ -4,71 +4,21 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke;
-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 java.util.ArrayList;
-import java.util.List;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 
 // Source code representing synthesized lambda bridge method.
-final class LambdaBridgeMethodSourceCode extends SynthesizedLambdaSourceCode {
 
-  private final DexMethod mainMethod;
+final class LambdaBridgeMethodSourceCode {
 
-  LambdaBridgeMethodSourceCode(
-      LambdaClass lambda, DexMethod mainMethod, DexMethod bridgeMethod, Position callerPosition) {
-    super(lambda, bridgeMethod, callerPosition);
-    this.mainMethod = mainMethod;
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    DexType[] currentParams = proto.parameters.values;
-    DexType[] enforcedParams = descriptor().enforcedProto.parameters.values;
-
-    // Prepare call arguments.
-    List<ValueType> argValueTypes = new ArrayList<>();
-    List<Integer> argRegisters = new ArrayList<>();
-
-    // Always add a receiver representing 'this' of the lambda class.
-    argValueTypes.add(ValueType.OBJECT);
-    argRegisters.add(getReceiverRegister());
-
-    // Prepare arguments.
-    for (int i = 0; i < currentParams.length; i++) {
-      DexType expectedParamType = enforcedParams[i];
-      argValueTypes.add(ValueType.fromDexType(expectedParamType));
-      argRegisters.add(enforceParameterType(
-          getParamRegister(i), currentParams[i], expectedParamType));
-    }
-
-    // Method call to the main functional interface method.
-    add(
-        builder ->
-            builder.addInvoke(
-                Invoke.Type.VIRTUAL,
-                this.mainMethod,
-                this.mainMethod.proto,
-                argValueTypes,
-                argRegisters,
-                false /* isInterface */));
-
-    // Does the method have return value?
-    if (proto.returnType == factory().voidType) {
-      add(IRBuilder::addReturn);
-    } else {
-      ValueType valueType = ValueType.fromDexType(proto.returnType);
-      int tempValue = nextRegister(valueType);
-      add(builder -> builder.addMoveResult(tempValue));
-      // We lack precise sub-type information, but there should not be a need to cast to object.
-      if (proto.returnType != mainMethod.proto.returnType
-          && proto.returnType != factory().objectType) {
-        add(builder -> builder.addCheckCast(tempValue, proto.returnType));
-      }
-      add(builder -> builder.addReturn(tempValue));
-    }
+  public static CfCode build(
+      LambdaClass lambdaClass, DexMethod bridgeMethod, DexMethod mainMethod) {
+    return ForwardMethodBuilder.builder(lambdaClass.appView.dexItemFactory())
+        .setNonStaticSource(bridgeMethod)
+        .setVirtualTarget(mainMethod, false)
+        .setCastArguments(lambdaClass.appView.appInfoForDesugaring())
+        .setCastResult()
+        .build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
deleted file mode 100644
index 6e109dd..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSynthesizedCode.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (c) 2019, 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.desugar;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.UseRegistry;
-import java.util.function.Consumer;
-
-class LambdaBridgeMethodSynthesizedCode extends LambdaSynthesizedCode {
-
-  private final DexMethod mainMethod;
-  private final DexMethod bridgeMethod;
-
-  LambdaBridgeMethodSynthesizedCode(
-      LambdaClass lambda, DexMethod mainMethod, DexMethod bridgeMethod) {
-    super(lambda);
-    this.mainMethod = mainMethod;
-    this.bridgeMethod = bridgeMethod;
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition ->
-        new LambdaBridgeMethodSourceCode(lambda, mainMethod, bridgeMethod, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      registry.registerInvokeVirtual(mainMethod);
-
-      DexType bridgeMethodReturnType = bridgeMethod.proto.returnType;
-      if (!bridgeMethodReturnType.isVoidType()
-          && bridgeMethodReturnType != mainMethod.proto.returnType
-          && bridgeMethodReturnType != dexItemFactory().objectType) {
-        registry.registerCheckCast(bridgeMethodReturnType);
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index cc6fec7..6f4cad3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
+
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
@@ -234,7 +236,7 @@
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new LambdaMainMethodSynthesizedCode(this, mainMethod),
+            LambdaMainMethodSourceCode.build(this, mainMethod),
             true);
 
     // Synthesize bridge methods.
@@ -252,7 +254,7 @@
                   false),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new LambdaBridgeMethodSynthesizedCode(this, mainMethod, bridgeMethod),
+              LambdaBridgeMethodSourceCode.build(this, bridgeMethod, mainMethod),
               true);
     }
     return methods;
@@ -273,7 +275,7 @@
                 true),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new LambdaConstructorSynthesizedCode(this),
+            LambdaConstructorSourceCode.build(this),
             true);
 
     // Class constructor for stateless lambda classes.
@@ -285,7 +287,7 @@
                   Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new LambdaClassConstructorSynthesizedCode(this),
+              LambdaClassConstructorSourceCode.build(this),
               true);
     }
     return methods;
@@ -300,8 +302,13 @@
       FieldAccessFlags accessFlags =
           FieldAccessFlags.fromSharedAccessFlags(
               Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC);
-      fields[i] = new DexEncodedField(
-          getCaptureField(i), accessFlags, DexAnnotationSet.empty(), null);
+      fields[i] =
+          new DexEncodedField(
+              getCaptureField(i),
+              accessFlags,
+              NO_FIELD_TYPE_SIGNATURE,
+              DexAnnotationSet.empty(),
+              null);
     }
     return fields;
   }
@@ -323,6 +330,7 @@
                     | Constants.ACC_FINAL
                     | Constants.ACC_SYNTHETIC
                     | Constants.ACC_STATIC),
+            NO_FIELD_TYPE_SIGNATURE,
             DexAnnotationSet.empty(),
             DexValueNull.NULL);
     return fields;
@@ -748,13 +756,14 @@
           MethodAccessFlags.fromSharedAccessFlags(
               Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
               false);
+
       DexEncodedMethod accessorEncodedMethod =
           new DexEncodedMethod(
               callTarget,
               accessorFlags,
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              new LambdaAccessorMethodWithSynthesizedCode(LambdaClass.this),
+              AccessorMethodSourceCode.build(LambdaClass.this, callTarget),
               true);
 
       // We may arrive here concurrently so we need must update the methods of the class atomically.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
index 6b85039..fd7a8b9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
@@ -4,40 +4,34 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.ir.code.Invoke;
-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.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfNew;
+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.graph.CfCode;
 import com.google.common.collect.ImmutableList;
+import org.objectweb.asm.Opcodes;
 
 // Source code representing synthesized lambda class constructor.
 // Used for stateless lambdas to instantiate singleton instance.
-final class LambdaClassConstructorSourceCode extends SynthesizedLambdaSourceCode {
+final class LambdaClassConstructorSourceCode {
 
-  LambdaClassConstructorSourceCode(LambdaClass lambda, Position callerPosition) {
-    super(lambda, lambda.classConstructor, callerPosition, null /* Class initializer is static */);
-    assert lambda.lambdaField != null;
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    // Create and initialize an instance.
-    int instance = nextRegister(ValueType.OBJECT);
-    add(builder -> builder.addNewInstance(instance, lambda.type));
-    add(
-        builder ->
-            builder.addInvoke(
-                Invoke.Type.DIRECT,
-                lambda.constructor,
-                lambda.constructor.proto,
-                ImmutableList.of(ValueType.OBJECT),
-                ImmutableList.of(instance),
-                false /* isInterface */));
-
-    // Assign to a field.
-    add(builder -> builder.addStaticPut(instance, lambda.lambdaField));
-
-    // Final return.
-    add(IRBuilder::addReturn);
+  public static CfCode build(LambdaClass lambda) {
+    int maxStack = 2;
+    int maxLocals = 0;
+    return new CfCode(
+        lambda.type,
+        maxStack,
+        maxLocals,
+        ImmutableList.of(
+            new CfNew(lambda.type),
+            new CfStackInstruction(Opcode.Dup),
+            new CfInvoke(Opcodes.INVOKESPECIAL, lambda.constructor, false),
+            new CfFieldInstruction(Opcodes.PUTSTATIC, lambda.lambdaField, lambda.lambdaField),
+            new CfReturnVoid()),
+        ImmutableList.of(),
+        ImmutableList.of());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
deleted file mode 100644
index 2dbac91..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSynthesizedCode.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2019, 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.desugar;
-
-import com.android.tools.r8.graph.UseRegistry;
-import java.util.function.Consumer;
-
-class LambdaClassConstructorSynthesizedCode extends LambdaSynthesizedCode {
-
-  LambdaClassConstructorSynthesizedCode(LambdaClass lambda) {
-    super(lambda);
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition -> new LambdaClassConstructorSourceCode(lambda, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      registry.registerNewInstance(lambda.type);
-      registry.registerInvokeDirect(lambda.constructor);
-      registry.registerStaticFieldWrite(lambda.lambdaField);
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
index a8161d4..65ead77 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
@@ -4,70 +4,56 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
 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.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import java.util.Collections;
-
 // Source code representing synthesized lambda constructor.
-final class LambdaConstructorSourceCode extends SynthesizedLambdaSourceCode {
+import com.android.tools.r8.ir.code.ValueType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import org.objectweb.asm.Opcodes;
 
-  LambdaConstructorSourceCode(LambdaClass lambda, Position callerPosition) {
-    super(lambda, lambda.constructor, callerPosition);
-  }
+final class LambdaConstructorSourceCode {
 
-  @Override
-  protected void prepareInstructions() {
+  public static CfCode build(LambdaClass lambda) {
+    int maxStack = 1;
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    Builder<CfInstruction> instructions = ImmutableList.builder();
     // Super constructor call (always java.lang.Object.<init>()).
-    DexMethod objectInitMethod = factory().objectMembers.constructor;
-    add(
-        builder -> {
-          assert builder.getReceiverValue() != null;
-          builder.addInvoke(
-              Invoke.Type.DIRECT,
-              objectInitMethod,
-              objectInitMethod.proto,
-              Collections.singletonList(builder.getReceiverValue()),
-              false /* isInterface */);
-        });
-
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(
+        new CfInvoke(
+            Opcodes.INVOKESPECIAL,
+            lambda.appView.dexItemFactory().objectMembers.constructor,
+            false));
     // Assign capture fields.
-    DexType[] capturedTypes = captures();
-    int capturedValues = capturedTypes.length;
-    if (capturedValues > 0) {
-      for (int i = 0; i < capturedValues; i++) {
-        DexField field = lambda.getCaptureField(i);
-        int idx = i;
-        add(builder -> builder.addInstancePut(getParamRegister(idx), getReceiverRegister(), field));
-      }
+    DexType[] capturedTypes = lambda.descriptor.captures.values;
+    int maxLocals = 1;
+    for (int i = 0; i < capturedTypes.length; i++) {
+      DexField field = lambda.getCaptureField(i);
+      assert field.type == capturedTypes[i];
+      ValueType type = ValueType.fromDexType(field.type);
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(new CfLoad(type, maxLocals));
+      instructions.add(new CfFieldInstruction(Opcodes.PUTFIELD, field, field));
+      maxLocals += type.requiredRegisters();
+      maxStack += type.requiredRegisters();
     }
-
     // Final return.
-    add(IRBuilder::addReturn);
-  }
-
-  @Override
-  public int hashCode() {
-    // We want all zero-parameter constructor source code instances to
-    // be treated as equal, since it only has one call to super constructor,
-    // which is always java.lang.Object.<init>().
-    return captures().length == 0
-        ? System.identityHashCode(factory().objectMembers.constructor)
-        : super.hashCode();
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (!(obj instanceof LambdaConstructorSourceCode)) {
-      return false;
-    }
-    if (captures().length == 0) {
-      // See comment in hashCode().
-      return ((LambdaConstructorSourceCode) obj).captures().length == 0;
-    }
-    return super.equals(obj);
+    instructions.add(new CfReturnVoid());
+    return new CfCode(
+        lambda.constructor.holder,
+        maxStack,
+        maxLocals,
+        instructions.build(),
+        tryCatchRanges,
+        localVariables);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
deleted file mode 100644
index 1809d69..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSynthesizedCode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2019, 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.desugar;
-
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.UseRegistry;
-import java.util.function.Consumer;
-
-class LambdaConstructorSynthesizedCode extends LambdaSynthesizedCode {
-
-  LambdaConstructorSynthesizedCode(LambdaClass lambda) {
-    super(lambda);
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition -> new LambdaConstructorSourceCode(lambda, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      registry.registerInvokeDirect(dexItemFactory().objectMembers.constructor);
-      DexType[] capturedTypes = captures();
-      for (int i = 0; i < capturedTypes.length; i++) {
-        registry.registerInstanceFieldWrite(lambda.getCaptureField(i));
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index ac55e68..30ea01c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -4,7 +4,21 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNumberConversion;
+import com.android.tools.r8.cf.code.CfReturn;
+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.CfThrow;
+import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -12,27 +26,25 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.NumericType;
-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.ir.desugar.LambdaClass.InvalidLambdaImplTarget;
-import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
+import org.objectweb.asm.Opcodes;
 
 // Source code representing synthesized lambda main method
-final class LambdaMainMethodSourceCode extends SynthesizedLambdaSourceCode {
+final class LambdaMainMethodSourceCode {
 
-  LambdaMainMethodSourceCode(LambdaClass lambda, DexMethod mainMethod, Position callerPosition) {
-    super(lambda, mainMethod, callerPosition);
-  }
-
-  private boolean checkSignatures(
-      DexType[] captures, DexType[] enforcedParams, DexType enforcedReturnType,
-      List<DexType> implReceiverAndArgs, DexType implReturnType) {
+  private static boolean checkSignatures(
+      DexType[] captures,
+      DexType[] enforcedParams,
+      DexType enforcedReturnType,
+      List<DexType> implReceiverAndArgs,
+      DexType implReturnType,
+      DexItemFactory factory) {
     List<DexType> capturesAndParams = new ArrayList<>();
     capturesAndParams.addAll(Lists.newArrayList(captures));
     capturesAndParams.addAll(Lists.newArrayList(enforcedParams));
@@ -43,23 +55,19 @@
     }
 
     for (int i = 0; i < size; i++) {
-      if (!isSameOrAdaptableTo(capturesAndParams.get(i), implReceiverAndArgs.get(i))) {
+      if (!isSameOrAdaptableTo(capturesAndParams.get(i), implReceiverAndArgs.get(i), factory)) {
         assert false;
       }
     }
 
     if (!enforcedReturnType.isVoidType()
-        && !isSameOrAdaptableTo(implReturnType, enforcedReturnType)) {
+        && !isSameOrAdaptableTo(implReturnType, enforcedReturnType, factory)) {
       assert false;
     }
     return true;
   }
 
-  private DexType getPrimitiveFromBoxed(DexType boxedPrimitive) {
-    return factory().getPrimitiveFromBoxed(boxedPrimitive);
-  }
-
-  private DexType getBoxedForPrimitiveType(DexType primitive) {
+  private static DexType getBoxedForPrimitiveType(DexType primitive, DexItemFactory factory) {
     switch (primitive.descriptor.content[0]) {
       case 'Z':  // byte
       case 'B':  // byte
@@ -69,19 +77,18 @@
       case 'J':  // long
       case 'F':  // float
       case 'D':  // double
-        return factory().getBoxedForPrimitiveType(primitive);
+        return factory.getBoxedForPrimitiveType(primitive);
       default:
         throw new Unreachable("Invalid primitive type descriptor: " + primitive);
     }
   }
 
   // Checks if the types are the same OR type `a` is adaptable to type `b`.
-  private boolean isSameOrAdaptableTo(DexType a, DexType b) {
+  private static boolean isSameOrAdaptableTo(DexType a, DexType b, DexItemFactory factory) {
     if (a == b) {
       return true;
     }
 
-    DexItemFactory factory = factory();
     if (a.isArrayType()) {
       // Arrays are only adaptable to java.lang.Object or other arrays, note that we
       // don't check element type inheritance in the second case since we assume the
@@ -100,7 +107,7 @@
       }
 
       // `a` is primitive and `b` is a supertype of the boxed type `a`.
-      DexType boxedPrimitiveType = getBoxedForPrimitiveType(a);
+      DexType boxedPrimitiveType = getBoxedForPrimitiveType(a, factory);
       if (b == boxedPrimitiveType ||
           b == factory.objectType ||
           b == factory.serializableType ||
@@ -120,7 +127,7 @@
       }
       // `a` is a boxed type for `a*` which can be
       // widened to primitive type `b`.
-      DexType unboxedA = getPrimitiveFromBoxed(a);
+      DexType unboxedA = factory.getPrimitiveFromBoxed(a);
       return unboxedA != null &&
           isSameOrAdaptableTo(unboxedA.descriptor.content[0], b.descriptor.content[0]);
     }
@@ -135,7 +142,7 @@
 
   // For two primitive types `a` is adjustable to `b` iff `a` is the same as `b`
   // or can be converted to `b` via a primitive widening conversion.
-  private boolean isSameOrAdaptableTo(byte from, byte to) {
+  private static boolean isSameOrAdaptableTo(byte from, byte to) {
     if (from == to) {
       return true;
     }
@@ -159,33 +166,31 @@
     }
   }
 
-  @Override
-  protected void prepareInstructions() {
-    DexType[] capturedTypes = captures();
-    DexType[] erasedParams = descriptor().erasedProto.parameters.values;
-    DexType erasedReturnType = descriptor().erasedProto.returnType;
-    DexType[] enforcedParams = descriptor().enforcedProto.parameters.values;
-    DexType enforcedReturnType = descriptor().enforcedProto.returnType;
-
+  public static CfCode build(LambdaClass lambda, DexMethod mainMethod) {
+    DexItemFactory factory = lambda.appView.dexItemFactory();
     LambdaClass.Target target = lambda.target;
     if (target instanceof InvalidLambdaImplTarget) {
-      add(
-          builder -> {
-            InvalidLambdaImplTarget invalidTarget = (InvalidLambdaImplTarget) target;
-            ExceptionThrowingSourceCode.build(builder, invalidTarget.exceptionType);
-          });
-      return;
+      InvalidLambdaImplTarget invalidTarget = (InvalidLambdaImplTarget) target;
+      DexType exceptionType = invalidTarget.exceptionType;
+      return buildThrowingCode(mainMethod, exceptionType, factory);
     }
 
     DexMethod methodToCall = target.callTarget;
+    DexType[] capturedTypes = lambda.descriptor.captures.values;
+    DexType[] erasedParams = lambda.descriptor.erasedProto.parameters.values;
+    DexType erasedReturnType = lambda.descriptor.erasedProto.returnType;
+    DexType[] enforcedParams = lambda.descriptor.enforcedProto.parameters.values;
+    DexType enforcedReturnType = lambda.descriptor.enforcedProto.returnType;
 
     // Only constructor call should use direct invoke type since super
     // and private methods require accessor methods.
     boolean constructorTarget = target.invokeType == Invoke.Type.DIRECT;
-    assert !constructorTarget || methodToCall.name == factory().constructorMethodName;
+    assert !constructorTarget || methodToCall.name == factory.constructorMethodName;
 
+    boolean targetWithReceiver =
+        target.invokeType == Invoke.Type.VIRTUAL || target.invokeType == Invoke.Type.INTERFACE;
     List<DexType> implReceiverAndArgs = new ArrayList<>();
-    if (target.invokeType == Invoke.Type.VIRTUAL || target.invokeType == Invoke.Type.INTERFACE) {
+    if (targetWithReceiver) {
       implReceiverAndArgs.add(methodToCall.holder);
     }
     implReceiverAndArgs.addAll(Lists.newArrayList(methodToCall.proto.parameters.values));
@@ -195,87 +200,129 @@
         || target.invokeType == Invoke.Type.VIRTUAL
         || target.invokeType == Invoke.Type.DIRECT
         || target.invokeType == Invoke.Type.INTERFACE;
-    assert checkSignatures(capturedTypes, enforcedParams,
-        enforcedReturnType, implReceiverAndArgs,
-        constructorTarget ? target.callTarget.holder : implReturnType);
+    assert checkSignatures(
+        capturedTypes,
+        enforcedParams,
+        enforcedReturnType,
+        implReceiverAndArgs,
+        constructorTarget ? target.callTarget.holder : implReturnType,
+        factory);
 
-    // Prepare call arguments.
-    List<ValueType> argValueTypes = new ArrayList<>();
-    List<Integer> argRegisters = new ArrayList<>();
+    int maxStack = 0;
+    Builder<CfInstruction> instructions = ImmutableList.builder();
 
     // If the target is a constructor, we need to create the instance first.
-    // This instance will be the first argument to the call.
+    // This instance will be the first argument to the call and the dup will be on stack at return.
     if (constructorTarget) {
-      int instance = nextRegister(ValueType.OBJECT);
-      add(builder -> builder.addNewInstance(instance, methodToCall.holder));
-      argValueTypes.add(ValueType.OBJECT);
-      argRegisters.add(instance);
+      instructions.add(new CfNew(methodToCall.holder));
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      maxStack += 2;
     }
 
     // Load captures if needed.
     int capturedValues = capturedTypes.length;
     for (int i = 0; i < capturedValues; i++) {
-      ValueType valueType = ValueType.fromDexType(capturedTypes[i]);
-      int register = nextRegister(valueType);
-
-      argValueTypes.add(valueType);
-      argRegisters.add(register);
-
-      // Read field into tmp local.
       DexField field = lambda.getCaptureField(i);
-      add(builder -> builder.addInstanceGet(register, getReceiverRegister(), field));
+      ValueType valueType = ValueType.fromDexType(field.type);
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
+      maxStack += valueType.requiredRegisters();
     }
 
     // Prepare arguments.
+    int maxLocals = 1; // Local 0 is the lambda/receiver.
     for (int i = 0; i < erasedParams.length; i++) {
+      ValueType valueType = ValueType.fromDexType(mainMethod.getParameters().values[i]);
+      instructions.add(new CfLoad(valueType, maxLocals));
+      maxLocals += valueType.requiredRegisters();
       DexType expectedParamType = implReceiverAndArgs.get(i + capturedValues);
-      argValueTypes.add(ValueType.fromDexType(expectedParamType));
-      argRegisters.add(prepareParameterValue(
-          getParamRegister(i), erasedParams[i], enforcedParams[i], expectedParamType));
+      maxStack =
+          Math.max(
+              maxStack,
+              prepareParameterValue(
+                  erasedParams[i], enforcedParams[i], expectedParamType, instructions, factory));
     }
 
-    // Method call to the method implementing lambda or method-ref.
-    add(
-        builder ->
-            builder.addInvoke(
-                target.invokeType,
-                methodToCall,
-                methodToCall.proto,
-                argValueTypes,
-                argRegisters,
-                target.isInterface()));
+    instructions.add(
+        new CfInvoke(target.invokeType.getCfOpcode(), methodToCall, target.isInterface()));
+    DexType methodToCallReturnType = methodToCall.getReturnType();
+    if (!methodToCallReturnType.isVoidType()) {
+      maxStack =
+          Math.max(maxStack, ValueType.fromDexType(methodToCallReturnType).requiredRegisters());
+    }
 
-    // Does the method have return value?
     if (enforcedReturnType.isVoidType()) {
-      add(IRBuilder::addReturn);
-    } else if (constructorTarget) {
-      // Return newly created instance
-      int instanceRegister = argRegisters.get(0);
-      int adjustedValue = prepareReturnValue(instanceRegister,
-          erasedReturnType, enforcedReturnType, methodToCall.holder);
-      add(builder -> builder.addReturn(adjustedValue));
+      if (!methodToCallReturnType.isVoidType()) {
+        instructions.add(
+            new CfStackInstruction(methodToCallReturnType.isWideType() ? Opcode.Pop2 : Opcode.Pop));
+      }
+      instructions.add(new CfReturnVoid());
     } else {
-      ValueType implValueType = ValueType.fromDexType(implReturnType);
-      int tempValue = nextRegister(implValueType);
-      add(builder -> builder.addMoveResult(tempValue));
-      int adjustedValue = prepareReturnValue(tempValue,
-          erasedReturnType, enforcedReturnType, methodToCall.proto.returnType);
-      add(builder -> builder.addReturn(adjustedValue));
+      // Either the new instance or the called-method result is on top of stack.
+      assert constructorTarget || !methodToCallReturnType.isVoidType();
+      maxStack =
+          Math.max(
+              maxStack,
+              prepareReturnValue(
+                  erasedReturnType,
+                  enforcedReturnType,
+                  constructorTarget ? methodToCall.holder : methodToCallReturnType,
+                  instructions,
+                  factory));
+      instructions.add(new CfReturn(ValueType.fromDexType(enforcedReturnType)));
     }
+
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    CfCode code =
+        new CfCode(
+            mainMethod.holder,
+            maxStack,
+            maxLocals,
+            instructions.build(),
+            tryCatchRanges,
+            localVariables);
+    return code;
+  }
+
+  private static CfCode buildThrowingCode(
+      DexMethod method, DexType exceptionType, DexItemFactory factory) {
+    DexProto initProto = factory.createProto(factory.voidType);
+    DexMethod initMethod =
+        factory.createMethod(exceptionType, initProto, factory.constructorMethodName);
+    int maxStack = 2;
+    int maxLocals = 1;
+    for (DexType param : method.proto.parameters.values) {
+      maxLocals += ValueType.fromDexType(param).requiredRegisters();
+    }
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    return new CfCode(
+        method.holder,
+        maxStack,
+        maxLocals,
+        ImmutableList.of(
+            new CfNew(exceptionType),
+            new CfStackInstruction(Opcode.Dup),
+            new CfInvoke(Opcodes.INVOKESPECIAL, initMethod, false),
+            new CfThrow()),
+        tryCatchRanges,
+        localVariables);
   }
 
   // Adds necessary casts and transformations to adjust the value
   // returned by impl-method to expected return type of the method.
-  private int prepareReturnValue(int register,
-      DexType erasedType, DexType enforcedType, DexType actualType) {
-    // `actualType` must be adjusted to `enforcedType` first.
-    register = adjustType(register, actualType, enforcedType, true);
-
+  private static int prepareReturnValue(
+      DexType erasedType,
+      DexType enforcedType,
+      DexType actualType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
     // `erasedType` and `enforcedType` may only differ when they both
     // are class types and `erasedType` is a base type of `enforcedType`,
     // so no transformation is actually needed.
-    assert LambdaDescriptor.isSameOrDerived(factory(), enforcedType, erasedType);
-    return register;
+    assert LambdaDescriptor.isSameOrDerived(factory, enforcedType, erasedType);
+    return adjustType(actualType, enforcedType, true, instructions, factory);
   }
 
   // Adds necessary casts and transformations to adjust parameter
@@ -285,16 +332,50 @@
   // be converted to enforced parameter type (`enforcedType`), which,
   // in its turn, may need to be adjusted to the parameter type of
   // the impl-method (`expectedType`).
-  private int prepareParameterValue(int register,
-      DexType erasedType, DexType enforcedType, DexType expectedType) {
-    register = enforceParameterType(register, erasedType, enforcedType);
-    register = adjustType(register, enforcedType, expectedType, false);
-    return register;
+  private static int prepareParameterValue(
+      DexType erasedType,
+      DexType enforcedType,
+      DexType expectedType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
+    enforceParameterType(erasedType, enforcedType, instructions, factory);
+    return adjustType(enforcedType, expectedType, false, instructions, factory);
   }
 
-  private int adjustType(int register, DexType fromType, DexType toType, boolean returnType) {
+  private static void enforceParameterType(
+      DexType paramType,
+      DexType enforcedType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
+    // `paramType` must be either same as `enforcedType` or both must be class
+    // types and `enforcedType` must be a subclass of `paramType` in which case
+    // a cast need to be inserted.
+    if (paramType != enforcedType) {
+      assert LambdaDescriptor.isSameOrDerived(factory, enforcedType, paramType);
+      instructions.add(new CfCheckCast(enforcedType));
+    }
+  }
+
+  private static int adjustType(
+      DexType fromType,
+      DexType toType,
+      boolean returnType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
+    internalAdjustType(fromType, toType, returnType, instructions, factory);
+    return Math.max(
+        ValueType.fromDexType(fromType).requiredRegisters(),
+        ValueType.fromDexType(toType).requiredRegisters());
+  }
+
+  private static void internalAdjustType(
+      DexType fromType,
+      DexType toType,
+      boolean returnType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
     if (fromType == toType) {
-      return register;
+      return;
     }
 
     boolean fromTypePrimitive = fromType.isPrimitiveType();
@@ -302,7 +383,8 @@
 
     // If both are primitive they must be convertible via primitive widening conversion.
     if (fromTypePrimitive && toTypePrimitive) {
-      return addPrimitiveWideningConversion(register, fromType, toType);
+      addPrimitiveWideningConversion(fromType, toType, instructions);
+      return;
     }
 
     // If the first one is a boxed primitive type and the second one is a primitive
@@ -310,62 +392,64 @@
     // widening conversion.
     if (toTypePrimitive) {
       DexType boxedType = fromType;
-      if (boxedType == factory().objectType) {
+      if (boxedType == factory.objectType) {
         // We are in situation when from(=java.lang.Object) is being adjusted to a
         // primitive type, in which case we assume it is of proper box type.
-        boxedType = getBoxedForPrimitiveType(toType);
-        register = castToBoxedType(register, boxedType);
+        boxedType = getBoxedForPrimitiveType(toType, factory);
+        instructions.add(new CfCheckCast(boxedType));
       }
-      DexType fromTypeAsPrimitive = getPrimitiveFromBoxed(boxedType);
+      DexType fromTypeAsPrimitive = factory.getPrimitiveFromBoxed(boxedType);
       if (fromTypeAsPrimitive != null) {
-        int unboxedRegister = addPrimitiveUnboxing(register, fromTypeAsPrimitive, boxedType);
-        return addPrimitiveWideningConversion(unboxedRegister, fromTypeAsPrimitive, toType);
+        addPrimitiveUnboxing(fromTypeAsPrimitive, boxedType, instructions, factory);
+        addPrimitiveWideningConversion(fromTypeAsPrimitive, toType, instructions);
+        return;
       }
     }
 
     // If the first one is a primitive type and the second one is a boxed
     // type for this primitive type, just box the value.
     if (fromTypePrimitive) {
-      DexType boxedFromType = getBoxedForPrimitiveType(fromType);
-      if (toType == boxedFromType ||
-          toType == factory().objectType ||
-          toType == factory().serializableType ||
-          toType == factory().comparableType ||
-          (boxedFromType != factory().booleanType &&
-              boxedFromType != factory().charType &&
-              toType == factory().boxedNumberType)) {
-        return addPrimitiveBoxing(register, fromType, boxedFromType);
+      DexType boxedFromType = getBoxedForPrimitiveType(fromType, factory);
+      if (toType == boxedFromType
+          || toType == factory.objectType
+          || toType == factory.serializableType
+          || toType == factory.comparableType
+          || (boxedFromType != factory.booleanType
+              && boxedFromType != factory.charType
+              && toType == factory.boxedNumberType)) {
+        addPrimitiveBoxing(fromType, boxedFromType, instructions, factory);
+        return;
       }
     }
 
-    if (fromType.isArrayType() && (toType == factory().objectType || toType.isArrayType())) {
+    if (fromType.isArrayType() && (toType == factory.objectType || toType.isArrayType())) {
       // If `fromType` is an array and `toType` is java.lang.Object, no cast is needed.
       // If both `fromType` and `toType` are arrays, no cast is needed since we assume
       // the input code is verifiable.
-      return register;
+      return;
     }
 
     if ((fromType.isClassType() && toType.isClassType())
-        || (fromType == factory().objectType && toType.isArrayType())) {
-      if (returnType) {
+        || (fromType == factory.objectType && toType.isArrayType())) {
+      if (returnType && toType != factory.objectType) {
         // For return type adjustment in case `fromType` and `toType` are both reference types,
         // `fromType` does NOT have to be deriving from `toType` and we need to add a cast.
         // NOTE: we don't check `toType` for being actually a supertype, since we
         // might not have full classpath with inheritance information to do that.
-        int finalRegister = register;
-        add(builder -> builder.addCheckCast(finalRegister, toType));
+        instructions.add(new CfCheckCast(toType));
       }
-      return register;
+      return;
     }
 
     throw new Unreachable("Unexpected type adjustment from "
         + fromType.toSourceString() + " to " + toType);
   }
 
-  private int addPrimitiveWideningConversion(int register, DexType fromType, DexType toType) {
+  private static void addPrimitiveWideningConversion(
+      DexType fromType, DexType toType, Builder<CfInstruction> instructions) {
     assert fromType.isPrimitiveType() && toType.isPrimitiveType();
     if (fromType == toType) {
-      return register;
+      return;
     }
 
     NumericType from = NumericType.fromDexType(fromType);
@@ -379,14 +463,13 @@
           if (from != NumericType.BYTE) {
             break; // Only BYTE can be converted to SHORT via widening conversion.
           }
-          int result = nextRegister(ValueType.INT);
-          add(builder -> builder.addConversion(to, NumericType.INT, result, register));
-          return result;
+            instructions.add(new CfNumberConversion(NumericType.INT, to));
+            return;
         }
 
         case INT:
           if (from == NumericType.BYTE || from == NumericType.CHAR || from == NumericType.SHORT) {
-            return register; // No actual conversion is needed.
+            return;
           }
           break;
 
@@ -394,27 +477,24 @@
           if (from == NumericType.FLOAT || from == NumericType.DOUBLE) {
             break; // Not a widening conversion.
           }
-          int result = nextRegister(ValueType.LONG);
-          add(builder -> builder.addConversion(to, NumericType.INT, result, register));
-          return result;
+            instructions.add(new CfNumberConversion(NumericType.INT, to));
+            return;
         }
 
         case FLOAT: {
           if (from == NumericType.DOUBLE) {
             break; // Not a widening conversion.
           }
-          int result = nextRegister(ValueType.FLOAT);
           NumericType type = (from == NumericType.LONG) ? NumericType.LONG : NumericType.INT;
-          add(builder -> builder.addConversion(to, type, result, register));
-          return result;
+            instructions.add(new CfNumberConversion(type, to));
+            return;
         }
 
         case DOUBLE: {
-          int result = nextRegister(ValueType.DOUBLE);
           NumericType type = (from == NumericType.FLOAT || from == NumericType.LONG)
               ? from : NumericType.INT;
-          add(builder -> builder.addConversion(to, type, result, register));
-          return result;
+            instructions.add(new CfNumberConversion(type, to));
+            return;
         }
         default:
           // exception is thrown below
@@ -426,8 +506,7 @@
         "converted to " + toType.toSourceString() + " via primitive widening conversion.");
   }
 
-  private DexMethod getUnboxMethod(byte primitive, DexType boxType) {
-    DexItemFactory factory = factory();
+  private static DexMethod getUnboxMethod(byte primitive, DexType boxType, DexItemFactory factory) {
     DexProto proto;
     switch (primitive) {
       case 'Z':  // byte
@@ -459,53 +538,23 @@
     }
   }
 
-  private int addPrimitiveUnboxing(int register, DexType primitiveType, DexType boxType) {
-    DexMethod method = getUnboxMethod(primitiveType.descriptor.content[0], boxType);
-
-    List<ValueType> argValueTypes = ImmutableList.of(ValueType.OBJECT);
-    List<Integer> argRegisters = Collections.singletonList(register);
-    add(
-        builder ->
-            builder.addInvoke(
-                Invoke.Type.VIRTUAL,
-                method,
-                method.proto,
-                argValueTypes,
-                argRegisters,
-                false /* isInterface */));
-
-    ValueType valueType = ValueType.fromDexType(primitiveType);
-    int result = nextRegister(valueType);
-    add(builder -> builder.addMoveResult(result));
-    return result;
+  private static void addPrimitiveUnboxing(
+      DexType primitiveType,
+      DexType boxType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
+    DexMethod method = getUnboxMethod(primitiveType.descriptor.content[0], boxType, factory);
+    instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
   }
 
-  private int castToBoxedType(int register, DexType boxType) {
-    add(builder -> builder.addCheckCast(register, boxType));
-    return register;
-  }
-
-  private int addPrimitiveBoxing(int register, DexType primitiveType, DexType boxType) {
+  private static void addPrimitiveBoxing(
+      DexType primitiveType,
+      DexType boxType,
+      Builder<CfInstruction> instructions,
+      DexItemFactory factory) {
     // Generate factory method fo boxing.
-    DexItemFactory factory = factory();
     DexProto proto = factory.createProto(boxType, primitiveType);
     DexMethod method = factory.createMethod(boxType, proto, factory.valueOfMethodName);
-
-    ValueType valueType = ValueType.fromDexType(primitiveType);
-    List<ValueType> argValueTypes = ImmutableList.of(valueType);
-    List<Integer> argRegisters = Collections.singletonList(register);
-    add(
-        builder ->
-            builder.addInvoke(
-                Invoke.Type.STATIC,
-                method,
-                method.proto,
-                argValueTypes,
-                argRegisters,
-                false /* isInterface */));
-
-    int result = nextRegister(ValueType.OBJECT);
-    add(builder -> builder.addMoveResult(result));
-    return result;
+    instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
deleted file mode 100644
index 7c24b63..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSynthesizedCode.java
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2019, 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.desugar;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import java.util.function.Consumer;
-
-public class LambdaMainMethodSynthesizedCode extends LambdaSynthesizedCode {
-
-  private final DexMethod mainMethod;
-
-  LambdaMainMethodSynthesizedCode(LambdaClass lambda, DexMethod mainMethod) {
-    super(lambda);
-    this.mainMethod = mainMethod;
-  }
-
-  @Override
-  public SourceCodeProvider getSourceCodeProvider() {
-    return callerPosition -> new LambdaMainMethodSourceCode(lambda, mainMethod, callerPosition);
-  }
-
-  @Override
-  public Consumer<UseRegistry> getRegistryCallback() {
-    return registry -> {
-      LambdaClass.Target target = lambda.target;
-      assert target.invokeType == Invoke.Type.STATIC
-          || target.invokeType == Invoke.Type.VIRTUAL
-          || target.invokeType == Invoke.Type.DIRECT
-          || target.invokeType == Invoke.Type.INTERFACE;
-
-      boolean constructorTarget = target.invokeType == Type.DIRECT;
-      if (constructorTarget) {
-        registry.registerNewInstance(target.callTarget.holder);
-      }
-
-      DexType[] capturedTypes = captures();
-      for (int i = 0; i < capturedTypes.length; i++) {
-        registry.registerInstanceFieldRead(lambda.getCaptureField(i));
-      }
-
-      switch (target.invokeType) {
-        case DIRECT:
-          registry.registerInvokeDirect(target.callTarget);
-          break;
-        case INTERFACE:
-          registry.registerInvokeInterface(target.callTarget);
-          break;
-        case STATIC:
-          registry.registerInvokeStatic(target.callTarget);
-          break;
-        case VIRTUAL:
-          registry.registerInvokeVirtual(target.callTarget);
-          break;
-        default:
-          throw new Unreachable();
-      }
-    };
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
deleted file mode 100644
index 5643288..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaSynthesizedCode.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2019, 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.desugar;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
-import java.util.function.Consumer;
-
-abstract class LambdaSynthesizedCode extends SynthesizedCode {
-
-  final LambdaClass lambda;
-
-  LambdaSynthesizedCode(LambdaClass lambda) {
-    super(null);
-    this.lambda = lambda;
-  }
-
-  final DexItemFactory dexItemFactory() {
-    return lambda.appView.dexItemFactory();
-  }
-
-  final LambdaDescriptor descriptor() {
-    return lambda.descriptor;
-  }
-
-  final DexType[] captures() {
-    DexTypeList captures = descriptor().captures;
-    assert captures != null;
-    return captures.values;
-  }
-
-  @Override
-  public abstract SourceCodeProvider getSourceCodeProvider();
-
-  @Override
-  public abstract Consumer<UseRegistry> getRegistryCallback();
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
deleted file mode 100644
index fc12713..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
+++ /dev/null
@@ -1,62 +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.desugar;
-
-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.DexTypeList;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-
-// Represents source code of synthesized lambda class methods.
-abstract class SynthesizedLambdaSourceCode extends SyntheticSourceCode {
-
-  final DexMethod currentMethod;
-  final LambdaClass lambda;
-
-  SynthesizedLambdaSourceCode(
-      LambdaClass lambda, DexMethod currentMethod, Position callerPosition, DexType receiver) {
-    super(receiver, currentMethod, callerPosition);
-    this.lambda = lambda;
-    this.currentMethod = currentMethod;
-  }
-
-  SynthesizedLambdaSourceCode(
-      LambdaClass lambda, DexMethod currentMethod, Position callerPosition) {
-    this(lambda, currentMethod, callerPosition, lambda.type);
-  }
-
-  final LambdaDescriptor descriptor() {
-    return lambda.descriptor;
-  }
-
-  final DexType[] captures() {
-    DexTypeList captures = descriptor().captures;
-    assert captures != null;
-    return captures.values;
-  }
-
-  final DexItemFactory factory() {
-    return lambda.appView.dexItemFactory();
-  }
-
-  final int enforceParameterType(int register, DexType paramType, DexType enforcedType) {
-    // `paramType` must be either same as `enforcedType` or both must be class
-    // types and `enforcedType` must be a subclass of `paramType` in which case
-    // a cast need to be inserted.
-    if (paramType != enforcedType) {
-      assert LambdaDescriptor.isSameOrDerived(factory(), enforcedType, paramType);
-      add(builder -> builder.addCheckCast(register, enforcedType));
-    }
-    return register;
-  }
-
-  @Override
-  public String toString() {
-    return currentMethod.toSourceString();
-  }
-}
-
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
index 90654b2..ec0a554 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BooleanMethodRewrites.java
@@ -4,52 +4,27 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.And;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.cf.code.CfLogicalBinop;
+import com.android.tools.r8.cf.code.CfLogicalBinop.Opcode;
 import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Or;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.Xor;
-import java.util.List;
-import java.util.Set;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
 
 public final class BooleanMethodRewrites {
-  public static void rewriteLogicalAnd(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> inValues = invoke.inValues();
-    assert inValues.size() == 2;
 
-    iterator.replaceCurrentInstruction(
-        new And(NumericType.INT, invoke.outValue(), inValues.get(0), inValues.get(1)));
+  private static MethodInvokeRewriter createRewriter(CfLogicalBinop.Opcode op) {
+    return (invoke, factory) -> new CfLogicalBinop(op, NumericType.INT);
   }
 
-  public static void rewriteLogicalOr(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> inValues = invoke.inValues();
-    assert inValues.size() == 2;
-
-    iterator.replaceCurrentInstruction(
-        new Or(NumericType.INT, invoke.outValue(), inValues.get(0), inValues.get(1)));
+  public static MethodInvokeRewriter rewriteLogicalAnd() {
+    return createRewriter(Opcode.And);
   }
 
-  public static void rewriteLogicalXor(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> inValues = invoke.inValues();
-    assert inValues.size() == 2;
+  public static MethodInvokeRewriter rewriteLogicalOr() {
+    return createRewriter(Opcode.Or);
+  }
 
-    iterator.replaceCurrentInstruction(
-        new Xor(NumericType.INT, invoke.outValue(), inValues.get(0), inValues.get(1)));
+  public static MethodInvokeRewriter rewriteLogicalXor() {
+    return createRewriter(Opcode.Xor);
   }
 
   private BooleanMethodRewrites() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
index 354040f..64c94c5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
@@ -4,54 +4,31 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Collections;
-import java.util.Set;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import org.objectweb.asm.Opcodes;
 
 public final class CollectionMethodRewrites {
 
   private CollectionMethodRewrites() {}
 
-  public static void rewriteListOfEmpty(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    rewriteToCollectionMethod(invoke, iterator, factory, "emptyList");
+  public static MethodInvokeRewriter rewriteListOfEmpty() {
+    return rewriteToCollectionMethod("emptyList");
   }
 
-  public static void rewriteSetOfEmpty(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    rewriteToCollectionMethod(invoke, iterator, factory, "emptySet");
+  public static MethodInvokeRewriter rewriteSetOfEmpty() {
+    return rewriteToCollectionMethod("emptySet");
   }
 
-  public static void rewriteMapOfEmpty(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    rewriteToCollectionMethod(invoke, iterator, factory, "emptyMap");
+  public static MethodInvokeRewriter rewriteMapOfEmpty() {
+    return rewriteToCollectionMethod("emptyMap");
   }
 
-  private static void rewriteToCollectionMethod(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      String methodName) {
-    assert invoke.inValues().isEmpty();
-
-    DexMethod collectionsEmptyList =
-        factory.createMethod(factory.collectionsType, invoke.getInvokedMethod().proto, methodName);
-    InvokeStatic newInvoke =
-        new InvokeStatic(collectionsEmptyList, invoke.outValue(), Collections.emptyList());
-    iterator.replaceCurrentInstruction(newInvoke);
+  private static MethodInvokeRewriter rewriteToCollectionMethod(String methodName) {
+    return (invoke, factory) ->
+        new CfInvoke(
+            Opcodes.INVOKESTATIC,
+            factory.createMethod(factory.collectionsType, invoke.getMethod().proto, methodName),
+            false);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
index 3601c51..8670d3b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/FloatMethodRewrites.java
@@ -4,29 +4,20 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import org.objectweb.asm.Opcodes;
 
 public final class FloatMethodRewrites {
 
   private FloatMethodRewrites() {}
 
-  public static void rewriteHashCode(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeStatic mathInvoke =
-        new InvokeStatic(
+  public static MethodInvokeRewriter rewriteHashCode() {
+    return (invoke, factory) ->
+        new CfInvoke(
+            Opcodes.INVOKESTATIC,
             factory.createMethod(
-                factory.boxedFloatType, invoke.getInvokedMethod().proto, "floatToIntBits"),
-            invoke.outValue(),
-            invoke.inValues(),
+                factory.boxedFloatType, invoke.getMethod().proto, "floatToIntBits"),
             false);
-    iterator.replaceCurrentInstruction(mathInvoke);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
index 348194b..5c29f81 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/LongMethodRewrites.java
@@ -4,28 +4,16 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.Cmp;
+import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.ir.code.Cmp.Bias;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Value;
-import java.util.List;
-import java.util.Set;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
 
 public final class LongMethodRewrites {
 
   private LongMethodRewrites() {}
 
-  public static void rewriteCompare(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> inValues = invoke.inValues();
-    assert inValues.size() == 2;
-    iterator.replaceCurrentInstruction(
-        new Cmp(NumericType.LONG, Bias.NONE, invoke.outValue(), inValues.get(0), inValues.get(1)));
+  public static MethodInvokeRewriter rewriteCompare() {
+    return (invoke, factory) -> new CfCmp(Bias.NONE, NumericType.LONG);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
index 4a413bc..050aefa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
@@ -1,56 +1,44 @@
 package com.android.tools.r8.ir.desugar.backports;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.Add;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.Value;
-import java.util.List;
-import java.util.Set;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
 
 public final class NumericMethodRewrites {
-  public static void rewriteToInvokeMath(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeStatic mathInvoke =
-        new InvokeStatic(
-            factory.createMethod(
-                factory.mathType, invoke.getInvokedMethod().proto, invoke.getInvokedMethod().name),
-            invoke.outValue(),
-            invoke.inValues(),
-            false);
-    iterator.replaceCurrentInstruction(mathInvoke);
+
+  public static MethodInvokeRewriter rewriteToInvokeMath() {
+    return (invoke, factory) -> {
+      DexMethod method = invoke.getMethod();
+      return new CfInvoke(
+          Opcodes.INVOKESTATIC,
+          factory.createMethod(factory.mathType, method.proto, method.name),
+          false);
+    };
   }
 
-  public static void rewriteToAddInstruction(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> values = invoke.inValues();
-    assert values.size() == 2;
-
-    NumericType numericType = NumericType.fromDexType(invoke.getReturnType());
-    Add add = new Add(numericType, invoke.outValue(), values.get(0), values.get(1));
-    iterator.replaceCurrentInstruction(add);
+  public static MethodInvokeRewriter rewriteToAddInstruction() {
+    return (invoke, factory) -> {
+      NumericType numericType = NumericType.fromDexType(invoke.getMethod().getReturnType());
+      return new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, numericType);
+    };
   }
 
-  public static void rewriteAsIdentity(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    List<Value> values = invoke.inValues();
-    assert values.size() == 1;
-    if (invoke.hasOutValue()) {
-      invoke.outValue().replaceUsers(values.get(0));
-    }
-    // TODO(b/152853271): Debugging information is lost here (DebugLocalWrite may be required).
-    iterator.removeOrReplaceByDebugLocalRead();
+  public static MethodInvokeRewriter rewriteAsIdentity() {
+    return new FullMethodInvokeRewriter() {
+      @Override
+      public void rewrite(
+          CfInvoke invoke, ListIterator<CfInstruction> iterator, DexItemFactory factory) {
+        // The invoke consumes the stack value and pushes another assumed to be the same.
+        iterator.remove();
+      }
+    };
   }
 
   private NumericMethodRewrites() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
index de16e98..69253be 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethodRewrites.java
@@ -4,43 +4,41 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 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.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.FullMethodInvokeRewriter;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
 
 public final class ObjectsMethodRewrites {
 
-  public static void rewriteToArraysHashCode(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    DexType arraysType = factory.createType(factory.arraysDescriptor);
-    DexMethod hashCodeMethod =
-        factory.createMethod(arraysType, invoke.getInvokedMethod().proto, "hashCode");
-    InvokeStatic arraysHashCode =
-        new InvokeStatic(hashCodeMethod, invoke.outValue(), invoke.inValues(), false);
-    iterator.replaceCurrentInstruction(arraysHashCode);
+  public static MethodInvokeRewriter rewriteToArraysHashCode() {
+    return (invoke, factory) -> {
+      DexType arraysType = factory.createType(factory.arraysDescriptor);
+      return new CfInvoke(
+          Opcodes.INVOKESTATIC,
+          factory.createMethod(arraysType, invoke.getMethod().proto, "hashCode"),
+          false);
+    };
   }
 
-  public static void rewriteRequireNonNull(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getClass =
-        new InvokeVirtual(factory.objectMembers.getClass, null, invoke.inValues());
-    if (invoke.hasOutValue()) {
-      affectedValues.addAll(invoke.outValue().affectedValues());
-      invoke.outValue().replaceUsers(invoke.inValues().get(0));
-      invoke.setOutValue(null);
-    }
-    iterator.replaceCurrentInstruction(getClass);
+  public static MethodInvokeRewriter rewriteRequireNonNull() {
+    return new FullMethodInvokeRewriter() {
+
+      @Override
+      public void rewrite(
+          CfInvoke invoke, ListIterator<CfInstruction> iterator, DexItemFactory factory) {
+        iterator.remove();
+        // requireNonNull returns the operand, so dup top-of-stack, do getClass and pop the class.
+        iterator.add(new CfStackInstruction(Opcode.Dup));
+        iterator.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false));
+        iterator.add(new CfStackInstruction(Opcode.Pop));
+      }
+    };
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
index 1334c62..d66d7f5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/OptionalMethodRewrites.java
@@ -4,69 +4,40 @@
 
 package com.android.tools.r8.ir.desugar.backports;
 
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.Value;
-import java.util.Set;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import java.util.function.Function;
+import org.objectweb.asm.Opcodes;
 
 public final class OptionalMethodRewrites {
 
   private OptionalMethodRewrites() {}
 
-  public static void rewriteOrElseGet(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getInvoke =
-        new InvokeVirtual(
-            factory.createMethod(factory.optionalType, invoke.getInvokedMethod().proto, "get"),
-            invoke.outValue(),
-            invoke.inValues());
-    iterator.replaceCurrentInstruction(getInvoke);
+  private static MethodInvokeRewriter createRewriter(
+      Function<DexItemFactory, DexType> holderTypeSupplier, String methodName) {
+    return (invoke, factory) ->
+        new CfInvoke(
+            Opcodes.INVOKEVIRTUAL,
+            factory.createMethod(
+                holderTypeSupplier.apply(factory), invoke.getMethod().proto, methodName),
+            false);
   }
 
-  public static void rewriteDoubleOrElseGet(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getInvoke =
-        new InvokeVirtual(
-            factory.createMethod(
-                factory.optionalDoubleType, invoke.getInvokedMethod().proto, "getAsDouble"),
-            invoke.outValue(),
-            invoke.inValues());
-    iterator.replaceCurrentInstruction(getInvoke);
+  public static MethodInvokeRewriter rewriteOrElseGet() {
+    return createRewriter(factory -> factory.optionalType, "get");
   }
 
-  public static void rewriteIntOrElseGet(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getInvoke =
-        new InvokeVirtual(
-            factory.createMethod(
-                factory.optionalIntType, invoke.getInvokedMethod().proto, "getAsInt"),
-            invoke.outValue(),
-            invoke.inValues());
-    iterator.replaceCurrentInstruction(getInvoke);
+  public static MethodInvokeRewriter rewriteDoubleOrElseGet() {
+    return createRewriter(factory -> factory.optionalDoubleType, "getAsDouble");
   }
 
-  public static void rewriteLongOrElseGet(
-      InvokeMethod invoke,
-      InstructionListIterator iterator,
-      DexItemFactory factory,
-      Set<Value> affectedValues) {
-    InvokeVirtual getInvoke =
-        new InvokeVirtual(
-            factory.createMethod(
-                factory.optionalLongType, invoke.getInvokedMethod().proto, "getAsLong"),
-            invoke.outValue(),
-            invoke.inValues());
-    iterator.replaceCurrentInstruction(getInvoke);
+  public static MethodInvokeRewriter rewriteIntOrElseGet() {
+    return createRewriter(factory -> factory.optionalIntType, "getAsInt");
+  }
+
+  public static MethodInvokeRewriter rewriteLongOrElseGet() {
+    return createRewriter(factory -> factory.optionalLongType, "getAsLong");
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index c937356..4a2c485 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
 import com.android.tools.r8.dex.Constants;
@@ -421,6 +422,7 @@
         field,
         FieldAccessFlags.fromSharedAccessFlags(
             Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC),
+        NO_FIELD_TYPE_SIGNATURE,
         DexAnnotationSet.empty(),
         null);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index db7f56c..a29db28 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -288,12 +289,22 @@
     int size = capture.length();
     DexEncodedField[] result = new DexEncodedField[1 + size];
 
-    result[0] = new DexEncodedField(group.getLambdaIdField(factory),
-        CAPTURE_FIELD_FLAGS_RELAXED, DexAnnotationSet.empty(), null);
+    result[0] =
+        new DexEncodedField(
+            group.getLambdaIdField(factory),
+            CAPTURE_FIELD_FLAGS_RELAXED,
+            NO_FIELD_TYPE_SIGNATURE,
+            DexAnnotationSet.empty(),
+            null);
 
     for (int id = 0; id < size; id++) {
-      result[id + 1] = new DexEncodedField(group.getCaptureField(factory, id),
-          CAPTURE_FIELD_FLAGS_RELAXED, DexAnnotationSet.empty(), null);
+      result[id + 1] =
+          new DexEncodedField(
+              group.getCaptureField(factory, id),
+              CAPTURE_FIELD_FLAGS_RELAXED,
+              NO_FIELD_TYPE_SIGNATURE,
+              DexAnnotationSet.empty(),
+              null);
     }
 
     return result;
@@ -313,7 +324,11 @@
             DexField field = group.getSingletonInstanceField(factory, info.id);
             DexEncodedField encodedField =
                 new DexEncodedField(
-                    field, SINGLETON_FIELD_FLAGS, DexAnnotationSet.empty(), DexValueNull.NULL);
+                    field,
+                    SINGLETON_FIELD_FLAGS,
+                    NO_FIELD_TYPE_SIGNATURE,
+                    DexAnnotationSet.empty(),
+                    DexValueNull.NULL);
             result.add(encodedField);
 
             // Record that the field is definitely not null. It is guaranteed to be assigned in the
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
index 3f3277f..a20b8bf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryFieldSynthesis.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.library;
 
 import static com.android.tools.r8.graph.DexLibraryClass.asLibraryClassOrNull;
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
@@ -38,6 +39,7 @@
                       field,
                       FieldAccessFlags.fromCfAccessFlags(
                           Constants.ACC_PRIVATE | Constants.ACC_FINAL),
+                      NO_FIELD_TYPE_SIGNATURE,
                       DexAnnotationSet.empty(),
                       null));
             }
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
new file mode 100644
index 0000000..8d8ca9f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -0,0 +1,243 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturn;
+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.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import org.objectweb.asm.Opcodes;
+
+public class ForwardMethodBuilder {
+
+  public static ForwardMethodBuilder builder(DexItemFactory factory) {
+    return new ForwardMethodBuilder(factory);
+  }
+
+  private enum InvokeType {
+    STATIC,
+    VIRTUAL,
+    SPECIAL
+  }
+
+  private final DexItemFactory factory;
+
+  private DexMethod sourceMethod = null;
+  private DexMethod targetMethod = null;
+
+  private boolean staticSource = false;
+
+  private InvokeType invokeType = null;
+  private Boolean isInterface = null;
+  private boolean castResult = false;
+  private boolean isConstructorDelegate = false;
+  private AppInfoWithClassHierarchy appInfoForCastArguments = null;
+
+  private ForwardMethodBuilder(DexItemFactory factory) {
+    this.factory = factory;
+  }
+
+  public ForwardMethodBuilder setNonStaticSource(DexMethod method) {
+    sourceMethod = method;
+    staticSource = false;
+    return this;
+  }
+
+  public ForwardMethodBuilder setStaticSource(DexMethod method) {
+    sourceMethod = method;
+    staticSource = true;
+    return this;
+  }
+
+  public ForwardMethodBuilder setStaticTarget(DexMethod method, boolean isInterface) {
+    targetMethod = method;
+    invokeType = InvokeType.STATIC;
+    this.isInterface = isInterface;
+    return this;
+  }
+
+  public ForwardMethodBuilder setVirtualTarget(DexMethod method, boolean isInterface) {
+    targetMethod = method;
+    invokeType = InvokeType.VIRTUAL;
+    this.isInterface = isInterface;
+    return this;
+  }
+
+  public ForwardMethodBuilder setDirectTarget(DexMethod method, boolean isInterface) {
+    targetMethod = method;
+    invokeType = InvokeType.SPECIAL;
+    this.isInterface = isInterface;
+    return this;
+  }
+
+  public ForwardMethodBuilder setCastResult() {
+    castResult = true;
+    return this;
+  }
+
+  public ForwardMethodBuilder setCastArguments(AppInfoWithClassHierarchy appInfo) {
+    appInfoForCastArguments = appInfo;
+    return this;
+  }
+
+  public ForwardMethodBuilder setConstructorTarget(DexMethod method, DexItemFactory factory) {
+    assert method.isInstanceInitializer(factory);
+    targetMethod = method;
+    isConstructorDelegate = true;
+    invokeType = InvokeType.SPECIAL;
+    isInterface = false;
+    return this;
+  }
+
+  public CfCode build() {
+    assert validate();
+    int maxStack = 0;
+    int maxLocals = 0;
+    Builder<CfInstruction> instructions = ImmutableList.builder();
+    if (isConstructorDelegate) {
+      // A constructor delegate allocates a new instance of the type.
+      // It is dup'ed on the stack so it is ready to return after the invoke call.
+      assert isStaticSource();
+      assert invokeType == InvokeType.SPECIAL;
+      instructions.add(new CfNew(targetMethod.getHolderType()));
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      maxStack += 2;
+    } else if (!isStaticSource()) {
+      // If source is not static, load the receiver.
+      instructions.add(new CfLoad(ValueType.OBJECT, maxLocals));
+      maybeInsertArgumentCast(-1, sourceMethod.holder, instructions);
+      maxStack += 1;
+      maxLocals += 1;
+    }
+    DexType[] sourceParameters = getSourceParameters();
+    for (int i = 0; i < sourceParameters.length; i++) {
+      DexType parameter = sourceParameters[i];
+      ValueType parameterType = ValueType.fromDexType(parameter);
+      instructions.add(new CfLoad(parameterType, maxLocals));
+      maxLocals += parameterType.requiredRegisters();
+      maybeInsertArgumentCast(i, parameter, instructions);
+    }
+    instructions.add(new CfInvoke(getInvokeOpcode(), targetMethod, isInterface));
+    if (isSourceReturnVoid()) {
+      assert !isConstructorDelegate;
+      instructions.add(new CfReturnVoid());
+    } else {
+      if (!isConstructorDelegate && sourceMethod.getReturnType() != targetMethod.getReturnType()) {
+        assert castResult;
+        if (sourceMethod.getReturnType() != factory.objectType) {
+          instructions.add(new CfCheckCast(sourceMethod.getReturnType()));
+        }
+      }
+      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);
+  }
+
+  private void maybeInsertArgumentCast(
+      int argumentIndex, DexType sourceArgumentType, Builder<CfInstruction> instructions) {
+    if (appInfoForCastArguments == null) {
+      return;
+    }
+    // Shift argument index if mapping between static and non-static.
+    if (isStaticSource() != isStaticTarget()) {
+      argumentIndex += isStaticSource() ? -1 : 1;
+    }
+    // Argument -1 is the receiver.
+    DexType targetArgumentType =
+        argumentIndex == -1
+            ? targetMethod.holder
+            : targetMethod.getParameters().values[argumentIndex];
+    if (sourceArgumentType != targetArgumentType
+        && targetArgumentType != appInfoForCastArguments.dexItemFactory().objectType) {
+      assert appInfoForCastArguments.isSubtype(targetArgumentType, sourceArgumentType);
+      instructions.add(new CfCheckCast(targetArgumentType));
+    }
+  }
+
+  private int getInvokeOpcode() {
+    switch (invokeType) {
+      case STATIC:
+        return Opcodes.INVOKESTATIC;
+      case VIRTUAL:
+        return Opcodes.INVOKEVIRTUAL;
+      case SPECIAL:
+        return Opcodes.INVOKESPECIAL;
+    }
+    throw new Unreachable("Unexpected invoke type: " + invokeType);
+  }
+
+  private DexType[] getSourceParameters() {
+    return sourceMethod.getParameters().values;
+  }
+
+  private boolean isSourceReturnVoid() {
+    return sourceMethod.getReturnType().isVoidType();
+  }
+
+  private ValueType getSourceReturnType() {
+    assert !isSourceReturnVoid();
+    return ValueType.fromDexType(sourceMethod.getReturnType());
+  }
+
+  private boolean isStaticSource() {
+    return staticSource;
+  }
+
+  private boolean isStaticTarget() {
+    return invokeType == InvokeType.STATIC;
+  }
+
+  private int sourceArguments() {
+    return sourceMethod.getParameters().size() + (isStaticSource() ? 0 : 1);
+  }
+
+  private int targetArguments() {
+    // A constructor delegate will allocate the instance so that is subtracted from args.
+    return targetMethod.getParameters().size()
+        + (isStaticTarget() || isConstructorDelegate ? 0 : 1);
+  }
+
+  private boolean validate() {
+    assert sourceMethod != null;
+    assert targetMethod != null;
+    assert invokeType != null;
+    assert isInterface != null;
+    assert sourceArguments() == targetArguments();
+    if (isConstructorDelegate) {
+      assert isStaticSource();
+      assert !sourceMethod.getReturnType().isVoidType();
+      assert targetMethod.getReturnType().isVoidType();
+      assert invokeType == InvokeType.SPECIAL;
+    } else if (castResult) {
+      assert ValueType.fromDexType(sourceMethod.getReturnType())
+          == ValueType.fromDexType(targetMethod.getReturnType());
+    } else {
+      assert sourceMethod.getReturnType() == targetMethod.getReturnType();
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 06e3679..ae7fcf5 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -340,7 +340,7 @@
     }
     String name = namingLens.lookupName(field.field).toString();
     String desc = namingLens.lookupDescriptor(field.field.type).toString();
-    String signature = getSignature(field.annotations());
+    String signature = field.getFieldSignature().toRenamedString(namingLens, isTypeMissing);
     Object value = getStaticValue(field);
     FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
     writeAnnotations(visitor::visitAnnotation, field.annotations().annotations);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 40713b8..e7ca5b5 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.shaking.ProguardPackageNameList;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableMap;
@@ -40,7 +39,6 @@
   private final ClassNamingStrategy classNamingStrategy;
   private final PackageNamingStrategy packageNamingStrategy;
   private final Iterable<? extends DexClass> classes;
-  private final PackageObfuscationMode packageObfuscationMode;
   private final boolean isAccessModificationAllowed;
   private final Set<String> noObfuscationPrefixes = Sets.newHashSet();
   private final Set<String> usedPackagePrefixes = Sets.newHashSet();
@@ -63,27 +61,16 @@
     this.packageNamingStrategy = packageNamingStrategy;
     this.classes = classes;
     InternalOptions options = appView.options();
-    this.packageObfuscationMode =
-        options.testing.enableExperimentalRepackaging
-            ? PackageObfuscationMode.NONE
-            : options.getProguardConfiguration().getPackageObfuscationMode();
     this.isAccessModificationAllowed =
         options.getProguardConfiguration().isAccessModificationAllowed();
     this.keepInnerClassStructure = options.keepInnerClassStructure();
 
     // Initialize top-level naming state.
-    if (options.testing.enableExperimentalRepackaging) {
-      topLevelState = new Namespace("");
-      String newPackageDescriptor =
-          StringUtils.replaceAll(options.getProguardConfiguration().getPackagePrefix(), ".", "/");
-      if (!newPackageDescriptor.isEmpty()) {
-        registerPackagePrefixesAsUsed(newPackageDescriptor, false);
-      }
-    } else {
-      topLevelState =
-          new Namespace(
-              getPackageBinaryNameFromJavaType(
-                  options.getProguardConfiguration().getPackagePrefix()));
+    topLevelState = new Namespace("");
+    String newPackageDescriptor =
+        StringUtils.replaceAll(options.getProguardConfiguration().getPackagePrefix(), ".", "/");
+    if (!newPackageDescriptor.isEmpty()) {
+      registerPackagePrefixesAsUsed(newPackageDescriptor, false);
     }
 
     states.put("", topLevelState);
@@ -284,25 +271,7 @@
     if (noObfuscationPrefixes.contains(packageName) || keepPackageNames.matches(type)) {
       return states.computeIfAbsent(packageName, Namespace::new);
     }
-    Namespace state = topLevelState;
-    switch (packageObfuscationMode) {
-      case NONE:
-        // For general obfuscation, rename all the involved package prefixes.
-        state = getStateForPackagePrefix(packageName);
-        break;
-      case REPACKAGE:
-        // For repackaging, all classes are repackaged to a single package.
-        state = topLevelState;
-        break;
-      case FLATTEN:
-        // For flattening, all packages are repackaged to a single package.
-        state = states.computeIfAbsent(packageName, k -> {
-          String renamedPackagePrefix = topLevelState.nextPackagePrefix();
-          return new Namespace(renamedPackagePrefix);
-        });
-        break;
-    }
-    return state;
+    return getStateForPackagePrefix(packageName);
   }
 
   private Namespace getStateForPackagePrefix(String prefix) {
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index e1a49d8..1a51edf 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -81,14 +81,8 @@
           clazz.setClassSignature(genericSignatureTypeRewriter.rewrite(classSignature));
           clazz.forEachField(
               field ->
-                  field.setAnnotations(
-                      rewriteGenericSignatures(
-                          field.annotations(),
-                          genericSignatureParser::parseFieldSignature,
-                          genericSignatureCollector::getRenamedSignature,
-                          (signature, e) ->
-                              options.warningInvalidSignature(
-                                  field, clazz.getOrigin(), signature, e))));
+                  field.setFieldSignature(
+                      genericSignatureTypeRewriter.rewrite(field.getFieldSignature())));
           clazz.forEachMethod(
               method ->
                   method.setAnnotations(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index 9e3cbce..3f625f2 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -45,6 +45,11 @@
   }
 
   @Override
+  public boolean hasCodeRewritings() {
+    return false;
+  }
+
+  @Override
   protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
     assert previous.getReboundReference() == null;
     return FieldLookupResult.builder(this)
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 4fa3212..fe2861a 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -207,6 +207,9 @@
   private void processField(DexEncodedField field) {
     field.setAnnotations(
         field.annotations().rewrite(annotation -> rewriteAnnotation(field, annotation)));
+    if (!keep.signature) {
+      field.clearFieldSignature();
+    }
   }
 
   private DexAnnotation rewriteAnnotation(DexDefinition holder, DexAnnotation original) {
@@ -285,9 +288,6 @@
           hasInnerClassesFromSet(clazz, classesToRetainInnerClassAttributeFor);
     }
     if (keptAnyway || keepForThisInnerClass || keepForThisEnclosingClass) {
-      if (!keep.signature) {
-        clazz.clearClassSignature();
-      }
       if (!keep.enclosingMethod) {
         clazz.clearEnclosingMethodAttribute();
       }
@@ -322,6 +322,9 @@
       // reflection. (Note that clearing these attributes can enable more vertical class merging.)
       clazz.clearEnclosingMethodAttribute();
       clazz.clearInnerClasses();
+    }
+    // TODO(b/170077516): Prune attributes.
+    if (!keep.signature) {
       clazz.clearClassSignature();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index b0ee416..3b68071 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -921,7 +921,7 @@
       return false;
     }
     return clazz
-        .traverseProgramMethods(
+        .traverseProgramMembers(
             member -> {
               if (keepInfo.getInfo(member).isRepackagingAllowed(options())) {
                 return TraversalContinuation.CONTINUE;
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
index fc5c0b2..4786db4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -80,6 +81,7 @@
           new DexEncodedField(
               appView.dexItemFactory().createField(clazz.type, clinitField.type, clinitField.name),
               accessFlags,
+              NO_FIELD_TYPE_SIGNATURE,
               DexAnnotationSet.empty(),
               null);
       clazz.appendStaticField(encodedClinitField);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index cd02bbb..d1200b4 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2611,7 +2611,7 @@
       // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
       // marking for not renaming it is in the root set.
       workList.enqueueMarkMethodKeptAction(valuesMethod, reason);
-      keepInfo.keepMethod(valuesMethod);
+      keepInfo.joinMethod(valuesMethod, joiner -> joiner.pin().disallowMinification());
       shouldNotBeMinified(valuesMethod.getReference());
     }
   }
@@ -2762,7 +2762,7 @@
       if (!modifiers.allowsShrinking) {
         // TODO(b/159589281): Evaluate this interpretation.
         joiner.pin();
-        if (!definition.getAccessFlags().isPublic()) {
+        if (definition.getAccessFlags().isPackagePrivateOrProtected()) {
           joiner.requireAccessModificationForRepackaging();
         }
       }
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 4ce026a..b7839ea 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -5,6 +5,7 @@
 
 import java.lang.reflect.Array;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.function.Function;
 import java.util.function.IntPredicate;
@@ -137,4 +138,18 @@
       array[i] += array[i - 1];
     }
   }
+
+  /**
+   * Copies the current array to a new array that can fit one more element and adds 'element' to
+   * index |ts|. Only use this if adding a single element since copying the array is expensive.
+   *
+   * @param ts the original array
+   * @param element the element to add
+   * @return a new array with element on index |ts|
+   */
+  public static <T> T[] appendSingleElement(T[] ts, T element) {
+    T[] newArray = Arrays.copyOf(ts, ts.length + 1);
+    newArray[ts.length] = element;
+    return newArray;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 091325a..a429fb1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1253,7 +1253,6 @@
     public boolean alwaysUsePessimisticRegisterAllocation = false;
     public boolean enableCheckCastAndInstanceOfRemoval = true;
     public boolean enableDeadSwitchCaseElimination = true;
-    public boolean enableExperimentalRepackaging = false;
     public boolean enableInvokeSuperToInvokeVirtualRewriting = true;
     public boolean enableSwitchToIfRewriting = true;
     public boolean enableEnumUnboxingDebugLogs = false;
diff --git a/src/test/examples/naming101/a/b/c.java b/src/test/examples/naming101/a/b/c.java
index b399bda..04dc965 100644
--- a/src/test/examples/naming101/a/b/c.java
+++ b/src/test/examples/naming101/a/b/c.java
@@ -4,5 +4,5 @@
 package naming101.a.b;
 
 public class c {
-  static boolean flag = true;
+  public static boolean flag = true;
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index f5c75aa..67721e4 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -642,7 +642,7 @@
               "530-checker-lse2",
               TestCondition.match(
                   TestCondition.tools(DexTool.DX),
-                  TestCondition.compilers(CompilerUnderTest.D8),
+                  TestCondition.compilers(CompilerUnderTest.R8, CompilerUnderTest.D8),
                   TestCondition.runtimesUpTo(DexVm.Version.V6_0_1)))
           .put(
               "534-checker-bce-deoptimization",
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index c888c1d..ddcb6c3 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -220,14 +220,6 @@
     return self();
   }
 
-  public CR disableVerifer() {
-    assert getBackend() == Backend.CF;
-    if (!vmArguments.contains("-noverify")) {
-      vmArguments.add("-noverify");
-    }
-    return self();
-  }
-
   public CR enableRuntimeAssertions() {
     assert getBackend() == Backend.CF;
     if (!vmArguments.contains("-ea")) {
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index b3ff5a3..279004e 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -210,6 +210,7 @@
 
   @Test
   public void testSignatures() throws Exception {
+    expectThrowsWithHorizontalClassMerging();
     testParseSignaturesInJar(r8R8Release.getFirst());
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
new file mode 100644
index 0000000..d25d516
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2020, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+
+public class MergingProducesFieldCollisionTest extends HorizontalClassMergingTestBase {
+  public MergingProducesFieldCollisionTest(
+      TestParameters parameters, boolean enableHorizontalClassMerging) {
+    super(parameters, enableHorizontalClassMerging);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    byte[] transformedC = transformer(C.class).renameAndRemapField("v$1", "v").transform();
+
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(Main.class)
+        .addProgramClassFileData(transformedC)
+        .addProgramClasses(Parent.class, A.class, B.class, Main.class)
+        .addOptionsModification(
+            options -> options.enableHorizontalClassMerging = enableHorizontalClassMerging)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccess()
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(Parent.class), isPresent());
+
+              ClassSubject aClassSubject = codeInspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              ClassSubject bClassSubject = codeInspector.clazz(B.class);
+              assertThat(bClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
+
+              ClassSubject cClassSubject = codeInspector.clazz(C.class);
+              assertThat(cClassSubject, isPresent());
+
+              if (enableHorizontalClassMerging) {
+                assertEquals(
+                    cClassSubject.allFields().get(0).type(),
+                    cClassSubject.allFields().get(1).type());
+              }
+            });
+  }
+
+  @NoVerticalClassMerging
+  public static class Parent {
+    @NeverInline
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class A extends Parent {
+    public A() {
+      System.out.println("b");
+    }
+  }
+
+  @NeverClassInline
+  public static class B extends Parent {
+    public B() {
+      System.out.println("b");
+    }
+
+    @NeverInline
+    public void foo() {
+      System.out.println("foo b");
+    }
+  }
+
+  @NeverClassInline
+  public static class C {
+    A v;
+    B v$1; // This field is renamed to v.
+
+    public C(A a, B b) {
+      v = a;
+      v$1 = b;
+    }
+
+    @NeverInline
+    public void foo() {
+      v.foo();
+      v$1.foo();
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      new C(new A(), new B()).foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
index ddff057..2519477 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -140,14 +140,8 @@
     DexEncodedField field = yyInZZ.getField();
     assertNotNull(field);
 
-    fieldTypeSignature =
-        GenericSignature.parseFieldTypeSignature(
-            field.field.qualifiedName(),
-            getGenericSignature(field, appView),
-            Origin.unknown(),
-            appView.dexItemFactory(),
-            appView.options().reporter);
-    assertNotNull(fieldTypeSignature);
+    fieldTypeSignature = field.getFieldSignature();
+    assertTrue(fieldTypeSignature.hasSignature());
 
     // field type: A$Y$YY
     assertTrue(fieldTypeSignature.isClassTypeSignature());
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
new file mode 100644
index 0000000..b0200fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/FieldSignatureTest.java
@@ -0,0 +1,110 @@
+// Copyright (c) 2020, 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.genericsignature;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.graph.GenericSignature.NO_FIELD_TYPE_SIGNATURE;
+import static com.google.common.base.Predicates.alwaysFalse;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignaturePrinter;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Reporter;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FieldSignatureTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public FieldSignatureTest(TestParameters parameters) {}
+
+  @Test
+  public void testClass() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz;");
+  }
+
+  @Test
+  public void testMissingSemicolon() {
+    testParsingAndPrintingError("Lfoo/bar/baz")
+        .assertAllErrorsMatch(
+            diagnosticMessage(containsString("Invalid signature 'Lfoo/bar/baz;' for field A.")));
+  }
+
+  @Test
+  public void testClassWithEmptyTypeArguments() {
+    testParsingAndPrintingError("Lfoo/bar/baz<>;")
+        .assertAllErrorsMatch(
+            diagnosticMessage(containsString("Invalid signature 'Lfoo/bar/baz;' for field A.")));
+  }
+
+  @Test
+  public void testClassWithTypeVariableArguments() {
+    testParsingAndPrintingEqual("Lfoo/bar/baz<TT;>;");
+  }
+
+  @Test
+  public void testTypeVariable() {
+    testParsingAndPrintingEqual("TT;");
+  }
+
+  @Test
+  public void testPrimitive() {
+    testParsingAndPrintingError("I")
+        .assertAllErrorsMatch(
+            diagnosticMessage(containsString("Invalid signature 'I' for field A.")));
+  }
+
+  @Test
+  public void testArray() {
+    testParsingAndPrintingEqual("[I");
+    testParsingAndPrintingEqual("[Lfoo/bar/baz;");
+  }
+
+  @Test
+  public void testArrayWithGeneric() {
+    testParsingAndPrintingEqual("[Lfoo/bar/baz<[I+Lfoo/Qux<*>.Inner<-Lfoo/Quux<TT;>;>;>;");
+  }
+
+  private void testParsingAndPrintingEqual(String signature) {
+    FieldTypeSignature parsed =
+        GenericSignature.parseFieldTypeSignature(
+            "A", signature, Origin.unknown(), new DexItemFactory(), new Reporter());
+    GenericSignaturePrinter genericSignaturePrinter =
+        new GenericSignaturePrinter(NamingLens.getIdentityLens(), alwaysFalse());
+    genericSignaturePrinter.visitFieldTypeSignature(parsed);
+    String outSignature = genericSignaturePrinter.toString();
+    assertEquals(signature, outSignature);
+  }
+
+  private TestDiagnosticMessages testParsingAndPrintingError(String signature) {
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+    FieldTypeSignature parsed =
+        GenericSignature.parseFieldTypeSignature(
+            "A",
+            signature,
+            Origin.unknown(),
+            new DexItemFactory(),
+            new Reporter(testDiagnosticMessages));
+    assertEquals(NO_FIELD_TYPE_SIGNATURE, parsed);
+    return testDiagnosticMessages;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/PruneNotNullBranchD8Test.java b/src/test/java/com/android/tools/r8/ir/optimize/PruneNotNullBranchD8Test.java
new file mode 100644
index 0000000..870787f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/PruneNotNullBranchD8Test.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.File;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PruneNotNullBranchD8Test extends TestBase {
+
+  private final TestParameters parameters;
+  private final String EXPECTED = "foo_bar_baz0";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public PruneNotNullBranchD8Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(
+            inspector -> {
+              ClassSubject main = inspector.clazz(Main.class);
+              assertThat(main, isPresent());
+              MethodSubject mainMethod = main.mainMethod();
+              assertThat(mainMethod, isPresent());
+              // TODO(b/170060113): We should be able to remove the throw.
+              assertTrue(mainMethod.streamInstructions().anyMatch(InstructionSubject::isThrow));
+            });
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      File file;
+      if (args.length == 0) {
+        file = new File("foo_bar_baz0");
+      } else {
+        file = new File("foo_bar_baz1");
+      }
+      if (file != null) {
+        System.out.println(file.getPath());
+        return;
+      }
+      throw new RuntimeException("Will always be non-zero");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index 12b18e4..53e52b4 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -21,16 +24,16 @@
 import static org.objectweb.asm.Opcodes.RETURN;
 import static org.objectweb.asm.Opcodes.V1_8;
 
-import com.android.tools.r8.DiagnosticsChecker;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -44,6 +47,18 @@
 
 @RunWith(Parameterized.class)
 public class MinifierFieldSignatureTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MinifierFieldSignatureTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   /*
 
   class Fields<X extends String> {
@@ -61,16 +76,6 @@
   private String anArrayOfXSignature = "[TX;";
   private String aFieldsOfXSignature = "LFields<TX;>;";
   private String aFieldsOfXInnerSignature = "LFields<TX;>.Inner;";
-  private Backend backend;
-
-  @Parameters(name = "Backend: {0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
-  }
-
-  public MinifierFieldSignatureTest(Backend backend) {
-    this.backend = backend;
-  }
 
   public byte[] dumpFields(Map<String, String> signatures) throws Exception {
 
@@ -172,25 +177,26 @@
 
   public void runTest(
       ImmutableMap<String, String> signatures,
-      Consumer<DiagnosticsChecker> diagnostics,
+      Consumer<TestDiagnosticMessages> diagnostics,
       Consumer<CodeInspector> inspect)
       throws Exception {
-    DiagnosticsChecker checker = new DiagnosticsChecker();
-    CodeInspector inspector =
-        new CodeInspector(
-            ToolHelper.runR8(
-                R8Command.builder(checker)
-                    .addClassProgramData(dumpFields(signatures), Origin.unknown())
-                    .addClassProgramData(dumpInner(), Origin.unknown())
-                    .addProguardConfiguration(
-                        ImmutableList.of(
-                            "-keepattributes InnerClasses,EnclosingMethod,Signature",
-                            "-keep,allowobfuscation class ** { *; }"),
-                        Origin.unknown())
-                    .setProgramConsumer(emptyConsumer(backend))
-                    .addLibraryFiles(runtimeJar(backend))
-                    .setProguardMapConsumer(StringConsumer.emptyConsumer())
-                    .build()));
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(dumpFields(signatures), dumpInner())
+            .addKeepAttributes(
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD,
+                ProguardKeepAttributes.SIGNATURE)
+            .addKeepAllClassesRuleWithAllowObfuscation()
+            .setMinApi(parameters.getApiLevel())
+            .allowDiagnosticMessages()
+            .addOptionsModification(
+                internalOptions ->
+                    internalOptions.testing.disableMappingToOriginalProgramVerification = true)
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+
     // All classes are kept, and renamed.
     ClassSubject clazz = inspector.clazz("Fields");
     assertThat(clazz, isPresentAndRenamed());
@@ -225,13 +231,18 @@
       assertEquals(
           aFieldsOfXInnerSignature, aFieldsOfXInner.getOriginalSignatureAttribute());
     }
-
-    diagnostics.accept(checker);
     inspect.accept(inspector);
+
+    TestDiagnosticMessages diagnosticMessages = compileResult.getDiagnosticMessages();
+    diagnosticMessages.assertNoInfos();
+    diagnosticMessages.assertNoErrors();
+    diagnostics.accept(diagnosticMessages);
   }
 
-  private void testSingleField(String name, String signature,
-      Consumer<DiagnosticsChecker> diagnostics,
+  private void testSingleField(
+      String name,
+      String signature,
+      Consumer<TestDiagnosticMessages> diagnostics,
       Consumer<CodeInspector> inspector)
       throws Exception {
     ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
@@ -242,11 +253,8 @@
     assertSame(Origin.unknown(), origin);
   }
 
-  private void noWarnings(DiagnosticsChecker checker) {
-    assertEquals(0, checker.warnings.size());
-  }
-
   private void noInspection(CodeInspector inspector) {
+    // Intentionally left empty.
   }
 
   private void noSignatureAttribute(FieldSubject field) {
@@ -258,48 +266,61 @@
   @Test
   public void originalJavacSignatures() throws Exception {
     // Test using the signatures generated by javac.
-    runTest(ImmutableMap.of(), this::noWarnings, this::noInspection);
+    runTest(ImmutableMap.of(), TestDiagnosticMessages::assertNoWarnings, this::noInspection);
   }
 
   @Test
   public void signatureEmpty() throws Exception {
-    testSingleField("anX", "", this::noWarnings,
+    testSingleField(
+        "anX",
+        "",
+        TestDiagnosticMessages::assertNoWarnings,
         inspector -> noSignatureAttribute(lookupAnX(inspector)));
   }
 
   @Test
   public void signatureInvalid() throws Exception {
-    testSingleField("anX", "X", diagnostics -> {
-      assertEquals(1, diagnostics.warnings.size());
-      // TODO(sgjesse): The position 2 reported here is one off.
-      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature 'X' for field",
-          "java.lang.String Fields.anX",
-          "Expected L, [ or T at position 2");
-    }, inspector -> noSignatureAttribute(lookupAnX(inspector)));
+    testSingleField(
+        "anX",
+        "X",
+        diagnostics -> {
+          diagnostics.assertWarningsMatch(
+              diagnosticMessage(
+                  allOf(
+                      containsString("Invalid signature 'X' for field anX"),
+                      // TODO(sgjesse): The position 2 reported here is one off.
+                      containsString("Expected L, [ or T at position 2"))));
+        },
+        inspector -> noSignatureAttribute(lookupAnX(inspector)));
   }
 
   @Test
   public void classNotFound() throws Exception {
     String signature = "LNotFound<TX;>.InnerNotFound.InnerAlsoNotFound;";
-    testSingleField("anX", signature, this::noWarnings, inspector -> {
-      assertThat(inspector.clazz("NotFound"), not(isPresent()));
-      assertEquals(signature, lookupAnX(inspector).getOriginalSignatureAttribute());
-    });
+    testSingleField(
+        "anX",
+        signature,
+        TestDiagnosticMessages::assertNoWarnings,
+        inspector -> {
+          assertThat(inspector.clazz("NotFound"), not(isPresent()));
+          assertEquals(signature, lookupAnX(inspector).getOriginalSignatureAttribute());
+        });
   }
 
   @Test
   public void multipleWarnings() throws Exception {
-    runTest(ImmutableMap.of(
-        "anX", "X",
-        "anArrayOfX", "X",
-        "aFieldsOfX", "X"
-    ), diagnostics -> {
-      assertEquals(3, diagnostics.warnings.size());
-    }, inspector -> {
-      noSignatureAttribute(lookupAnX(inspector));
-      noSignatureAttribute(lookupAnArrayOfX(inspector));
-      noSignatureAttribute(lookupAFieldsOfX(inspector));
-    });
+    runTest(
+        ImmutableMap.of(
+            "anX", "X",
+            "anArrayOfX", "X",
+            "aFieldsOfX", "X"),
+        diagnostics -> {
+          diagnostics.assertWarningsCount(3);
+        },
+        inspector -> {
+          noSignatureAttribute(lookupAnX(inspector));
+          noSignatureAttribute(lookupAnArrayOfX(inspector));
+          noSignatureAttribute(lookupAFieldsOfX(inspector));
+        });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingSignatureTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingSignatureTest.java
index 2980f0e..40ba160 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingSignatureTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import java.io.IOException;
@@ -36,7 +37,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private TestParameters parameters;
@@ -52,9 +53,12 @@
         .addProgramClasses(A.class, SignatureTest.class)
         .addKeepClassRulesWithAllowObfuscation(A.class)
         .addKeepClassAndMembersRules(SignatureTest.class)
-        .addKeepAttributes("Signature", "InnerClasses", "EnclosingMethod")
+        .addKeepAttributes(
+            ProguardKeepAttributes.SIGNATURE,
+            ProguardKeepAttributes.INNER_CLASSES,
+            ProguardKeepAttributes.ENCLOSING_METHOD)
         .addApplyMapping(A.class.getTypeName() + " -> foo:")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), SignatureTest.class)
         .assertSuccessWithOutput("HELLO")
         .inspect(
@@ -63,7 +67,7 @@
               assertThat(clazz, isPresent());
               FieldSubject field = clazz.uniqueFieldWithName("field");
               assertThat(field, isPresent());
-              assertThat(field.getSignatureAnnotationValue(), containsString("foo"));
+              assertThat(field.getFinalSignatureAttribute(), containsString("foo"));
             });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
index 18d0719..8757685 100644
--- a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
+++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -14,9 +14,11 @@
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -25,20 +27,26 @@
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class B124357885Test extends TestBase {
-  public final boolean minification;
 
-  @Parameterized.Parameters(name = "Minification: {0}")
-  public static Boolean[] data() {
-    return BooleanUtils.values();
+  private final TestParameters parameters;
+  private final boolean minification;
+
+  @Parameters(name = "{0}, minification: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
-  public B124357885Test(boolean minification) {
+  public B124357885Test(TestParameters parameters, boolean minification) {
+    this.parameters = parameters;
     this.minification = minification;
   }
 
@@ -69,11 +77,15 @@
   @Test
   public void test() throws Exception {
     R8TestCompileResult compileResult =
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addProgramClasses(Main.class, Service.class, Foo.class, FooImpl.class)
             .addKeepMainRule(Main.class)
-            .addKeepRules("-keepattributes Signature,InnerClasses,EnclosingMethod")
+            .addKeepAttributes(
+                ProguardKeepAttributes.SIGNATURE,
+                ProguardKeepAttributes.INNER_CLASSES,
+                ProguardKeepAttributes.ENCLOSING_METHOD)
             .minification(minification)
+            .setMinApi(parameters.getApiLevel())
             .compile()
             .inspect(
                 inspector -> {
@@ -88,31 +100,31 @@
                   checkSignatureAnnotation(inspector, signature);
                 });
 
-        String fooImplFinalName = compileResult.inspector().clazz(FooImpl.class).getFinalName();
+    String fooImplFinalName = compileResult.inspector().clazz(FooImpl.class).getFinalName();
 
-        compileResult
-            .run(Main.class)
-            .assertSuccessWithOutput(StringUtils.lines(fooImplFinalName, fooImplFinalName));
+    compileResult
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(StringUtils.lines(fooImplFinalName, fooImplFinalName));
   }
-}
 
-class Main {
-  public static void main(String... args) throws Exception {
-    Method method = Service.class.getMethod("fooList");
-    ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
-    Class<?> rawType = (Class<?>) type.getRawType();
-    System.out.println(rawType.getName());
+  public static class Main {
+    public static void main(String... args) throws Exception {
+      Method method = Service.class.getMethod("fooList");
+      ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
+      Class<?> rawType = (Class<?>) type.getRawType();
+      System.out.println(rawType.getName());
 
-    // Convince R8 we only use subtypes to get class merging of Foo into FooImpl.
-    Foo<String> foo = new FooImpl<>();
-    System.out.println(foo.getClass().getCanonicalName());
+      // Convince R8 we only use subtypes to get class merging of Foo into FooImpl.
+      Foo<String> foo = new FooImpl<>();
+      System.out.println(foo.getClass().getCanonicalName());
+    }
   }
+
+  interface Service {
+    Foo<String> fooList();
+  }
+
+  interface Foo<T> {}
+
+  public static class FooImpl<T> implements Foo<T> {}
 }
-
-interface Service {
-  Foo<String> fooList();
-}
-
-interface Foo<T> {}
-
-class FooImpl<T> implements Foo<T> {}
diff --git a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
index 78f0d0c..de81c15 100644
--- a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
+++ b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
@@ -57,7 +57,7 @@
         .compile()
         .inspect(
             inspector -> {
-              String genericTypeDescriptor = "Ljava/lang/Object;";
+              String genericTypeDescriptor = "*";
               if (genericTypeLive) {
                 ClassSubject genericType = inspector.clazz(GenericType.class);
                 assertThat(genericType, isPresentAndRenamed(minify));
@@ -66,8 +66,7 @@
               String expectedSignature = "Ljava/util/List<" + genericTypeDescriptor + ">;";
               FieldSubject list = inspector.clazz(A.class).uniqueFieldWithName("list");
               assertThat(list, isPresent());
-              assertThat(list.getSignatureAnnotation(), isPresent());
-              assertEquals(expectedSignature, list.getSignatureAnnotationValue());
+              assertEquals(expectedSignature, list.getFinalSignatureAttribute());
             })
         .run(mainClass)
         .assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index a29c664..ddf20ef 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -140,7 +140,7 @@
     assertThat(testClass, isPresent());
 
     // Test the totally unused method.
-    MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
+    MethodSubject staticMethod = testClass.uniqueMethodWithName("unused");
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
     if (shrinker.isR8()) {
@@ -150,7 +150,7 @@
     }
 
     // Test an indirectly referred method.
-    staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
+    staticMethod = testClass.uniqueMethodWithName("staticMethod");
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
     boolean publicizeCondition = shrinker.isR8() ? allowAccessModification
@@ -191,7 +191,7 @@
     assertThat(testClass, isPresent());
 
     // Test the totally unused method.
-    MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
+    MethodSubject staticMethod = testClass.uniqueMethodWithName("unused");
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
     if (shrinker.isR8()) {
@@ -201,7 +201,7 @@
     }
 
     // Test an indirectly referred method.
-    staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
+    staticMethod = testClass.uniqueMethodWithName("staticMethod");
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
     boolean publicizeCondition = shrinker.isR8() ? allowAccessModification
@@ -254,11 +254,11 @@
     assertThat(testClass, isPresent());
 
     // Test the totally unused method.
-    MethodSubject staticMethod = testClass.method("void", "unused", ImmutableList.of());
+    MethodSubject staticMethod = testClass.uniqueMethodWithName("unused");
     assertThat(staticMethod, not(isPresent()));
 
     // Test an indirectly referred method.
-    staticMethod = testClass.method("java.lang.String", "staticMethod", ImmutableList.of());
+    staticMethod = testClass.uniqueMethodWithName("staticMethod");
     assertThat(staticMethod, isPresent());
     assertEquals(minify, staticMethod.isRenamed());
     boolean publicizeCondition = shrinker.isR8() ? allowAccessModification
diff --git a/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java b/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
index 8d62a04..b078b2f 100644
--- a/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
@@ -62,7 +62,7 @@
 
   @Test
   public void testProguard() throws Exception {
-    runTest(testForProguard(), "Proguard");
+    runTest(testForProguard().addKeepRules("-dontwarn " + getClass().getTypeName()), "Proguard");
   }
 
   private void runTest(TestShrinkerBuilder<?, ?, ?, ?, ?> builder, String shrinker)
@@ -125,11 +125,11 @@
         throw new Unreachable();
     }
   }
-}
 
-class RepackagingCompatabilityTestClass {
+  public static class RepackagingCompatabilityTestClass {
 
-  public static void main(String[] args) {
-    System.out.println("Hello world!");
+    public static void main(String[] args) {
+      System.out.println("Hello world!");
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
index b9d3c40..150a7cc 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTest.java
@@ -66,28 +66,25 @@
 
   private final boolean allowAccessModification;
 
-  @Parameters(name = "{3}, allow access modification: {0}, experimental: {1}, kind: {2}")
+  @Parameters(name = "{2}, allow access modification: {0}, kind: {1}")
   public static List<Object[]> data() {
     return buildParameters(
         BooleanUtils.values(),
-        BooleanUtils.values(),
         ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
         getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public RepackageTest(
       boolean allowAccessModification,
-      boolean enableExperimentalRepackaging,
       String flattenPackageHierarchyOrRepackageClasses,
       TestParameters parameters) {
-    super(enableExperimentalRepackaging, flattenPackageHierarchyOrRepackageClasses, parameters);
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
     this.allowAccessModification = allowAccessModification;
   }
 
   @Test
   public void testJvm() throws Exception {
     assumeFalse(allowAccessModification);
-    assumeFalse(isExperimentalRepackaging());
     assumeTrue(isFlattenPackageHierarchy());
     assumeTrue(parameters.isCfRuntime());
     testForJvm()
@@ -134,11 +131,7 @@
    * eligible for repackaging (or it needs to stay in its original package).
    */
   private void forEachClass(BiConsumer<Class<?>, Boolean> consumer) {
-    // TODO(b/165783399): This should be renamed to markAlwaysEligible() and always pass `true` to
-    //  the consumer, since these classes should be repackaged independent of
-    //  -allowaccessmodification.
-    Consumer<Class<?>> markShouldAlwaysBeEligible =
-        clazz -> consumer.accept(clazz, allowAccessModification || isExperimentalRepackaging());
+    Consumer<Class<?>> markShouldAlwaysBeEligible = clazz -> consumer.accept(clazz, true);
     Consumer<Class<?>> markEligibleWithAllowAccessModification =
         clazz -> consumer.accept(clazz, allowAccessModification);
 
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
index 3e2f695..9a1ad72 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
 import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
-import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestBase;
@@ -24,7 +23,6 @@
 
 public abstract class RepackageTestBase extends TestBase {
 
-  private final boolean enableExperimentalRepackaging;
   private final String flattenPackageHierarchyOrRepackageClasses;
   protected final TestParameters parameters;
 
@@ -36,15 +34,8 @@
   }
 
   public RepackageTestBase(
-      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
-    this(true, flattenPackageHierarchyOrRepackageClasses, parameters);
-  }
-
-  public RepackageTestBase(
-      boolean enableExperimentalRepackaging,
       String flattenPackageHierarchyOrRepackageClasses,
       TestParameters parameters) {
-    this.enableExperimentalRepackaging = enableExperimentalRepackaging;
     this.flattenPackageHierarchyOrRepackageClasses = flattenPackageHierarchyOrRepackageClasses;
     this.parameters = parameters;
   }
@@ -140,18 +131,8 @@
   }
 
   protected void configureRepackaging(R8TestBuilder<?> testBuilder) {
-    testBuilder
-        .addKeepRules(
-            "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + getRepackagePackage() + "\"")
-        .addOptionsModification(
-            options -> {
-              assertFalse(options.testing.enableExperimentalRepackaging);
-              options.testing.enableExperimentalRepackaging = enableExperimentalRepackaging;
-            });
-  }
-
-  protected boolean isExperimentalRepackaging() {
-    return enableExperimentalRepackaging;
+    testBuilder.addKeepRules(
+        "-" + flattenPackageHierarchyOrRepackageClasses + " \"" + getRepackagePackage() + "\"");
   }
 
   protected boolean isFlattenPackageHierarchy() {
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/ClassSignaturesTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/ClassSignaturesTest.java
new file mode 100644
index 0000000..7ca532d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/ClassSignaturesTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, 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.shaking.attributes;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Iterator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassSignaturesTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String EXPECTED = "Hello World!";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassSignaturesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addKeepAttributes(
+            ProguardKeepAttributes.SIGNATURE,
+            ProguardKeepAttributes.INNER_CLASSES,
+            ProguardKeepAttributes.ENCLOSING_METHOD)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClass = inspector.clazz(A.class);
+    assertThat(aClass, isPresent());
+    assertEquals(
+        "Ljava/lang/Object;Ljava/lang/Iterable<"
+            + "Lcom/android/tools/r8/shaking/attributes/ClassSignaturesTest$Main;>;",
+        aClass.getFinalSignatureAttribute());
+  }
+
+  @NeverClassInline
+  public static class A implements Iterable<Main> {
+
+    @Override
+    @NeverInline
+    public Iterator<Main> iterator() {
+      throw new RuntimeException("FooBar");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      try {
+        new A().iterator();
+      } catch (RuntimeException e) {
+        if (!e.getMessage().contains("FooBar")) {
+          throw e;
+        }
+        System.out.println("Hello World!");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumSafeGuardPruneTestRunner.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumSafeGuardPruneTestRunner.java
new file mode 100644
index 0000000..b40ce28
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumSafeGuardPruneTestRunner.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2020, 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.shaking.enums;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumSafeGuardPruneTestRunner extends TestBase {
+
+  private final TestParameters parameters;
+  private final String EXPECTED = "SOUTH";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EnumSafeGuardPruneTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(EnumSafeGuardPruneTestRunner.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumSafeGuardPruneTestRunner.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject main = inspector.clazz(Main.class);
+    assertThat(main, isPresent());
+    MethodSubject mainMethod = main.mainMethod();
+    assertThat(mainMethod, isPresent());
+    boolean hasRemoveThisBranch =
+        mainMethod
+            .streamInstructions()
+            .anyMatch(instr -> instr.isConstString("Remove this branch", JumboStringMode.DISALLOW));
+    // TODO(b/170084314): Consider removing this.
+    assertTrue(hasRemoveThisBranch);
+  }
+
+  public enum Corners {
+    NORTH,
+    SOUTH,
+    WEST
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Corners corners =
+          args.length == 0 ? Corners.SOUTH : (args.length == 1 ? Corners.NORTH : Corners.WEST);
+      switch (corners) {
+        case NORTH:
+          System.out.println("NORTH");
+          return;
+        case SOUTH:
+          System.out.println("SOUTH");
+          return;
+        case WEST:
+          System.out.println("WEST");
+          return;
+        default:
+          throw new RuntimeException("Remove this branch");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 4779b8e..d9f3b1d 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -474,6 +474,11 @@
     boolean test(int access, String name, String descriptor, String signature, Object value);
   }
 
+  @FunctionalInterface
+  public interface FieldSignaturePredicate {
+    boolean test(String name, String typeDescriptor);
+  }
+
   public ClassFileTransformer removeInnerClasses() {
     return addClassTransformer(
         new ClassTransformer() {
@@ -522,6 +527,42 @@
         });
   }
 
+  public ClassFileTransformer remapField(FieldSignaturePredicate predicate, String newName) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public void visitFieldInsn(
+              final int opcode, final String owner, final String name, final String descriptor) {
+            if (predicate.test(name, descriptor)) {
+              super.visitFieldInsn(opcode, owner, newName, descriptor);
+            } else {
+              super.visitFieldInsn(opcode, owner, name, descriptor);
+            }
+          }
+        });
+  }
+
+  public ClassFileTransformer renameField(FieldSignaturePredicate predicate, String newName) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            if (predicate.test(name, descriptor)) {
+              return super.visitField(access, newName, descriptor, signature, value);
+            } else {
+              return super.visitField(access, name, descriptor, signature, value);
+            }
+          }
+        });
+  }
+
+  public ClassFileTransformer renameAndRemapField(String oldName, String newName) {
+    FieldSignaturePredicate matchPredicate = (name, signature) -> oldName.equals(name);
+    remapField(matchPredicate, newName);
+    return renameField(matchPredicate, newName);
+  }
+
   /** Abstraction of the MethodVisitor.visitMethodInsn method with its sub visitor. */
   @FunctionalInterface
   public interface MethodInsnTransform {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index 9ef7938..fce43c7 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -134,13 +134,12 @@
   @Override
   public String getOriginalSignatureAttribute() {
     return codeInspector.getOriginalSignatureAttribute(
-        codeInspector.getFinalSignatureAttribute(dexField.annotations()),
-        GenericSignatureParser::parseFieldSignature);
+        dexField.getFieldSignature().toString(), GenericSignatureParser::parseFieldSignature);
   }
 
   @Override
   public String getFinalSignatureAttribute() {
-    return codeInspector.getFinalSignatureAttribute(dexField.annotations());
+    return dexField.getFieldSignature().toString();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index e2561b0..77172c3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -4,9 +4,6 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
-import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 
 public abstract class MemberSubject extends Subject {
@@ -53,32 +50,6 @@
 
   public abstract AnnotationSubject annotation(String name);
 
-  public AnnotationSubject getSignatureAnnotation() {
-    return annotation("dalvik.annotation.Signature");
-  }
-
-  public String getSignatureAnnotationValue() {
-    AnnotationSubject annotation = getSignatureAnnotation();
-    if (!annotation.isPresent()) {
-      return null;
-    }
-
-    assert annotation.getAnnotation().elements.length == 1;
-    DexAnnotationElement element = annotation.getAnnotation().elements[0];
-    assert element.name.toString().equals("value");
-    assert element.value.isDexValueArray();
-    DexValueArray array = element.value.asDexValueArray();
-    StringBuilder builder = new StringBuilder();
-    for (DexValue value : array.getValues()) {
-      if (value.isDexValueString()) {
-        builder.append(value.asDexValueString().value);
-      } else {
-        builder.append(value.toString());
-      }
-    }
-    return builder.toString();
-  }
-
   public FieldSubject asFieldSubject() {
     return null;
   }