Merge commit 'cd8160b889e496347c224bb0065884a6953f45ad' into dev-release
diff --git a/scripts/add-android-jar.sh b/scripts/add-android-jar.sh
index 3eb0d21..4a0f57c 100755
--- a/scripts/add-android-jar.sh
+++ b/scripts/add-android-jar.sh
@@ -8,6 +8,8 @@
 set -x
 
 echo "Update this script manually before using"
+echo "If updating API database also update API_LEVEL in " \
+    "AndroidApiHashingDatabaseBuilderGeneratorTest"
 exit -1
 
 # Download Platform SDK in @SDK_HOME
@@ -21,21 +23,46 @@
 THIRD_PARTY_ANDROID_JAR=third_party/android_jar
 THIRD_PARTY_ANDROID_JAR_LIB=$THIRD_PARTY_ANDROID_JAR/lib-v$SDK_VERSION
 
-rm -rf $THIRD_PARTY_ANDROID_JAR_LIB
-rm -f ${THIRD_PARTY_ANDROID_JAR_LIB}.tar.gz
-rm -f ${THIRD_PARTY_ANDROID_JAR_LIB}.tar.sha1
+UPDATE_ANDROID_JAR="no"
+if [[ "$UPDATE_ANDROID_JAR" == "yes" ]]; then
+  rm -rf $THIRD_PARTY_ANDROID_JAR_LIB
+  rm -f ${THIRD_PARTY_ANDROID_JAR_LIB}.tar.gz
+  rm -f ${THIRD_PARTY_ANDROID_JAR_LIB}.tar.sha1
 
-mkdir -p $THIRD_PARTY_ANDROID_JAR_LIB/optional
-cp $SDK_DIR/android.jar $THIRD_PARTY_ANDROID_JAR_LIB/android.jar
-cp $SDK_DIR/optional/*.jar $THIRD_PARTY_ANDROID_JAR_LIB/optional
-cp $SDK_DIR/optional/optional.json $THIRD_PARTY_ANDROID_JAR_LIB/optional
-cp $THIRD_PARTY_ANDROID_JAR/lib-v31/README.google $THIRD_PARTY_ANDROID_JAR_LIB
-vi $THIRD_PARTY_ANDROID_JAR_LIB/README.google
+  mkdir -p $THIRD_PARTY_ANDROID_JAR_LIB/optional
+  cp $SDK_DIR/android.jar $THIRD_PARTY_ANDROID_JAR_LIB/android.jar
+  cp $SDK_DIR/optional/*.jar $THIRD_PARTY_ANDROID_JAR_LIB/optional
+  cp $SDK_DIR/optional/optional.json $THIRD_PARTY_ANDROID_JAR_LIB/optional
+  cp $THIRD_PARTY_ANDROID_JAR/lib-v31/README.google $THIRD_PARTY_ANDROID_JAR_LIB
+  vi $THIRD_PARTY_ANDROID_JAR_LIB/README.google
 
-(cd $THIRD_PARTY_ANDROID_JAR \
-	&& upload_to_google_storage.py -a --bucket r8-deps lib-v$SDK_VERSION)
-rm -rf $THIRD_PARTY_ANDROID_JAR_LIB
-rm ${THIRD_PARTY_ANDROID_JAR_LIB}.tar.gz
-git add ${THIRD_PARTY_ANDROID_JAR_LIB}.tar.gz.sha1
+  (cd $THIRD_PARTY_ANDROID_JAR \
+      && upload_to_google_storage.py -a --bucket r8-deps lib-v$SDK_VERSION)
+  rm -rf $THIRD_PARTY_ANDROID_JAR_LIB
+  rm ${THIRD_PARTY_ANDROID_JAR_LIB}.tar.gz
+  git add ${THIRD_PARTY_ANDROID_JAR_LIB}.tar.gz.sha1
+fi
 
-echo "Update build.gradle with this new cloud dependency, and verify with tools/gradle.py downloadDeps"
\ No newline at end of file
+UPDATE_API_DATABASE="no"
+if [[ "$UPDATE_API_DATABASE" == "yes" ]]; then
+  rm -rf $THIRD_PARTY_ANDROID_JAR/api-versions
+  rm -f $THIRD_PARTY_ANDROID_JAR/api-versions.tar.gz
+  rm -f $THIRD_PARTY_ANDROID_JAR/api-versions.tar.gz.sha1
+  mkdir -p $THIRD_PARTY_ANDROID_JAR/api-versions
+  cp $SDK_DIR/data/api-versions.xml $THIRD_PARTY_ANDROID_JAR/api-versions
+  (cd $THIRD_PARTY_ANDROID_JAR \
+      && upload_to_google_storage.py -a --bucket r8-deps api-versions)
+  tools/gradle.py r8NoManifestWithoutDeps testJar repackageTestDeps
+  java -cp build/libs/r8_no_manifest_without_deps.jar:build/libs/deps_all.jar:build/libs/r8tests.jar:build/libs/test_deps_all.jar \
+      com.android.tools.r8.apimodel.AndroidApiHashingDatabaseBuilderGeneratorTest
+
+  rm -rf $THIRD_PARTY_ANDROID_JAR/api-versions
+  rm -f $THIRD_PARTY_ANDROID_JAR/api-versions.tar.gz
+  git add $THIRD_PARTY_ANDROID_JAR/api-versions.tar.gz.sha1
+  git add src/main/resources/api_database/api_database_ambiguous.txt
+  git add src/main/resources/api_database/api_database_api_level.ser
+  git add src/main/resources/api_database/api_database_hash_lookup.ser
+fi
+
+echo "Update build.gradle with this new cloud dependency, " \
+    "and verify with tools/gradle.py downloadDeps"
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 242064b..1e6d847 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -486,7 +486,6 @@
     assert !internal.enableClassStaticizer;
     assert !internal.enableEnumValueOptimization;
     assert !internal.outline.enabled;
-    assert !internal.enableValuePropagation;
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
 
     // TODO(b/187675788): Enable class merging for synthetics in D8.
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index c0fb797..745d94d 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -182,7 +182,6 @@
     assert !internal.enableClassStaticizer;
     assert !internal.enableEnumValueOptimization;
     assert !internal.outline.enabled;
-    assert !internal.enableValuePropagation;
     assert !internal.enableTreeShakingOfLibraryMethodOverrides;
 
     HorizontalClassMergerOptions horizontalClassMergerOptions =
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d68ff6b..a0ac288 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -979,9 +979,10 @@
                         }
                         if (code.isCfCode()) {
                           assert verifyOriginalMethodInPosition(code.asCfCode(), originalMethod);
-                        } else {
-                          assert code.isDexCode();
+                        } else if (code.isDexCode()) {
                           assert verifyOriginalMethodInDebugInfo(code.asDexCode(), originalMethod);
+                        } else {
+                          assert code.isDefaultInstanceInitializerCode() || code.isThrowNullCode();
                         }
                       }
                     }));
@@ -994,7 +995,7 @@
         continue;
       }
       CfPosition position = instruction.asPosition();
-      assert position.getPosition().getOutermostCaller().method == originalMethod;
+      assert position.getPosition().getOutermostCaller().getMethod() == originalMethod;
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index ab9165b..2f90085 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -26,6 +26,8 @@
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -44,11 +46,14 @@
 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.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
+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;
@@ -75,7 +80,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
-import org.objectweb.asm.Opcodes;
 
 /** Rudimentary printer to print the source representation for creating CfCode object. */
 public class CfCodePrinter extends CfPrinter {
@@ -531,24 +535,28 @@
   }
 
   @Override
-  public void print(CfFieldInstruction insn) {
-    printNewInstruction(
-        "CfFieldInstruction", fieldInstructionOpcode(insn), dexField(insn.getField()));
+  public void print(CfInstanceFieldRead insn) {
+    printNewInstruction("CfInstanceFieldRead", dexField(insn.getField()));
   }
 
-  private String fieldInstructionOpcode(CfFieldInstruction insn) {
-    switch (insn.getOpcode()) {
-      case Opcodes.GETSTATIC:
-        return asmOpcodesType() + ".GETSTATIC";
-      case Opcodes.PUTSTATIC:
-        return asmOpcodesType() + ".PUTSTATIC";
-      case Opcodes.GETFIELD:
-        return asmOpcodesType() + ".GETFIELD";
-      case Opcodes.PUTFIELD:
-        return asmOpcodesType() + ".PUTFIELD";
-      default:
-        throw new Unimplemented();
-    }
+  @Override
+  public void print(CfInstanceFieldWrite insn) {
+    printNewInstruction("CfInstanceFieldWrite", dexField(insn.getField()));
+  }
+
+  @Override
+  public void print(CfStaticFieldRead insn) {
+    printNewInstruction("CfStaticFieldRead", dexField(insn.getField()));
+  }
+
+  @Override
+  public void print(CfStaticFieldWrite insn) {
+    printNewInstruction("CfStaticFieldWrite", dexField(insn.getField()));
+  }
+
+  @Override
+  public void print(CfFieldInstruction insn) {
+    throw new Unreachable();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 98808c7..d0a3c23 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -25,6 +25,8 @@
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfIinc;
 import com.android.tools.r8.cf.code.CfInitClass;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -46,6 +48,8 @@
 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.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfSwitch.Kind;
@@ -472,6 +476,22 @@
     appendClass(insn.getType());
   }
 
+  public void print(CfInstanceFieldRead insn) {
+    print(insn.asFieldInstruction());
+  }
+
+  public void print(CfInstanceFieldWrite insn) {
+    print(insn.asFieldInstruction());
+  }
+
+  public void print(CfStaticFieldRead insn) {
+    print(insn.asFieldInstruction());
+  }
+
+  public void print(CfStaticFieldWrite insn) {
+    print(insn.asFieldInstruction());
+  }
+
   public void print(CfFieldInstruction insn) {
     indent();
     switch (insn.getOpcode()) {
@@ -555,8 +575,8 @@
   public void print(CfPosition instruction) {
     Position position = instruction.getPosition();
     indent();
-    builder.append(".line ").append(position.line);
-    if (position.file != null || position.callerPosition != null) {
+    builder.append(".line ").append(position.getLine());
+    if (position.hasCallerPosition() || position.hasFile()) {
       appendComment(position.toString());
     }
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index a2f4e80..2585329 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -10,14 +10,12 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
-import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.CfState;
 import com.android.tools.r8.ir.conversion.CfState.Slot;
@@ -28,11 +26,10 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
-import java.util.ListIterator;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
-public class CfFieldInstruction extends CfInstruction {
+public abstract class CfFieldInstruction extends CfInstruction {
 
   private final int opcode;
   private final DexField field;
@@ -53,6 +50,21 @@
     assert field.type == declaringField.type;
   }
 
+  public static CfFieldInstruction create(int opcode, DexField field, DexField declaringField) {
+    switch (opcode) {
+      case Opcodes.GETSTATIC:
+        return new CfStaticFieldRead(field, declaringField);
+      case Opcodes.PUTSTATIC:
+        return new CfStaticFieldWrite(field, declaringField);
+      case Opcodes.GETFIELD:
+        return new CfInstanceFieldRead(field, declaringField);
+      case Opcodes.PUTFIELD:
+        return new CfInstanceFieldWrite(field, declaringField);
+      default:
+        throw new Unreachable("Unexpected opcode " + opcode);
+    }
+  }
+
   public DexField getField() {
     return field;
   }
@@ -110,27 +122,6 @@
   }
 
   @Override
-  void internalRegisterUse(
-      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
-    switch (opcode) {
-      case Opcodes.GETFIELD:
-        registry.registerInstanceFieldRead(field);
-        break;
-      case Opcodes.PUTFIELD:
-        registry.registerInstanceFieldWrite(field);
-        break;
-      case Opcodes.GETSTATIC:
-        registry.registerStaticFieldRead(field);
-        break;
-      case Opcodes.PUTSTATIC:
-        registry.registerStaticFieldWrite(field);
-        break;
-      default:
-        throw new Unreachable("Unexpected opcode " + opcode);
-    }
-  }
-
-  @Override
   public boolean canThrow() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
new file mode 100644
index 0000000..145bc94
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
+
+public class CfInstanceFieldRead extends CfFieldInstruction implements CfOrDexInstanceFieldRead {
+
+  public CfInstanceFieldRead(DexField field) {
+    this(field, field);
+  }
+
+  public CfInstanceFieldRead(DexField field, DexField declaringField) {
+    super(Opcodes.GETFIELD, field, declaringField);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerInstanceFieldReadInstruction(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
new file mode 100644
index 0000000..6d01f49
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
+
+public class CfInstanceFieldWrite extends CfFieldInstruction {
+
+  public CfInstanceFieldWrite(DexField field) {
+    this(field, field);
+  }
+
+  public CfInstanceFieldWrite(DexField field, DexField declaringField) {
+    super(Opcodes.PUTFIELD, field, declaringField);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerInstanceFieldWrite(getField());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 742a2be..4427f41 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.code.CfOrDexInstruction;
+import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -110,6 +111,11 @@
     return true;
   }
 
+  @Override
+  public Instruction asDexInstruction() {
+    return null;
+  }
+
   public CfRecordFieldValues asRecordFieldValues() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 45077e2..0112d3f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -28,8 +28,6 @@
   private final CfLabel label;
   private final Position position;
 
-
-
   public CfPosition(CfLabel label, Position position) {
     this.label = label;
     this.position = position;
@@ -47,7 +45,7 @@
         this,
         (CfPosition) other,
         spec ->
-            spec.withInt(p -> p.position.line)
+            spec.withInt(p -> p.position.getLine())
                 .withCustomItem(p -> p.label, helper.labelAcceptor()));
   }
 
@@ -61,7 +59,7 @@
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    visitor.visitLineNumber(position.line, label.getLabel());
+    visitor.visitLineNumber(position.getLine(), label.getLabel());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
new file mode 100644
index 0000000..66fc48e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.code.CfOrDexStaticFieldRead;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
+
+public class CfStaticFieldRead extends CfFieldInstruction implements CfOrDexStaticFieldRead {
+
+  public CfStaticFieldRead(DexField field) {
+    super(Opcodes.GETSTATIC, field);
+  }
+
+  public CfStaticFieldRead(DexField field, DexField declaringField) {
+    super(Opcodes.GETSTATIC, field, declaringField);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerStaticFieldReadInstruction(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
new file mode 100644
index 0000000..2cd0f26
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.UseRegistry;
+import java.util.ListIterator;
+import org.objectweb.asm.Opcodes;
+
+public class CfStaticFieldWrite extends CfFieldInstruction {
+
+  public CfStaticFieldWrite(DexField field) {
+    super(Opcodes.PUTSTATIC, field);
+  }
+
+  public CfStaticFieldWrite(DexField field, DexField declaringField) {
+    super(Opcodes.PUTSTATIC, field, declaringField);
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerStaticFieldWrite(getField());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/code/CfOrDexInstanceFieldRead.java b/src/main/java/com/android/tools/r8/code/CfOrDexInstanceFieldRead.java
new file mode 100644
index 0000000..3d560fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/CfOrDexInstanceFieldRead.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.code;
+
+import com.android.tools.r8.graph.DexField;
+
+public interface CfOrDexInstanceFieldRead extends CfOrDexInstruction {
+
+  DexField getField();
+}
diff --git a/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java b/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
index e85c75c..e8ac111 100644
--- a/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
+++ b/src/main/java/com/android/tools/r8/code/CfOrDexInstruction.java
@@ -11,4 +11,6 @@
   CfInstruction asCfInstruction();
 
   boolean isCfInstruction();
+
+  Instruction asDexInstruction();
 }
diff --git a/src/main/java/com/android/tools/r8/code/CfOrDexStaticFieldRead.java b/src/main/java/com/android/tools/r8/code/CfOrDexStaticFieldRead.java
new file mode 100644
index 0000000..121023a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/CfOrDexStaticFieldRead.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.code;
+
+import com.android.tools.r8.graph.DexField;
+
+public interface CfOrDexStaticFieldRead extends CfOrDexInstruction {
+
+  DexField getField();
+}
diff --git a/src/main/java/com/android/tools/r8/code/Iget.java b/src/main/java/com/android/tools/r8/code/Iget.java
index 71c11c3..ee8e7a0 100644
--- a/src/main/java/com/android/tools/r8/code/Iget.java
+++ b/src/main/java/com/android/tools/r8/code/Iget.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class Iget extends IgetOrIput {
+public class Iget extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x52;
   public static final String NAME = "Iget";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetBoolean.java b/src/main/java/com/android/tools/r8/code/IgetBoolean.java
index 36c00b6..3212878 100644
--- a/src/main/java/com/android/tools/r8/code/IgetBoolean.java
+++ b/src/main/java/com/android/tools/r8/code/IgetBoolean.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetBoolean extends IgetOrIput {
+public class IgetBoolean extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x55;
   public static final String NAME = "IgetBoolean";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetByte.java b/src/main/java/com/android/tools/r8/code/IgetByte.java
index 8d57bb5..a876d70 100644
--- a/src/main/java/com/android/tools/r8/code/IgetByte.java
+++ b/src/main/java/com/android/tools/r8/code/IgetByte.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetByte extends IgetOrIput {
+public class IgetByte extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x56;
   public static final String NAME = "IgetByte";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetChar.java b/src/main/java/com/android/tools/r8/code/IgetChar.java
index 2ba0e26..efa458d 100644
--- a/src/main/java/com/android/tools/r8/code/IgetChar.java
+++ b/src/main/java/com/android/tools/r8/code/IgetChar.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetChar extends IgetOrIput {
+public class IgetChar extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x57;
   public static final String NAME = "IgetChar";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetObject.java b/src/main/java/com/android/tools/r8/code/IgetObject.java
index c685229..93540d6 100644
--- a/src/main/java/com/android/tools/r8/code/IgetObject.java
+++ b/src/main/java/com/android/tools/r8/code/IgetObject.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetObject extends IgetOrIput {
+public class IgetObject extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x54;
   public static final String NAME = "IgetObject";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetShort.java b/src/main/java/com/android/tools/r8/code/IgetShort.java
index 77667ba..23bd792 100644
--- a/src/main/java/com/android/tools/r8/code/IgetShort.java
+++ b/src/main/java/com/android/tools/r8/code/IgetShort.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetShort extends IgetOrIput {
+public class IgetShort extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x58;
   public static final String NAME = "IgetShort";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/IgetWide.java b/src/main/java/com/android/tools/r8/code/IgetWide.java
index a71b95a..12ad976 100644
--- a/src/main/java/com/android/tools/r8/code/IgetWide.java
+++ b/src/main/java/com/android/tools/r8/code/IgetWide.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class IgetWide extends IgetOrIput {
+public class IgetWide extends IgetOrIput implements CfOrDexInstanceFieldRead {
 
   public static final int OPCODE = 0x53;
   public static final String NAME = "IgetWide";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerInstanceFieldRead(getField());
+    registry.registerInstanceFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 03d7a3d..8671b68 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -133,6 +133,10 @@
     write32BitValue(item.getOffset(mapping), dest);
   }
 
+  public boolean hasOffset() {
+    return offset >= 0;
+  }
+
   public int getOffset() {
     return offset;
   }
@@ -151,6 +155,11 @@
     return false;
   }
 
+  @Override
+  public Instruction asDexInstruction() {
+    return this;
+  }
+
   public CheckCast asCheckCast() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/code/Sget.java b/src/main/java/com/android/tools/r8/code/Sget.java
index c2d93df..2bd4653 100644
--- a/src/main/java/com/android/tools/r8/code/Sget.java
+++ b/src/main/java/com/android/tools/r8/code/Sget.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class Sget extends SgetOrSput {
+public class Sget extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x60;
   public static final String NAME = "Sget";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetBoolean.java b/src/main/java/com/android/tools/r8/code/SgetBoolean.java
index 3d79934..fb51776 100644
--- a/src/main/java/com/android/tools/r8/code/SgetBoolean.java
+++ b/src/main/java/com/android/tools/r8/code/SgetBoolean.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetBoolean extends SgetOrSput {
+public class SgetBoolean extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x63;
   public static final String NAME = "SgetBoolean";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetByte.java b/src/main/java/com/android/tools/r8/code/SgetByte.java
index 08f2b97..ee693ac 100644
--- a/src/main/java/com/android/tools/r8/code/SgetByte.java
+++ b/src/main/java/com/android/tools/r8/code/SgetByte.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetByte extends SgetOrSput {
+public class SgetByte extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x64;
   public static final String NAME = "SgetByte";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetChar.java b/src/main/java/com/android/tools/r8/code/SgetChar.java
index 5ce7f8c..5f61f90 100644
--- a/src/main/java/com/android/tools/r8/code/SgetChar.java
+++ b/src/main/java/com/android/tools/r8/code/SgetChar.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetChar extends SgetOrSput {
+public class SgetChar extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x65;
   public static final String NAME = "SgetChar";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetObject.java b/src/main/java/com/android/tools/r8/code/SgetObject.java
index e4ae240..8e5018e 100644
--- a/src/main/java/com/android/tools/r8/code/SgetObject.java
+++ b/src/main/java/com/android/tools/r8/code/SgetObject.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetObject extends SgetOrSput {
+public class SgetObject extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x62;
   public static final String NAME = "SgetObject";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetShort.java b/src/main/java/com/android/tools/r8/code/SgetShort.java
index 62e2de7..99fc4ee 100644
--- a/src/main/java/com/android/tools/r8/code/SgetShort.java
+++ b/src/main/java/com/android/tools/r8/code/SgetShort.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetShort extends SgetOrSput {
+public class SgetShort extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x66;
   public static final String NAME = "SgetShort";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/code/SgetWide.java b/src/main/java/com/android/tools/r8/code/SgetWide.java
index 962d0c1..75a4343 100644
--- a/src/main/java/com/android/tools/r8/code/SgetWide.java
+++ b/src/main/java/com/android/tools/r8/code/SgetWide.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 
-public class SgetWide extends SgetOrSput {
+public class SgetWide extends SgetOrSput implements CfOrDexStaticFieldRead {
 
   public static final int OPCODE = 0x61;
   public static final String NAME = "SgetWide";
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry<?> registry) {
-    registry.registerStaticFieldRead(getField());
+    registry.registerStaticFieldReadInstruction(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index 24b42d3..b313b75 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -7,12 +7,17 @@
 import com.android.tools.r8.CompatProguardCommandBuilder;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.MapIdProvider;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8;
+import com.android.tools.r8.SourceFileProvider;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.MapIdTemplateProvider;
+import com.android.tools.r8.utils.SourceFileTemplateProvider;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import java.util.List;
@@ -39,6 +44,8 @@
     public final boolean includeDataResources;
     public final boolean multiDex;
     public final String mainDexList;
+    public final MapIdProvider mapIdProvider;
+    public final SourceFileProvider sourceFileProvider;
     public final List<String> proguardConfig;
     public boolean printHelpAndExit;
 
@@ -54,6 +61,8 @@
         boolean forceProguardCompatibility,
         boolean includeDataResources,
         String mainDexList,
+        MapIdProvider mapIdProvider,
+        SourceFileProvider sourceFileProvider,
         boolean printHelpAndExit,
         boolean disableVerticalClassMerging) {
       this.output = output;
@@ -64,11 +73,14 @@
       this.multiDex = multiDex;
       this.mainDexList = mainDexList;
       this.proguardConfig = proguardConfig;
+      this.mapIdProvider = mapIdProvider;
+      this.sourceFileProvider = sourceFileProvider;
       this.printHelpAndExit = printHelpAndExit;
       this.disableVerticalClassMerging = disableVerticalClassMerging;
     }
 
     public static CompatProguardOptions parse(String[] args) {
+      DiagnosticsHandler handler = new DiagnosticsHandler() {};
       String output = null;
       CompilationMode mode = null;
       int minApi = 1;
@@ -77,6 +89,8 @@
       boolean multiDex = false;
       String mainDexList = null;
       boolean printHelpAndExit = false;
+      MapIdProvider mapIdProvider = null;
+      SourceFileProvider sourceFileProvider = null;
       // Flags to disable experimental features.
       boolean disableVerticalClassMerging = false;
       // These flags are currently ignored.
@@ -116,6 +130,10 @@
               mainDexList = args[++i];
             } else if (arg.startsWith("--main-dex-list=")) {
               mainDexList = arg.substring("--main-dex-list=".length());
+            } else if (arg.equals("--map-id-template")) {
+              mapIdProvider = MapIdTemplateProvider.create(args[++i], handler);
+            } else if (arg.equals("--source-file-template")) {
+              sourceFileProvider = SourceFileTemplateProvider.create(args[++i], handler);
             } else if (arg.equals("--no-vertical-class-merging")) {
               disableVerticalClassMerging = true;
             } else if (arg.equals("--minimal-main-dex")) {
@@ -153,6 +171,8 @@
           forceProguardCompatibility,
           includeDataResources,
           mainDexList,
+          mapIdProvider,
+          sourceFileProvider,
           printHelpAndExit,
           disableVerticalClassMerging);
     }
@@ -199,7 +219,9 @@
     builder
         .setOutput(Paths.get(options.output), OutputMode.DexIndexed, options.includeDataResources)
         .addProguardConfiguration(options.proguardConfig, CommandLineOrigin.INSTANCE)
-        .setMinApiLevel(options.minApi);
+        .setMinApiLevel(options.minApi)
+        .setMapIdProvider(options.mapIdProvider)
+        .setSourceFileProvider(options.sourceFileProvider);
     if (options.mode != null) {
       builder.setMode(options.mode);
     }
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 dab9181..10e78f3 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.SourceFileEnvironment;
-import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer;
@@ -25,8 +24,6 @@
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexCallSite;
-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;
@@ -36,6 +33,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -127,7 +125,7 @@
     }
 
     @Override
-    public boolean add(DexCode dexCode) {
+    public boolean add(DexEncodedMethod method, DexWritableCode dexCode) {
       return true;
     }
 
@@ -642,18 +640,9 @@
   }
 
   private void setCallSiteContexts(DexProgramClass clazz) {
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (method.hasCode()) {
-        DexCode code = method.getCode().asDexCode();
-        assert code != null;
-        for (Instruction instruction : code.instructions) {
-          DexCallSite callSite = instruction.getCallSite();
-          if (callSite != null) {
-            callSite.setContext(method.getReference(), instruction.getOffset());
-          }
-        }
-      }
-    }
+    clazz.forEachProgramMethodMatching(
+        DexEncodedMethod::hasCode,
+        method -> method.getDefinition().getCode().asDexWritableCode().setCallSiteContexts(method));
   }
 
   /**
@@ -683,20 +672,23 @@
     // for all code objects and write the processed results into that map.
     // TODO(b/181636450): Reconsider the code mapping setup now that synthetics are never duplicated
     //  in outputs.
-    Map<DexEncodedMethod, DexCode> codeMapping = new IdentityHashMap<>();
+    Map<DexEncodedMethod, DexWritableCode> codeMapping = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      clazz.forEachMethod(
+      clazz.forEachProgramMethodMatching(
+          DexEncodedMethod::hasCode,
           method -> {
-            DexCode code =
-                method.rewriteCodeWithJumboStrings(
+            DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
+            DexWritableCode rewrittenCode =
+                code.rewriteCodeWithJumboStrings(
+                    method,
                     mapping,
                     application.dexItemFactory,
                     options.testing.forceJumboStringProcessing);
-            codeMapping.put(method, code);
+            codeMapping.put(method.getDefinition(), rewrittenCode);
             // The mapping now has ownership of the methods code object. This ensures freeing of
             // code resources once the map entry is cleared and also ensures that we don't end up
             // using the incorrect code pointer again later!
-            method.removeCode();
+            method.getDefinition().unsetCode();
           });
     }
     return MethodToCodeObjectMapping.fromMapBacking(codeMapping);
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 0e1edc9..44affb4 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -33,11 +33,11 @@
     return new DesugaredLibraryCodeToKeep(namingLens, options);
   }
 
-  abstract void recordMethod(DexMethod method);
+  public abstract void recordMethod(DexMethod method);
 
-  abstract void recordField(DexField field);
+  public abstract void recordField(DexField field);
 
-  abstract void recordClass(DexType type);
+  public abstract void recordClass(DexType type);
 
   abstract void recordClassAllAccesses(DexType type);
 
@@ -82,7 +82,7 @@
     }
 
     @Override
-    void recordMethod(DexMethod method) {
+    public void recordMethod(DexMethod method) {
       DexType baseType = method.holder.toBaseType(options.dexItemFactory());
       if (shouldKeep(baseType)) {
         keepClass(baseType);
@@ -101,7 +101,7 @@
     }
 
     @Override
-    void recordField(DexField field) {
+    public void recordField(DexField field) {
       DexType baseType = field.holder.toBaseType(options.dexItemFactory());
       if (shouldKeep(baseType)) {
         keepClass(baseType);
@@ -115,7 +115,7 @@
     }
 
     @Override
-    void recordClass(DexType type) {
+    public void recordClass(DexType type) {
       if (shouldKeep(type)) {
         keepClass(type);
       }
@@ -218,13 +218,13 @@
   public static class NopCodeToKeep extends CodeToKeep {
 
     @Override
-    void recordMethod(DexMethod method) {}
+    public void recordMethod(DexMethod method) {}
 
     @Override
-    void recordField(DexField field) {}
+    public void recordField(DexField field) {}
 
     @Override
-    void recordClass(DexType type) {}
+    public void recordClass(DexType type) {}
 
     @Override
     void recordClassAllAccesses(DexType type) {}
diff --git a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
index b193e73..08f8cad 100644
--- a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
+++ b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
@@ -4,11 +4,8 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.ByteBufferProvider;
-import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.EncodedValueUtils;
@@ -95,37 +92,17 @@
   }
 
   public void putInstructions(
-      DexCode code,
+      DexWritableCode code,
       ProgramMethod context,
       ObjectToOffsetMapping mapping,
       CodeToKeep desugaredLibraryCodeToKeep) {
-    int size = 0;
-    Instruction[] instructions = code.instructions;
-    for (Instruction instruction : instructions) {
-      size += instruction.getSize();
-    }
+    int size = code.codeSizeInBytes();
     ensureSpaceFor(size * Short.BYTES);
     assert byteBuffer.position() % 2 == 0;
     ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
-    for (int i = 0; i < instructions.length; i++) {
-      Instruction insn = instructions[i];
-      DexMethod method = insn.getMethod();
-      DexField field = insn.getField();
-      if (field != null) {
-        assert method == null;
-        desugaredLibraryCodeToKeep.recordField(field);
-      } else if (method != null) {
-        desugaredLibraryCodeToKeep.recordMethod(method);
-      } else if (insn.isConstClass()) {
-        desugaredLibraryCodeToKeep.recordClass(insn.asConstClass().getType());
-      } else if (insn.isInstanceOf()) {
-        desugaredLibraryCodeToKeep.recordClass(insn.asInstanceOf().getType());
-      } else if (insn.isCheckCast()) {
-        desugaredLibraryCodeToKeep.recordClass(insn.asCheckCast().getType());
-      }
-      insn.write(
-          shortBuffer, context, mapping.getGraphLens(), mapping, mapping.getLensCodeRewriter());
-    }
+    code.writeDex(
+        shortBuffer, context, mapping.getGraphLens(), mapping.getLensCodeRewriter(), mapping);
+    code.writeKeepRulesForDesugaredLibrary(desugaredLibraryCodeToKeep);
     byteBuffer.position(byteBuffer.position() + shortBuffer.position() * Short.BYTES);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index dc93c37..079d0d6 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.utils.LebUtils.sizeAsUleb128;
 
 import com.android.tools.r8.ByteBufferProvider;
-import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DefaultInterfaceMethodDiagnostic;
 import com.android.tools.r8.errors.InvokeCustomDiagnostic;
@@ -20,7 +19,6 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
 import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
@@ -43,6 +41,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
@@ -341,7 +340,7 @@
     for (DexProgramClass clazz : mapping.getClasses()) {
       clazz.forEachProgramMethod(
           method -> {
-            DexCode code = codeMapping.getCode(method.getDefinition());
+            DexWritableCode code = codeMapping.getCode(method.getDefinition());
             assert code != null || method.getDefinition().shouldNotHaveCode();
             if (code != null) {
               ProgramDexCode programCode = new ProgramDexCode(code, method);
@@ -409,18 +408,15 @@
     return size;
   }
 
-  private int sizeOfCodeItem(DexCode code) {
+  private int sizeOfCodeItem(DexWritableCode code) {
     int result = 16;
-    int insnSize = 0;
-    for (Instruction insn : code.instructions) {
-      insnSize += insn.getSize();
-    }
+    int insnSize = code.codeSizeInBytes();
     result += insnSize * 2;
-    result += code.tries.length * 8;
-    if (code.handlers.length > 0) {
+    result += code.getTries().length * 8;
+    if (code.getHandlers().length > 0) {
       result = alignSize(4, result);
-      result += LebUtils.sizeAsUleb128(code.handlers.length);
-      for (TryHandler handler : code.handlers) {
+      result += LebUtils.sizeAsUleb128(code.getHandlers().length);
+      for (TryHandler handler : code.getHandlers()) {
         boolean hasCatchAll = handler.catchAllAddr != TryHandler.NO_HANDLER;
         result += LebUtils
             .sizeAsSleb128(hasCatchAll ? -handler.pairs.length : handler.pairs.length);
@@ -501,13 +497,13 @@
     writeCodeItem(code.getCode(), code.getMethod());
   }
 
-  private void writeCodeItem(DexCode code, ProgramMethod method) {
-    mixedSectionOffsets.setOffsetFor(code, dest.align(4));
+  private void writeCodeItem(DexWritableCode code, ProgramMethod method) {
+    mixedSectionOffsets.setOffsetFor(method.getDefinition(), code, dest.align(4));
     // Fixed size header information.
-    dest.putShort((short) code.registerSize);
-    dest.putShort((short) code.incomingRegisterSize);
-    dest.putShort((short) code.outgoingRegisterSize);
-    dest.putShort((short) code.tries.length);
+    dest.putShort((short) code.getRegisterSize(method));
+    dest.putShort((short) code.getIncomingRegisterSize(method));
+    dest.putShort((short) code.getOutgoingRegisterSize());
+    dest.putShort((short) code.getTries().length);
     dest.putInt(mixedSectionOffsets.getOffsetFor(code.getDebugInfoForWriting()));
     // Jump over the size.
     int insnSizeOffset = dest.position();
@@ -519,16 +515,16 @@
     dest.rewind(insnSize + 4);
     dest.putInt(insnSize / 2);
     dest.forward(insnSize);
-    if (code.tries.length > 0) {
+    if (code.getTries().length > 0) {
       // The tries need to be 4 byte aligned.
       int beginOfTriesOffset = dest.align(4);
       // First write the handlers, so that we know their mixedSectionOffsets.
-      dest.forward(code.tries.length * 8);
+      dest.forward(code.getTries().length * 8);
       int beginOfHandlersOffset = dest.position();
-      dest.putUleb128(code.handlers.length);
-      short[] offsets = new short[code.handlers.length];
+      dest.putUleb128(code.getHandlers().length);
+      short[] offsets = new short[code.getHandlers().length];
       int i = 0;
-      for (TryHandler handler : code.handlers) {
+      for (TryHandler handler : code.getHandlers()) {
         offsets[i++] = (short) (dest.position() - beginOfHandlersOffset);
         boolean hasCatchAll = handler.catchAllAddr != TryHandler.NO_HANDLER;
         dest.putSleb128(hasCatchAll ? -handler.pairs.length : handler.pairs.length);
@@ -544,7 +540,7 @@
       int endOfCodeOffset = dest.position();
       // Now write the tries.
       dest.moveTo(beginOfTriesOffset);
-      for (Try aTry : code.tries) {
+      for (Try aTry : code.getTries()) {
         dest.putInt(aTry.startAddress);
         dest.putShort((short) aTry.instructionCount);
         dest.putShort(offsets[aTry.handlerIndex]);
@@ -665,13 +661,13 @@
       dest.putUleb128(nextOffset - currentOffset);
       currentOffset = nextOffset;
       dest.putUleb128(method.accessFlags.getAsDexAccessFlags());
-      DexCode code = codeMapping.getCode(method);
+      DexWritableCode code = codeMapping.getCode(method);
       desugaredLibraryCodeToKeep.recordMethod(method.getReference());
       if (code == null) {
         assert method.shouldNotHaveCode();
         dest.putUleb128(0);
       } else {
-        dest.putUleb128(mixedSectionOffsets.getOffsetFor(code));
+        dest.putUleb128(mixedSectionOffsets.getOffsetFor(method, code));
         // Writing the methods starts to take up memory so we are going to flush the
         // code objects since they are no longer necessary after this.
         codeMapping.clearCode(method);
@@ -867,7 +863,7 @@
     dest.putInt((int) adler.getValue());
   }
 
-  private int alignSize(int bytes, int value) {
+  private static int alignSize(int bytes, int value) {
     int mask = bytes - 1;
     return (value + mask) & ~mask;
   }
@@ -1083,7 +1079,7 @@
 
     private final MethodToCodeObjectMapping codeMapping;
 
-    private final Reference2IntMap<DexCode> codes = createReference2IntMap();
+    private final Reference2IntMap<DexEncodedMethod> codes = createReference2IntMap();
     private final Object2IntMap<DexDebugInfo> debugInfos = createObject2IntMap();
     private final Object2IntMap<DexTypeList> typeLists = createObject2IntMap();
     private final Reference2IntMap<DexString> stringData = createReference2IntMap();
@@ -1159,8 +1155,8 @@
     }
 
     @Override
-    public boolean add(DexCode code) {
-      return add(codes, code);
+    public boolean add(DexEncodedMethod method, DexWritableCode code) {
+      return add(codes, method);
     }
 
     @Override
@@ -1201,7 +1197,7 @@
       return add(stringData, string);
     }
 
-    public Collection<DexCode> getCodes() {
+    public Collection<DexEncodedMethod> getCodes() {
       return codes.keySet();
     }
 
@@ -1312,8 +1308,8 @@
       return lookup(annotationSetRefList, annotationSetRefLists);
     }
 
-    public int getOffsetFor(DexCode code) {
-      return lookup(code, codes);
+    public int getOffsetFor(DexEncodedMethod method, DexWritableCode code) {
+      return lookup(method, codes);
     }
 
     private <T> void setOffsetFor(T item, int offset, Object2IntMap<T> map) {
@@ -1330,8 +1326,8 @@
       setOffsetFor(debugInfo, offset, debugInfos);
     }
 
-    void setOffsetFor(DexCode code, int offset) {
-      setOffsetFor(code, offset, codes);
+    void setOffsetFor(DexEncodedMethod method, DexWritableCode code, int offset) {
+      setOffsetFor(method, offset, codes);
     }
 
     void setOffsetFor(DexTypeList typeList, int offset) {
diff --git a/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java b/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
index 5541398..dfdb07a 100644
--- a/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
+++ b/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
@@ -4,24 +4,27 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexWritableCode;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 public abstract class MethodToCodeObjectMapping {
 
-  public abstract DexCode getCode(DexEncodedMethod method);
+  public abstract DexWritableCode getCode(DexEncodedMethod method);
 
   public abstract void clearCode(DexEncodedMethod method);
 
-  public abstract boolean verifyCodeObjects(Collection<DexCode> codes);
+  public abstract boolean verifyCodeObjects(Collection<DexEncodedMethod> codes);
 
   public static MethodToCodeObjectMapping fromMethodBacking() {
     return MethodBacking.INSTANCE;
   }
 
-  public static MethodToCodeObjectMapping fromMapBacking(Map<DexEncodedMethod, DexCode> map) {
+  public static MethodToCodeObjectMapping fromMapBacking(
+      Map<DexEncodedMethod, DexWritableCode> map) {
     return new MapBacking(map);
   }
 
@@ -30,33 +33,33 @@
     private static final MethodBacking INSTANCE = new MethodBacking();
 
     @Override
-    public DexCode getCode(DexEncodedMethod method) {
+    public DexWritableCode getCode(DexEncodedMethod method) {
       Code code = method.getCode();
-      assert code == null || code.isDexCode();
-      return code == null ? null : code.asDexCode();
+      assert code == null || code.isDexWritableCode();
+      return code == null ? null : code.asDexWritableCode();
     }
 
     @Override
     public void clearCode(DexEncodedMethod method) {
-      method.removeCode();
+      method.unsetCode();
     }
 
     @Override
-    public boolean verifyCodeObjects(Collection<DexCode> codes) {
+    public boolean verifyCodeObjects(Collection<DexEncodedMethod> codes) {
       return true;
     }
   }
 
   private static class MapBacking extends MethodToCodeObjectMapping {
 
-    private final Map<DexEncodedMethod, DexCode> codes;
+    private final Map<DexEncodedMethod, DexWritableCode> codes;
 
-    public MapBacking(Map<DexEncodedMethod, DexCode> codes) {
+    public MapBacking(Map<DexEncodedMethod, DexWritableCode> codes) {
       this.codes = codes;
     }
 
     @Override
-    public DexCode getCode(DexEncodedMethod method) {
+    public DexWritableCode getCode(DexEncodedMethod method) {
       return codes.get(method);
     }
 
@@ -67,7 +70,10 @@
     }
 
     @Override
-    public boolean verifyCodeObjects(Collection<DexCode> codes) {
+    public boolean verifyCodeObjects(Collection<DexEncodedMethod> methods) {
+      // TODO(b/204056443): Convert to a Set<DexWritableCode> when DexCode#hashCode() works.
+      List<DexWritableCode> codes =
+          methods.stream().map(this::getCode).collect(Collectors.toList());
       assert this.codes.values().containsAll(codes);
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
index 2cedf5c..0ca695e 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -6,13 +6,13 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
-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.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 
 /**
@@ -66,11 +66,11 @@
   /**
    * Adds the given code item to the collection.
    *
-   * Does not add any dependencies.
+   * <p>Does not add any dependencies.
    *
    * @return true if the item was not added before
    */
-  public abstract boolean add(DexCode dexCode);
+  public abstract boolean add(DexEncodedMethod method, DexWritableCode dexCode);
 
   /**
    * Adds the given debug info to the collection.
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 4d3d50a..cfb13ae 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -514,6 +514,10 @@
     return options().testing;
   }
 
+  public boolean hasRootSet() {
+    return rootSet != null;
+  }
+
   public RootSet rootSet() {
     return rootSet;
   }
@@ -711,7 +715,10 @@
       setProguardCompatibilityActions(
           getProguardCompatibilityActions().withoutPrunedItems(prunedItems));
     }
-    if (mainDexRootSet != null) {
+    if (hasRootSet()) {
+      rootSet.pruneItems(prunedItems);
+    }
+    if (hasMainDexRootSet()) {
       setMainDexRootSet(mainDexRootSet.withoutPrunedItems(prunedItems));
     }
   }
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 c043d4a..a48fb5c 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
@@ -61,7 +62,7 @@
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
-public class CfCode extends Code implements StructuralItem<CfCode> {
+public class CfCode extends Code implements CfWritableCode, StructuralItem<CfCode> {
 
   public enum StackMapStatus {
     NOT_VERIFIED,
@@ -181,6 +182,11 @@
   }
 
   @Override
+  public CfWritableCodeKind getCfWritableCodeKind() {
+    return CfWritableCodeKind.DEFAULT;
+  }
+
+  @Override
   public StructuralMapping<CfCode> getStructuralMapping() {
     throw new Unreachable();
   }
@@ -260,11 +266,21 @@
   }
 
   @Override
+  public boolean isCfWritableCode() {
+    return true;
+  }
+
+  @Override
   public CfCode asCfCode() {
     return this;
   }
 
   @Override
+  public CfWritableCode asCfWritableCode() {
+    return this;
+  }
+
+  @Override
   public void acceptHashing(HashingVisitor visitor) {
     // Rather than hash the entire content, hash the sizes and each instruction "type" which
     // should provide a fast yet reasonably distinct key.
@@ -315,7 +331,8 @@
     return true;
   }
 
-  public void write(
+  @Override
+  public void writeCf(
       ProgramMethod method,
       CfVersion classFileVersion,
       AppView<?> appView,
@@ -763,7 +780,7 @@
 
   @Override
   public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
-    Position callerPosition = Position.synthetic(0, caller, null);
+    Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     List<CfInstruction> newInstructions = new ArrayList<>(instructions.size() + 2);
     CfLabel firstLabel;
     if (instructions.get(0).isLabel()) {
diff --git a/src/main/java/com/android/tools/r8/graph/CfWritableCode.java b/src/main/java/com/android/tools/r8/graph/CfWritableCode.java
new file mode 100644
index 0000000..939695f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/CfWritableCode.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.HashingVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+public interface CfWritableCode {
+
+  enum CfWritableCodeKind {
+    DEFAULT,
+    DEFAULT_INSTANCE_INITIALIZER,
+    THROW_NULL
+  }
+
+  default int acceptCompareTo(CfWritableCode code, CompareToVisitor visitor) {
+    CfWritableCodeKind kind = getCfWritableCodeKind();
+    CfWritableCodeKind otherKind = code.getCfWritableCodeKind();
+    if (kind != otherKind) {
+      return kind.compareTo(otherKind);
+    }
+    switch (kind) {
+      case DEFAULT:
+        return asCfCode().acceptCompareTo(code.asCfCode(), visitor);
+      case DEFAULT_INSTANCE_INITIALIZER:
+        return 0;
+      case THROW_NULL:
+        return 0;
+      default:
+        throw new Unreachable();
+    }
+  }
+
+  void acceptHashing(HashingVisitor visitor);
+
+  CfWritableCodeKind getCfWritableCodeKind();
+
+  default boolean isCfCode() {
+    return false;
+  }
+
+  default CfCode asCfCode() {
+    return null;
+  }
+
+  void writeCf(
+      ProgramMethod method,
+      CfVersion classFileVersion,
+      AppView<?> appView,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 205422c..6e41fa0 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -3,12 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.optimize.OutlinerImpl.OutlineCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -30,6 +31,10 @@
         + getClass().getCanonicalName());
   }
 
+  public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) {
+    return null;
+  }
+
   public abstract void registerCodeReferences(ProgramMethod method, UseRegistry registry);
 
   public abstract void registerCodeReferencesForDesugaring(
@@ -53,10 +58,26 @@
     return false;
   }
 
+  public boolean isCfWritableCode() {
+    return false;
+  }
+
+  public boolean isDefaultInstanceInitializerCode() {
+    return false;
+  }
+
+  public DefaultInstanceInitializerCode asDefaultInstanceInitializerCode() {
+    return null;
+  }
+
   public boolean isDexCode() {
     return false;
   }
 
+  public boolean isDexWritableCode() {
+    return false;
+  }
+
   public boolean isHorizontalClassMergingCode() {
     return false;
   }
@@ -65,6 +86,17 @@
     return false;
   }
 
+  public boolean isSharedCodeObject() {
+    return false;
+  }
+
+  public boolean isThrowNullCode() {
+    return false;
+  }
+
+  public ThrowNullCode asThrowNullCode() {
+    return null;
+  }
 
   /** Estimate the number of IR instructions emitted by buildIR(). */
   public int estimatedSizeForInlining() {
@@ -82,6 +114,10 @@
     throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()");
   }
 
+  public CfWritableCode asCfWritableCode() {
+    throw new Unreachable(getClass().getCanonicalName() + ".asCfWritableCode()");
+  }
+
   public LazyCfCode asLazyCfCode() {
     throw new Unreachable(getClass().getCanonicalName() + ".asLazyCfCode()");
   }
@@ -90,8 +126,8 @@
     throw new Unreachable(getClass().getCanonicalName() + ".asDexCode()");
   }
 
-  public OutlineCode asOutlineCode() {
-    throw new Unreachable(getClass().getCanonicalName() + ".asOutlineCode()");
+  public DexWritableCode asDexWritableCode() {
+    throw new Unreachable(getClass().getCanonicalName() + ".asDexWritableCode()");
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
new file mode 100644
index 0000000..f336d89
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -0,0 +1,404 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.cf.CfVersion;
+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.code.InvokeDirect;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.dex.CodeToKeep;
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.structural.HashingVisitor;
+import com.google.common.collect.ImmutableList;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * A piece of code on the form:
+ *
+ * <pre>
+ *   aload_0
+ *   invoke-special LSuperClass;-><init>()V
+ *   return
+ * </pre>
+ *
+ * <p>Note that (i) {@code SuperClass} may be different from {@link java.lang.Object} and (ii) the
+ * method holding this code object may have a non-empty proto.
+ */
+public class DefaultInstanceInitializerCode extends Code
+    implements CfWritableCode, DexWritableCode {
+
+  private static final DefaultInstanceInitializerCode INSTANCE =
+      new DefaultInstanceInitializerCode();
+
+  private DefaultInstanceInitializerCode() {}
+
+  public static DefaultInstanceInitializerCode get() {
+    return INSTANCE;
+  }
+
+  public static boolean canonicalizeCodeIfPossible(AppView<?> appView, ProgramMethod method) {
+    if (hasDefaultInstanceInitializerCode(method, appView)) {
+      method.getDefinition().setCode(get(), appView);
+      return true;
+    }
+    return false;
+  }
+
+  public static void uncanonicalizeCode(AppView<?> appView, ProgramMethod method) {
+    uncanonicalizeCode(appView, method, method.getHolder().getSuperType());
+  }
+
+  public static void uncanonicalizeCode(
+      AppView<?> appView, ProgramMethod method, DexType superType) {
+    DexEncodedMethod definition = method.getDefinition();
+    assert definition.getCode().isDefaultInstanceInitializerCode();
+    definition.setCode(get().toCfCode(method, appView.dexItemFactory(), superType), appView);
+  }
+
+  private static boolean hasDefaultInstanceInitializerCode(
+      ProgramMethod method, AppView<?> appView) {
+    if (!method.getDefinition().isInstanceInitializer()) {
+      return false;
+    }
+    Code code = method.getDefinition().getCode();
+    if (!code.isCfCode()) {
+      return false;
+    }
+    CfCode cfCode = code.asCfCode();
+    if (!method.getDefinition().isInstanceInitializer()
+        || !cfCode.getLocalVariables().isEmpty()
+        || !cfCode.getTryCatchRanges().isEmpty()) {
+      return false;
+    }
+    if (cfCode.getInstructions().size() > 6) {
+      // Default instance initializers typically have the following instruction sequence:
+      // [CfLabel, CfPosition, CfLoad, CfInvoke, CfReturnVoid, CfLabel].
+      return false;
+    }
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    Iterator<CfInstruction> instructionIterator = cfCode.getInstructions().iterator();
+    // Allow skipping CfPosition instructions in instance initializers that only call Object.<init>.
+    Predicate<CfInstruction> instructionOfInterest =
+        method.getHolder().getSuperType() == dexItemFactory.objectType
+            ? instruction -> !instruction.isLabel() && !instruction.isPosition()
+            : instruction -> !instruction.isLabel();
+    CfLoad load = IteratorUtils.nextUntil(instructionIterator, instructionOfInterest).asLoad();
+    if (load == null || load.getLocalIndex() != 0) {
+      return false;
+    }
+    CfInvoke invoke = instructionIterator.next().asInvoke();
+    if (invoke == null
+        || !invoke.isInvokeConstructor(dexItemFactory)
+        || invoke.getMethod() != getParentConstructor(method, dexItemFactory)) {
+      return false;
+    }
+    return instructionIterator.next().isReturnVoid();
+  }
+
+  @Override
+  public void acceptHashing(HashingVisitor visitor) {
+    visitor.visitInt(getCfWritableCodeKind().hashCode());
+  }
+
+  @Override
+  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    DefaultInstanceInitializerSourceCode source = new DefaultInstanceInitializerSourceCode(method);
+    return IRBuilder.create(method, appView, source, origin).build(method);
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      ProgramMethod context,
+      ProgramMethod method,
+      AppView<?> appView,
+      GraphLens codeLens,
+      NumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin,
+      RewrittenPrototypeDescription protoChanges) {
+    DefaultInstanceInitializerSourceCode source =
+        new DefaultInstanceInitializerSourceCode(method, callerPosition);
+    return IRBuilder.createForInlining(
+            method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
+        .build(context);
+  }
+
+  @Override
+  public int codeSizeInBytes() {
+    return InvokeDirect.SIZE + ReturnVoid.SIZE;
+  }
+
+  @Override
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    getParentConstructor(context, rewriter.dexItemFactory()).collectIndexedItems(indexedItems);
+  }
+
+  @Override
+  public void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+    // Intentionally empty.
+  }
+
+  @Override
+  protected int computeHashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    return this == other;
+  }
+
+  @Override
+  public int estimatedDexCodeSizeUpperBoundInBytes() {
+    return codeSizeInBytes();
+  }
+
+  @Override
+  public CfWritableCodeKind getCfWritableCodeKind() {
+    return CfWritableCodeKind.DEFAULT_INSTANCE_INITIALIZER;
+  }
+
+  @Override
+  public DexWritableCodeKind getDexWritableCodeKind() {
+    return DexWritableCodeKind.DEFAULT_INSTANCE_INITIALIZER;
+  }
+
+  @Override
+  public DexDebugInfoForWriting getDebugInfoForWriting() {
+    return null;
+  }
+
+  @Override
+  public TryHandler[] getHandlers() {
+    return new TryHandler[0];
+  }
+
+  @Override
+  public DexString getHighestSortingString() {
+    return null;
+  }
+
+  @Override
+  public int getIncomingRegisterSize(ProgramMethod method) {
+    return getMaxLocals(method);
+  }
+
+  static DexMethod getParentConstructor(DexClassAndMethod method, DexItemFactory dexItemFactory) {
+    return dexItemFactory.createInstanceInitializer(method.getHolder().getSuperType());
+  }
+
+  private int getMaxLocals(ProgramMethod method) {
+    int maxLocals = method.getAccessFlags().isStatic() ? 0 : 1;
+    for (DexType parameter : method.getParameters()) {
+      maxLocals += parameter.getRequiredRegisters();
+    }
+    return maxLocals;
+  }
+
+  private int getMaxStack() {
+    return 1;
+  }
+
+  @Override
+  public int getOutgoingRegisterSize() {
+    return 1;
+  }
+
+  @Override
+  public int getRegisterSize(ProgramMethod method) {
+    return getIncomingRegisterSize(method);
+  }
+
+  @Override
+  public Try[] getTries() {
+    return new Try[0];
+  }
+
+  @Override
+  public boolean isCfWritableCode() {
+    return true;
+  }
+
+  @Override
+  public CfWritableCode asCfWritableCode() {
+    return this;
+  }
+
+  @Override
+  public boolean isDexWritableCode() {
+    return true;
+  }
+
+  @Override
+  public DexWritableCode asDexWritableCode() {
+    return this;
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    return false;
+  }
+
+  @Override
+  public boolean isDefaultInstanceInitializerCode() {
+    return true;
+  }
+
+  @Override
+  public DefaultInstanceInitializerCode asDefaultInstanceInitializerCode() {
+    return this;
+  }
+
+  @Override
+  public boolean isSharedCodeObject() {
+    return true;
+  }
+
+  @Override
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    internalRegisterCodeReferences(method, registry);
+  }
+
+  @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    internalRegisterCodeReferences(method, registry);
+  }
+
+  private void internalRegisterCodeReferences(DexClassAndMethod method, UseRegistry<?> registry) {
+    registry.registerInvokeDirect(getParentConstructor(method, registry.dexItemFactory()));
+  }
+
+  @Override
+  public DexWritableCode rewriteCodeWithJumboStrings(
+      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+    // Intentionally empty. This piece of code does not have any const-string instructions.
+    return this;
+  }
+
+  @Override
+  public void setCallSiteContexts(ProgramMethod method) {
+    // Intentionally empty. This piece of code does not have any call sites.
+  }
+
+  public CfCode toCfCode(ProgramMethod method, DexItemFactory dexItemFactory) {
+    return toCfCode(method, dexItemFactory, method.getHolder().getSuperType());
+  }
+
+  public CfCode toCfCode(ProgramMethod method, DexItemFactory dexItemFactory, DexType supertype) {
+    List<CfInstruction> instructions =
+        Arrays.asList(
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                Opcodes.INVOKESPECIAL, dexItemFactory.createInstanceInitializer(supertype), false),
+            new CfReturnVoid());
+    return new CfCode(method.getHolderType(), getMaxStack(), getMaxLocals(method), instructions);
+  }
+
+  @Override
+  public void writeCf(
+      ProgramMethod method,
+      CfVersion classFileVersion,
+      AppView<?> appView,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+    visitor.visitMethodInsn(
+        Opcodes.INVOKESPECIAL,
+        namingLens.lookupInternalName(method.getHolder().getSuperType()),
+        "<init>",
+        "()V",
+        false);
+    visitor.visitInsn(Opcodes.RETURN);
+    visitor.visitEnd();
+    visitor.visitMaxs(getMaxStack(), getMaxLocals(method));
+  }
+
+  @Override
+  public void writeDex(
+      ShortBuffer shortBuffer,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils lensCodeRewriter,
+      ObjectToOffsetMapping mapping) {
+    new InvokeDirect(1, getParentConstructor(context, mapping.dexItemFactory()), 0, 0, 0, 0, 0)
+        .write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+    new ReturnVoid().write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+  }
+
+  @Override
+  public void writeKeepRulesForDesugaredLibrary(CodeToKeep codeToKeep) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public String toString() {
+    return "DefaultInstanceInitializerCode";
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    return toString();
+  }
+
+  static class DefaultInstanceInitializerSourceCode extends SyntheticStraightLineSourceCode {
+
+    DefaultInstanceInitializerSourceCode(ProgramMethod method) {
+      this(method, null);
+    }
+
+    DefaultInstanceInitializerSourceCode(ProgramMethod method, Position callerPosition) {
+      super(
+          getInstructionBuilders(),
+          SyntheticPosition.builder()
+              .setLine(0)
+              .setMethod(method.getReference())
+              .setCallerPosition(callerPosition)
+              .build());
+    }
+
+    private static List<Consumer<IRBuilder>> getInstructionBuilders() {
+      return ImmutableList.of(
+          builder ->
+              builder.add(
+                  com.android.tools.r8.ir.code.InvokeDirect.builder()
+                      .setMethod(
+                          getParentConstructor(
+                              builder.getProgramMethod(), builder.dexItemFactory()))
+                      .setSingleArgument(builder.getReceiverValue())
+                      .build()),
+          IRBuilder::addReturn);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 8409ee1..8c16e46 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -3,18 +3,25 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.code.CfOrDexInstruction;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SwitchPayload;
+import com.android.tools.r8.dex.CodeToKeep;
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.JumboStringRewriter;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
@@ -23,11 +30,13 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.Equatable;
 import com.android.tools.r8.utils.structural.HashCodeVisitor;
+import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import com.google.common.base.Strings;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import java.nio.ShortBuffer;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -37,7 +46,7 @@
 import java.util.Set;
 
 // DexCode corresponds to code item in dalvik/dex-format.html
-public class DexCode extends Code implements StructuralItem<DexCode> {
+public class DexCode extends Code implements DexWritableCode, StructuralItem<DexCode> {
 
   public static final String FAKE_THIS_PREFIX = "_";
   public static final String FAKE_THIS_SUFFIX = "this";
@@ -53,6 +62,8 @@
   private DexDebugInfo debugInfo;
   private DexDebugInfoForWriting debugInfoForWriting;
 
+  private final BytecodeMetadata<Instruction> metadata;
+
   private static void specify(StructuralSpecification<DexCode, ?> spec) {
     spec.withInt(c -> c.registerSize)
         .withInt(c -> c.incomingRegisterSize)
@@ -71,6 +82,26 @@
       Try[] tries,
       TryHandler[] handlers,
       DexDebugInfo debugInfo) {
+    this(
+        registerSize,
+        insSize,
+        outsSize,
+        instructions,
+        tries,
+        handlers,
+        debugInfo,
+        BytecodeMetadata.empty());
+  }
+
+  public DexCode(
+      int registerSize,
+      int insSize,
+      int outsSize,
+      Instruction[] instructions,
+      Try[] tries,
+      TryHandler[] handlers,
+      DexDebugInfo debugInfo,
+      BytecodeMetadata<Instruction> metadata) {
     this.incomingRegisterSize = insSize;
     this.registerSize = registerSize;
     this.outgoingRegisterSize = outsSize;
@@ -78,6 +109,7 @@
     this.tries = tries;
     this.handlers = handlers;
     this.debugInfo = debugInfo;
+    this.metadata = metadata;
     assert tries != null;
     assert handlers != null;
     assert instructions != null;
@@ -90,10 +122,54 @@
   }
 
   @Override
+  public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) {
+    return getMetadata(instruction.asDexInstruction());
+  }
+
+  public BytecodeInstructionMetadata getMetadata(Instruction instruction) {
+    return metadata.getMetadata(instruction);
+  }
+
+  @Override
+  public DexWritableCodeKind getDexWritableCodeKind() {
+    return DexWritableCodeKind.DEFAULT;
+  }
+
+  @Override
   public StructuralMapping<DexCode> getStructuralMapping() {
     return DexCode::specify;
   }
 
+  @Override
+  public DexWritableCode rewriteCodeWithJumboStrings(
+      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+    DexString firstJumboString = null;
+    if (force) {
+      firstJumboString = mapping.getFirstString();
+    } else {
+      assert highestSortingString != null
+          || Arrays.stream(instructions).noneMatch(Instruction::isConstString);
+      assert Arrays.stream(instructions).noneMatch(Instruction::isDexItemBasedConstString);
+      if (highestSortingString != null
+          && mapping.getOffsetFor(highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
+        firstJumboString = mapping.getFirstJumboString();
+      }
+    }
+    return firstJumboString != null
+        ? new JumboStringRewriter(method.getDefinition(), firstJumboString, factory).rewrite()
+        : this;
+  }
+
+  @Override
+  public void setCallSiteContexts(ProgramMethod method) {
+    for (Instruction instruction : instructions) {
+      DexCallSite callSite = instruction.getCallSite();
+      if (callSite != null) {
+        callSite.setContext(method.getReference(), instruction.getOffset());
+      }
+    }
+  }
+
   public DexCode withoutThisParameter() {
     // Note that we assume the original code has a register associated with 'this'
     // argument of the (former) instance method. We also assume (but do not check)
@@ -117,6 +193,16 @@
   }
 
   @Override
+  public boolean isDexWritableCode() {
+    return true;
+  }
+
+  @Override
+  public DexWritableCode asDexWritableCode() {
+    return this;
+  }
+
+  @Override
   public int estimatedSizeForInlining() {
     return codeSizeInBytes();
   }
@@ -180,7 +266,7 @@
   }
 
   private DexDebugInfo debugInfoAsInlining(DexMethod caller, DexMethod callee) {
-    Position callerPosition = Position.synthetic(0, caller, null);
+    Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     if (debugInfo == null) {
       // If the method has no debug info we generate a preamble position to denote the inlining.
       // This is consistent with the building IR for inlining which will always ensure the method
@@ -239,6 +325,11 @@
   }
 
   @Override
+  public void acceptHashing(HashingVisitor visitor) {
+    visitor.visit(this, getStructuralMapping());
+  }
+
+  @Override
   public int computeHashCode() {
     return incomingRegisterSize * 2
         + registerSize * 3
@@ -451,6 +542,7 @@
     return builder.toString();
   }
 
+  @Override
   public void collectIndexedItems(
       IndexedItemCollection indexedItems,
       ProgramMethod context,
@@ -474,6 +566,7 @@
       }
   }
 
+  @Override
   public DexDebugInfoForWriting getDebugInfoForWriting() {
     if (debugInfo == null) {
       return null;
@@ -485,6 +578,36 @@
     return debugInfoForWriting;
   }
 
+  @Override
+  public TryHandler[] getHandlers() {
+    return handlers;
+  }
+
+  @Override
+  public DexString getHighestSortingString() {
+    return highestSortingString;
+  }
+
+  @Override
+  public Try[] getTries() {
+    return tries;
+  }
+
+  @Override
+  public int getRegisterSize(ProgramMethod method) {
+    return registerSize;
+  }
+
+  @Override
+  public int getIncomingRegisterSize(ProgramMethod method) {
+    return incomingRegisterSize;
+  }
+
+  @Override
+  public int getOutgoingRegisterSize() {
+    return outgoingRegisterSize;
+  }
+
   private void updateHighestSortingString(DexString candidate) {
     assert candidate != null;
     if (highestSortingString == null || highestSortingString.compareTo(candidate) < 0) {
@@ -497,17 +620,59 @@
   }
 
   @Override
-  void collectMixedSectionItems(MixedSectionCollection mixedItems) {
-    if (mixedItems.add(this)) {
-      if (debugInfo != null) {
-        getDebugInfoForWriting().collectMixedSectionItems(mixedItems);
+  public void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+    if (debugInfo != null) {
+      getDebugInfoForWriting().collectMixedSectionItems(mixedItems);
+    }
+  }
+
+  @Override
+  public int codeSizeInBytes() {
+    Instruction last = instructions[instructions.length - 1];
+    assert last.hasOffset();
+    int result = last.getOffset() + last.getSize();
+    assert result == computeCodeSizeInBytes();
+    return result;
+  }
+
+  private int computeCodeSizeInBytes() {
+    int size = 0;
+    for (Instruction insn : instructions) {
+      size += insn.getSize();
+    }
+    return size;
+  }
+
+  @Override
+  public void writeKeepRulesForDesugaredLibrary(CodeToKeep desugaredLibraryCodeToKeep) {
+    for (Instruction instruction : instructions) {
+      DexMethod method = instruction.getMethod();
+      DexField field = instruction.getField();
+      if (field != null) {
+        assert method == null;
+        desugaredLibraryCodeToKeep.recordField(field);
+      } else if (method != null) {
+        desugaredLibraryCodeToKeep.recordMethod(method);
+      } else if (instruction.isConstClass()) {
+        desugaredLibraryCodeToKeep.recordClass(instruction.asConstClass().getType());
+      } else if (instruction.isInstanceOf()) {
+        desugaredLibraryCodeToKeep.recordClass(instruction.asInstanceOf().getType());
+      } else if (instruction.isCheckCast()) {
+        desugaredLibraryCodeToKeep.recordClass(instruction.asCheckCast().getType());
       }
     }
   }
 
-  public int codeSizeInBytes() {
-    Instruction last = instructions[instructions.length - 1];
-    return last.getOffset() + last.getSize();
+  @Override
+  public void writeDex(
+      ShortBuffer shortBuffer,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils lensCodeRewriter,
+      ObjectToOffsetMapping mapping) {
+    for (Instruction instruction : instructions) {
+      instruction.write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+    }
   }
 
   public static class Try extends DexItem implements StructuralItem<Try> {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index 8eb786a..2547996 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -63,8 +63,8 @@
       builder.append(":").append(method.name);
       Position caller = callerPosition;
       while (caller != null) {
-        builder.append(";").append(caller.line).append(":").append(caller.method.name);
-        caller = caller.callerPosition;
+        builder.append(";").append(caller.getLine()).append(":").append(caller.getMethod().name);
+        caller = caller.getCallerPosition();
       }
     }
     if (prologueEnd) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index 09bc3b7..ce338d6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame;
 import com.android.tools.r8.ir.code.ValueType;
 import com.google.common.collect.ImmutableMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
@@ -113,6 +115,16 @@
   }
 
   @Override
+  public void visit(SetOutlineFrame setOutlineFrame) {
+    positionState.visit(setOutlineFrame);
+  }
+
+  @Override
+  public void visit(SetOutlineCallerFrame setOutlineCallerFrame) {
+    positionState.visit(setOutlineCallerFrame);
+  }
+
+  @Override
   public void visit(DexDebugEvent.Default defaultEvent) {
     positionState.visit(defaultEvent);
     entryEventReceived(true);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index dfd4f1c..bb93147 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -21,6 +22,8 @@
 
   // Compare ID(s) for virtual debug events.
   private static final int DBG_SET_INLINE_FRAME_COMPARE_ID = Constants.DBG_LAST_SPECIAL + 1;
+  private static final int DBG_SET_OUTLINE_FRAME_COMPARE_ID = Constants.DBG_LAST_SPECIAL + 2;
+  private static final int DBG_SET_OUTLINE_CALLER_COMPARE_ID = Constants.DBG_LAST_SPECIAL + 3;
 
   public static final DexDebugEvent[] EMPTY_ARRAY = {};
 
@@ -84,10 +87,22 @@
     return false;
   }
 
+  public boolean isSetOutlineFrame() {
+    return false;
+  }
+
+  public boolean isSetOutlineCallerFrame() {
+    return false;
+  }
+
   public SetInlineFrame asSetInlineFrame() {
     return null;
   }
 
+  public SetOutlineCallerFrame asSetOutlineCallerFrame() {
+    return null;
+  }
+
   public static class AdvancePC extends DexDebugEvent {
 
     public final int delta;
@@ -579,7 +594,118 @@
 
     public boolean hasOuterPosition(DexMethod method) {
       return (caller == null && callee == method)
-          || (caller != null && caller.getOutermostCaller().method == method);
+          || (caller != null && caller.getOutermostCaller().getMethod() == method);
+    }
+  }
+
+  public static class SetOutlineFrame extends DexDebugEvent {
+
+    @Override
+    public String toString() {
+      return "SET_OUTLINE_FRAME";
+    }
+
+    @Override
+    public int hashCode() {
+      return 7;
+    }
+
+    @Override
+    int getCompareToId() {
+      return DBG_SET_OUTLINE_FRAME_COMPARE_ID;
+    }
+
+    @Override
+    int internalAcceptCompareTo(DexDebugEvent other, CompareToVisitor visitor) {
+      return 0;
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      // Intentionally empty: no content besides the compare-id
+    }
+
+    @Override
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+      // Will not be written
+    }
+
+    @Override
+    public void accept(DexDebugEventVisitor visitor) {
+      visitor.visit(this);
+    }
+  }
+
+  public static class SetOutlineCallerFrame extends DexDebugEvent {
+
+    private final DexMethod outlineCallee;
+    private final Int2StructuralItemArrayMap<Position> outlinePositions;
+
+    private static void specify(StructuralSpecification<SetOutlineCallerFrame, ?> spec) {
+      spec.withItem(e -> e.outlineCallee).withNullableItem(e -> e.outlinePositions);
+    }
+
+    SetOutlineCallerFrame(
+        DexMethod outlineCallee, Int2StructuralItemArrayMap<Position> outlinePositions) {
+      assert outlineCallee != null;
+      assert !outlinePositions.isEmpty();
+      this.outlineCallee = outlineCallee;
+      this.outlinePositions = outlinePositions;
+    }
+
+    public DexMethod getOutlineCallee() {
+      return outlineCallee;
+    }
+
+    public Int2StructuralItemArrayMap<Position> getOutlinePositions() {
+      return outlinePositions;
+    }
+
+    @Override
+    public void writeOn(
+        DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
+      // CallerPosition will not be written.
+    }
+
+    @Override
+    public void accept(DexDebugEventVisitor visitor) {
+      visitor.visit(this);
+    }
+
+    @Override
+    public String toString() {
+      return String.format("SET_OUTLINE_CALLER_FRAME %s %s", outlineCallee, outlinePositions);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(outlineCallee, outlinePositions);
+    }
+
+    @Override
+    int getCompareToId() {
+      return DBG_SET_OUTLINE_CALLER_COMPARE_ID;
+    }
+
+    @Override
+    int internalAcceptCompareTo(DexDebugEvent other, CompareToVisitor visitor) {
+      return visitor.visit(this, (SetOutlineCallerFrame) other, SetOutlineCallerFrame::specify);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visit(this, SetOutlineCallerFrame::specify);
+    }
+
+    @Override
+    public boolean isSetOutlineCallerFrame() {
+      return true;
+    }
+
+    @Override
+    public SetOutlineCallerFrame asSetOutlineCallerFrame() {
+      return this;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 7a8a3f6..f8bfbeb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.utils.InternalOptions;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
@@ -183,17 +184,17 @@
     assert !position.equals(emittedPosition);
     if (startLine == NO_LINE_INFO) {
       assert emittedPosition.isNone();
-      if (position.synthetic && position.callerPosition == null) {
+      if (position.isSyntheticPosition() && !position.hasCallerPosition()) {
         // Ignore synthetic positions prior to any actual position.
         // We do need to preserve synthetic position establishing the stack frame for inlined
         // methods.
         return;
       }
-      startLine = position.line;
+      startLine = position.getLine();
       emittedPosition =
-          Position.builder()
-              .setLine(position.line)
-              .setMethod(position.getOutermostCaller().method)
+          SourcePosition.builder()
+              .setLine(position.getLine())
+              .setMethod(position.getOutermostCaller().getMethod())
               .build();
     }
     assert emittedPc != pc;
@@ -235,21 +236,30 @@
     assert previousPc >= 0;
     int pcDelta = nextPc - previousPc;
     assert !previousPosition.isNone() || nextPosition.isNone();
-    assert nextPosition.isNone() || nextPosition.line >= 0;
-    int lineDelta = nextPosition.isNone() ? 0 : nextPosition.line - previousPosition.line;
+    assert nextPosition.isNone() || nextPosition.getLine() >= 0;
+    int lineDelta = nextPosition.isNone() ? 0 : nextPosition.getLine() - previousPosition.getLine();
     assert pcDelta >= 0;
-    if (nextPosition.file != previousPosition.file) {
-      events.add(factory.createSetFile(nextPosition.file));
+    if (nextPosition.getFile() != previousPosition.getFile()) {
+      events.add(factory.createSetFile(nextPosition.getFile()));
     }
     // The LineNumberOptimizer maps new positions based on the outer most caller with
     // callerPosition == null.
-    assert null != nextPosition.callerPosition
-        || null != previousPosition.callerPosition
-        || nextPosition.method == previousPosition.method
+    assert nextPosition.hasCallerPosition()
+        || previousPosition.hasCallerPosition()
+        || nextPosition.getMethod() == previousPosition.getMethod()
         || optimizingLineNumbers;
-    if (nextPosition.callerPosition != previousPosition.callerPosition
-        || nextPosition.method != previousPosition.method) {
-      events.add(factory.createSetInlineFrame(nextPosition.method, nextPosition.callerPosition));
+    if (nextPosition.getCallerPosition() != previousPosition.getCallerPosition()
+        || nextPosition.getMethod() != previousPosition.getMethod()) {
+      events.add(
+          factory.createSetInlineFrame(nextPosition.getMethod(), nextPosition.getCallerPosition()));
+    }
+    if (nextPosition.isOutline()) {
+      events.add(factory.createSetOutlineFrame());
+    }
+    if (nextPosition.getOutlineCallee() != null) {
+      events.add(
+          factory.createSetOutlineCallerFrame(
+              nextPosition.getOutlineCallee(), nextPosition.getOutlinePositions()));
     }
     if (lineDelta < Constants.DBG_LINE_BASE
         || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventVisitor.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventVisitor.java
index 464c93d..165858b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventVisitor.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
 import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 
@@ -21,6 +23,10 @@
 
   void visit(SetInlineFrame setInlineFrame);
 
+  void visit(SetOutlineFrame setOutlineFrame);
+
+  void visit(SetOutlineCallerFrame setOutlineCallerFrame);
+
   void visit(Default defaultEvent);
 
   void visit(SetFile setFile);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
index b56ad04..9c5ffd6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
@@ -12,9 +12,12 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
 import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
 
 /**
  * State machine to process and accumulate position-related DexDebugEvents. Clients should retrieve
@@ -27,6 +30,9 @@
   private DexString currentFile = null;
   private DexMethod currentMethod;
   private Position currentCallerPosition = null;
+  private boolean isOutline;
+  private DexMethod outlineCallee;
+  private Int2StructuralItemArrayMap<Position> outlineCallerPositions;
 
   public DexDebugPositionState(int startLine, DexMethod method) {
     currentLine = startLine;
@@ -51,6 +57,17 @@
   }
 
   @Override
+  public void visit(SetOutlineFrame setOutlineFrame) {
+    isOutline = true;
+  }
+
+  @Override
+  public void visit(SetOutlineCallerFrame setOutlineCallerFrame) {
+    outlineCallee = setOutlineCallerFrame.getOutlineCallee();
+    outlineCallerPositions = setOutlineCallerFrame.getOutlinePositions();
+  }
+
+  @Override
   public void visit(Default defaultEvent) {
     assert defaultEvent.getPCDelta() >= 0;
     currentPc += defaultEvent.getPCDelta();
@@ -106,4 +123,22 @@
   public Position getCurrentCallerPosition() {
     return currentCallerPosition;
   }
+
+  public boolean isOutline() {
+    return isOutline;
+  }
+
+  public DexMethod getOutlineCallee() {
+    return outlineCallee;
+  }
+
+  public Int2StructuralItemArrayMap<Position> getOutlineCallerPositions() {
+    return outlineCallerPositions;
+  }
+
+  public void resetOutlineInformation() {
+    isOutline = false;
+    outlineCallee = null;
+    outlineCallerPositions = null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 209e51c..d3be3c2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -40,8 +40,6 @@
 import com.android.tools.r8.code.Return;
 import com.android.tools.r8.code.Throw;
 import com.android.tools.r8.code.XorIntLit8;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.dex.JumboStringRewriter;
 import com.android.tools.r8.dex.MethodToCodeObjectMapping;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
@@ -50,6 +48,7 @@
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumericType;
@@ -305,21 +304,22 @@
   }
 
   private static int compareCodeObject(Code code1, Code code2, CompareToVisitor visitor) {
-    if (code1.isCfCode() && code2.isCfCode()) {
-      return code1.asCfCode().acceptCompareTo(code2.asCfCode(), visitor);
+    if (code1.isCfWritableCode() && code2.isCfWritableCode()) {
+      return code1.asCfWritableCode().acceptCompareTo(code2.asCfWritableCode(), visitor);
     }
-    if (code1.isDexCode() && code2.isDexCode()) {
-      return code1.asDexCode().acceptCompareTo(code2.asDexCode(), visitor);
+    if (code1.isDexWritableCode() && code2.isDexWritableCode()) {
+      return code1.asDexWritableCode().acceptCompareTo(code2.asDexWritableCode(), visitor);
     }
     throw new Unreachable(
         "Unexpected attempt to compare incompatible synthetic objects: " + code1 + " and " + code2);
   }
 
   private static void hashCodeObject(Code code, HashingVisitor visitor) {
-    if (code.isCfCode()) {
-      code.asCfCode().acceptHashing(visitor);
+    if (code.isCfWritableCode()) {
+      code.asCfWritableCode().acceptHashing(visitor);
     } else {
-      code.asDexCode().acceptHashing(visitor);
+      assert code.isDexWritableCode();
+      code.asDexWritableCode().acceptHashing(visitor);
     }
   }
 
@@ -395,6 +395,11 @@
     return null;
   }
 
+  public ProgramMethod asProgramMethod(DexProgramClass holder) {
+    assert getHolderType() == holder.getType();
+    return new ProgramMethod(holder, this);
+  }
+
   public ProgramMethod asProgramMethod(DexDefinitionSupplier definitions) {
     assert getReference().holder.isClassType();
     DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(getReference()));
@@ -727,9 +732,13 @@
     code = newCode;
   }
 
-  public void setCode(IRCode ir, RegisterAllocator registerAllocator, AppView<?> appView) {
+  public void setCode(
+      IRCode ir,
+      BytecodeMetadataProvider bytecodeMetadataProvider,
+      RegisterAllocator registerAllocator,
+      AppView<?> appView) {
     checkIfObsolete();
-    DexBuilder builder = new DexBuilder(ir, registerAllocator);
+    DexBuilder builder = new DexBuilder(ir, bytecodeMetadataProvider, registerAllocator);
     setCode(builder.build(), appView);
   }
 
@@ -771,8 +780,8 @@
 
   public void collectMixedSectionItemsWithCodeMapping(
       MixedSectionCollection mixedItems, MethodToCodeObjectMapping mapping) {
-    DexCode code = mapping.getCode(this);
-    if (code != null) {
+    DexWritableCode code = mapping.getCode(this);
+    if (code != null && mixedItems.add(this, code)) {
       code.collectMixedSectionItems(mixedItems);
     }
     annotations().collectMixedSectionItems(mixedItems);
@@ -792,11 +801,6 @@
     return code;
   }
 
-  public void removeCode() {
-    checkIfObsolete();
-    code = null;
-  }
-
   public CfVersion getClassFileVersion() {
     checkIfObsolete();
     assert classFileVersion != null;
@@ -1293,34 +1297,6 @@
     return method;
   }
 
-  /** Rewrites the code in this method to have JumboString bytecode if required by mapping. */
-  public DexCode rewriteCodeWithJumboStrings(
-      ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
-    checkIfObsolete();
-    assert code == null || code.isDexCode();
-    if (code == null) {
-      return null;
-    }
-    DexCode code = this.code.asDexCode();
-    DexString firstJumboString = null;
-    if (force) {
-      firstJumboString = mapping.getFirstString();
-    } else {
-      assert code.highestSortingString != null
-          || Arrays.stream(code.instructions).noneMatch(Instruction::isConstString);
-      assert Arrays.stream(code.instructions).noneMatch(Instruction::isDexItemBasedConstString);
-      if (code.highestSortingString != null
-          && mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
-        firstJumboString = mapping.getFirstJumboString();
-      }
-    }
-    if (firstJumboString != null) {
-      JumboStringRewriter rewriter = new JumboStringRewriter(this, firstJumboString, factory);
-      return rewriter.rewrite();
-    }
-    return code;
-  }
-
   public String codeToString() {
     checkIfObsolete();
     return code == null ? "<no code>" : code.toString(this, null);
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 c1c3fb7..d72df8c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -18,6 +18,8 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
 import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetOutlineCallerFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetOutlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
@@ -34,6 +36,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.LRUCacheTable;
 import com.android.tools.r8.utils.ListUtils;
@@ -92,6 +95,7 @@
   private final SetEpilogueBegin setEpilogueBegin = new SetEpilogueBegin();
   private final SetPrologueEnd setPrologueEnd = new SetPrologueEnd();
   private final Map<DexString, SetFile> setFiles = new HashMap<>();
+  private final SetOutlineFrame setOutlineFrame = new SetOutlineFrame();
   private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
 
   // ReferenceTypeElement canonicalization.
@@ -2768,6 +2772,15 @@
     }
   }
 
+  public SetOutlineFrame createSetOutlineFrame() {
+    return setOutlineFrame;
+  }
+
+  public SetOutlineCallerFrame createSetOutlineCallerFrame(
+      DexMethod outlineCallee, Int2StructuralItemArrayMap<Position> outlinePositions) {
+    return new SetOutlineCallerFrame(outlineCallee, outlinePositions);
+  }
+
   public boolean isConstructor(DexMethod method) {
     return method.name == constructorMethodName;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
new file mode 100644
index 0000000..664b4e8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.dex.CodeToKeep;
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.HashingVisitor;
+import java.nio.ShortBuffer;
+
+public interface DexWritableCode {
+
+  enum DexWritableCodeKind {
+    DEFAULT,
+    DEFAULT_INSTANCE_INITIALIZER,
+    THROW_NULL
+  }
+
+  default int acceptCompareTo(DexWritableCode code, CompareToVisitor visitor) {
+    DexWritableCodeKind kind = getDexWritableCodeKind();
+    DexWritableCodeKind otherKind = code.getDexWritableCodeKind();
+    if (kind != otherKind) {
+      return kind.compareTo(otherKind);
+    }
+    switch (kind) {
+      case DEFAULT:
+        return asDexCode().acceptCompareTo(code.asDexCode(), visitor);
+      case DEFAULT_INSTANCE_INITIALIZER:
+        return 0;
+      case THROW_NULL:
+        return 0;
+      default:
+        throw new Unreachable();
+    }
+  }
+
+  void acceptHashing(HashingVisitor visitor);
+
+  int codeSizeInBytes();
+
+  void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter);
+
+  void collectMixedSectionItems(MixedSectionCollection mixedItems);
+
+  void writeKeepRulesForDesugaredLibrary(CodeToKeep codeToKeep);
+
+  DexDebugInfoForWriting getDebugInfoForWriting();
+
+  DexWritableCodeKind getDexWritableCodeKind();
+
+  DexString getHighestSortingString();
+
+  TryHandler[] getHandlers();
+
+  Try[] getTries();
+
+  int getRegisterSize(ProgramMethod method);
+
+  int getIncomingRegisterSize(ProgramMethod method);
+
+  int getOutgoingRegisterSize();
+
+  default boolean isDexCode() {
+    return false;
+  }
+
+  default DexCode asDexCode() {
+    return null;
+  }
+
+  /** Rewrites the code to have JumboString bytecode if required by mapping. */
+  DexWritableCode rewriteCodeWithJumboStrings(
+      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force);
+
+  void setCallSiteContexts(ProgramMethod method);
+
+  void writeDex(
+      ShortBuffer shortBuffer,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils lensCodeRewriter,
+      ObjectToOffsetMapping mapping);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index f222dbf..9589d1e 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -168,6 +168,8 @@
         // If code is (lazy) CF code, then use the CF code object rather than the lazy wrapper.
         if (code.isCfCode()) {
           code = code.asCfCode();
+        } else if (code.isSharedCodeObject()) {
+          continue;
         }
         DexEncodedMethod otherMethod = codeOwners.put(code, method);
         assert otherMethod == null;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index f0675ba..9236d1f 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -284,6 +284,10 @@
 
   public abstract DexField getOriginalFieldSignature(DexField field);
 
+  public final DexMember<?, ?> getOriginalMemberSignature(DexMember<?, ?> member) {
+    return member.apply(this::getOriginalFieldSignature, this::getOriginalMethodSignature);
+  }
+
   public final DexMethod getOriginalMethodSignature(DexMethod method) {
     return getOriginalMethodSignature(method, null);
   }
@@ -572,7 +576,7 @@
   }
 
   public <R extends DexReference, T> Map<R, T> rewriteReferenceKeys(
-      Map<R, T> map, Function<List<T>, T> merge) {
+      Map<R, T> map, BiFunction<R, List<T>, T> merge) {
     Map<R, T> result = new IdentityHashMap<>();
     Map<R, List<T>> needsMerge = new IdentityHashMap<>();
     map.forEach(
@@ -593,7 +597,7 @@
         });
     needsMerge.forEach(
         (rewrittenReference, unmergedValues) -> {
-          T mergedValue = merge.apply(unmergedValues);
+          T mergedValue = merge.apply(rewrittenReference, unmergedValues);
           if (mergedValue != null) {
             result.put(rewrittenReference, mergedValue);
           }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 0aaa24e..2403804 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -57,6 +57,7 @@
 import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -120,6 +121,11 @@
   }
 
   @Override
+  public boolean isCfWritableCode() {
+    return true;
+  }
+
+  @Override
   public LazyCfCode asLazyCfCode() {
     return this;
   }
@@ -133,6 +139,11 @@
     return code;
   }
 
+  @Override
+  public CfWritableCode asCfWritableCode() {
+    return asCfCode();
+  }
+
   private void internalParseCode() {
     ReparseContext context = this.context;
     JarApplicationReader application = this.application;
@@ -781,7 +792,7 @@
           factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name);
       // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper
       // renaming in the backend, but it is not available here in the frontend.
-      instructions.add(new CfFieldInstruction(opcode, field, field));
+      instructions.add(CfFieldInstruction.create(opcode, field, field));
     }
 
     @Override
@@ -1007,7 +1018,7 @@
       if (debugParsingOptions.lineInfo) {
         instructions.add(
             new CfPosition(
-                getLabel(start), Position.builder().setLine(line).setMethod(method).build()));
+                getLabel(start), SourcePosition.builder().setLine(line).setMethod(method).build()));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index c1ff02b..287ef0f 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -84,6 +84,10 @@
     return null;
   }
 
+  public DexClass getResolvedHolder() {
+    return null;
+  }
+
   public DexEncodedMethod getResolvedMethod() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 459c008..aa5e4c8 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -19,8 +19,10 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
@@ -144,6 +146,25 @@
     return builder(true, null).rewrittenWithLens(this, definitions, lens).build(definitions);
   }
 
+  public ObjectAllocationInfoCollectionImpl withoutPrunedItems(PrunedItems prunedItems) {
+    if (prunedItems.hasRemovedMethods()) {
+      Iterator<Entry<DexProgramClass, Set<DexEncodedMethod>>> iterator =
+          classesWithAllocationSiteTracking.entrySet().iterator();
+      while (iterator.hasNext()) {
+        Entry<DexProgramClass, Set<DexEncodedMethod>> entry = iterator.next();
+        Set<DexEncodedMethod> allocationSites = entry.getValue();
+        allocationSites.removeIf(
+            allocationSite ->
+                prunedItems.getRemovedMethods().contains(allocationSite.getReference()));
+        if (allocationSites.isEmpty()) {
+          classesWithoutAllocationSiteTracking.add(entry.getKey());
+          iterator.remove();
+        }
+      }
+    }
+    return this;
+  }
+
   public void forEachInstantiatedSubType(
       DexType type,
       Consumer<DexProgramClass> onClass,
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index 196bc31..04b3eb0 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -29,6 +29,7 @@
   private final static int NOT_FOUND = -1;
   private final static int NOT_SET = -2;
 
+  private final AppView<?> appView;
   private final GraphLens graphLens;
   private final NamingLens namingLens;
   private final InitClassLens initClassLens;
@@ -74,6 +75,7 @@
     assert callSites != null;
     assert methodHandles != null;
     assert initClassLens != null;
+    this.appView = appView;
     this.graphLens = graphLens;
     this.namingLens = namingLens;
     this.initClassLens = initClassLens;
@@ -228,6 +230,10 @@
     return map == null ? Collections.emptyList() : map.keySet();
   }
 
+  public DexItemFactory dexItemFactory() {
+    return appView.dexItemFactory();
+  }
+
   public GraphLens getGraphLens() {
     return graphLens;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java b/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java
index 93e42d1..4e15eac 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java
@@ -6,15 +6,15 @@
 
 public class ProgramDexCode {
 
-  private final DexCode code;
+  private final DexWritableCode code;
   private final ProgramMethod method;
 
-  public ProgramDexCode(DexCode code, ProgramMethod method) {
+  public ProgramDexCode(DexWritableCode code, ProgramMethod method) {
     this.code = code;
     this.method = method;
   }
 
-  public DexCode getCode() {
+  public DexWritableCode getCode() {
     return code;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 146e33f..844b45e 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -61,11 +61,10 @@
       IndexedItemCollection indexedItems, GraphLens graphLens, LensCodeRewriterUtils rewriter) {
     DexEncodedMethod definition = getDefinition();
     assert !definition.isObsolete();
-    assert !definition.hasCode() || definition.getCode().isDexCode();
     getReference().collectIndexedItems(indexedItems);
-    Code code = definition.getCode();
-    if (code != null && code.isDexCode()) {
-      code.asDexCode().collectIndexedItems(indexedItems, this, graphLens, rewriter);
+    if (definition.hasCode()) {
+      Code code = definition.getCode();
+      code.asDexWritableCode().collectIndexedItems(indexedItems, this, graphLens, rewriter);
     }
     definition.annotations().collectIndexedItems(indexedItems);
     definition.parameterAnnotationsList.collectIndexedItems(indexedItems);
@@ -106,9 +105,8 @@
   public void convertToThrowNullMethod(AppView<?> appView) {
     MethodAccessFlags accessFlags = getAccessFlags();
     accessFlags.demoteFromAbstract();
-    Code emptyThrowingCode = getDefinition().buildEmptyThrowingCode(appView.options());
     getDefinition().setApiLevelForCode(AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView));
-    getDefinition().setCode(emptyThrowingCode, appView);
+    getDefinition().setCode(ThrowNullCode.get(), appView);
     getSimpleFeedback().markProcessed(getDefinition(), ConstraintWithTarget.ALWAYS);
     getSimpleFeedback().unsetOptimizationInfoForThrowNullMethod(this);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index 4489421..4f901fd 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -55,6 +55,10 @@
     return removedMethods.contains(method) || removedClasses.contains(method.getHolderType());
   }
 
+  public boolean isRemoved(DexReference reference) {
+    return reference.apply(this::isRemoved, this::isRemoved, this::isRemoved);
+  }
+
   public boolean isRemoved(DexType type) {
     return removedClasses.contains(type);
   }
@@ -107,7 +111,7 @@
     private final Set<DexType> noLongerSyntheticItems = Sets.newIdentityHashSet();
     private Set<DexType> removedClasses = Sets.newIdentityHashSet();
     private final Set<DexField> removedFields = Sets.newIdentityHashSet();
-    private final Set<DexMethod> removedMethods = Sets.newIdentityHashSet();
+    private Set<DexMethod> removedMethods = Sets.newIdentityHashSet();
 
     public Builder setPrunedApp(DexApplication prunedApp) {
       this.prunedApp = prunedApp;
@@ -146,6 +150,11 @@
       return this;
     }
 
+    public Builder setRemovedMethods(Set<DexMethod> removedMethods) {
+      this.removedMethods = removedMethods;
+      return this;
+    }
+
     public PrunedItems build() {
       return new PrunedItems(
           prunedApp,
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
new file mode 100644
index 0000000..eb7728c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -0,0 +1,281 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.dex.CodeToKeep;
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.structural.HashingVisitor;
+import com.google.common.collect.ImmutableList;
+import java.nio.ShortBuffer;
+import java.util.List;
+import java.util.function.Consumer;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class ThrowNullCode extends Code implements CfWritableCode, DexWritableCode {
+
+  private static final ThrowNullCode INSTANCE = new ThrowNullCode();
+
+  private ThrowNullCode() {}
+
+  public static ThrowNullCode get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public void acceptHashing(HashingVisitor visitor) {
+    visitor.visitInt(getCfWritableCodeKind().hashCode());
+  }
+
+  @Override
+  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    ThrowNullSourceCode source = new ThrowNullSourceCode(method);
+    return IRBuilder.create(method, appView, source, origin).build(method);
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      ProgramMethod context,
+      ProgramMethod method,
+      AppView<?> appView,
+      GraphLens codeLens,
+      NumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin,
+      RewrittenPrototypeDescription protoChanges) {
+    ThrowNullSourceCode source = new ThrowNullSourceCode(method, callerPosition);
+    return IRBuilder.createForInlining(
+            method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
+        .build(context);
+  }
+
+  @Override
+  public int codeSizeInBytes() {
+    return Const4.SIZE + Throw.SIZE;
+  }
+
+  @Override
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils rewriter) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+    // Intentionally empty.
+  }
+
+  @Override
+  protected int computeHashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    return this == other;
+  }
+
+  @Override
+  public int estimatedDexCodeSizeUpperBoundInBytes() {
+    return codeSizeInBytes();
+  }
+
+  @Override
+  public CfWritableCodeKind getCfWritableCodeKind() {
+    return CfWritableCodeKind.THROW_NULL;
+  }
+
+  @Override
+  public DexWritableCodeKind getDexWritableCodeKind() {
+    return DexWritableCodeKind.THROW_NULL;
+  }
+
+  @Override
+  public DexDebugInfoForWriting getDebugInfoForWriting() {
+    return null;
+  }
+
+  @Override
+  public TryHandler[] getHandlers() {
+    return new TryHandler[0];
+  }
+
+  @Override
+  public DexString getHighestSortingString() {
+    return null;
+  }
+
+  @Override
+  public int getIncomingRegisterSize(ProgramMethod method) {
+    return getMaxLocals(method);
+  }
+
+  private int getMaxLocals(ProgramMethod method) {
+    int maxLocals = method.getAccessFlags().isStatic() ? 0 : 1;
+    for (DexType parameter : method.getParameters()) {
+      maxLocals += parameter.getRequiredRegisters();
+    }
+    return maxLocals;
+  }
+
+  @Override
+  public int getOutgoingRegisterSize() {
+    return 0;
+  }
+
+  @Override
+  public int getRegisterSize(ProgramMethod method) {
+    return Math.max(getIncomingRegisterSize(method), 1);
+  }
+
+  @Override
+  public Try[] getTries() {
+    return new Try[0];
+  }
+
+  @Override
+  public boolean isCfWritableCode() {
+    return true;
+  }
+
+  @Override
+  public CfWritableCode asCfWritableCode() {
+    return this;
+  }
+
+  @Override
+  public boolean isDexWritableCode() {
+    return true;
+  }
+
+  @Override
+  public DexWritableCode asDexWritableCode() {
+    return this;
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    return false;
+  }
+
+  @Override
+  public boolean isSharedCodeObject() {
+    return true;
+  }
+
+  @Override
+  public boolean isThrowNullCode() {
+    return true;
+  }
+
+  @Override
+  public ThrowNullCode asThrowNullCode() {
+    return this;
+  }
+
+  @Override
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public DexWritableCode rewriteCodeWithJumboStrings(
+      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+    // Intentionally empty. This piece of code does not have any const-string instructions.
+    return this;
+  }
+
+  @Override
+  public void setCallSiteContexts(ProgramMethod method) {
+    // Intentionally empty. This piece of code does not have any call sites.
+  }
+
+  @Override
+  public void writeCf(
+      ProgramMethod method,
+      CfVersion classFileVersion,
+      AppView<?> appView,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    int maxStack = 1;
+    visitor.visitInsn(Opcodes.ACONST_NULL);
+    visitor.visitInsn(Opcodes.ATHROW);
+    visitor.visitEnd();
+    visitor.visitMaxs(maxStack, getMaxLocals(method));
+  }
+
+  @Override
+  public void writeDex(
+      ShortBuffer shortBuffer,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils lensCodeRewriter,
+      ObjectToOffsetMapping mapping) {
+    int register = 0;
+    new Const4(register, 0).write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+    new Throw(register).write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+  }
+
+  @Override
+  public void writeKeepRulesForDesugaredLibrary(CodeToKeep codeToKeep) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public String toString() {
+    return "ThrowNullCode";
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    return "ThrowNullCode";
+  }
+
+  static class ThrowNullSourceCode extends SyntheticStraightLineSourceCode {
+
+    ThrowNullSourceCode(ProgramMethod method) {
+      this(method, null);
+    }
+
+    ThrowNullSourceCode(ProgramMethod method, Position callerPosition) {
+      super(
+          getInstructionBuilders(),
+          SyntheticPosition.builder()
+              .setLine(0)
+              .setMethod(method.getReference())
+              .setCallerPosition(callerPosition)
+              .build());
+    }
+
+    private static List<Consumer<IRBuilder>> getInstructionBuilders() {
+      return ImmutableList.of(builder -> builder.addNullConst(0), builder -> builder.addThrow(0));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 4dd921c..8ac7c59 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
 import com.android.tools.r8.code.CfOrDexInstruction;
+import com.android.tools.r8.code.CfOrDexStaticFieldRead;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.ListIterator;
@@ -84,6 +86,10 @@
 
   public abstract void registerInstanceFieldRead(DexField field);
 
+  public void registerInstanceFieldReadInstruction(CfOrDexInstanceFieldRead instruction) {
+    registerInstanceFieldRead(instruction.getField());
+  }
+
   public void registerInstanceFieldReadFromMethodHandle(DexField field) {
     registerInstanceFieldRead(field);
   }
@@ -108,6 +114,10 @@
 
   public abstract void registerStaticFieldRead(DexField field);
 
+  public void registerStaticFieldReadInstruction(CfOrDexStaticFieldRead instruction) {
+    registerStaticFieldRead(instruction.getField());
+  }
+
   public void registerStaticFieldReadFromMethodHandle(DexField field) {
     registerStaticFieldRead(field);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index 37e08d2..45bff3f 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfLogicalBinop;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -109,9 +110,9 @@
           CfConstNumber.class,
           CfGoto.class,
           CfConstNumber.class,
-          CfFieldInstruction.class);
+          CfStaticFieldWrite.class);
   private static List<Class<?>> r8InstructionSequence =
-      ImmutableList.of(CfConstNumber.class, CfLogicalBinop.class, CfFieldInstruction.class);
+      ImmutableList.of(CfConstNumber.class, CfLogicalBinop.class, CfStaticFieldWrite.class);
   private static List<Class<?>> jacocoInstructionSequence =
       ImmutableList.of(CfLoad.class, CfConstNumber.class, CfConstNumber.class, CfArrayStore.class);
 
diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java
new file mode 100644
index 0000000..97839cf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeInstructionMetadata.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2021, 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.bytecodemetadata;
+
+/**
+ * A piece of information that can be attached to instructions in {@link
+ * com.android.tools.r8.graph.CfCode} and {@link com.android.tools.r8.graph.DexCode}.
+ */
+public class BytecodeInstructionMetadata {
+
+  /**
+   * Set for instance and static field read instructions which are only used to write the same
+   * field.
+   *
+   * <p>Used by {@link com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor}
+   * to skip such instructions in the "is-field-read" analysis.
+   */
+  private final boolean isReadForWrite;
+
+  BytecodeInstructionMetadata(boolean isReadForWrite) {
+    this.isReadForWrite = isReadForWrite;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static BytecodeInstructionMetadata none() {
+    return null;
+  }
+
+  public boolean isReadForWrite() {
+    return isReadForWrite;
+  }
+
+  public static class Builder {
+
+    private boolean isReadForWrite;
+
+    private boolean isEmpty() {
+      return !isReadForWrite;
+    }
+
+    public Builder setIsReadForWrite() {
+      isReadForWrite = true;
+      return this;
+    }
+
+    public BytecodeInstructionMetadata build() {
+      assert !isEmpty();
+      return new BytecodeInstructionMetadata(isReadForWrite);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadata.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadata.java
new file mode 100644
index 0000000..ad7dee4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadata.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2021, 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.bytecodemetadata;
+
+import com.android.tools.r8.ir.code.Instruction;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A collection of information that pertains to the instructions in a piece of {@link
+ * com.android.tools.r8.graph.CfCode} or {@link com.android.tools.r8.graph.DexCode}.
+ */
+public class BytecodeMetadata<I> {
+
+  private static final BytecodeMetadata<?> EMPTY = new BytecodeMetadata<>(Collections.emptyMap());
+
+  private final Map<I, BytecodeInstructionMetadata> backing;
+
+  BytecodeMetadata(Map<I, BytecodeInstructionMetadata> backing) {
+    assert backing.values().stream().noneMatch(Objects::isNull);
+    this.backing = backing;
+  }
+
+  public static <I> Builder<I> builder(BytecodeMetadataProvider bytecodeMetadataProvider) {
+    return new Builder<>(bytecodeMetadataProvider);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <I> BytecodeMetadata<I> empty() {
+    return (BytecodeMetadata<I>) EMPTY;
+  }
+
+  public BytecodeInstructionMetadata getMetadata(I bytecodeInstruction) {
+    return backing.get(bytecodeInstruction);
+  }
+
+  public static class Builder<I> {
+
+    private final BytecodeMetadataProvider bytecodeMetadataProvider;
+
+    private final Map<I, BytecodeInstructionMetadata> backing = new IdentityHashMap<>();
+
+    Builder(BytecodeMetadataProvider bytecodeMetadataProvider) {
+      this.bytecodeMetadataProvider = bytecodeMetadataProvider;
+    }
+
+    public Builder<I> setMetadata(Instruction irInstruction, I bytecodeInstruction) {
+      BytecodeInstructionMetadata instructionMetadata =
+          bytecodeMetadataProvider.getMetadata(irInstruction);
+      if (instructionMetadata != null) {
+        backing.put(bytecodeInstruction, instructionMetadata);
+      }
+      return this;
+    }
+
+    public BytecodeMetadata<I> build() {
+      return backing.isEmpty() ? empty() : new BytecodeMetadata<>(backing);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java
new file mode 100644
index 0000000..02ba8d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/bytecodemetadata/BytecodeMetadataProvider.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2021, 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.bytecodemetadata;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.ir.code.Instruction;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Mapping from IR instructions to the metadata that should be attached to the resulting CF or DEX
+ * bytecode instruction that results from the given IR instruction.
+ */
+public class BytecodeMetadataProvider {
+
+  private static final BytecodeMetadataProvider EMPTY =
+      new BytecodeMetadataProvider(Collections.emptyMap());
+
+  private final Map<Instruction, BytecodeInstructionMetadata> backing;
+
+  BytecodeMetadataProvider(Map<Instruction, BytecodeInstructionMetadata> backing) {
+    this.backing = backing;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static BytecodeMetadataProvider empty() {
+    return EMPTY;
+  }
+
+  /**
+   * Returns the metadata for a given IR instruction that should be attached to the CF or DEX
+   * instruction when finalizing the IR to CF or DEX.
+   */
+  public BytecodeInstructionMetadata getMetadata(Instruction instruction) {
+    return backing.get(instruction);
+  }
+
+  public static class Builder {
+
+    private final Map<Instruction, BytecodeInstructionMetadata.Builder> builders =
+        new IdentityHashMap<>();
+
+    /**
+     * Used to mutate the metadata that should be attached to the CF or DEX instruction that results
+     * from the given IR instruction.
+     */
+    public Builder addMetadata(
+        Instruction instruction, Consumer<BytecodeInstructionMetadata.Builder> fn) {
+      assert !builders.containsKey(instruction);
+      BytecodeInstructionMetadata.Builder builder =
+          builders.computeIfAbsent(instruction, ignoreKey(BytecodeInstructionMetadata::builder));
+      fn.accept(builder);
+      return this;
+    }
+
+    public BytecodeMetadataProvider build() {
+      if (builders.isEmpty()) {
+        return empty();
+      }
+      Map<Instruction, BytecodeInstructionMetadata> backing =
+          new IdentityHashMap<>(builders.size());
+      builders.forEach((instruction, builder) -> backing.put(instruction, builder.build()));
+      return new BytecodeMetadataProvider(backing);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 2f9a4ee..e16a15f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -117,7 +117,7 @@
     appView.setHorizontallyMergedClasses(mergedClasses, mode);
 
     HorizontalClassMergerGraphLens horizontalClassMergerGraphLens =
-        createLens(mergedClasses, lensBuilder, syntheticArgumentClass);
+        createLens(mergedClasses, lensBuilder, mode, syntheticArgumentClass);
 
     assert verifyNoCyclesInInterfaceHierarchies(groups);
 
@@ -235,8 +235,9 @@
   private HorizontalClassMergerGraphLens createLens(
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
+      Mode mode,
       SyntheticArgumentClass syntheticArgumentClass) {
-    return new TreeFixer(appView, mergedClasses, lensBuilder, syntheticArgumentClass)
+    return new TreeFixer(appView, mergedClasses, lensBuilder, mode, syntheticArgumentClass)
         .fixupTypeReferences();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 2b12d6d..03a044d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -250,13 +250,7 @@
             MutableBidirectionalManyToOneRepresentativeMap<R, R> newMemberSignatures,
             MutableBidirectionalManyToOneRepresentativeMap<R, R> pendingNewMemberSignatureUpdates) {
       newMemberSignatures.removeAll(pendingNewMemberSignatureUpdates.keySet());
-      pendingNewMemberSignatureUpdates.forEachManyToOneMapping(
-          (keys, value, representative) -> {
-            newMemberSignatures.put(keys, value);
-            if (keys.size() > 1) {
-              newMemberSignatures.setRepresentative(value, representative);
-            }
-          });
+      newMemberSignatures.putAll(pendingNewMemberSignatureUpdates);
       pendingNewMemberSignatureUpdates.clear();
     }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index e68cc71..2affeaf 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.analysis.value.SingleConstValue;
 import com.android.tools.r8.ir.analysis.value.SingleDexItemBasedStringValue;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.utils.IntBox;
@@ -110,8 +111,14 @@
     ImmutableList.Builder<CfInstruction> instructionBuilder = ImmutableList.builder();
 
     // Set position.
-    Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
-    Position calleePosition = Position.synthetic(0, originalMethodReference, callerPosition);
+    Position callerPosition =
+        SyntheticPosition.builder().setLine(0).setMethod(syntheticMethodReference).build();
+    Position calleePosition =
+        SyntheticPosition.builder()
+            .setLine(0)
+            .setMethod(originalMethodReference)
+            .setCallerPosition(callerPosition)
+            .build();
     CfPosition position = new CfPosition(new CfLabel(), calleePosition);
     instructionBuilder.add(position);
     instructionBuilder.add(position.getLabel());
@@ -122,7 +129,7 @@
       int classIdLocalIndex = maxLocals - 1;
       instructionBuilder.add(new CfLoad(ValueType.OBJECT, 0));
       instructionBuilder.add(new CfLoad(ValueType.INT, classIdLocalIndex));
-      instructionBuilder.add(new CfFieldInstruction(Opcodes.PUTFIELD, group.getClassIdField()));
+      instructionBuilder.add(new CfInstanceFieldWrite(group.getClassIdField()));
       maxStack.set(2);
     } else {
       assert !hasClassId;
@@ -180,7 +187,7 @@
           int stackSizeForInitializationInfo =
               addCfInstructionsForInitializationInfo(
                   instructionBuilder, initializationInfo, argumentToLocalIndex, field.getType());
-          instructionBuilder.add(new CfFieldInstruction(Opcodes.PUTFIELD, field));
+          instructionBuilder.add(new CfInstanceFieldWrite(field));
           maxStack.setMax(stackSizeForInitializationInfo + 1);
         });
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 2ab6c46..3c97009 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -375,8 +375,8 @@
         syntheticInitializerConverterBuilder.add(
             new ProgramMethod(group.getTarget(), newInstanceInitializer));
       } else {
-        assert !appView.options().isGeneratingClassFiles()
-            || newInstanceInitializer.getCode().isCfCode();
+        assert appView.options().isGeneratingDex()
+            || newInstanceInitializer.getCode().isCfWritableCode();
       }
     }
   }
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 b85eab3..7a74a2b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.shaking.AnnotationFixer;
 import com.android.tools.r8.utils.ArrayUtils;
@@ -28,8 +30,10 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 
@@ -42,9 +46,12 @@
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final HorizontallyMergedClasses mergedClasses;
+  private final Mode mode;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
   private final DexItemFactory dexItemFactory;
   private final SyntheticArgumentClass syntheticArgumentClass;
+
+  private final Map<DexProgramClass, DexType> originalSuperTypes = new IdentityHashMap<>();
   private final BiMap<DexMethodSignature, DexMethodSignature> reservedInterfaceSignatures =
       HashBiMap.create();
 
@@ -52,10 +59,12 @@
       AppView<? extends AppInfoWithClassHierarchy> appView,
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
+      Mode mode,
       SyntheticArgumentClass syntheticArgumentClass) {
     super(appView);
     this.appView = appView;
     this.mergedClasses = mergedClasses;
+    this.mode = mode;
     this.lensBuilder = lensBuilder;
     this.syntheticArgumentClass = syntheticArgumentClass;
     this.dexItemFactory = appView.dexItemFactory();
@@ -145,7 +154,11 @@
   }
 
   private void fixupProgramClassSuperTypes(DexProgramClass clazz) {
-    clazz.superType = fixupType(clazz.superType);
+    DexType rewrittenSuperType = fixupType(clazz.getSuperType());
+    if (rewrittenSuperType != clazz.getSuperType()) {
+      originalSuperTypes.put(clazz, clazz.getSuperType());
+      clazz.superType = rewrittenSuperType;
+    }
     clazz.setInterfaces(fixupInterfaces(clazz, clazz.getInterfaces()));
   }
 
@@ -166,7 +179,7 @@
             method -> fixupVirtualMethod(remappedClassVirtualMethods, newMethodReferences, method));
     clazz
         .getMethodCollection()
-        .replaceAllDirectMethods(method -> fixupDirectMethod(newMethodReferences, method));
+        .replaceAllDirectMethods(method -> fixupDirectMethod(newMethodReferences, clazz, method));
 
     Set<DexField> newFieldReferences = Sets.newIdentityHashSet();
     DexEncodedField[] instanceFields = clazz.clearInstanceFields();
@@ -220,7 +233,7 @@
     Set<DexMethodSignature> newDirectMethods = new LinkedHashSet<>();
     iface
         .getMethodCollection()
-        .replaceDirectMethods(method -> fixupDirectMethod(newDirectMethods, method));
+        .replaceDirectMethods(method -> fixupDirectMethod(newDirectMethods, iface, method));
     iface.getMethodCollection().replaceVirtualMethods(this::fixupVirtualInterfaceMethod);
 
     assert !iface.hasInstanceFields();
@@ -263,7 +276,7 @@
   }
 
   private DexEncodedMethod fixupDirectMethod(
-      Set<DexMethodSignature> newMethods, DexEncodedMethod method) {
+      Set<DexMethodSignature> newMethods, DexProgramClass clazz, DexEncodedMethod method) {
     DexMethod originalMethodReference = method.getReference();
 
     // Fix all type references in the method prototype.
@@ -299,6 +312,17 @@
     boolean changed = newMethods.add(newMethodReference.getSignature());
     assert changed;
 
+    // Convert out of DefaultInstanceInitializerCode, since this piece of code will require lens
+    // code rewriting.
+    if (mode.isInitial()
+        && method.hasCode()
+        && method.getCode().isDefaultInstanceInitializerCode()
+        && mergedClasses.hasBeenMergedOrIsMergeTarget(clazz.getSuperType())) {
+      DexType originalSuperType = originalSuperTypes.getOrDefault(clazz, clazz.getSuperType());
+      DefaultInstanceInitializerCode.uncanonicalizeCode(
+          appView, method.asProgramMethod(clazz), originalSuperType);
+    }
+
     return fixupProgramMethod(newMethodReference, method);
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index 8372926..085276f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -132,7 +133,8 @@
     public CfCode build(DexMethod syntheticMethodReference) {
       // Building the instructions will adjust maxStack and maxLocals. Build it here before invoking
       // the CfCode constructor to ensure that the value passed in is the updated values.
-      Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
+      Position callerPosition =
+          SyntheticPosition.builder().setLine(0).setMethod(syntheticMethodReference).build();
       List<CfInstruction> instructions = buildInstructions(callerPosition);
       return new CfCode(
           syntheticMethodReference.getHolderType(),
@@ -201,7 +203,8 @@
     public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
       assert !classInitializers.isEmpty();
 
-      Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
+      Position callerPosition =
+          SyntheticPosition.builder().setLine(0).setMethod(syntheticMethodReference).build();
       IRMetadata metadata = new IRMetadata();
       NumberGenerator blockNumberGenerator = new NumberGenerator();
       NumberGenerator valueNumberGenerator = new NumberGenerator();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java
index fce04fa..6e88f4d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIllegalInlining.java
@@ -34,19 +34,20 @@
     }
 
     // For non-jar/cf code we currently cannot guarantee that markForceInline() will succeed.
-    if (code == null || !code.isCfCode()) {
+    if (code == null) {
       return true;
     }
 
-    CfCode cfCode = code.asCfCode();
-
-    ConstraintWithTarget constraint =
-        cfCode.computeInliningConstraint(method, appView, appView.graphLens(), method);
-    if (constraint == ConstraintWithTarget.NEVER) {
+    if (code.isCfCode()) {
+      CfCode cfCode = code.asCfCode();
+      ConstraintWithTarget constraint =
+          cfCode.computeInliningConstraint(method, appView, appView.graphLens(), method);
+      return constraint == ConstraintWithTarget.NEVER;
+    } else if (code.isDefaultInstanceInitializerCode()) {
+      return false;
+    } else {
       return true;
     }
-
-    return false;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/inspector/ClassInspector.java b/src/main/java/com/android/tools/r8/inspector/ClassInspector.java
index b18dd26..6234592 100644
--- a/src/main/java/com/android/tools/r8/inspector/ClassInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/ClassInspector.java
@@ -14,6 +14,9 @@
   /** Get the class reference for the class of this inspector. */
   ClassReference getClassReference();
 
+  /** Get the source file attribute content if present, otherwise null. */
+  String getSourceFile();
+
   /** Iterate all fields declared in the class/interface (unspecified order). */
   void forEachField(Consumer<FieldInspector> inspection);
 
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java
index c2ebc8e..9525bdc 100644
--- a/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java
+++ b/src/main/java/com/android/tools/r8/inspector/internal/ClassInspectorImpl.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.inspector.internal;
 
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.inspector.ClassInspector;
 import com.android.tools.r8.inspector.FieldInspector;
 import com.android.tools.r8.inspector.MethodInspector;
@@ -29,6 +30,12 @@
   }
 
   @Override
+  public String getSourceFile() {
+    DexString sourceFile = clazz.getSourceFile();
+    return sourceFile == null ? null : sourceFile.toString();
+  }
+
+  @Override
   public void forEachField(Consumer<FieldInspector> inspection) {
     clazz.forEachField(field -> inspection.accept(new FieldInspectorImpl(this, field)));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index 2b5e70f..150f206 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -20,33 +21,34 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.annotations.VisibleForTesting;
 
 public class FieldAccessAnalysis {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final FieldAssignmentTracker fieldAssignmentTracker;
   private final FieldBitAccessAnalysis fieldBitAccessAnalysis;
+  private final FieldReadForWriteAnalysis fieldReadForWriteAnalysis;
 
   public FieldAccessAnalysis(AppView<AppInfoWithLiveness> appView) {
     InternalOptions options = appView.options();
     this.appView = appView;
     this.fieldBitAccessAnalysis =
         options.enableFieldBitAccessAnalysis ? new FieldBitAccessAnalysis() : null;
-    this.fieldAssignmentTracker =
-        options.enableFieldAssignmentTracker ? new FieldAssignmentTracker(appView) : null;
+    this.fieldAssignmentTracker = new FieldAssignmentTracker(appView);
+    this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView);
   }
 
+  @VisibleForTesting
   public FieldAccessAnalysis(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       FieldAssignmentTracker fieldAssignmentTracker,
-      FieldBitAccessAnalysis fieldBitAccessAnalysis) {
+      FieldBitAccessAnalysis fieldBitAccessAnalysis,
+      FieldReadForWriteAnalysis fieldReadForWriteAnalysis) {
     this.appView = appView;
     this.fieldAssignmentTracker = fieldAssignmentTracker;
     this.fieldBitAccessAnalysis = fieldBitAccessAnalysis;
-  }
-
-  public static boolean enable(InternalOptions options) {
-    return options.enableFieldBitAccessAnalysis || options.enableFieldAssignmentTracker;
+    this.fieldReadForWriteAnalysis = fieldReadForWriteAnalysis;
   }
 
   public FieldAssignmentTracker fieldAssignmentTracker() {
@@ -61,7 +63,10 @@
   }
 
   public void recordFieldAccesses(
-      IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
+      IRCode code,
+      BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor) {
     if (!methodProcessor.isPrimaryMethodProcessor()) {
       return;
     }
@@ -87,6 +92,10 @@
               fieldBitAccessAnalysis.recordFieldAccess(
                   fieldInstruction, field.getDefinition(), feedback);
             }
+            if (fieldReadForWriteAnalysis != null) {
+              fieldReadForWriteAnalysis.recordFieldAccess(
+                  fieldInstruction, field, bytecodeMetadataProviderBuilder);
+            }
           }
         }
       } else if (instruction.isNewInstance()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java
new file mode 100644
index 0000000..3b4f8dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, 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.analysis.fieldaccess;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata.Builder;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.FieldGet;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.FieldPut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.WorkList;
+
+public class FieldReadForWriteAnalysis {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  FieldReadForWriteAnalysis(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  public void recordFieldAccess(
+      FieldInstruction instruction,
+      ProgramField field,
+      BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder) {
+    if (instruction.isFieldPut()) {
+      return;
+    }
+
+    FieldGet fieldGet = instruction.asFieldGet();
+    if (isValueOnlyUsedToWriteField(fieldGet.outValue(), field)) {
+      bytecodeMetadataProviderBuilder.addMetadata(instruction, Builder::setIsReadForWrite);
+    }
+  }
+
+  private boolean isValueOnlyUsedToWriteField(Value value, ProgramField field) {
+    WorkList<Instruction> users = WorkList.newIdentityWorkList(value.uniqueUsers());
+    if (!enqueueUsersForAnalysis(value, users)) {
+      return false;
+    }
+    boolean foundWrite = false;
+    while (users.hasNext()) {
+      Instruction user = users.next();
+      if (user.isArithmeticBinop() || user.isLogicalBinop() || user.isUnop()) {
+        if (enqueueUsersForAnalysis(user.outValue(), users)) {
+          // OK.
+          continue;
+        }
+      } else if (user.isFieldPut()) {
+        FieldPut fieldPut = user.asFieldPut();
+        DexField writtenFieldReference = fieldPut.getField();
+        if (writtenFieldReference.match(field.getReference())
+            && fieldPut.isStaticPut() == field.getAccessFlags().isStatic()) {
+          ProgramField writtenField =
+              appView.appInfo().resolveField(writtenFieldReference).getProgramField();
+          if (writtenField != null && writtenField.isStructurallyEqualTo(field)) {
+            // OK.
+            foundWrite = true;
+            continue;
+          }
+        }
+      }
+      return false;
+    }
+    return foundWrite;
+  }
+
+  private boolean enqueueUsersForAnalysis(Value value, WorkList<Instruction> users) {
+    if (value.hasDebugUsers() || value.hasPhiUsers()) {
+      return false;
+    }
+    users.addIfNotSeen(value.uniqueUsers());
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 7e6d5d3..f392dec 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.fieldaccess;
 
+import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
+
+import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
+import com.android.tools.r8.code.CfOrDexStaticFieldRead;
 import com.android.tools.r8.graph.AbstractAccessContexts;
 import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
 import com.android.tools.r8.graph.AppView;
@@ -19,6 +23,7 @@
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -26,7 +31,6 @@
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -95,7 +99,14 @@
 
     constantFields.forEach(this::markFieldAsDead);
     readFields.keySet().forEach(this::markFieldAsDead);
-    writtenFields.keySet().forEach(this::markFieldAsDead);
+    writtenFields.keySet().forEach(this::markWriteOnlyFieldAsDead);
+  }
+
+  private void markWriteOnlyFieldAsDead(DexEncodedField field) {
+    markFieldAsDead(field);
+    getSimpleFeedback()
+        .recordFieldHasAbstractValue(
+            field, appView, appView.abstractValueFactory().createNullValue());
   }
 
   private void markFieldAsDead(DexEncodedField field) {
@@ -104,7 +115,7 @@
     if (appView.appInfo().isPinned(field)) {
       assert field.getType().isAlwaysNull(appView);
     } else {
-      OptimizationFeedbackSimple.getInstance().markFieldAsDead(field);
+      getSimpleFeedback().markFieldAsDead(field);
     }
   }
 
@@ -294,7 +305,11 @@
       super(appView, method);
     }
 
-    private void registerFieldAccess(DexField reference, boolean isStatic, boolean isWrite) {
+    private void registerFieldAccess(
+        DexField reference,
+        boolean isStatic,
+        boolean isWrite,
+        BytecodeInstructionMetadata metadata) {
       SuccessfulFieldResolutionResult resolutionResult =
           appView.appInfo().resolveField(reference).asSuccessfulResolution();
       if (resolutionResult == null) {
@@ -311,6 +326,13 @@
         return;
       }
 
+      if (metadata != null && metadata.isReadForWrite()) {
+        // Ignore this read. If the field ends up only being written, then we will still reprocess
+        // the method with the read-for-write instruction, since the method contains a write that
+        // requires reprocessing.
+        return;
+      }
+
       // Record access.
       if (field.isProgramField() && appView.appInfo().mayPropagateValueFor(field)) {
         if (field.getAccessFlags().isStatic() == isStatic) {
@@ -372,22 +394,36 @@
 
     @Override
     public void registerInstanceFieldRead(DexField field) {
-      registerFieldAccess(field, false, false);
+      registerFieldAccess(field, false, false, BytecodeInstructionMetadata.none());
+    }
+
+    @Override
+    public void registerInstanceFieldReadInstruction(CfOrDexInstanceFieldRead instruction) {
+      BytecodeInstructionMetadata metadata =
+          getContext().getDefinition().getCode().getMetadata(instruction);
+      registerFieldAccess(instruction.getField(), false, false, metadata);
     }
 
     @Override
     public void registerInstanceFieldWrite(DexField field) {
-      registerFieldAccess(field, false, true);
+      registerFieldAccess(field, false, true, null);
     }
 
     @Override
     public void registerStaticFieldRead(DexField field) {
-      registerFieldAccess(field, true, false);
+      registerFieldAccess(field, true, false, BytecodeInstructionMetadata.none());
+    }
+
+    @Override
+    public void registerStaticFieldReadInstruction(CfOrDexStaticFieldRead instruction) {
+      BytecodeInstructionMetadata metadata =
+          getContext().getDefinition().getCode().getMetadata(instruction);
+      registerFieldAccess(instruction.getField(), true, false, metadata);
     }
 
     @Override
     public void registerStaticFieldWrite(DexField field) {
-      registerFieldAccess(field, true, true);
+      registerFieldAccess(field, true, true, null);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
index f0e63e9..6accb73 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
@@ -64,7 +64,7 @@
     }
     // The optimization relies on shrinking and member value propagation to actually clear
     // the anonymous subclasses of EnumLiteMap.
-    if (!appView.options().isShrinking() || !appView.options().enableValuePropagation) {
+    if (!appView.options().isShrinking()) {
       return;
     }
     internalClearDeadEnumLiteMaps();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 01c9fe0..42a035f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -32,7 +32,7 @@
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -166,8 +166,8 @@
       IRConverter converter, ExecutorService executorService, Timing timing)
       throws ExecutionException {
     timing.begin("[Proto] Post optimize generated extension registry");
-    SortedProgramMethodSet wave =
-        SortedProgramMethodSet.create(this::forEachMethodThatRequiresPostOptimization);
+    ProgramMethodSet wave =
+        ProgramMethodSet.create(this::forEachMethodThatRequiresPostOptimization);
     OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
     methodProcessor.forEachWaveWithExtension(
         (method, methodProcessingContext) ->
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 4e36bed..7596684 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -34,7 +34,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.shaking.DependentMinimumKeepInfoCollection;
 import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -114,7 +114,7 @@
       IRConverter converter, ExecutorService executorService, Timing timing)
       throws ExecutionException {
     timing.begin("[Proto] Post optimize dynamic methods");
-    SortedProgramMethodSet wave = SortedProgramMethodSet.create(this::forEachDynamicMethod);
+    ProgramMethodSet wave = ProgramMethodSet.create(this::forEachDynamicMethod);
     OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
     methodProcessor.forEachWaveWithExtension(
         (method, methodProcessingContext) ->
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
index ad16c77..85467b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Supplier;
@@ -36,7 +38,7 @@
           methodIsSynthesized
               ? callerPosition
               : getCanonical(
-                  Position.builder()
+                  SourcePosition.builder()
                       .setLine(0)
                       .setMethod(method)
                       .setCallerPosition(callerPosition)
@@ -44,7 +46,8 @@
     } else {
       this.callerPosition = null;
       isCompilerSynthesizedInlinee = false;
-      preamblePosition = getCanonical(Position.synthetic(0, method, null));
+      preamblePosition =
+          getCanonical(SyntheticPosition.builder().setLine(0).setMethod(method).build());
     }
   }
 
@@ -81,7 +84,11 @@
     Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
     return getCanonical(
         caller.isNone()
-            ? Position.noneWithMethod(caller.method, callerOfCaller)
+            ? SourcePosition.builder()
+                .setMethod(caller.method)
+                .setCallerPosition(callerOfCaller)
+                .disableLineCheck()
+                .build()
             : caller.builderWithCopy().setCallerPosition(callerOfCaller).build());
   }
 
@@ -106,7 +113,11 @@
         syntheticPosition =
             (min == Integer.MAX_VALUE)
                 ? getPreamblePosition()
-                : Position.synthetic(min < max ? min - 1 : min, originalMethod, callerPosition);
+                : SyntheticPosition.builder()
+                    .setLine(min < max ? min - 1 : min)
+                    .setMethod(originalMethod)
+                    .setCallerPosition(callerPosition)
+                    .build();
       } else {
         // If in release mode we explicitly associate a synthetic none position with monitor exit.
         // This is safe as the runtime must never throw at this position because the monitor cannot
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index c347f87..d4d76da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -42,7 +42,7 @@
 
   @Override
   public void buildDex(DexBuilder builder) {
-    assert getPosition().isSome() && !getPosition().synthetic;
+    assert getPosition().isSome() && !getPosition().isSyntheticPosition();
     builder.addDebugPosition(this);
   }
 
@@ -84,7 +84,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    assert getPosition().isSome() && !getPosition().synthetic;
+    assert getPosition().isSome() && !getPosition().isSyntheticPosition();
     // All redundant debug positions are removed. Remaining ones must force a pc advance.
     builder.add(new CfNop());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
new file mode 100644
index 0000000..3f62f1f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, 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.code;
+
+public interface FieldGet {
+
+  Value outValue();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index e5c792f..f4a2cdc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -595,7 +595,11 @@
     for (Instruction instruction : instructions()) {
       if (instruction.outValue != null && instruction.outValue.getType().isClassType()) {
         ClassTypeElement classTypeLattice = instruction.outValue.getType().asClassType();
-        assert !mergedClasses.hasBeenMergedIntoDifferentType(classTypeLattice.getClassType());
+        assert !mergedClasses.hasBeenMergedIntoDifferentType(classTypeLattice.getClassType())
+            : "Expected reference to "
+                + classTypeLattice.getClassType().getTypeName()
+                + " to be rewritten at instruction "
+                + instruction.toString();
         assert !classTypeLattice
             .getInterfaces()
             .anyMatch(
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 090812b..347ca58 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
 import com.android.tools.r8.code.Iget;
 import com.android.tools.r8.code.IgetBoolean;
 import com.android.tools.r8.code.IgetByte;
@@ -32,7 +32,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
-public class InstanceGet extends FieldInstruction implements InstanceFieldInstruction {
+public class InstanceGet extends FieldInstruction implements FieldGet, InstanceFieldInstruction {
 
   public InstanceGet(Value dest, Value object, DexField field) {
     super(field, dest, object);
@@ -148,6 +148,16 @@
   }
 
   @Override
+  public boolean isFieldGet() {
+    return true;
+  }
+
+  @Override
+  public FieldGet asFieldGet() {
+    return this;
+  }
+
+  @Override
   public boolean isInstanceFieldInstruction() {
     return true;
   }
@@ -190,9 +200,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(
-        new CfFieldInstruction(
-            org.objectweb.asm.Opcodes.GETFIELD, getField(), builder.resolveField(getField())));
+    builder.add(new CfInstanceFieldRead(getField(), builder.resolveField(getField())));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 3dff277..2a5d60e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.cf.LoadStoreHelper;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.code.Iput;
 import com.android.tools.r8.code.IputBoolean;
 import com.android.tools.r8.code.IputByte;
@@ -195,6 +195,16 @@
   }
 
   @Override
+  public boolean isFieldPut() {
+    return true;
+  }
+
+  @Override
+  public FieldPut asFieldPut() {
+    return this;
+  }
+
+  @Override
   public boolean isInstanceFieldInstruction() {
     return true;
   }
@@ -226,9 +236,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(
-        new CfFieldInstruction(
-            org.objectweb.asm.Opcodes.PUTFIELD, getField(), builder.resolveField(getField())));
+    builder.add(new CfInstanceFieldWrite(getField(), builder.resolveField(getField())));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 090fb45..3409c01 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -956,12 +956,20 @@
     return null;
   }
 
-  public final boolean isFieldGet() {
-    return isInstanceGet() || isStaticGet();
+  public boolean isFieldGet() {
+    return false;
   }
 
-  public final boolean isFieldPut() {
-    return isInstancePut() || isStaticPut();
+  public FieldGet asFieldGet() {
+    return null;
+  }
+
+  public boolean isFieldPut() {
+    return false;
+  }
+
+  public FieldPut asFieldPut() {
+    return null;
   }
 
   public boolean isInstancePut() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index b5aa980..cb20fd4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -48,6 +48,10 @@
     return seenBlocks.contains(basicBlock);
   }
 
+  public Set<BasicBlock> getSeenBlocks() {
+    return seenBlocks;
+  }
+
   @Override
   public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
     currentBlockIterator.replaceCurrentInstruction(newInstruction, affectedValues);
@@ -199,6 +203,7 @@
       if (!isLinearEdge(target, candidate)) {
         break;
       }
+      seenBlocks.add(target);
       target = candidate;
     }
     currentBlock = target;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index cb2ff68..cf718aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -7,114 +7,125 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
+import com.android.tools.r8.utils.structural.Equatable;
+import com.android.tools.r8.utils.structural.HashCodeVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
-import com.google.common.annotations.VisibleForTesting;
-import java.util.Objects;
 
-public class Position implements StructuralItem<Position> {
+public abstract class Position implements StructuralItem<Position> {
 
-  // A no-position marker. Not having a position means the position is implicitly defined by the
-  // context, e.g., the marker does not materialize anything concrete.
-  private static final Position NO_POSITION = new Position(-1, null, null, null, false, false);
+  // Compare ID(s) for positions.
+  private static final int SOURCE_POSITION_COMPARE_ID = 1;
+  private static final int SYNTHETIC_POSITION_COMPARE_ID = 2;
+  private static final int OUTLINE_POSITION_COMPARE_ID = 3;
+  private static final int OUTLINE_CALLER_POSITION_COMPARE_ID = 4;
 
-  // A synthetic marker position that should never materialize.
-  // This is used specifically to mark exceptional exit blocks from synchronized methods in release.
-  private static final Position NO_POSITION_SYNTHETIC =
-      new Position(-1, null, null, null, true, false);
-
-  // Fake position to use for representing an actual position in testing code.
-  private static final Position TESTING_POSITION = new Position(0, null, null, null, true, false);
-
-  public final int line;
-  public final DexString file;
-  public final boolean synthetic;
+  protected final int line;
+  protected final DexMethod method;
 
   // If there's no inlining, callerPosition is null.
   //
   // For an inlined instruction its Position contains the inlinee's line and method and
   // callerPosition is the position of the invoke instruction in the caller.
+  protected final Position callerPosition;
 
-  public final DexMethod method;
-  public final Position callerPosition;
-  public final boolean removeInnerFrameIfThrowingNpe;
-
-  private static void specify(StructuralSpecification<Position, ?> spec) {
-    spec.withInt(p -> p.line)
-        .withNullableItem(p -> p.file)
-        .withBool(p -> p.synthetic)
-        .withNullableItem(p -> p.method)
-        .withNullableItem(p -> p.callerPosition);
-  }
+  private final boolean removeInnerFramesIfThrowingNpe;
 
   private Position(
-      int line,
-      DexString file,
-      DexMethod method,
-      Position callerPosition,
-      boolean synthetic,
-      boolean removeInnerFrameIfThrowingNpe) {
+      int line, DexMethod method, Position callerPosition, boolean removeInnerFramesIfThrowingNpe) {
     this.line = line;
-    this.file = file;
-    this.synthetic = synthetic;
     this.method = method;
     this.callerPosition = callerPosition;
-    this.removeInnerFrameIfThrowingNpe = removeInnerFrameIfThrowingNpe;
-    assert callerPosition == null || callerPosition.method != null;
+    this.removeInnerFramesIfThrowingNpe = removeInnerFramesIfThrowingNpe;
   }
 
-  public static Position synthetic(int line, DexMethod method, Position callerPosition) {
-    assert line >= 0;
-    assert method != null;
-    return new Position(line, null, method, callerPosition, true, false);
+  public boolean isSyntheticPosition() {
+    return false;
   }
 
-  public static Position none() {
-    return NO_POSITION;
+  public boolean isAdditionalMappingInfoPosition() {
+    return false;
   }
 
-  public static Position syntheticNone() {
-    return NO_POSITION_SYNTHETIC;
+  public boolean isRemoveInnerFramesIfThrowingNpe() {
+    return removeInnerFramesIfThrowingNpe;
   }
 
-  @VisibleForTesting
-  public static Position testingPosition() {
-    return TESTING_POSITION;
+  public boolean isOutline() {
+    return false;
   }
 
-  // This factory method is used by the Inliner to create Positions when the caller has no valid
-  // positions. Since the callee still may have valid positions we need a non-null Position to set
-  // it as the caller of the inlined Positions.
-  public static Position noneWithMethod(DexMethod method, Position callerPosition) {
-    assert method != null;
-    return new Position(-1, null, method, callerPosition, false, false);
+  public DexMethod getOutlineCallee() {
+    return null;
   }
 
-  public static Position getPositionForInlining(
-      AppView<?> appView, InvokeMethod invoke, ProgramMethod context) {
-    Position position = invoke.getPosition();
-    if (position.method == null) {
-      assert position.isNone();
-      position = Position.noneWithMethod(context.getReference(), null);
-    }
-    assert position.getOutermostCaller().method
-        == appView.graphLens().getOriginalMethodSignature(context.getReference());
-    return position;
+  public Int2StructuralItemArrayMap<Position> getOutlinePositions() {
+    return null;
   }
 
   public boolean hasCallerPosition() {
     return callerPosition != null;
   }
 
+  public Position getCallerPosition() {
+    return callerPosition;
+  }
+
+  public int getLine() {
+    return line;
+  }
+
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public static Position none() {
+    return SourcePosition.NO_POSITION;
+  }
+
+  public boolean hasFile() {
+    return false;
+  }
+
+  public DexString getFile() {
+    return null;
+  }
+
   @Override
   public Position self() {
     return this;
   }
 
+  // Unique id to determine the ordering of positions
+  public abstract int getCompareToId();
+
   @Override
-  public StructuralMapping<Position> getStructuralMapping() {
-    return Position::specify;
+  public abstract StructuralMapping<Position> getStructuralMapping();
+
+  private static void specifyBasePosition(StructuralSpecification<Position, ?> spec) {
+    spec.withInt(Position::getCompareToId)
+        .withInt(Position::getLine)
+        .withNullableItem(Position::getMethod)
+        .withNullableItem(Position::getCallerPosition)
+        .withBool(Position::isRemoveInnerFramesIfThrowingNpe);
+  }
+
+  public static Position syntheticNone() {
+    return SyntheticPosition.NO_POSITION_SYNTHETIC;
+  }
+
+  public static Position getPositionForInlining(
+      AppView<?> appView, InvokeMethod invoke, ProgramMethod context) {
+    Position position = invoke.getPosition();
+    if (position.method == null) {
+      assert position.isNone();
+      position = SourcePosition.builder().setMethod(context.getReference()).build();
+    }
+    assert position.getOutermostCaller().method
+        == appView.graphLens().getOriginalMethodSignature(context.getReference());
+    return position;
   }
 
   public boolean isNone() {
@@ -122,7 +133,7 @@
   }
 
   public boolean isSyntheticNone() {
-    return this == NO_POSITION_SYNTHETIC;
+    return this == syntheticNone();
   }
 
   public boolean isSome() {
@@ -139,10 +150,6 @@
     return lastPosition;
   }
 
-  public Position getCallerPosition() {
-    return callerPosition;
-  }
-
   public Position withOutermostCallerPosition(Position newOutermostCallerPosition) {
     return builderWithCopy()
         .setCallerPosition(
@@ -153,14 +160,13 @@
   }
 
   @Override
-  public boolean equals(Object other) {
-    return other instanceof Position && compareTo((Position) other) == 0;
+  public final boolean equals(Object other) {
+    return Equatable.equalsImpl(this, other);
   }
 
   @Override
-  public int hashCode() {
-    return Objects.hash(
-        line, file, synthetic, method, callerPosition, removeInnerFrameIfThrowingNpe);
+  public final int hashCode() {
+    return HashCodeVisitor.run(this);
   }
 
   private String toString(boolean forceMethod) {
@@ -168,8 +174,8 @@
       return "--";
     }
     StringBuilder builder = new StringBuilder();
-    if (file != null) {
-      builder.append(file).append(":");
+    if (hasFile()) {
+      builder.append(getFile()).append(":");
     }
     builder.append("#").append(line);
     if (method != null && (forceMethod || callerPosition != null)) {
@@ -190,64 +196,367 @@
     return toString(false);
   }
 
-  public static Builder builder() {
-    return new Builder();
-  }
+  public abstract PositionBuilder<?, ?> builderWithCopy();
 
-  public Builder builderWithCopy() {
-    return new Builder()
-        .setLine(line)
-        .setFile(file)
-        .setMethod(method)
-        .setCallerPosition(callerPosition)
-        .setSynthetic(synthetic)
-        .setRemoveInnerFramesIfThrowingNpe(removeInnerFrameIfThrowingNpe);
-  }
+  public abstract static class PositionBuilder<
+      P extends Position, B extends PositionBuilder<P, B>> {
 
-  public static class Builder {
+    protected int line = -1;
+    protected DexMethod method;
+    protected Position callerPosition;
+    protected boolean removeInnerFramesIfThrowingNpe;
 
-    public int line;
-    public DexString file;
-    public boolean synthetic;
-    public DexMethod method;
-    public Position callerPosition;
-    public boolean removeInnerFrameIfThrowingNpe;
+    protected boolean noCheckOfPosition;
+    protected boolean noCheckOfMethod;
 
-    public Builder setLine(int line) {
+    abstract B self();
+
+    public B setLine(int line) {
       this.line = line;
-      return this;
+      return self();
     }
 
-    public Builder setFile(DexString file) {
-      this.file = file;
-      return this;
+    public boolean hasLine() {
+      return line > -1;
     }
 
-    public Builder setSynthetic(boolean synthetic) {
-      this.synthetic = synthetic;
-      return this;
-    }
-
-    public Builder setMethod(DexMethod method) {
+    public B setMethod(DexMethod method) {
       this.method = method;
-      return this;
+      return self();
     }
 
-    public Builder setCallerPosition(Position callerPosition) {
+    public B setCallerPosition(Position callerPosition) {
       this.callerPosition = callerPosition;
-      return this;
+      return self();
     }
 
-    public Builder setRemoveInnerFramesIfThrowingNpe(boolean removeInnerFramesIfThrowingNpe) {
-      this.removeInnerFrameIfThrowingNpe = removeInnerFramesIfThrowingNpe;
-      return this;
+    public B setRemoveInnerFramesIfThrowingNpe(boolean removeInnerFramesIfThrowingNpe) {
+      this.removeInnerFramesIfThrowingNpe = removeInnerFramesIfThrowingNpe;
+      return self();
     }
 
-    public Position build() {
-      assert line >= 0;
-      assert method != null;
-      return new Position(
-          line, file, method, callerPosition, synthetic, removeInnerFrameIfThrowingNpe);
+    public B disableLineCheck() {
+      noCheckOfPosition = true;
+      return self();
+    }
+
+    public B disableMethodCheck() {
+      noCheckOfMethod = true;
+      return self();
+    }
+
+    public abstract P build();
+  }
+
+  public static class SourcePosition extends Position {
+
+    // A no-position marker. Not having a position means the position is implicitly defined by the
+    // context, e.g., the marker does not materialize anything concrete.
+    private static final SourcePosition NO_POSITION =
+        new SourcePosition(-1, null, null, false, null);
+
+    public final DexString file;
+
+    private static void specify(StructuralSpecification<Position, ?> spec) {
+      spec.withSpec(Position::specifyBasePosition).withNullableItem(Position::getFile);
+    }
+
+    private SourcePosition(
+        int line,
+        DexMethod method,
+        Position callerPosition,
+        boolean removeInnerFramesIfThrowingNpe,
+        DexString file) {
+      super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
+      this.file = file;
+      assert callerPosition == null || callerPosition.method != null;
+    }
+
+    @Override
+    public boolean hasFile() {
+      return file != null;
+    }
+
+    @Override
+    public DexString getFile() {
+      return file;
+    }
+
+    @Override
+    public int getCompareToId() {
+      return SOURCE_POSITION_COMPARE_ID;
+    }
+
+    @Override
+    public PositionBuilder<?, ?> builderWithCopy() {
+      return builder()
+          .setLine(line)
+          .setFile(file)
+          .setMethod(method)
+          .setCallerPosition(callerPosition);
+    }
+
+    @Override
+    public StructuralMapping<Position> getStructuralMapping() {
+      return SourcePosition::specify;
+    }
+
+    public static SourcePositionBuilder builder() {
+      return new SourcePositionBuilder();
+    }
+
+    public static class SourcePositionBuilder
+        extends PositionBuilder<SourcePosition, SourcePositionBuilder> {
+
+      private DexString file;
+
+      @Override
+      SourcePositionBuilder self() {
+        return this;
+      }
+
+      public SourcePositionBuilder setFile(DexString file) {
+        this.file = file;
+        return this;
+      }
+
+      @Override
+      public SourcePosition build() {
+        assert noCheckOfPosition || line >= 0;
+        assert noCheckOfMethod || method != null;
+        return new SourcePosition(
+            line, method, callerPosition, removeInnerFramesIfThrowingNpe, file);
+      }
+    }
+  }
+
+  public static class SyntheticPosition extends Position {
+
+    // A synthetic marker position that should never materialize.
+    // This is used specifically to mark exceptional exit blocks from synchronized methods in
+    // release.
+    private static final Position NO_POSITION_SYNTHETIC =
+        new SyntheticPosition(-1, null, null, false);
+
+    private SyntheticPosition(
+        int line,
+        DexMethod method,
+        Position callerPosition,
+        boolean removeInnerFramesIfThrowingNpe) {
+      super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
+    }
+
+    @Override
+    public boolean isSyntheticPosition() {
+      return true;
+    }
+
+    @Override
+    public int getCompareToId() {
+      return SYNTHETIC_POSITION_COMPARE_ID;
+    }
+
+    @Override
+    public PositionBuilder<?, ?> builderWithCopy() {
+      return builder().setLine(line).setMethod(method).setCallerPosition(callerPosition);
+    }
+
+    @Override
+    public StructuralMapping<Position> getStructuralMapping() {
+      return Position::specifyBasePosition;
+    }
+
+    public static SyntheticPositionBuilder builder() {
+      return new SyntheticPositionBuilder();
+    }
+
+    public static class SyntheticPositionBuilder
+        extends PositionBuilder<SyntheticPosition, SyntheticPositionBuilder> {
+
+      private SyntheticPositionBuilder() {}
+
+      @Override
+      SyntheticPositionBuilder self() {
+        return this;
+      }
+
+      @Override
+      public SyntheticPosition build() {
+        assert noCheckOfPosition || line >= 0;
+        assert noCheckOfMethod || method != null;
+        return new SyntheticPosition(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
+      }
+    }
+  }
+
+  public static class OutlinePosition extends Position {
+
+    private OutlinePosition(
+        int line,
+        DexMethod method,
+        Position callerPosition,
+        boolean removeInnerFramesIfThrowingNpe) {
+      super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
+    }
+
+    @Override
+    public boolean isOutline() {
+      return true;
+    }
+
+    @Override
+    public int getCompareToId() {
+      return OUTLINE_POSITION_COMPARE_ID;
+    }
+
+    @Override
+    public PositionBuilder<?, ?> builderWithCopy() {
+      return builder().setLine(line).setMethod(method).setCallerPosition(callerPosition);
+    }
+
+    @Override
+    public StructuralMapping<Position> getStructuralMapping() {
+      return Position::specifyBasePosition;
+    }
+
+    public static OutlinePositionBuilder builder() {
+      return new OutlinePositionBuilder();
+    }
+
+    public static class OutlinePositionBuilder
+        extends PositionBuilder<OutlinePosition, OutlinePositionBuilder> {
+
+      private OutlinePositionBuilder() {}
+
+      @Override
+      OutlinePositionBuilder self() {
+        return this;
+      }
+
+      @Override
+      public OutlinePosition build() {
+        return new OutlinePosition(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
+      }
+    }
+  }
+
+  public static class OutlineCallerPosition extends Position {
+
+    private final Int2StructuralItemArrayMap<Position> outlinePositions;
+    private final DexMethod outlineCallee;
+    private final boolean isOutline;
+
+    public static void specify(StructuralSpecification<Position, ?> spec) {
+      spec.withSpec(Position::specifyBasePosition)
+          .withBool(Position::isOutline)
+          .withItem(Position::getOutlineCallee)
+          .withItem(Position::getOutlinePositions);
+    }
+
+    private OutlineCallerPosition(
+        int line,
+        DexMethod method,
+        Position callerPosition,
+        boolean removeInnerFramesIfThrowingNpe,
+        Int2StructuralItemArrayMap<Position> outlinePositions,
+        DexMethod outlineCallee,
+        boolean isOutline) {
+      super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
+      this.outlinePositions = outlinePositions;
+      this.outlineCallee = outlineCallee;
+      this.isOutline = isOutline;
+    }
+
+    @Override
+    public boolean isNone() {
+      return false;
+    }
+
+    @Override
+    public int getCompareToId() {
+      return OUTLINE_CALLER_POSITION_COMPARE_ID;
+    }
+
+    @Override
+    public PositionBuilder<?, ?> builderWithCopy() {
+      OutlineCallerPositionBuilder outlineCallerPositionBuilder =
+          builder()
+              .setLine(line)
+              .setMethod(method)
+              .setCallerPosition(callerPosition)
+              .setOutlineCallee(outlineCallee)
+              .setIsOutline(isOutline);
+      outlinePositions.forEach(outlineCallerPositionBuilder::addOutlinePosition);
+      return outlineCallerPositionBuilder;
+    }
+
+    @Override
+    public boolean isOutline() {
+      return isOutline;
+    }
+
+    @Override
+    public DexMethod getOutlineCallee() {
+      return outlineCallee;
+    }
+
+    @Override
+    public Int2StructuralItemArrayMap<Position> getOutlinePositions() {
+      return outlinePositions;
+    }
+
+    @Override
+    public StructuralMapping<Position> getStructuralMapping() {
+      return OutlineCallerPosition::specify;
+    }
+
+    public static OutlineCallerPositionBuilder builder() {
+      return new OutlineCallerPositionBuilder();
+    }
+
+    public static class OutlineCallerPositionBuilder
+        extends PositionBuilder<OutlineCallerPosition, OutlineCallerPositionBuilder> {
+
+      private final Int2StructuralItemArrayMap.Builder<Position> outlinePositionsBuilder =
+          Int2StructuralItemArrayMap.builder();
+      private DexMethod outlineCallee;
+      private boolean isOutline;
+
+      private OutlineCallerPositionBuilder() {}
+
+      @Override
+      OutlineCallerPositionBuilder self() {
+        return this;
+      }
+
+      public OutlineCallerPositionBuilder setOutlineCallee(DexMethod outlineCallee) {
+        this.outlineCallee = outlineCallee;
+        return this;
+      }
+
+      public OutlineCallerPositionBuilder addOutlinePosition(int line, Position callerPosition) {
+        outlinePositionsBuilder.put(line, callerPosition);
+        return this;
+      }
+
+      public OutlineCallerPositionBuilder setIsOutline(boolean isOutline) {
+        this.isOutline = isOutline;
+        return this;
+      }
+
+      @Override
+      public OutlineCallerPosition build() {
+        assert noCheckOfPosition || line >= 0;
+        assert noCheckOfMethod || method != null;
+        return new OutlineCallerPosition(
+            line,
+            method,
+            callerPosition,
+            removeInnerFramesIfThrowingNpe,
+            outlinePositionsBuilder.build(),
+            outlineCallee,
+            isOutline);
+      }
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index b4caedd..c7e076c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.code.Sget;
 import com.android.tools.r8.code.SgetBoolean;
 import com.android.tools.r8.code.SgetByte;
@@ -32,7 +32,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
-public class StaticGet extends FieldInstruction implements StaticFieldInstruction {
+public class StaticGet extends FieldInstruction implements FieldGet, StaticFieldInstruction {
 
   public StaticGet(Value dest, DexField field) {
     super(field, dest, (Value) null);
@@ -183,6 +183,16 @@
   }
 
   @Override
+  public boolean isFieldGet() {
+    return true;
+  }
+
+  @Override
+  public FieldGet asFieldGet() {
+    return this;
+  }
+
+  @Override
   public boolean isStaticFieldInstruction() {
     return true;
   }
@@ -204,9 +214,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(
-        new CfFieldInstruction(
-            org.objectweb.asm.Opcodes.GETSTATIC, getField(), builder.resolveField(getField())));
+    builder.add(new CfStaticFieldRead(getField(), builder.resolveField(getField())));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 92eea52..8cbeb6d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.cf.LoadStoreHelper;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.code.Sput;
 import com.android.tools.r8.code.SputBoolean;
 import com.android.tools.r8.code.SputByte;
@@ -185,6 +185,16 @@
   }
 
   @Override
+  public boolean isFieldPut() {
+    return true;
+  }
+
+  @Override
+  public FieldPut asFieldPut() {
+    return this;
+  }
+
+  @Override
   public boolean isStaticFieldInstruction() {
     return true;
   }
@@ -206,9 +216,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    builder.add(
-        new CfFieldInstruction(
-            org.objectweb.asm.Opcodes.PUTSTATIC, getField(), builder.resolveField(getField())));
+    builder.add(new CfStaticFieldWrite(getField(), builder.resolveField(getField())));
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 66d7fa4..6ed9ef6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
 import com.android.tools.r8.ir.conversion.CallSiteInformation.CallGraphBasedCallSiteInformation;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.Iterator;
 import java.util.Set;
@@ -276,16 +276,16 @@
     return nodes.isEmpty();
   }
 
-  public SortedProgramMethodSet extractLeaves() {
+  public ProgramMethodSet extractLeaves() {
     return extractNodes(Node::isLeaf, Node::cleanCallersAndReadersForRemoval);
   }
 
-  public SortedProgramMethodSet extractRoots() {
+  public ProgramMethodSet extractRoots() {
     return extractNodes(Node::isRoot, Node::cleanCalleesAndWritersForRemoval);
   }
 
-  private SortedProgramMethodSet extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
-    SortedProgramMethodSet result = SortedProgramMethodSet.create();
+  private ProgramMethodSet extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
+    ProgramMethodSet result = ProgramMethodSet.create();
     Set<Node> removed = Sets.newIdentityHashSet();
     Iterator<Node> nodeIterator = nodes.iterator();
     while (nodeIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 0085b70..b358828 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -519,7 +519,9 @@
             && position != currentPosition
             // Ignore synthetic positions prior to any actual position, except when inlined
             // (that is, callerPosition != null).
-            && !(currentPosition.isNone() && position.synthetic && position.callerPosition == null)
+            && !(currentPosition.isNone()
+                && position.isSyntheticPosition()
+                && !position.hasCallerPosition())
             && (appView.options().debug || instruction.instructionTypeCanThrow());
     if (!didLocalsChange && !didPositionChange) {
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 5abeb6e..1a39011 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -904,7 +904,7 @@
         position
             .builderWithCopy()
             .setCallerPosition(
-                canonicalPositions.canonicalizeCallerPosition(position.callerPosition))
+                canonicalPositions.canonicalizeCallerPosition(position.getCallerPosition()))
             .build());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 3bca6db..c4ac3ff 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -42,6 +42,8 @@
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEventBuilder;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -84,6 +86,9 @@
   // The IR representation of the code to build.
   private final IRCode ir;
 
+  // Extra information that should be attached to the bytecode instructions.
+  private final BytecodeMetadata.Builder<Instruction> bytecodeMetadataBuilder;
+
   // The register allocator providing register assignments for the code to build.
   private final RegisterAllocator registerAllocator;
 
@@ -120,13 +125,21 @@
 
   BasicBlock nextBlock;
 
-  public DexBuilder(IRCode ir, RegisterAllocator registerAllocator) {
-    this(ir, registerAllocator, registerAllocator.options());
+  public DexBuilder(
+      IRCode ir,
+      BytecodeMetadataProvider bytecodeMetadataProvider,
+      RegisterAllocator registerAllocator) {
+    this(ir, bytecodeMetadataProvider, registerAllocator, registerAllocator.options());
     assert ir != null;
   }
 
-  private DexBuilder(IRCode ir, RegisterAllocator registerAllocator, InternalOptions options) {
+  private DexBuilder(
+      IRCode ir,
+      BytecodeMetadataProvider bytecodeMetadataProvider,
+      RegisterAllocator registerAllocator,
+      InternalOptions options) {
     this.ir = ir;
+    this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider);
     this.registerAllocator = registerAllocator;
     this.options = options;
     if (isBuildingForComparison()) {
@@ -138,7 +151,8 @@
       com.android.tools.r8.ir.code.Instruction a,
       com.android.tools.r8.ir.code.Instruction b,
       RegisterAllocator allocator) {
-    DexBuilder builder = new DexBuilder(null, allocator, allocator.options());
+    DexBuilder builder =
+        new DexBuilder(null, BytecodeMetadataProvider.empty(), allocator, allocator.options());
     Info infoA = buildInfoForComparison(a, builder);
     Info infoB = buildInfoForComparison(b, builder);
     return infoA.identicalInstructions(infoB, builder);
@@ -314,7 +328,8 @@
             dexInstructions.toArray(Instruction.EMPTY_ARRAY),
             tryInfo.tries,
             tryInfo.handlers,
-            debugEventBuilder.build());
+            debugEventBuilder.build(),
+            bytecodeMetadataBuilder.build());
 
     return code;
   }
@@ -606,6 +621,7 @@
   public void add(com.android.tools.r8.ir.code.Instruction instr, Instruction dex) {
     assert !instr.isGoto();
     add(instr, new FixedSizeInfo(instr, dex));
+    bytecodeMetadataBuilder.setMetadata(instr, dex);
   }
 
   public void add(com.android.tools.r8.ir.code.Instruction ir, Instruction... dex) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 4181373..f2c1d8d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -43,6 +43,7 @@
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -255,10 +256,10 @@
   private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
     // If this instruction has already been inlined then this.method must be the outermost caller.
     assert entry.callerPosition == null
-        || entry.callerPosition.getOutermostCaller().method == originalMethod;
+        || entry.callerPosition.getOutermostCaller().getMethod() == originalMethod;
 
     return canonicalPositions.getCanonical(
-        Position.builder()
+        SourcePosition.builder()
             .setLine(entry.line)
             .setFile(entry.sourceFile)
             .setMethod(entry.method)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 40c7bb9..b6fcd8c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -829,7 +829,9 @@
           } else {
             current = position;
           }
-        } else if (position.isSome() && !position.synthetic && !position.equals(current)) {
+        } else if (position.isSome()
+            && !position.isSyntheticPosition()
+            && !position.equals(current)) {
           DebugPosition positionChange = new DebugPosition();
           positionChange.setPosition(position);
           it.previous();
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 07ba0ce..316fdb9 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
@@ -24,6 +24,8 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
@@ -110,16 +112,16 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.base.Suppliers;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -171,6 +173,7 @@
   private DexString highestSortingString;
 
   private List<Action> onWaveDoneActions = null;
+  private final Set<DexMethod> prunedMethodsInWave = Sets.newIdentityHashSet();
 
   private final List<DexString> neverMergePrefixes;
   // Use AtomicBoolean to satisfy TSAN checking (see b/153714743).
@@ -264,8 +267,7 @@
       this.classStaticizer =
           options.enableClassStaticizer ? new ClassStaticizer(appViewWithLiveness, this) : null;
       this.dynamicTypeOptimization = new DynamicTypeOptimization(appViewWithLiveness);
-      this.fieldAccessAnalysis =
-          FieldAccessAnalysis.enable(options) ? new FieldAccessAnalysis(appViewWithLiveness) : null;
+      this.fieldAccessAnalysis = new FieldAccessAnalysis(appViewWithLiveness);
       this.libraryMethodOverrideAnalysis =
           options.enableTreeShakingOfLibraryMethodOverrides
               ? new LibraryMethodOverrideAnalysis(appViewWithLiveness)
@@ -274,8 +276,7 @@
       this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
       this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
       this.outliner = Outliner.create(appViewWithLiveness);
-      this.memberValuePropagation =
-          options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
+      this.memberValuePropagation = new MemberValuePropagation(appViewWithLiveness);
       this.methodOptimizationInfoCollector =
           new MethodOptimizationInfoCollector(appViewWithLiveness, this);
       if (options.isMinifying()) {
@@ -326,6 +327,10 @@
     this(AppView.createForD8(appInfo), timing, printer);
   }
 
+  public Inliner getInliner() {
+    return inliner;
+  }
+
   private void synthesizeBridgesForNestBasedAccessesOnClasspath(
       D8MethodProcessor methodProcessor, ExecutorService executorService)
       throws ExecutionException {
@@ -562,7 +567,10 @@
     }
   }
 
-  private boolean needsIRConversion() {
+  private boolean needsIRConversion(ProgramMethod method) {
+    if (method.getDefinition().getCode().isThrowNullCode()) {
+      return false;
+    }
     if (appView.enableWholeProgramOptimizations()) {
       return true;
     }
@@ -710,7 +718,7 @@
     appView.withArgumentPropagator(
         argumentPropagator ->
             argumentPropagator.tearDownCodeScanner(
-                postMethodProcessorBuilder, executorService, timing));
+                this, postMethodProcessorBuilder, executorService, timing));
     appView.withCallSiteOptimizationInfoPropagator(
         callSiteOptimizationInfoPropagator ->
             callSiteOptimizationInfoPropagator.enqueueMethodsForReprocessing(
@@ -844,12 +852,11 @@
     onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
   }
 
-  private void waveDone(ProgramMethodSet wave) {
+  private void waveDone(ProgramMethodSet wave, ExecutorService executorService)
+      throws ExecutionException {
     delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
     delayedOptimizationFeedback.updateVisibleOptimizationInfo();
-    if (options.enableFieldAssignmentTracker) {
-      fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
-    }
+    fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
     appView.withArgumentPropagator(ArgumentPropagator::publishDelayedReprocessingCriteria);
     if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
       appView.protoShrinker().protoEnumSwitchMapRemover.updateVisibleStaticFieldValues();
@@ -858,6 +865,15 @@
     assert delayedOptimizationFeedback.noUpdatesLeft();
     onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
     onWaveDoneActions = null;
+    if (!prunedMethodsInWave.isEmpty()) {
+      appView.pruneItems(
+          PrunedItems.builder()
+              .setRemovedMethods(prunedMethodsInWave)
+              .setPrunedApp(appView.appInfo().app())
+              .build(),
+          executorService);
+      prunedMethodsInWave.clear();
+    }
   }
 
   public void addWaveDoneAction(com.android.tools.r8.utils.Action action) {
@@ -886,30 +902,6 @@
             });
   }
 
-  private void forEachSelectedOutliningMethod(
-      ProgramMethodSet methodsSelectedForOutlining,
-      Consumer<IRCode> consumer,
-      ExecutorService executorService)
-      throws ExecutionException {
-    assert !options.skipIR;
-    ThreadUtils.processItems(
-        methodsSelectedForOutlining,
-        method -> {
-          IRCode code = method.buildIR(appView);
-          assert code != null;
-          assert !method.getDefinition().getCode().isOutlineCode();
-          // Instead of repeating all the optimizations of rewriteCode(), only run the
-          // optimizations needed for outlining: rewriteMoveResult() to remove out-values on
-          // StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove
-          // unused out-values.
-          codeRewriter.rewriteMoveResult(code);
-          deadCodeRemover.run(code, Timing.empty());
-          CodeRewriter.removeAssumeInstructions(appView, code);
-          consumer.accept(code);
-        },
-        executorService);
-  }
-
   private void processSynthesizedServiceLoaderMethods(
       List<ProgramMethod> serviceLoadMethods, ExecutorService executorService)
       throws ExecutionException {
@@ -952,7 +944,7 @@
     RegisterAllocator registerAllocator =
         performRegisterAllocation(
             code, method, DefaultMethodConversionOptions.getInstance(), timing);
-    method.setCode(code, registerAllocator, appView);
+    method.setCode(code, BytecodeMetadataProvider.empty(), registerAllocator, appView);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
           method.toSourceString(), logCode(options, method));
@@ -963,7 +955,7 @@
       List<ProgramMethod> programMethods, ExecutorService executorService)
       throws ExecutionException {
     // Process the generated class, but don't apply any outlining.
-    SortedProgramMethodSet methods = SortedProgramMethodSet.create(programMethods::forEach);
+    ProgramMethodSet methods = ProgramMethodSet.create(programMethods::forEach);
     processMethodsConcurrently(methods, executorService);
   }
 
@@ -979,8 +971,8 @@
     }
   }
 
-  public void processMethodsConcurrently(
-      SortedProgramMethodSet wave, ExecutorService executorService) throws ExecutionException {
+  public void processMethodsConcurrently(ProgramMethodSet wave, ExecutorService executorService)
+      throws ExecutionException {
     if (!wave.isEmpty()) {
       OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
       methodProcessor.forEachWaveWithExtension(
@@ -1089,7 +1081,7 @@
       options.testing.hookInIrConversion.run();
     }
 
-    if (!needsIRConversion() || options.skipIR) {
+    if (!needsIRConversion(method) || options.skipIR) {
       feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return Timing.empty();
     }
@@ -1193,7 +1185,7 @@
     assert code.verifyTypes(appView);
     assert code.isConsistentSSA();
 
-    if (appView.isCfByteCodePassThrough(method)) {
+    if (shouldPassThrough(context)) {
       // If the code is pass trough, do not finalize by overwriting the existing code.
       assert appView.enableWholeProgramOptimizations();
       timing.begin("Collect optimization info");
@@ -1204,8 +1196,10 @@
           feedback,
           methodProcessor,
           conversionOptions,
+          BytecodeMetadataProvider.builder(),
           timing);
       timing.end();
+      markProcessed(code, feedback);
       return timing;
     }
 
@@ -1438,7 +1432,7 @@
 
     previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
 
-    // TODO(b/140766440): an ideal solution would be puttting CodeOptimization for this into
+    // TODO(b/140766440): an ideal solution would be putting CodeOptimization for this into
     //  the list for primary processing only.
     outliner.collectOutlineSites(code, timing);
 
@@ -1495,6 +1489,8 @@
 
     deadCodeRemover.run(code, timing);
 
+    BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder =
+        BytecodeMetadataProvider.builder();
     if (appView.enableWholeProgramOptimizations()) {
       timing.begin("Collect optimization info");
       collectOptimizationInfo(
@@ -1504,6 +1500,7 @@
           feedback,
           methodProcessor,
           conversionOptions,
+          bytecodeMetadataProviderBuilder,
           timing);
       timing.end();
     }
@@ -1531,11 +1528,20 @@
 
     printMethod(code, "Optimized IR (SSA)", previous);
     timing.begin("Finalize IR");
-    finalizeIR(code, feedback, conversionOptions, timing);
+    finalizeIR(code, feedback, conversionOptions, bytecodeMetadataProviderBuilder.build(), timing);
     timing.end();
     return timing;
   }
 
+  private boolean shouldPassThrough(ProgramMethod method) {
+    if (appView.isCfByteCodePassThrough(method.getDefinition())) {
+      return true;
+    }
+    Code code = method.getDefinition().getCode();
+    assert !code.isThrowNullCode();
+    return code.isDefaultInstanceInitializerCode();
+  }
+
   // Compute optimization info summary for the current method unless it is pinned
   // (in that case we should not be making any assumptions about the behavior of the method).
   public void collectOptimizationInfo(
@@ -1545,6 +1551,7 @@
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       MutableMethodConversionOptions conversionOptions,
+      BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder,
       Timing timing) {
     appView.withArgumentPropagator(
         argumentPropagator -> argumentPropagator.scan(method, code, methodProcessor, timing));
@@ -1561,7 +1568,8 @@
 
     if (fieldAccessAnalysis != null) {
       timing.begin("Analyze field accesses");
-      fieldAccessAnalysis.recordFieldAccesses(code, feedback, methodProcessor);
+      fieldAccessAnalysis.recordFieldAccesses(
+          code, bytecodeMetadataProviderBuilder, feedback, methodProcessor);
       if (classInitializerDefaultsResult != null) {
         fieldAccessAnalysis.acceptClassInitializerDefaultsResult(classInitializerDefaultsResult);
       }
@@ -1617,20 +1625,26 @@
       stringSwitchRemover.run(code);
     }
     deadCodeRemover.run(code, timing);
-    finalizeIR(code, feedback, DefaultMethodConversionOptions.getInstance(), timing);
+    finalizeIR(
+        code,
+        feedback,
+        DefaultMethodConversionOptions.getInstance(),
+        BytecodeMetadataProvider.empty(),
+        timing);
   }
 
   public void finalizeIR(
       IRCode code,
       OptimizationFeedback feedback,
       MethodConversionOptions conversionOptions,
+      BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
     code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
       finalizeToCf(code, feedback, conversionOptions);
     } else {
       assert options.isGeneratingDex();
-      finalizeToDex(code, feedback, conversionOptions, timing);
+      finalizeToDex(code, feedback, conversionOptions, bytecodeMetadataProvider, timing);
     }
   }
 
@@ -1648,6 +1662,7 @@
       IRCode code,
       OptimizationFeedback feedback,
       MethodConversionOptions conversionOptions,
+      BytecodeMetadataProvider bytecodeMetadataProvider,
       Timing timing) {
     DexEncodedMethod method = code.method();
     // Workaround massive dex2oat memory use for self-recursive methods.
@@ -1658,7 +1673,7 @@
     RegisterAllocator registerAllocator =
         performRegisterAllocation(code, method, conversionOptions, timing);
     timing.begin("Build DEX code");
-    method.setCode(code, registerAllocator, appView);
+    method.setCode(code, bytecodeMetadataProvider, registerAllocator, appView);
     timing.end();
     updateHighestSortingStrings(method);
     if (Log.ENABLED) {
@@ -1697,7 +1712,9 @@
   }
 
   private synchronized void updateHighestSortingStrings(DexEncodedMethod method) {
-    DexString highestSortingReferencedString = method.getCode().asDexCode().highestSortingString;
+    Code code = method.getCode();
+    assert code.isDexWritableCode();
+    DexString highestSortingReferencedString = code.asDexWritableCode().getHighestSortingString();
     if (highestSortingReferencedString != null) {
       if (highestSortingString == null
           || highestSortingReferencedString.compareTo(highestSortingString) > 0) {
@@ -1961,9 +1978,13 @@
     appView.withArgumentPropagator(argumentPropagator -> argumentPropagator.onMethodPruned(method));
     enumUnboxer.onMethodPruned(method);
     outliner.onMethodPruned(method);
+    if (classStaticizer != null) {
+      classStaticizer.onMethodPruned(method);
+    }
     if (inliner != null) {
       inliner.onMethodPruned(method);
     }
+    prunedMethodsInWave.add(method.getReference());
   }
 
   /**
@@ -1977,6 +1998,9 @@
         argumentPropagator -> argumentPropagator.onMethodCodePruned(method));
     enumUnboxer.onMethodCodePruned(method);
     outliner.onMethodCodePruned(method);
+    if (classStaticizer != null) {
+      classStaticizer.onMethodCodePruned(method);
+    }
     if (inliner != null) {
       inliner.onMethodCodePruned(method);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
index dd5b328..c411187 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -61,6 +61,10 @@
     this.rewrittenCallSiteCache = null;
   }
 
+  public DexItemFactory dexItemFactory() {
+    return definitions.dexItemFactory();
+  }
+
   public DexCallSite rewriteCallSite(DexCallSite callSite, ProgramMethod context) {
     if (rewrittenCallSiteCache == null) {
       return rewriteCallSiteInternal(callSite, context);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java
index d0f2413..f0c766d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java
@@ -4,12 +4,12 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 
 public abstract class MethodProcessorWithWave extends MethodProcessor {
 
-  protected SortedProgramMethodSet wave;
-  protected SortedProgramMethodSet waveExtension = SortedProgramMethodSet.createConcurrent();
+  protected ProgramMethodSet wave;
+  protected ProgramMethodSet waveExtension = ProgramMethodSet.createConcurrent();
 
   @Override
   public CallSiteInformation getCallSiteInformation() {
@@ -28,10 +28,10 @@
 
   protected void prepareForWaveExtensionProcessing() {
     if (waveExtension.isEmpty()) {
-      wave = SortedProgramMethodSet.empty();
+      wave = ProgramMethodSet.empty();
     } else {
       wave = waveExtension;
-      waveExtension = SortedProgramMethodSet.createConcurrent();
+      waveExtension = ProgramMethodSet.createConcurrent();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index 1ca91a7..65a3287 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -20,7 +20,7 @@
 
   private final ProcessorContext processorContext;
 
-  private OneTimeMethodProcessor(ProcessorContext processorContext, SortedProgramMethodSet wave) {
+  private OneTimeMethodProcessor(ProcessorContext processorContext, ProgramMethodSet wave) {
     this.processorContext = processorContext;
     this.wave = wave;
   }
@@ -30,21 +30,21 @@
   }
 
   public static OneTimeMethodProcessor create(ProgramMethod methodToProcess, AppView<?> appView) {
-    return create(SortedProgramMethodSet.create(methodToProcess), appView);
+    return create(ProgramMethodSet.create(methodToProcess), appView);
   }
 
   public static OneTimeMethodProcessor create(
       ProgramMethod methodToProcess, ProcessorContext processorContext) {
-    return create(SortedProgramMethodSet.create(methodToProcess), processorContext);
+    return create(ProgramMethodSet.create(methodToProcess), processorContext);
   }
 
   public static OneTimeMethodProcessor create(
-      SortedProgramMethodSet methodsToProcess, AppView<?> appView) {
+      ProgramMethodSet methodsToProcess, AppView<?> appView) {
     return create(methodsToProcess, appView.createProcessorContext());
   }
 
   public static OneTimeMethodProcessor create(
-      SortedProgramMethodSet methodsToProcess, ProcessorContext processorContext) {
+      ProgramMethodSet methodsToProcess, ProcessorContext processorContext) {
     return new OneTimeMethodProcessor(processorContext, methodsToProcess);
   }
 
@@ -85,7 +85,7 @@
 
   public static class Builder {
 
-    private final SortedProgramMethodSet methodsToProcess = SortedProgramMethodSet.create();
+    private final ProgramMethodSet methodsToProcess = ProgramMethodSet.create();
     private final ProcessorContext processorContext;
 
     Builder(ProcessorContext processorContext) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 6435f37..c0f6e90 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.utils.Timing.TimingMerger;
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
@@ -34,7 +33,7 @@
 public class PostMethodProcessor extends MethodProcessorWithWave {
 
   private final ProcessorContext processorContext;
-  private final Deque<SortedProgramMethodSet> waves;
+  private final Deque<ProgramMethodSet> waves;
   private final ProgramMethodSet processed = ProgramMethodSet.create();
 
   private PostMethodProcessor(
@@ -116,11 +115,11 @@
     }
   }
 
-  private Deque<SortedProgramMethodSet> createWaves(CallGraph callGraph) {
-    Deque<SortedProgramMethodSet> waves = new ArrayDeque<>();
+  private Deque<ProgramMethodSet> createWaves(CallGraph callGraph) {
+    Deque<ProgramMethodSet> waves = new ArrayDeque<>();
     int waveCount = 1;
     while (!callGraph.isEmpty()) {
-      SortedProgramMethodSet wave = callGraph.extractLeaves();
+      ProgramMethodSet wave = callGraph.extractLeaves();
       waves.addLast(wave);
       if (Log.ENABLED && Log.isLoggingEnabledFor(PostMethodProcessor.class)) {
         Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index bea221a..be8f65e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -16,14 +16,12 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Timing.TimingMerger;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
 
 /**
  * A {@link MethodProcessor} that processes methods in the whole program in a bottom-up manner,
@@ -36,9 +34,15 @@
     void notifyWaveStart(ProgramMethodSet wave);
   }
 
+  interface WaveDoneAction {
+
+    void notifyWaveDone(ProgramMethodSet wave, ExecutorService executorService)
+        throws ExecutionException;
+  }
+
   private final AppView<?> appView;
   private final CallSiteInformation callSiteInformation;
-  private final Deque<SortedProgramMethodSet> waves;
+  private final Deque<ProgramMethodSet> waves;
 
   private ProcessorContext processorContext;
 
@@ -80,13 +84,13 @@
     return callSiteInformation;
   }
 
-  private Deque<SortedProgramMethodSet> createWaves(AppView<?> appView, CallGraph callGraph) {
+  private Deque<ProgramMethodSet> createWaves(AppView<?> appView, CallGraph callGraph) {
     InternalOptions options = appView.options();
-    Deque<SortedProgramMethodSet> waves = new ArrayDeque<>();
+    Deque<ProgramMethodSet> waves = new ArrayDeque<>();
     Set<Node> nodes = callGraph.nodes;
     int waveCount = 1;
     while (!nodes.isEmpty()) {
-      SortedProgramMethodSet wave = callGraph.extractLeaves();
+      ProgramMethodSet wave = callGraph.extractLeaves();
       waves.addLast(wave);
       if (Log.ENABLED && Log.isLoggingEnabledFor(PrimaryMethodProcessor.class)) {
         Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
@@ -110,7 +114,7 @@
   <E extends Exception> void forEachMethod(
       MethodAction<E> consumer,
       WaveStartAction waveStartAction,
-      Consumer<ProgramMethodSet> waveDone,
+      WaveDoneAction waveDoneAction,
       Timing timing,
       ExecutorService executorService)
       throws ExecutionException {
@@ -133,7 +137,7 @@
                 },
                 executorService);
         merger.add(timings);
-        waveDone.accept(wave);
+        waveDoneAction.notifyWaveDone(wave, executorService);
         prepareForWaveExtensionProcessing();
       } while (!wave.isEmpty());
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java
new file mode 100644
index 0000000..b505cf0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.utils.ConsumerUtils;
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class SyntheticStraightLineSourceCode implements SourceCode {
+
+  private final List<Consumer<IRBuilder>> instructionBuilders;
+  private final Position position;
+
+  protected SyntheticStraightLineSourceCode(
+      List<Consumer<IRBuilder>> instructionBuilders, Position position) {
+    this.instructionBuilders = instructionBuilders;
+    this.position = position;
+  }
+
+  @Override
+  public int instructionCount() {
+    return instructionBuilders.size();
+  }
+
+  @Override
+  public int instructionIndex(int instructionOffset) {
+    return instructionOffset;
+  }
+
+  @Override
+  public int instructionOffset(int instructionIndex) {
+    return instructionIndex;
+  }
+
+  @Override
+  public void buildPrelude(IRBuilder builder) {
+    int firstArgumentRegister = 0;
+    builder.buildArgumentsWithRewrittenPrototypeChanges(
+        firstArgumentRegister, builder.getMethod(), ConsumerUtils.emptyBiConsumer());
+  }
+
+  @Override
+  public void buildInstruction(
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
+    instructionBuilders.get(instructionIndex).accept(builder);
+  }
+
+  @Override
+  public void buildPostlude(IRBuilder builder) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void clear() {
+    // Intentionally empty.
+  }
+
+  @Override
+  public Position getCanonicalDebugPositionAtOffset(int offset) {
+    return null;
+  }
+
+  @Override
+  public CatchHandlers<Integer> getCurrentCatchHandlers(IRBuilder builder) {
+    return null;
+  }
+
+  @Override
+  public Position getCurrentPosition() {
+    return position;
+  }
+
+  @Override
+  public DebugLocalInfo getIncomingLocal(int register) {
+    return null;
+  }
+
+  @Override
+  public DebugLocalInfo getIncomingLocalAtBlock(int register, int blockOffset) {
+    return null;
+  }
+
+  @Override
+  public DebugLocalInfo getOutgoingLocal(int register) {
+    return null;
+  }
+
+  @Override
+  public void setUp() {
+    // Intentionally empty.
+  }
+
+  @Override
+  public int traceInstruction(int instructionIndex, IRBuilder builder) {
+    // This instruction does not close the block.
+    return -1;
+  }
+
+  @Override
+  public boolean verifyCurrentInstructionCanThrow() {
+    return true;
+  }
+
+  @Override
+  public boolean verifyRegister(int register) {
+    return true;
+  }
+
+  @Override
+  public void buildBlockTransfer(
+      IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int getMoveExceptionRegister(int instructionIndex) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void resolveAndBuildNewArrayFilledData(
+      int arrayRef, int payloadOffset, IRBuilder builder) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void resolveAndBuildSwitch(
+      int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public boolean verifyLocalInScope(DebugLocalInfo local) {
+    throw new Unreachable();
+  }
+}
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 fd7a8b9..b0abf2a 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,12 +4,12 @@
 
 package com.android.tools.r8.ir.desugar;
 
-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.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.graph.CfCode;
 import com.google.common.collect.ImmutableList;
 import org.objectweb.asm.Opcodes;
@@ -29,7 +29,7 @@
             new CfNew(lambda.type),
             new CfStackInstruction(Opcode.Dup),
             new CfInvoke(Opcodes.INVOKESPECIAL, lambda.constructor, false),
-            new CfFieldInstruction(Opcodes.PUTSTATIC, lambda.lambdaField, lambda.lambdaField),
+            new CfStaticFieldWrite(lambda.lambdaField, lambda.lambdaField),
             new CfReturnVoid()),
         ImmutableList.of(),
         ImmutableList.of());
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 65ead77..7ccba56 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,7 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLoad;
@@ -42,7 +42,7 @@
       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));
+      instructions.add(new CfInstanceFieldWrite(field));
       maxLocals += type.requiredRegisters();
       maxStack += type.requiredRegisters();
     }
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 acfee7b..eb5a2eb 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
@@ -5,7 +5,7 @@
 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.CfInstanceFieldRead;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLoad;
@@ -233,7 +233,7 @@
       DexField field = lambda.getCaptureField(i);
       ValueType valueType = ValueType.fromDexType(field.type);
       instructions.add(new CfLoad(ValueType.OBJECT, 0));
-      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
+      instructions.add(new CfInstanceFieldRead(field));
       maxStack += valueType.requiredRegisters();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index 3d7ae96..3f9e078 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -7,9 +7,7 @@
 import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.THROW_ICCE;
 import static com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass.Behaviour.THROW_NSME;
 import static com.android.tools.r8.utils.AndroidApiLevel.minApiLevelIfEnabledOrUnknown;
-import static org.objectweb.asm.Opcodes.GETSTATIC;
 import static org.objectweb.asm.Opcodes.INVOKESTATIC;
-import static org.objectweb.asm.Opcodes.PUTSTATIC;
 
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstClass;
@@ -17,7 +15,6 @@
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfGoto;
@@ -30,6 +27,8 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
@@ -242,7 +241,7 @@
     CfLabel tryCatchTarget = new CfLabel();
     CfLabel tryCatchEndFinally = new CfLabel();
 
-    instructions.add(new CfFieldInstruction(GETSTATIC, initializedValueField));
+    instructions.add(new CfStaticFieldRead(initializedValueField));
     instructions.add(new CfIf(If.Type.NE, ValueType.INT, initializedTrue));
 
     instructions.add(new CfConstClass(builder.getType()));
@@ -251,13 +250,13 @@
     instructions.add(new CfMonitor(Monitor.Type.ENTER));
     instructions.add(tryCatchStart);
 
-    instructions.add(new CfFieldInstruction(GETSTATIC, initializedValueField));
+    instructions.add(new CfStaticFieldRead(initializedValueField));
     instructions.add(new CfIf(If.Type.NE, ValueType.INT, initializedTrueSecond));
 
     invokeBootstrapMethod(instructions);
-    instructions.add(new CfFieldInstruction(PUTSTATIC, constantValueField));
+    instructions.add(new CfStaticFieldWrite(constantValueField));
     instructions.add(new CfConstNumber(1, ValueType.INT));
-    instructions.add(new CfFieldInstruction(PUTSTATIC, initializedValueField));
+    instructions.add(new CfStaticFieldWrite(initializedValueField));
 
     instructions.add(initializedTrueSecond);
     instructions.add(
@@ -287,7 +286,7 @@
 
     instructions.add(initializedTrue);
     instructions.add(new CfFrame(ImmutableInt2ReferenceSortedMap.empty(), ImmutableDeque.of()));
-    instructions.add(new CfFieldInstruction(GETSTATIC, constantValueField));
+    instructions.add(new CfStaticFieldRead(constantValueField));
     instructions.add(new CfReturn(ValueType.OBJECT));
 
     List<CfTryCatch> tryCatchRanges =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index 6b76836..7226037 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -6,11 +6,11 @@
 
 
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInitClass;
 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.CfStaticFieldRead;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -44,7 +44,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Predicate;
-import org.objectweb.asm.Opcodes;
 
 public class InterfaceDesugaringSyntheticHelper {
 
@@ -493,10 +492,7 @@
                   isWide ? 2 : 1,
                   0,
                   ImmutableList.of(
-                      new CfFieldInstruction(
-                          Opcodes.GETSTATIC,
-                          clinitField.getReference(),
-                          clinitField.getReference()),
+                      new CfStaticFieldRead(clinitField.getReference(), clinitField.getReference()),
                       isWide
                           ? new CfStackInstruction(Opcode.Pop2)
                           : new CfStackInstruction(Opcode.Pop),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 7513257..c89f90c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -267,7 +267,7 @@
                       assert InvalidCode.isInvalidCode(defaultMethod.getCode());
                       assert !InvalidCode.isInvalidCode(companionMethod.getCode());
                       defaultMethod.accessFlags.setAbstract();
-                      defaultMethod.removeCode();
+                      defaultMethod.unsetCode();
                       graphLensBuilder.recordCodeMovedToCompanionClass(
                           defaultMethod.getReference(), companionMethod.getReference());
                     });
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index 206c276..4c45086 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar.lambda;
 
-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.CfInvokeDynamic;
@@ -12,6 +11,7 @@
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
@@ -123,8 +123,7 @@
 
     if (lambdaClass.isStateless()) {
       return ImmutableList.of(
-          new CfFieldInstruction(
-              Opcodes.GETSTATIC, lambdaClass.lambdaField, lambdaClass.lambdaField));
+          new CfStaticFieldRead(lambdaClass.lambdaField, lambdaClass.lambdaField));
     }
 
     DexTypeList captureTypes = lambdaClass.descriptor.captures;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index cdc2c6e..bcd27e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -79,6 +79,7 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Throw;
@@ -3673,7 +3674,8 @@
     InstructionListIterator iterator = block.listIterator(code);
 
     // Attach some synthetic position to all inserted code.
-    Position position = Position.synthetic(1, method.getReference(), null);
+    Position position =
+        SyntheticPosition.builder().setLine(1).setMethod(method.getReference()).build();
     iterator.setInsertionPosition(position);
 
     // Split arguments into their own block.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 32d2228..1e69a6b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -98,6 +98,8 @@
   // pruned when the wave ends.
   private final Map<DexProgramClass, ProgramMethodSet> singleCallerInlinedMethodsInWave =
       new ConcurrentHashMap<>();
+  private final Set<DexMethod> singleCallerInlinedPrunedMethodsForTesting =
+      Sets.newIdentityHashSet();
 
   private final AvailableApiExceptions availableApiExceptions;
 
@@ -1202,10 +1204,8 @@
     Set<BasicBlock> inlineeBlocks = SetUtils.newIdentityHashSet(inlinee.blocks);
 
     // Run member value propagation on the inlinee blocks.
-    if (appView.options().enableValuePropagation) {
-      rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
-      applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks);
-    }
+    rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
+    applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks);
 
     // Add non-null IRs only to the inlinee blocks.
     insertAssumeInstructions(code, blockIterator, block, inlineeBlocks, timing);
@@ -1268,10 +1268,13 @@
         (clazz, singleCallerInlinedMethodsForClass) -> {
           // Convert and remove virtual single caller inlined methods to abstract or throw null.
           singleCallerInlinedMethodsForClass.removeIf(
-              singleCallerInlinedMethod -> {
-                if (singleCallerInlinedMethod.getDefinition().belongsToVirtualPool() || true) {
-                  singleCallerInlinedMethod.convertToAbstractOrThrowNullMethod(appView);
-                  converter.onMethodCodePruned(singleCallerInlinedMethod);
+              method -> {
+                // TODO(b/203188583): Enable pruning of methods with generic signatures. For this to
+                //  work we need to pass in a seed to GenericSignatureContextBuilder.create in R8.
+                if (method.getDefinition().belongsToVirtualPool()
+                    || method.getDefinition().getGenericSignature().hasSignature()) {
+                  method.convertToAbstractOrThrowNullMethod(appView);
+                  converter.onMethodCodePruned(method);
                   return true;
                 }
                 return false;
@@ -1284,7 +1287,10 @@
                 .removeMethods(
                     singleCallerInlinedMethodsForClass.toDefinitionSet(
                         SetUtils::newIdentityHashSet));
-            singleCallerInlinedMethodsForClass.forEach(converter::onMethodPruned);
+            for (ProgramMethod method : singleCallerInlinedMethodsForClass) {
+              converter.onMethodPruned(method);
+              singleCallerInlinedPrunedMethodsForTesting.add(method.getReference());
+            }
           }
         });
     singleCallerInlinedMethodsInWave.clear();
@@ -1302,4 +1308,9 @@
     }
     return true;
   }
+
+  public boolean verifyIsPrunedDueToSingleCallerInlining(DexMethod method) {
+    assert singleCallerInlinedPrunedMethodsForTesting.contains(method);
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index 31801c6..921bc7f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -43,10 +43,14 @@
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
 import com.android.tools.r8.ir.code.Mul;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
+import com.android.tools.r8.ir.code.Position.OutlinePosition;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Sub;
 import com.android.tools.r8.ir.code.Value;
@@ -73,7 +77,9 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -83,6 +89,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
@@ -761,9 +768,8 @@
   abstract private class OutlineSpotter {
 
     final ProgramMethod method;
-    final BasicBlock block;
-    // instructionArrayCache is block.getInstructions() copied to an ArrayList.
-    private List<Instruction> instructionArrayCache = null;
+    final IRCode irCode;
+    final List<Instruction> currentCandidateInstructions;
 
     int start;
     int index;
@@ -777,32 +783,17 @@
     int returnValueUniqueUsersLeft;
     int pendingNewInstanceIndex = -1;
 
-    OutlineSpotter(ProgramMethod method, BasicBlock block) {
+    OutlineSpotter(
+        ProgramMethod method, IRCode irCode, List<Instruction> currentCandidateInstructions) {
       this.method = method;
-      this.block = block;
+      this.irCode = irCode;
+      this.currentCandidateInstructions = currentCandidateInstructions;
       reset(0);
     }
 
-    protected List<Instruction> getInstructionArray() {
-      if (instructionArrayCache == null) {
-        instructionArrayCache = new ArrayList<>(block.getInstructions());
-      }
-      return instructionArrayCache;
-    }
-
-    // Call this before modifying block.getInstructions().
-    protected void invalidateInstructionArray() {
-      instructionArrayCache = null;
-    }
-
     protected void process() {
-      List<Instruction> instructions;
-      for (;;) {
-        instructions = getInstructionArray(); // ProcessInstruction may have invalidated it.
-        if (index >= instructions.size()) {
-          break;
-        }
-        processInstruction(instructions.get(index));
+      while (index < currentCandidateInstructions.size()) {
+        processInstruction(currentCandidateInstructions.get(index));
       }
     }
 
@@ -941,10 +932,9 @@
         assert index > 0;
         int offset = 0;
         Instruction previous;
-        List<Instruction> instructions = getInstructionArray();
         do {
           offset++;
-          previous = instructions.get(index - offset);
+          previous = currentCandidateInstructions.get(index - offset);
         } while (previous.isConstInstruction());
         if (!previous.isNewInstance()
             || invoke != previous.asNewInstance().getUniqueConstructorInvoke(dexItemFactory)) {
@@ -1134,8 +1124,7 @@
     protected abstract void handle(int start, int end, Outline outline);
 
     private void candidate(int start, int index) {
-      List<Instruction> instructions = getInstructionArray();
-      assert !instructions.get(start).isConstInstruction();
+      assert !currentCandidateInstructions.get(start).isConstInstruction();
 
       if (pendingNewInstanceIndex != -1) {
         if (pendingNewInstanceIndex == start) {
@@ -1148,7 +1137,7 @@
 
       // Back out of any const instructions ending this candidate.
       int end = index;
-      while (instructions.get(end - 1).isConstInstruction()) {
+      while (currentCandidateInstructions.get(end - 1).isConstInstruction()) {
         end--;
       }
 
@@ -1159,7 +1148,8 @@
       }
 
       Outline outline =
-          new Outline(instructions, argumentTypes, argumentsMap, returnType, start, end);
+          new Outline(
+              currentCandidateInstructions, argumentTypes, argumentsMap, returnType, start, end);
       handle(start, end, outline);
 
       // Start a new candidate search from the next instruction after this outline.
@@ -1189,8 +1179,11 @@
     private final List<Outline> outlinesForMethod;
 
     OutlineMethodIdentifier(
-        ProgramMethod method, BasicBlock block, List<Outline> outlinesForMethod) {
-      super(method, block);
+        ProgramMethod method,
+        IRCode irCode,
+        List<Instruction> currentCandidateInstructions,
+        List<Outline> outlinesForMethod) {
+      super(method, irCode, currentCandidateInstructions);
       this.outlinesForMethod = outlinesForMethod;
     }
 
@@ -1202,8 +1195,9 @@
 
   private class OutlineSiteIdentifier extends OutlineSpotter {
 
-    OutlineSiteIdentifier(ProgramMethod method, BasicBlock block) {
-      super(method, block);
+    OutlineSiteIdentifier(
+        ProgramMethod method, IRCode irCode, List<Instruction> currentCandidateInstructions) {
+      super(method, irCode, currentCandidateInstructions);
     }
 
     @Override
@@ -1218,45 +1212,53 @@
   private class OutlineRewriter extends OutlineSpotter {
 
     private final IRCode code;
-    private final ListIterator<BasicBlock> blocksIterator;
-    private final List<Integer> toRemove;
+    private final Set<Instruction> toRemove;
+    private final Set<Instruction> invokesToOutlineMethods;
     int argumentsMapIndex;
 
     OutlineRewriter(
         IRCode code,
-        ListIterator<BasicBlock> blocksIterator,
-        BasicBlock block,
-        List<Integer> toRemove) {
-      super(code.context(), block);
+        List<Instruction> currentCandidateInstructions,
+        Set<Instruction> toRemove,
+        Set<Instruction> invokesToOutlineMethods) {
+      super(code.context(), code, currentCandidateInstructions);
       this.code = code;
-      this.blocksIterator = blocksIterator;
       this.toRemove = toRemove;
+      this.invokesToOutlineMethods = invokesToOutlineMethods;
     }
 
     @Override
     protected void handle(int start, int end, Outline outline) {
-      DexMethod m = generatedOutlines.get(outline);
-      if (m != null) {
+      DexMethod outlineMethod = generatedOutlines.get(outline);
+      if (outlineMethod != null) {
         assert removeMethodFromOutlineList(outline);
         List<Value> in = new ArrayList<>();
         Value returnValue = null;
         argumentsMapIndex = 0;
+        OutlineCallerPositionBuilder positionBuilder =
+            OutlineCallerPosition.builder()
+                .setMethod(appView.graphLens().getOriginalMethodSignature(method.getReference()))
+                .setOutlineCallee(outlineMethod)
+                // We set the line number to 0 here and rely on the LineNumberOptimizer to
+                // set a new disjoint line.
+                .setLine(0);
+        Instruction lastInstruction = null;
         Position position = Position.none();
         { // Scope for 'instructions'.
-          List<Instruction> instructions = getInstructionArray();
+          int outlinePositionIndex = 0;
           for (int i = start; i < end; i++) {
-            Instruction current = instructions.get(i);
+            Instruction current = currentCandidateInstructions.get(i);
             if (current.isConstInstruction()) {
               // Leave any const instructions.
               continue;
             }
-            if (position.isNone()) {
-              position = current.getPosition();
+            if (current.getPosition() != null) {
+              positionBuilder.addOutlinePosition(outlinePositionIndex++, current.getPosition());
             }
+
             // Prepare to remove the instruction.
             List<Value> inValues = orderedInValues(current, returnValue);
-            for (int j = 0; j < inValues.size(); j++) {
-              Value value = inValues.get(j);
+            for (Value value : inValues) {
               value.removeUser(current);
               int argumentIndex = outline.argumentMap.get(argumentsMapIndex++);
               if (argumentIndex >= in.size()) {
@@ -1270,32 +1272,25 @@
             // The invoke of the outline method will be placed at the last instruction index,
             // so don't mark that for removal.
             if (i < end - 1) {
-              toRemove.add(i);
+              toRemove.add(current);
             }
+            lastInstruction = current;
           }
         }
-        assert m.proto.shorty.toString().length() - 1 == in.size();
+        assert lastInstruction != null;
+        assert outlineMethod.proto.shorty.toString().length() - 1 == in.size();
         if (returnValue != null && !returnValue.isUsed()) {
           returnValue = null;
         }
-        Invoke outlineInvoke = new InvokeStatic(m, returnValue, in);
-        outlineInvoke.setBlock(block);
-        outlineInvoke.setPosition(position);
-        if (position.isNone() && code.doAllThrowingInstructionsHavePositions()) {
-          // We have introduced a static invoke, but non of the outlines instructions could throw
-          // and none had a position. The code no longer has the previous property.
-          code.setAllThrowingInstructionsHavePositions(false);
-        }
-        InstructionListIterator endIterator = block.listIterator(code, end - 1);
-        Instruction instructionBeforeEnd = endIterator.next();
-        invalidateInstructionArray(); // Because we're about to modify the original linked list.
-        instructionBeforeEnd.clearBlock();
+        Invoke outlineInvoke = new InvokeStatic(outlineMethod, returnValue, in);
+        outlineInvoke.setBlock(lastInstruction.getBlock());
+        outlineInvoke.setPosition(positionBuilder.build());
+        InstructionListIterator endIterator =
+            lastInstruction.getBlock().listIterator(code, lastInstruction);
+        Instruction instructionBeforeEnd = endIterator.previous();
+        assert instructionBeforeEnd == lastInstruction;
         endIterator.set(outlineInvoke); // Replaces instructionBeforeEnd.
-        if (block.hasCatchHandlers()) {
-          // If the inserted invoke is inserted in a block with handlers, split the block after
-          // the inserted invoke.
-          endIterator.split(code, blocksIterator);
-        }
+        invokesToOutlineMethods.add(outlineInvoke);
       }
     }
 
@@ -1419,20 +1414,71 @@
 
     timing.begin("Collect outlines");
     List<Outline> outlinesForMethod = new ArrayList<>();
-    for (BasicBlock block : code.blocks) {
-      new OutlineMethodIdentifier(context, block, outlinesForMethod).process();
-    }
+    getInstructions(
+        appView,
+        code,
+        instructions ->
+            new OutlineMethodIdentifier(context, code, instructions, outlinesForMethod).process());
     outlineCollection.set(appView, context, outlinesForMethod);
     timing.end();
   }
 
+  public static void getInstructions(
+      AppView<?> appView, IRCode code, Consumer<List<Instruction>> consumer) {
+    int maxNumberOfInstructionsToBeConsidered =
+        appView.options().outline.maxNumberOfInstructionsToBeConsidered;
+    int minSize = appView.options().outline.minSize;
+    Set<BasicBlock> seenBlocks = Sets.newIdentityHashSet();
+    for (BasicBlock block : code.blocks) {
+      if (seenBlocks.add(block)) {
+        ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
+        LinearFlowInstructionListIterator instructionIterator =
+            new LinearFlowInstructionListIterator(code, block);
+        // Maintaining the last seen block ensure that we always consider all instructions in a
+        // block before adding it to the seen set.
+        BasicBlock lastSeenBlock = block;
+        int counter = 0;
+        boolean sawLinearFlowWithCatchHandlers = false;
+        while (instructionIterator.hasNext()) {
+          Instruction instruction = instructionIterator.next();
+          // Disregard linear flow when there are catch handlers
+          if (instruction.getBlock() != block
+              && (block.hasCatchHandlers() || instruction.getBlock().hasCatchHandlers())) {
+            lastSeenBlock = instruction.getBlock();
+            sawLinearFlowWithCatchHandlers = true;
+            break;
+          }
+          builder.add(instruction);
+          counter++;
+          if (counter > maxNumberOfInstructionsToBeConsidered
+              && instruction.getBlock() != lastSeenBlock) {
+            // Ensure we only break on whole blocks.
+            break;
+          }
+          lastSeenBlock = instruction.getBlock();
+        }
+        seenBlocks.addAll(instructionIterator.getSeenBlocks());
+        if (sawLinearFlowWithCatchHandlers) {
+          assert lastSeenBlock != block;
+          // Remove the last seen block since we just visited the first instruction in that block
+          // and terminated without adding it.
+          seenBlocks.remove(lastSeenBlock);
+        }
+        if (counter >= minSize) {
+          consumer.accept(builder.build());
+        }
+      }
+    }
+  }
+
   public void identifyOutlineSites(IRCode code) {
     ProgramMethod context = code.context();
     assert !context.getDefinition().getCode().isOutlineCode();
     assert !ClassToFeatureSplitMap.isInFeature(context.getHolder(), appView);
-    for (BasicBlock block : code.blocks) {
-      new OutlineSiteIdentifier(context, block).process();
-    }
+    getInstructions(
+        appView,
+        code,
+        instructions -> new OutlineSiteIdentifier(context, code, instructions).process());
   }
 
   public ProgramMethodSet selectMethodsForOutlining() {
@@ -1512,13 +1558,33 @@
   }
 
   public void applyOutliningCandidate(IRCode code) {
-    assert !code.method().getCode().isOutlineCode();
-    ListIterator<BasicBlock> blocksIterator = code.listIterator();
-    while (blocksIterator.hasNext()) {
-      BasicBlock block = blocksIterator.next();
-      List<Integer> toRemove = new ArrayList<>();
-      new OutlineRewriter(code, blocksIterator, block, toRemove).process();
-      block.removeInstructions(toRemove);
+    assert !code.context().getDefinition().getCode().isOutlineCode();
+    Set<Instruction> toRemove = Sets.newIdentityHashSet();
+    Set<Instruction> invokesToOutlineMethods = Sets.newIdentityHashSet();
+    getInstructions(
+        appView,
+        code,
+        instructions ->
+            new OutlineRewriter(code, instructions, toRemove, invokesToOutlineMethods).process());
+    if (!toRemove.isEmpty()) {
+      assert !invokesToOutlineMethods.isEmpty();
+      // Scan over the entire code to remove outline instructions.
+      ListIterator<BasicBlock> blocksIterator = code.listIterator();
+      while (blocksIterator.hasNext()) {
+        BasicBlock block = blocksIterator.next();
+        InstructionListIterator instructionListIterator = block.listIterator(code);
+        instructionListIterator.forEachRemaining(
+            instruction -> {
+              if (toRemove.contains(instruction)) {
+                instructionListIterator.removeInstructionIgnoreOutValue();
+              } else if (invokesToOutlineMethods.contains(instruction)
+                  && block.hasCatchHandlers()) {
+                // If the inserted invoke is inserted in a block with handlers, split the block
+                // after the inserted invoke.
+                instructionListIterator.split(code, blocksIterator);
+              }
+            });
+      }
     }
   }
 
@@ -1531,13 +1597,14 @@
 
   private class OutlineSourceCode implements SourceCode {
 
-    final private Outline outline;
-    private final Position position;
+    private final Outline outline;
+    private final DexMethod method;
+    private int position;
     private int argumentMapIndex = 0;
 
     OutlineSourceCode(Outline outline, DexMethod method) {
       this.outline = outline;
-      this.position = Position.synthetic(0, method, null);
+      this.method = method;
     }
 
     @Override
@@ -1623,6 +1690,7 @@
         }
         return;
       }
+      position = instructionIndex;
       // Build IR from the template.
       argumentMapIndex =
           outline
@@ -1660,7 +1728,7 @@
 
     @Override
     public Position getCurrentPosition() {
-      return position;
+      return OutlinePosition.builder().setLine(position).setMethod(method).build();
     }
 
     @Override
@@ -1705,11 +1773,6 @@
     }
 
     @Override
-    public OutlineCode asOutlineCode() {
-      return this;
-    }
-
-    @Override
     public boolean isEmptyVoidMethod() {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 70b07a6..f51dc69 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -72,15 +72,22 @@
 
       if (current.isInvokeMethodWithReceiver()) {
         InvokeMethodWithReceiver methodWithReceiver = current.asInvokeMethodWithReceiver();
+        Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
+        if (!receiver.getType().isClassType()
+            || !appView
+                .appInfo()
+                .isSubtype(receiver.getType().asClassType().getClassType(), factory.enumType)) {
+          continue;
+        }
+
         DexMethod invokedMethod = methodWithReceiver.getInvokedMethod();
-        boolean isOrdinalInvoke = invokedMethod == factory.enumMembers.ordinalMethod;
-        boolean isNameInvoke = invokedMethod == factory.enumMembers.nameMethod;
-        boolean isToStringInvoke = invokedMethod == factory.enumMembers.toString;
+        boolean isOrdinalInvoke = invokedMethod.match(factory.enumMembers.ordinalMethod);
+        boolean isNameInvoke = invokedMethod.match(factory.enumMembers.nameMethod);
+        boolean isToStringInvoke = invokedMethod.match(factory.enumMembers.toString);
         if (!isOrdinalInvoke && !isNameInvoke && !isToStringInvoke) {
           continue;
         }
 
-        Value receiver = methodWithReceiver.getReceiver().getAliasedValue();
         if (receiver.isPhi()) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 10ea774..90d49ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfConstNumber;
-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;
@@ -18,6 +17,8 @@
 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.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -267,7 +268,7 @@
         instructions.add(new CfConstNumber(i + 1, ValueType.INT));
         instructions.add(new CfArrayStore(MemberType.INT));
       }
-      instructions.add(new CfFieldInstruction(Opcodes.PUTSTATIC, valuesField.getReference()));
+      instructions.add(new CfStaticFieldWrite(valuesField.getReference()));
       instructions.add(new CfReturnVoid());
 
       int maxStack = 4;
@@ -317,7 +318,7 @@
               new CfNewArray(dexItemFactory.intArrayType),
               new CfStore(ValueType.OBJECT, resultLocalSlot),
               // System.arraycopy(SharedUtilityClass.$VALUES, 0, result, 0, size);
-              new CfFieldInstruction(Opcodes.GETSTATIC, valuesField.getReference()),
+              new CfStaticFieldRead(valuesField.getReference()),
               new CfConstNumber(0, ValueType.INT),
               new CfLoad(ValueType.OBJECT, resultLocalSlot),
               new CfConstNumber(0, ValueType.INT),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 748253f..30b0da1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -105,6 +105,8 @@
   final Map<CandidateInfo, LongLivedProgramMethodSetBuilder<?>> referencedFrom =
       new ConcurrentHashMap<>();
 
+  private final Set<DexMethod> prunedMethods = Sets.newIdentityHashSet();
+
   // The map storing all the potential candidates for staticizing.
   final ConcurrentHashMap<DexType, CandidateInfo> candidates = new ConcurrentHashMap<>();
 
@@ -114,6 +116,14 @@
     this.converter = converter;
   }
 
+  public void onMethodPruned(ProgramMethod method) {
+    onMethodCodePruned(method);
+  }
+
+  public void onMethodCodePruned(ProgramMethod method) {
+    prunedMethods.add(method.getReference());
+  }
+
   public void prepareForPrimaryOptimizationPass(GraphLens graphLensForPrimaryOptimizationPass) {
     collectCandidates();
     this.graphLensForOptimizationPass = graphLensForPrimaryOptimizationPass;
@@ -129,8 +139,11 @@
         .values()
         .forEach(
             referencedFromBuilder ->
-                referencedFromBuilder.rewrittenWithLens(graphLensForSecondaryOptimizationPass));
+                referencedFromBuilder
+                    .removeAll(prunedMethods)
+                    .rewrittenWithLens(graphLensForSecondaryOptimizationPass));
     this.graphLensForOptimizationPass = graphLensForSecondaryOptimizationPass;
+    prunedMethods.clear();
   }
 
   // Before doing any usage-based analysis we collect a set of classes that can be
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index c73132f..8ab0859 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -49,7 +50,6 @@
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
@@ -74,7 +74,7 @@
   private final ClassStaticizer classStaticizer;
   private final IRConverter converter;
 
-  private final SortedProgramMethodSet methodsToReprocess = SortedProgramMethodSet.create();
+  private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.create();
 
   // Optimization order matters, hence a collection that preserves orderings.
   private final Map<DexEncodedMethod, ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>>
@@ -237,9 +237,6 @@
         referencedFrom =
             referencedFromBuilder
                 .rewrittenWithLens(appView)
-                .removeIf(
-                    appView,
-                    method -> method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite())
                 .build(appView);
         materializedReferencedFromCollections.put(info, referencedFrom);
       } else {
@@ -414,6 +411,7 @@
             feedback,
             methodProcessor,
             new MutableMethodConversionOptions(methodProcessor),
+            BytecodeMetadataProvider.builder(),
             Timing.empty());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 7395cbf..6777d57 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -7,10 +7,11 @@
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
 import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -80,7 +81,7 @@
       // use vivifiedTypes.
 
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, wrapperField, wrapperField));
+      instructions.add(new CfInstanceFieldRead(wrapperField));
       int index = 1;
       int stackIndex = 1;
       DexType[] newParameters = forwardMethod.proto.parameters.values.clone();
@@ -250,7 +251,7 @@
     @Override
     void generatePushReceiver(List<CfInstruction> instructions) {
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, wrapperField, wrapperField));
+      instructions.add(new CfInstanceFieldRead(wrapperField));
     }
 
     @Override
@@ -299,8 +300,7 @@
       instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
       instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
       instructions.add(new CfCheckCast(reverseWrapperField.holder));
-      instructions.add(
-          new CfFieldInstruction(Opcodes.GETFIELD, reverseWrapperField, reverseWrapperField));
+      instructions.add(new CfInstanceFieldRead(reverseWrapperField));
       instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
       instructions.add(unwrapDest);
       instructions.add(new CfFrame(locals, ImmutableDeque.of()));
@@ -410,7 +410,7 @@
               false));
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
       instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.type), 1));
-      instructions.add(new CfFieldInstruction(Opcodes.PUTFIELD, wrapperField, wrapperField));
+      instructions.add(new CfInstanceFieldWrite(wrapperField));
       instructions.add(new CfReturnVoid());
       return standardCfCodeFromInstructions(instructions);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index cb5de3e..5ef065a 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIf;
@@ -20,6 +19,8 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStaticFieldWrite;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -241,17 +242,17 @@
       //    return VALUES$com$x$MyEnum;
       List<CfInstruction> instructions = new ArrayList<>();
       CfLabel nullDest = new CfLabel();
-      instructions.add(new CfFieldInstruction(Opcodes.GETSTATIC, utilityField, utilityField));
+      instructions.add(new CfStaticFieldRead(utilityField, utilityField));
       instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest));
       instructions.add((new CfConstNumber(numEnumInstances, ValueType.INT)));
       assert initializationMethod.getArity() == 1;
       instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, initializationMethod, false));
-      instructions.add(new CfFieldInstruction(Opcodes.PUTSTATIC, utilityField, utilityField));
+      instructions.add(new CfStaticFieldWrite(utilityField, utilityField));
       instructions.add(nullDest);
       instructions.add(
           new CfFrame(
               ImmutableInt2ReferenceSortedMap.<FrameType>builder().build(), ImmutableDeque.of()));
-      instructions.add(new CfFieldInstruction(Opcodes.GETSTATIC, utilityField, utilityField));
+      instructions.add(new CfStaticFieldRead(utilityField, utilityField));
       instructions.add(new CfReturn(ValueType.OBJECT));
       return standardCfCodeFromInstructions(instructions);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
index d6794c6..f549990 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
@@ -103,7 +103,7 @@
     // Get or set the field.
     int opcode =
         Opcodes.GETSTATIC + BooleanUtils.intValue(isSetter()) + (isInstanceField.ordinal() << 1);
-    instructions.add(new CfFieldInstruction(opcode, field, field));
+    instructions.add(CfFieldInstruction.create(opcode, field, field));
 
     // Return.
     if (isSetter()) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
index 46124b9..be34a74 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
@@ -7,10 +7,10 @@
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNumber;
-import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
@@ -118,7 +118,7 @@
     private void loadFieldAsObject(List<CfInstruction> instructions, DexField field) {
       DexItemFactory factory = appView.dexItemFactory();
       instructions.add(new CfLoad(ValueType.OBJECT, 0));
-      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
+      instructions.add(new CfInstanceFieldRead(field));
       if (field.type.isPrimitiveType()) {
         factory.primitiveToBoxed.forEach(
             (primitiveType, boxedType) -> {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index ae06b60..c81383c 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -64,7 +65,12 @@
       this.paramRegisters[i] = nextRegister(ValueType.fromDexType(params[i]));
     }
 
-    position = Position.synthetic(0, originalMethod, callerPosition);
+    position =
+        SyntheticPosition.builder()
+            .setLine(0)
+            .setMethod(originalMethod)
+            .setCallerPosition(callerPosition)
+            .build();
   }
 
   protected final void add(Consumer<IRBuilder> constructor) {
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 7b8da09..b10c36a 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.errors.ConstantPoolOverflowDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -591,8 +591,10 @@
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    CfCode code = method.getDefinition().getCode().asCfCode();
-    code.write(method, classFileVersion, appView, namingLens, rewriter, visitor);
+    Code code = method.getDefinition().getCode();
+    assert code.isCfWritableCode();
+    code.asCfWritableCode()
+        .writeCf(method, classFileVersion, appView, namingLens, rewriter, visitor);
   }
 
   public static String printCf(byte[] result) {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index a22a766..2e21467 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -156,8 +156,7 @@
           instructions[i] = constString;
         }
       }
-    } else {
-      assert code.isCfCode();
+    } else if (code.isCfCode()) {
       List<CfInstruction> instructions = code.asCfCode().getInstructions();
       List<CfInstruction> newInstructions =
           ListUtils.mapOrElse(
@@ -176,6 +175,8 @@
               },
               instructions);
       code.asCfCode().setInstructions(newInstructions);
+    } else {
+      assert code.isDefaultInstanceInitializerCode() || code.isThrowNullCode();
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 391f2c7..ea756b5 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -375,9 +375,9 @@
             + " Thus, not all identifier strings flowing to that " + kind
             + " are renamed, which can cause resolution failures at runtime.";
     StringDiagnostic diagnostic =
-        instruction.getPosition().line >= 1
+        instruction.getPosition().getLine() >= 1
             ? new StringDiagnostic(
-                message, origin, new TextPosition(0L, instruction.getPosition().line, 1))
+                message, origin, new TextPosition(0L, instruction.getPosition().getLine(), 1))
             : new StringDiagnostic(message, origin);
     appView.options().reporter.warning(diagnostic);
   }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
index d451d82..7f0beee 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.MapVersion;
 import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
 import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
 import java.util.function.Consumer;
@@ -31,7 +32,15 @@
 
   @Override
   public String serialize() {
-    throw new CompilationError("Should not yet serialize this");
+    JsonObject result = new JsonObject();
+    result.add(MAPPING_ID_KEY, new JsonPrimitive(ID));
+    JsonObject mappedPositions = new JsonObject();
+    positions.forEach(
+        (obfuscatedPosition, originalPosition) -> {
+          mappedPositions.add(obfuscatedPosition + "", new JsonPrimitive(originalPosition));
+        });
+    result.add(POSITIONS_KEY, mappedPositions);
+    return result.toString();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java
index 697c5b6..20df16d 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java
@@ -4,8 +4,9 @@
 
 package com.android.tools.r8.naming.mappinginformation;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.MapVersion;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
 import java.util.function.Consumer;
 
 public class OutlineMappingInformation extends MappingInformation {
@@ -20,7 +21,9 @@
 
   @Override
   public String serialize() {
-    throw new CompilationError("Should not yet serialize this");
+    JsonObject object = new JsonObject();
+    object.add(MAPPING_ID_KEY, new JsonPrimitive(ID));
+    return object.toString();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 23e0e7a..bc1b7e3 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -3,9 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -14,19 +16,23 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
-import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BiForEachable;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.TriConsumer;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -40,52 +46,120 @@
 
 public class MemberRebindingAnalysis {
 
+  private final AndroidApiLevelCompute androidApiLevelCompute;
   private final AppView<AppInfoWithLiveness> appView;
-  private final GraphLens lens;
   private final InternalOptions options;
 
   private final MemberRebindingLens.Builder lensBuilder;
 
   public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
     assert appView.graphLens().isContextFreeForMethods();
+    this.androidApiLevelCompute = AndroidApiLevelCompute.create(appView);
     this.appView = appView;
-    this.lens = appView.graphLens();
     this.options = appView.options();
     this.lensBuilder = MemberRebindingLens.builder(appView);
   }
 
-  private DexMethod validTargetFor(DexMethod target, DexMethod original) {
-    DexClass clazz = appView.definitionFor(target.getHolderType());
-    assert clazz != null;
-    if (clazz.isProgramClass()) {
-      return target;
+  private DexMethod validMemberRebindingTargetForNonProgramMethod(
+      DexClassAndMethod resolvedMethod,
+      SingleResolutionResult resolutionResult,
+      ProgramMethodSet contexts,
+      Type invokeType,
+      DexMethod original) {
+    assert !resolvedMethod.isProgramMethod();
+
+    if (invokeType.isDirect()) {
+      return original;
     }
-    DexType newHolder;
-    if (clazz.isInterface()) {
-      newHolder =
-          firstLibraryClassForInterfaceTarget(
-              appView, target, original.holder, DexClass::lookupMethod);
-    } else {
-      newHolder = firstLibraryClass(appView, target.getHolderType(), original.getHolderType());
+
+    LibraryMethod eligibleLibraryMethod = null;
+    SingleResolutionResult currentResolutionResult = resolutionResult;
+    while (currentResolutionResult != null) {
+      DexClassAndMethod currentResolvedMethod = currentResolutionResult.getResolutionPair();
+      if (canRebindDirectlyToLibraryMethod(
+          currentResolvedMethod, currentResolutionResult, contexts, invokeType)) {
+        eligibleLibraryMethod = currentResolvedMethod.asLibraryMethod();
+      }
+      if (appView.appInfo().isAssumeMethod(currentResolvedMethod)) {
+        break;
+      }
+      DexClass currentResolvedHolder = currentResolvedMethod.getHolder();
+      if (resolvedMethod.getDefinition().belongsToVirtualPool()
+          && !currentResolvedHolder.isInterface()
+          && currentResolvedHolder.getSuperType() != null) {
+        currentResolutionResult =
+            appView
+                .appInfo()
+                .resolveMethodOnClass(original, currentResolvedHolder.getSuperType())
+                .asSingleResolution();
+      } else {
+        break;
+      }
     }
-    return newHolder == null
-        ? original
-        : appView.dexItemFactory().createMethod(newHolder, original.proto, original.name);
+    if (eligibleLibraryMethod != null) {
+      return eligibleLibraryMethod.getReference();
+    }
+
+    DexType newHolder =
+        resolvedMethod.getHolder().isInterface()
+            ? firstLibraryClassForInterfaceTarget(
+                appView,
+                resolvedMethod.getReference(),
+                original.getHolderType(),
+                DexClass::lookupMethod)
+            : firstLibraryClass(appView, original.getHolderType());
+    return newHolder != null ? original.withHolder(newHolder, appView.dexItemFactory()) : original;
+  }
+
+  private boolean canRebindDirectlyToLibraryMethod(
+      DexClassAndMethod resolvedMethod,
+      SingleResolutionResult resolutionResult,
+      ProgramMethodSet contexts,
+      Type invokeType) {
+    // TODO(b/194422791): It could potentially be that `original.holder` is not a subtype of
+    //  `original.holder` on all API levels, in which case it is not OK to rebind to the resolved
+    //  method.
+    return resolvedMethod.isLibraryMethod()
+        && isAccessibleInAllContexts(resolvedMethod, resolutionResult, contexts)
+        && !isInvokeSuperToInterfaceMethod(resolvedMethod, invokeType)
+        && isPresentSinceMinApi(resolvedMethod.asLibraryMethod());
+  }
+
+  private boolean isAccessibleInAllContexts(
+      DexClassAndMethod resolvedMethod,
+      SingleResolutionResult resolutionResult,
+      ProgramMethodSet contexts) {
+    if (resolvedMethod.getHolder().isPublic() && resolvedMethod.getDefinition().isPublic()) {
+      return true;
+    }
+    return Iterables.all(
+        contexts,
+        context -> resolutionResult.isAccessibleFrom(context, appView.appInfo()).isTrue());
+  }
+
+  private boolean isInvokeSuperToInterfaceMethod(DexClassAndMethod method, Type invokeType) {
+    return method.getHolder().isInterface() && invokeType.isSuper();
+  }
+
+  private boolean isPresentSinceMinApi(LibraryMethod method) {
+    AndroidApiLevel apiLevel =
+        androidApiLevelCompute.computeApiLevelForLibraryReference(method.getReference());
+    return apiLevel.isLessThanOrEqualTo(options.minApiLevel);
   }
 
   public static DexField validMemberRebindingTargetFor(
       DexDefinitionSupplier definitions, DexClassAndField field, DexField original) {
-    DexClass clazz = field.getHolder();
     if (field.isProgramField()) {
       return field.getReference();
     }
+    DexClass fieldHolder = field.getHolder();
     DexType newHolder =
-        clazz.isInterface()
+        fieldHolder.isInterface()
             ? firstLibraryClassForInterfaceTarget(
                 definitions, field.getReference(), original.getHolderType(), DexClass::lookupField)
-            : firstLibraryClass(definitions, field.getHolderType(), original.getHolderType());
+            : firstLibraryClass(definitions, original.getHolderType());
     return newHolder != null
-        ? field.getReference().withHolder(newHolder, definitions.dexItemFactory())
+        ? original.withHolder(newHolder, definitions.dexItemFactory())
         : original;
   }
 
@@ -111,7 +185,7 @@
         return clazz.isNotProgramClass() ? current : matchingSuper;
       }
     }
-    for (DexType iface : clazz.interfaces.values) {
+    for (DexType iface : clazz.getInterfaces()) {
       DexType matchingIface =
           firstLibraryClassForInterfaceTarget(definitions, target, iface, lookup);
       if (matchingIface != null) {
@@ -122,9 +196,7 @@
     return null;
   }
 
-  private static DexType firstLibraryClass(
-      DexDefinitionSupplier definitions, DexType top, DexType bottom) {
-    assert definitions.definitionFor(top).isNotProgramClass();
+  private static DexType firstLibraryClass(DexDefinitionSupplier definitions, DexType bottom) {
     DexClass searchClass = definitions.definitionFor(bottom);
     while (searchClass.isProgramClass()) {
       searchClass = definitions.definitionFor(searchClass.superType);
@@ -132,43 +204,42 @@
     return searchClass.type;
   }
 
-  private DexEncodedMethod classLookup(DexMethod method) {
-    return appView.appInfo().resolveMethodOnClass(method, method.holder).getSingleTarget();
+  private MethodResolutionResult resolveMethodOnClass(DexMethod method) {
+    return appView.appInfo().resolveMethodOnClass(method, method.holder);
   }
 
-  private DexEncodedMethod interfaceLookup(DexMethod method) {
-    return appView.appInfo().resolveMethodOnInterface(method.holder, method).getSingleTarget();
+  private MethodResolutionResult resolveMethodOnInterface(DexMethod method) {
+    return appView.appInfo().resolveMethodOnInterface(method.holder, method);
   }
 
-  private DexEncodedMethod anyLookup(DexMethod method) {
-    return appView.appInfo().unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
+  private MethodResolutionResult resolveMethod(DexMethod method) {
+    return appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
   }
 
   private void computeMethodRebinding(MethodAccessInfoCollection methodAccessInfoCollection) {
     // Virtual invokes are on classes, so use class resolution.
     computeMethodRebinding(
-        methodAccessInfoCollection::forEachVirtualInvoke, this::classLookup, Type.VIRTUAL);
+        methodAccessInfoCollection::forEachVirtualInvoke, this::resolveMethodOnClass, Type.VIRTUAL);
     // Interface invokes are always on interfaces, so use interface resolution.
     computeMethodRebinding(
-        methodAccessInfoCollection::forEachInterfaceInvoke, this::interfaceLookup, Type.INTERFACE);
+        methodAccessInfoCollection::forEachInterfaceInvoke,
+        this::resolveMethodOnInterface,
+        Type.INTERFACE);
     // Super invokes can be on both kinds, decide using the holder class.
     computeMethodRebinding(
-        methodAccessInfoCollection::forEachSuperInvoke, this::anyLookup, Type.SUPER);
-    // Direct invokes (private/constructor) can also be on both kinds.
-    computeMethodRebinding(
-        methodAccessInfoCollection::forEachDirectInvoke, this::anyLookup, Type.DIRECT);
+        methodAccessInfoCollection::forEachSuperInvoke, this::resolveMethod, Type.SUPER);
     // Likewise static invokes.
     computeMethodRebinding(
-        methodAccessInfoCollection::forEachStaticInvoke, this::anyLookup, Type.STATIC);
+        methodAccessInfoCollection::forEachStaticInvoke, this::resolveMethod, Type.STATIC);
   }
 
   private void computeMethodRebinding(
       BiForEachable<DexMethod, ProgramMethodSet> methodsWithContexts,
-      Function<DexMethod, DexEncodedMethod> lookupTarget,
+      Function<DexMethod, MethodResolutionResult> resolver,
       Type invokeType) {
-
-    Map<DexProgramClass, List<Pair<DexMethod, DexEncodedMethod>>> bridges = new IdentityHashMap<>();
-    TriConsumer<DexProgramClass, DexMethod, DexEncodedMethod> addBridge =
+    Map<DexProgramClass, List<Pair<DexMethod, DexClassAndMethod>>> bridges =
+        new IdentityHashMap<>();
+    TriConsumer<DexProgramClass, DexMethod, DexClassAndMethod> addBridge =
         (bridgeHolder, method, target) ->
             bridges
                 .computeIfAbsent(bridgeHolder, k -> new ArrayList<>())
@@ -176,48 +247,58 @@
 
     methodsWithContexts.forEach(
         (method, contexts) -> {
-          // We can safely ignore array types, as the corresponding methods are defined in a
-          // library.
-          if (!method.holder.isClassType()) {
+          MethodResolutionResult resolutionResult = resolver.apply(method);
+          if (!resolutionResult.isSingleResolution()) {
             return;
           }
-          DexClass originalClass = appView.definitionFor(method.holder);
-          if (originalClass == null || originalClass.isNotProgramClass()) {
-            return;
-          }
-          DexEncodedMethod target = lookupTarget.apply(method);
+
           // TODO(b/128404854) Rebind to the lowest library class or program class. For now we allow
           //  searching in library for methods, but this should be done on classpath instead.
-          if (target == null || target.getReference() == method) {
+          DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
+          if (resolvedMethod.getReference() == method) {
             return;
           }
-          DexClass targetClass = appView.definitionFor(target.getHolderType());
-          DexMethod targetMethod = target.getReference();
-          if (originalClass.isProgramClass()) {
+
+          DexClass initialResolutionHolder = resolutionResult.getInitialResolutionHolder();
+          DexMethod bridgeMethod = null;
+          if (initialResolutionHolder.isProgramClass()) {
             // In Java bytecode, it is only possible to target interface methods that are in one of
             // the immediate super-interfaces via a super-invocation (see
             // IndirectSuperInterfaceTest).
             // To avoid introducing an IncompatibleClassChangeError at runtime we therefore insert a
             // bridge method when we are about to rebind to an interface method that is not the
             // original target.
-            if (needsBridgeForInterfaceMethod(originalClass, targetClass, invokeType)) {
-              targetMethod =
+            if (needsBridgeForInterfaceMethod(
+                initialResolutionHolder, resolvedMethod, invokeType)) {
+              bridgeMethod =
                   insertBridgeForInterfaceMethod(
-                      method, target, originalClass.asProgramClass(), targetClass, addBridge);
-            }
-
-            // If the target class is not public but the targeted method is, we might run into
-            // visibility problems when rebinding.
-            final DexEncodedMethod finalTarget = target;
-            if (contexts.stream()
-                .anyMatch(context -> mayNeedBridgeForVisibility(context, finalTarget))) {
-              targetMethod =
-                  insertBridgeForVisibilityIfNeeded(
-                      method, target, originalClass, targetClass, addBridge);
+                      method, resolvedMethod, initialResolutionHolder.asProgramClass(), addBridge);
+            } else {
+              // If the target class is not public but the targeted method is, we might run into
+              // visibility problems when rebinding.
+              if (contexts.stream()
+                  .anyMatch(context -> mayNeedBridgeForVisibility(context, resolvedMethod))) {
+                bridgeMethod =
+                    insertBridgeForVisibilityIfNeeded(
+                        method, resolvedMethod, initialResolutionHolder, addBridge);
+              }
             }
           }
-          lensBuilder.map(
-              method, lens.lookupMethod(validTargetFor(targetMethod, method)), invokeType);
+          if (bridgeMethod != null) {
+            lensBuilder.map(method, bridgeMethod, invokeType);
+          } else if (resolvedMethod.isProgramMethod()) {
+            lensBuilder.map(method, resolvedMethod.getReference(), invokeType);
+          } else {
+            lensBuilder.map(
+                method,
+                validMemberRebindingTargetForNonProgramMethod(
+                    resolvedMethod,
+                    resolutionResult.asSingleResolution(),
+                    contexts,
+                    invokeType,
+                    method),
+                invokeType);
+          }
         });
 
     bridges.forEach(
@@ -225,35 +306,34 @@
           // Sorting the list of bridges within a class maintains a deterministic order of entries
           // in the method collection.
           targets.sort((p1, p2) -> p1.getFirst().compareTo(p2.getFirst()));
-          for (Pair<DexMethod, DexEncodedMethod> pair : targets) {
+          for (Pair<DexMethod, DexClassAndMethod> pair : targets) {
             DexMethod method = pair.getFirst();
-            DexEncodedMethod target = pair.getSecond();
+            DexClassAndMethod target = pair.getSecond();
             DexMethod bridgeMethod =
                 method.withHolder(bridgeHolder.getType(), appView.dexItemFactory());
             if (bridgeHolder.getMethodCollection().getMethod(bridgeMethod) == null) {
               DexEncodedMethod bridgeMethodDefinition =
-                  target.toForwardingMethod(bridgeHolder, appView);
+                  target.getDefinition().toForwardingMethod(bridgeHolder, appView);
               bridgeHolder.addMethod(bridgeMethodDefinition);
             }
-            assert lookupTarget.apply(method).getReference() == bridgeMethod;
+            assert resolver.apply(method).getResolvedMethod().getReference() == bridgeMethod;
           }
         });
   }
 
   private boolean needsBridgeForInterfaceMethod(
-      DexClass originalClass, DexClass targetClass, Type invokeType) {
+      DexClass originalClass, DexClassAndMethod method, Type invokeType) {
     return options.isGeneratingClassFiles()
         && invokeType == Type.SUPER
-        && targetClass != originalClass
-        && targetClass.accessFlags.isInterface();
+        && method.getHolder() != originalClass
+        && method.getHolder().isInterface();
   }
 
   private DexMethod insertBridgeForInterfaceMethod(
       DexMethod method,
-      DexEncodedMethod target,
+      DexClassAndMethod target,
       DexProgramClass originalClass,
-      DexClass targetClass,
-      TriConsumer<DexProgramClass, DexMethod, DexEncodedMethod> bridges) {
+      TriConsumer<DexProgramClass, DexMethod, DexClassAndMethod> bridges) {
     // If `targetClass` is a class, then insert the bridge method on the upper-most super class that
     // implements the interface. Otherwise, if it is an interface, then insert the bridge method
     // directly on the interface (because that interface must be the immediate super type, assuming
@@ -263,9 +343,9 @@
     // invoke-super instructions that hit indirect interface methods such that they always target
     // a method in an immediate super-interface, since this works on Art but not on the JVM.
     DexProgramClass bridgeHolder =
-        findHolderForInterfaceMethodBridge(originalClass, targetClass.type);
+        findHolderForInterfaceMethodBridge(originalClass, target.getHolderType());
     assert bridgeHolder != null;
-    assert bridgeHolder != targetClass;
+    assert bridgeHolder != target.getHolder();
     bridges.accept(bridgeHolder, method, target);
     return target.getReference().withHolder(bridgeHolder.getType(), appView.dexItemFactory());
   }
@@ -283,16 +363,18 @@
     return findHolderForInterfaceMethodBridge(superClass.asProgramClass(), iface);
   }
 
-  private boolean mayNeedBridgeForVisibility(ProgramMethod context, DexEncodedMethod method) {
+  private boolean mayNeedBridgeForVisibility(ProgramMethod context, DexClassAndMethod method) {
     DexType holderType = method.getHolderType();
     DexClass holder = appView.definitionFor(holderType);
     if (holder == null) {
       return false;
     }
     ConstraintWithTarget classVisibility =
-        ConstraintWithTarget.deriveConstraint(context, holderType, holder.accessFlags, appView);
+        ConstraintWithTarget.deriveConstraint(
+            context, holderType, holder.getAccessFlags(), appView);
     ConstraintWithTarget methodVisibility =
-        ConstraintWithTarget.deriveConstraint(context, holderType, method.accessFlags, appView);
+        ConstraintWithTarget.deriveConstraint(
+            context, holderType, method.getAccessFlags(), appView);
     // We may need bridge for visibility if the target class is not visible while the target method
     // is visible from the calling context.
     return classVisibility == ConstraintWithTarget.NEVER
@@ -301,19 +383,18 @@
 
   private DexMethod insertBridgeForVisibilityIfNeeded(
       DexMethod method,
-      DexEncodedMethod target,
+      DexClassAndMethod target,
       DexClass originalClass,
-      DexClass targetClass,
-      TriConsumer<DexProgramClass, DexMethod, DexEncodedMethod> bridges) {
+      TriConsumer<DexProgramClass, DexMethod, DexClassAndMethod> bridges) {
     // If the original class is public and this method is public, it might have been called
     // from anywhere, so we need a bridge. Likewise, if the original is in a different
     // package, we might need a bridge, too.
     String packageDescriptor =
         originalClass.accessFlags.isPublic() ? null : method.holder.getPackageDescriptor();
     if (packageDescriptor == null
-        || !packageDescriptor.equals(targetClass.type.getPackageDescriptor())) {
+        || !packageDescriptor.equals(target.getHolderType().getPackageDescriptor())) {
       DexProgramClass bridgeHolder =
-          findHolderForVisibilityBridge(originalClass, targetClass, packageDescriptor);
+          findHolderForVisibilityBridge(originalClass, target.getHolder(), packageDescriptor);
       assert bridgeHolder != null;
       bridges.accept(bridgeHolder, method, target);
       return target.getReference().withHolder(bridgeHolder.getType(), appView.dexItemFactory());
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 158a8de..45f4984 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -130,6 +130,7 @@
   }
 
   public void tearDownCodeScanner(
+      IRConverter converter,
       PostMethodProcessor.Builder postMethodProcessorBuilder,
       ExecutorService executorService,
       Timing timing)
@@ -152,6 +153,7 @@
     Map<Set<DexProgramClass>, DexMethodSignatureSet> interfaceDispatchOutsideProgram =
         new IdentityHashMap<>();
     populateParameterOptimizationInfo(
+        converter,
         immediateSubtypingInfo,
         stronglyConnectedProgramComponents,
         (stronglyConnectedProgramComponent, signature) -> {
@@ -189,6 +191,7 @@
    * optimization info.
    */
   private void populateParameterOptimizationInfo(
+      IRConverter converter,
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
       List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
       BiConsumer<Set<DexProgramClass>, DexMethodSignature> interfaceDispatchOutsideProgram,
@@ -209,7 +212,7 @@
             reprocessingCriteriaCollection,
             stronglyConnectedProgramComponents,
             interfaceDispatchOutsideProgram)
-        .populateOptimizationInfo(executorService, timing);
+        .populateOptimizationInfo(converter, executorService, timing);
     reprocessingCriteriaCollection = null;
     timing.end();
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 4d19a71..96e08b6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -305,7 +305,7 @@
     // If we already don't know anything about the parameters for the given type bounds, then don't
     // compute a method state.
     if (existingMethodStateForBounds.isUnknown()) {
-      return MethodState.unknown();
+      return MethodState.bottom();
     }
 
     ConcreteMonomorphicMethodStateOrUnknown methodStateForBounds =
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index 5470ed5..e050de8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -82,7 +83,8 @@
    * Computes an over-approximation of each parameter's value and type and stores the result in
    * {@link com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo}.
    */
-  void populateOptimizationInfo(ExecutorService executorService, Timing timing)
+  void populateOptimizationInfo(
+      IRConverter converter, ExecutorService executorService, Timing timing)
       throws ExecutionException {
     // TODO(b/190154391): Propagate argument information to handle virtual dispatch.
     // TODO(b/190154391): To deal with arguments that are themselves passed as arguments to invoke
@@ -98,7 +100,7 @@
 
     // Solve the parameter flow constraints.
     timing.begin("Solve flow constraints");
-    new InParameterFlowPropagator(appView, methodStates).run(executorService);
+    new InParameterFlowPropagator(appView, converter, methodStates).run(executorService);
     timing.end();
 
     // The information stored on each method is now sound, and can be used as optimization info.
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
index 9b583c7..9b10015 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InParameterFlowPropagator.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
@@ -42,11 +43,15 @@
 public class InParameterFlowPropagator {
 
   final AppView<AppInfoWithLiveness> appView;
+  final IRConverter converter;
   final MethodStateCollectionByReference methodStates;
 
   public InParameterFlowPropagator(
-      AppView<AppInfoWithLiveness> appView, MethodStateCollectionByReference methodStates) {
+      AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      MethodStateCollectionByReference methodStates) {
     this.appView = appView;
+    this.converter = converter;
     this.methodStates = methodStates;
   }
 
@@ -206,6 +211,16 @@
       ParameterNode node = getOrCreateParameterNode(method, parameterIndex, methodState);
       for (MethodParameter inParameter : concreteParameterState.getInParameters()) {
         ProgramMethod enclosingMethod = getEnclosingMethod(inParameter);
+        if (enclosingMethod == null) {
+          // This is a parameter of a single caller inlined method. Since this method has been
+          // pruned, the call from inside the method no longer exists, and we can therefore safely
+          // skip it.
+          assert converter
+              .getInliner()
+              .verifyIsPrunedDueToSingleCallerInlining(inParameter.getMethod());
+          continue;
+        }
+
         MethodState enclosingMethodState = getMethodState(enclosingMethod);
         if (enclosingMethodState.isBottom()) {
           // The current method is called from a dead method; no need to propagate any information
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 b0f3ea9..61c9077 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -355,7 +355,7 @@
         pruneMethods(previous.liveMethods, prunedItems, executorService, futures),
         previous.fieldAccessInfoCollection,
         previous.methodAccessInfoCollection,
-        previous.objectAllocationInfoCollection,
+        previous.objectAllocationInfoCollection.withoutPrunedItems(prunedItems),
         previous.callSites,
         extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()),
         previous.mayHaveSideEffects,
@@ -439,6 +439,7 @@
   private static <T> Set<T> pruneItems(
       Set<T> items, Set<T> removedItems, ExecutorService executorService, List<Future<?>> futures) {
     if (!removedItems.isEmpty()) {
+
       futures.add(
           ThreadUtils.processAsynchronously(
               () -> {
@@ -796,6 +797,10 @@
     return neverInlineDueToSingleCaller.contains(method.getReference());
   }
 
+  public boolean isAssumeMethod(DexClassAndMethod method) {
+    return isAssumeNoSideEffectsMethod(method) || isAssumeValuesMethod(method);
+  }
+
   public boolean isAssumeNoSideEffectsMethod(DexMethod method) {
     return noSideEffects.containsKey(method);
   }
@@ -804,6 +809,14 @@
     return isAssumeNoSideEffectsMethod(method.getReference());
   }
 
+  public boolean isAssumeValuesMethod(DexMethod method) {
+    return assumedValues.containsKey(method);
+  }
+
+  public boolean isAssumeValuesMethod(DexClassAndMethod method) {
+    return isAssumeValuesMethod(method.getReference());
+  }
+
   public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
     return whyAreYouNotInlining.contains(method);
   }
@@ -1089,7 +1102,7 @@
 
   public boolean mayPropagateValueFor(DexField field) {
     assert checkIfObsolete();
-    if (!options().enableValuePropagation || neverPropagateValue.contains(field)) {
+    if (neverPropagateValue.contains(field)) {
       return false;
     }
     if (isPinned(field) && !field.getType().isAlwaysNull(this)) {
@@ -1100,7 +1113,7 @@
 
   public boolean mayPropagateValueFor(DexMethod method) {
     assert checkIfObsolete();
-    if (!options().enableValuePropagation || neverPropagateValue.contains(method)) {
+    if (neverPropagateValue.contains(method)) {
       return false;
     }
     if (!method.getReturnType().isAlwaysNull(this)
@@ -1271,10 +1284,14 @@
         lens.rewriteCallSites(callSites, definitionSupplier),
         keepInfo.rewrite(definitionSupplier, lens, application.options),
         // Take any rule in case of collisions.
-        lens.rewriteReferenceKeys(mayHaveSideEffects, ListUtils::first),
-        // Drop assume rules in case of collisions.
-        lens.rewriteReferenceKeys(noSideEffects, rules -> null),
-        lens.rewriteReferenceKeys(assumedValues, rules -> null),
+        lens.rewriteReferenceKeys(mayHaveSideEffects, (reference, rules) -> ListUtils.first(rules)),
+        // Take the assume rule from the representative in case of collisions.
+        lens.rewriteReferenceKeys(
+            noSideEffects,
+            (reference, rules) -> noSideEffects.get(lens.getOriginalMemberSignature(reference))),
+        lens.rewriteReferenceKeys(
+            assumedValues,
+            (reference, rules) -> assumedValues.get(lens.getOriginalMemberSignature(reference))),
         lens.rewriteReferences(alwaysInline),
         lens.rewriteReferences(neverInline),
         lens.rewriteReferences(neverInlineDueToSingleCaller),
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 fb814c6..fc81e6c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -129,7 +129,6 @@
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
 import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
 import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.OptionalBool;
@@ -3726,11 +3725,11 @@
                 : missingClassesBuilder.assertNoMissingClasses(appView),
             SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
             Enqueuer.toDescriptorSet(targetedMethods.getItems()),
-            Collections.unmodifiableSet(failedMethodResolutionTargets),
-            Collections.unmodifiableSet(failedFieldResolutionTargets),
-            Collections.unmodifiableSet(bootstrapMethods),
-            Collections.unmodifiableSet(methodsTargetedByInvokeDynamic),
-            Collections.unmodifiableSet(virtualMethodsTargetedByInvokeDirect),
+            failedMethodResolutionTargets,
+            failedFieldResolutionTargets,
+            bootstrapMethods,
+            methodsTargetedByInvokeDynamic,
+            virtualMethodsTargetedByInvokeDirect,
             toDescriptorSet(liveMethods.getItems()),
             // Filter out library fields and pinned fields, because these are read by default.
             fieldAccessInfoCollection,
@@ -3772,16 +3771,15 @@
     if (methods.isEmpty() || interfaceProcessor == null) {
       return methods;
     }
-    BooleanBox changed = new BooleanBox(false);
-    ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+    Set<DexMethod> companionMethods = Sets.newIdentityHashSet();
     interfaceProcessor.forEachMethodToMove(
         (method, companion) -> {
           if (methods.contains(method)) {
-            changed.set(true);
-            builder.add(companion);
+            companionMethods.add(companion);
           }
         });
-    return changed.isTrue() ? builder.addAll(methods).build() : methods;
+    methods.addAll(companionMethods);
+    return methods;
   }
 
   private boolean verifyReferences(DexApplication app) {
@@ -3850,11 +3848,11 @@
 
   private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
       Set<R> toDescriptorSet(Set<D> set) {
-    ImmutableSet.Builder<R> builder = new ImmutableSet.Builder<>();
+    Set<R> result = Sets.newIdentityHashSet();
     for (D item : set) {
-      builder.add(item.getReference());
+      result.add(item.getReference());
     }
-    return builder.build();
+    return result;
   }
 
   private static Object2BooleanMap<DexMember<?, ?>> joinIdentifierNameStrings(
@@ -4625,7 +4623,7 @@
         continue;
       }
 
-      DexProgramClass clazz = asProgramClassOrNull(definitionFor(type, method));
+      DexProgramClass clazz = getProgramClassOrNullFromReflectiveAccess(type, method);
       if (clazz != null && clazz.isInterface()) {
         KeepReason reason = KeepReason.reflectiveUseIn(method);
         markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
diff --git a/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
index ec1594a..8062beb 100644
--- a/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/MinimumKeepInfoCollection.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.shaking.KeepInfo.Joiner;
 import com.android.tools.r8.utils.MapUtils;
 import java.util.Collections;
@@ -131,6 +132,10 @@
         });
   }
 
+  public void pruneItems(PrunedItems prunedItems) {
+    minimumKeepInfo.keySet().removeIf(prunedItems::isRemoved);
+  }
+
   public KeepClassInfo.Joiner remove(DexType clazz) {
     return (KeepClassInfo.Joiner) minimumKeepInfo.remove(clazz);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index b105a80..03cff10 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1740,6 +1740,17 @@
           });
     }
 
+    public void pruneItems(PrunedItems prunedItems) {
+      MinimumKeepInfoCollection unconditionalMinimumKeepInfo =
+          getDependentMinimumKeepInfo().getUnconditionalMinimumKeepInfoOrDefault(null);
+      if (unconditionalMinimumKeepInfo != null) {
+        unconditionalMinimumKeepInfo.pruneItems(prunedItems);
+        if (unconditionalMinimumKeepInfo.isEmpty()) {
+          getDependentMinimumKeepInfo().remove(UnconditionalKeepInfoEvent.get());
+        }
+      }
+    }
+
     void shouldNotBeMinified(ProgramDefinition definition) {
       getDependentMinimumKeepInfo()
           .getOrCreateUnconditionalMinimumKeepInfoFor(definition.getReference())
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index f837095..db5dcdc 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
@@ -296,15 +297,16 @@
         firstUnreachableIndex(methods, method -> appInfo.isLiveMethod(method.getReference()));
     // Return the original array if all methods are used.
     if (firstUnreachable == -1) {
+      for (DexEncodedMethod method : methods) {
+        canonicalizeCode(method.asProgramMethod(clazz));
+      }
       return null;
     }
     ArrayList<DexEncodedMethod> reachableMethods = new ArrayList<>(methods.size());
-    for (int i = 0; i < firstUnreachable; i++) {
-      reachableMethods.add(methods.get(i));
-    }
-    for (int i = firstUnreachable; i < methods.size(); i++) {
+    for (int i = 0; i < methods.size(); i++) {
       DexEncodedMethod method = methods.get(i);
       if (appInfo.isLiveMethod(method.getReference())) {
+        canonicalizeCode(method.asProgramMethod(clazz));
         reachableMethods.add(method);
       } else if (options.configurationDebugging) {
         // Keep the method but rewrite its body, if it has one.
@@ -339,6 +341,12 @@
         : reachableMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
   }
 
+  private void canonicalizeCode(ProgramMethod method) {
+    if (method.getDefinition().hasCode()) {
+      DefaultInstanceInitializerCode.canonicalizeCodeIfPossible(appView, method);
+    }
+  }
+
   private DexEncodedField[] reachableFields(List<DexEncodedField> fields) {
     AppInfoWithLiveness appInfo = appView.appInfo();
     Predicate<DexEncodedField> isReachableOrReferencedField =
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index e157ff8..6d90250 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -15,6 +15,7 @@
 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.DefaultInstanceInitializerCode;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -1012,8 +1013,13 @@
         DexEncodedMethod shadowedBy = findMethodInTarget(virtualMethod);
         if (shadowedBy != null) {
           if (virtualMethod.isAbstract()) {
-            // Remove abstract/interface methods that are shadowed.
-            deferredRenamings.map(virtualMethod.getReference(), shadowedBy.getReference());
+            // Remove abstract/interface methods that are shadowed. The identity mapping below is
+            // needed to ensure we correctly fixup the mapping in case the signature refers to
+            // merged classes.
+            deferredRenamings
+                .map(virtualMethod.getReference(), shadowedBy.getReference())
+                .map(shadowedBy.getReference(), shadowedBy.getReference())
+                .recordMerge(virtualMethod.getReference(), shadowedBy.getReference());
 
             // The override now corresponds to the method in the parent, so unset its synthetic flag
             // if the method in the parent is not synthetic.
@@ -1119,6 +1125,12 @@
       // Rewrite generic signatures before we merge fields.
       rewriteGenericSignatures(target, source, directMethods.values(), virtualMethods.values());
 
+      // Convert out of DefaultInstanceInitializerCode, since this piece of code will require lens
+      // code rewriting.
+      target.forEachProgramInstanceInitializerMatching(
+          method -> method.getCode().isDefaultInstanceInitializerCode(),
+          method -> DefaultInstanceInitializerCode.uncanonicalizeCode(appView, method));
+
       // Step 2: Merge fields
       Set<DexString> existingFieldNames = new HashSet<>();
       for (DexEncodedField field : target.fields()) {
@@ -1891,6 +1903,8 @@
           return AbortReason.MAIN_DEX_ROOT_OUTSIDE_REFERENCE;
         }
         return null;
+      } else if (code.isDefaultInstanceInitializerCode()) {
+        return null;
       }
       // For non-jar/cf code we currently cannot guarantee that markForceInline() will succeed.
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
index ea9f971..21af6a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -16,9 +16,10 @@
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -74,7 +75,7 @@
       Set<DexMethod> mergedMethods,
       Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
           contextualVirtualToDirectMethodMaps,
-      BidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures,
+      BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> newMethodSignatures,
       Map<DexMethod, DexMethod> originalMethodSignaturesForBridges) {
     super(appView, fieldMap, methodMap, mergedClasses.getForwardMap(), newMethodSignatures);
     this.appView = appView;
@@ -164,8 +165,8 @@
     private final Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
         contextualVirtualToDirectMethodMaps = new IdentityHashMap<>();
 
-    private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
-        new BidirectionalOneToOneHashMap<>();
+    private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
+        newMethodSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
     private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
         new IdentityHashMap<>();
 
@@ -208,12 +209,17 @@
               context);
         }
       }
-      builder.newMethodSignatures.forEach(
-          (originalMethodSignature, renamedMethodSignature) ->
-              newBuilder.recordMove(
-                  originalMethodSignature,
-                  builder.getMethodSignatureAfterClassMerging(
-                      renamedMethodSignature, mergedClasses)));
+      builder.newMethodSignatures.forEachManyToOneMapping(
+          (originalMethodSignatures, renamedMethodSignature, representative) -> {
+            DexMethod methodSignatureAfterClassMerging =
+                builder.getMethodSignatureAfterClassMerging(renamedMethodSignature, mergedClasses);
+            newBuilder.newMethodSignatures.put(
+                originalMethodSignatures, methodSignatureAfterClassMerging);
+            if (originalMethodSignatures.size() > 1) {
+              newBuilder.newMethodSignatures.setRepresentative(
+                  methodSignatureAfterClassMerging, representative);
+            }
+          });
       for (Map.Entry<DexMethod, DexMethod> entry :
           builder.originalMethodSignaturesForBridges.entrySet()) {
         newBuilder.recordCreationOfBridgeMethod(
@@ -317,6 +323,12 @@
       return this;
     }
 
+    public void recordMerge(DexMethod from, DexMethod to) {
+      newMethodSignatures.put(from, to);
+      newMethodSignatures.put(to, to);
+      newMethodSignatures.setRepresentative(to, to);
+    }
+
     public void recordMove(DexMethod from, DexMethod to) {
       newMethodSignatures.put(from, to);
     }
@@ -336,7 +348,38 @@
       fieldMap.putAll(builder.fieldMap);
       methodMap.putAll(builder.methodMap);
       mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
-      newMethodSignatures.putAll(builder.newMethodSignatures);
+      builder.newMethodSignatures.forEachManyToOneMapping(
+          (keys, value, representative) -> {
+            boolean isRemapping =
+                Iterables.any(keys, key -> newMethodSignatures.containsValue(key) && key != value);
+            if (isRemapping) {
+              // If I and J are merged into A and both I.m() and J.m() exists, then we may map J.m()
+              // to I.m() as a result of merging J into A, and then subsequently merge I.m() to
+              // A.m() as a result of merging I into A.
+              assert keys.size() == 1;
+              DexMethod key = keys.iterator().next();
+
+              // When merging J.m() to I.m() we create the mappings {I.m(), J.m()} -> I.m().
+              DexMethod originalRepresentative = newMethodSignatures.getRepresentativeKey(key);
+              Set<DexMethod> originalKeys = newMethodSignatures.removeValue(key);
+              assert originalKeys.contains(key);
+
+              // Now that I.m() is merged to A.m(), we modify the existing mappings into
+              // {I.m(), J.m()} -> A.m().
+              newMethodSignatures.put(originalKeys, value);
+              newMethodSignatures.setRepresentative(value, originalRepresentative);
+            } else {
+              if (newMethodSignatures.containsValue(value)
+                  && !newMethodSignatures.hasExplicitRepresentativeKey(value)) {
+                newMethodSignatures.setRepresentative(
+                    value, newMethodSignatures.getRepresentativeKey(value));
+              }
+              newMethodSignatures.put(keys, value);
+              if (keys.size() > 1 && !newMethodSignatures.hasExplicitRepresentativeKey(value)) {
+                newMethodSignatures.setRepresentative(value, representative);
+              }
+            }
+          });
       originalMethodSignaturesForBridges.putAll(builder.originalMethodSignaturesForBridges);
       for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
         Map<DexMethod, GraphLensLookupResultProvider> current =
diff --git a/src/main/java/com/android/tools/r8/utils/Int2StructuralItemArrayMap.java b/src/main/java/com/android/tools/r8/utils/Int2StructuralItemArrayMap.java
new file mode 100644
index 0000000..9ed1e4f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/Int2StructuralItemArrayMap.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2021, 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.utils;
+
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Ints;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+
+public class Int2StructuralItemArrayMap<T extends StructuralItem<T>>
+    implements StructuralItem<Int2StructuralItemArrayMap<T>> {
+
+  private final int[] keys;
+  private final List<T> values;
+
+  private Int2StructuralItemArrayMap(int[] keys, List<T> values) {
+    this.keys = keys;
+    this.values = values;
+    assert keys.length == values.size();
+  }
+
+  @Override
+  public Int2StructuralItemArrayMap<T> self() {
+    return this;
+  }
+
+  @Override
+  public StructuralMapping<Int2StructuralItemArrayMap<T>> getStructuralMapping() {
+    return Int2StructuralItemArrayMap::specify;
+  }
+
+  private static <T extends StructuralItem<T>> void specify(
+      StructuralSpecification<Int2StructuralItemArrayMap<T>, ?> spec) {
+    spec.withIntArray(p -> p.keys).withItemCollection(p -> p.values);
+  }
+
+  public T lookup(int key) {
+    for (int i = 0; i < keys.length; i++) {
+      if (keys[i] == key) {
+        return values.get(i);
+      }
+    }
+    return null;
+  }
+
+  public void forEach(BiConsumer<Integer, T> visitor) {
+    for (int i = 0; i < keys.length; i++) {
+      visitor.accept(keys[i], values.get(i));
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public boolean equals(Object other) {
+    return other instanceof Int2StructuralItemArrayMap
+        && compareTo((Int2StructuralItemArrayMap<T>) other) == 0;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(Arrays.hashCode(keys), values);
+  }
+
+  public static <T extends StructuralItem<T>> Builder<T> builder() {
+    return new Builder<T>();
+  }
+
+  public boolean isEmpty() {
+    return keys.length == 0;
+  }
+
+  public static class Builder<T extends StructuralItem<T>> {
+
+    private final List<Integer> keys = new ArrayList<>();
+    private final ImmutableList.Builder<T> values = ImmutableList.builder();
+
+    private Builder() {}
+
+    public Builder<T> put(int key, T value) {
+      keys.add(key);
+      values.add(value);
+      return this;
+    }
+
+    public Int2StructuralItemArrayMap<T> build() {
+      return new Int2StructuralItemArrayMap<T>(Ints.toArray(keys), values.build());
+    }
+  }
+}
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 7da1e8e..759381b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -70,7 +70,7 @@
 import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
 import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering;
 import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Equivalence.Wrapper;
@@ -239,7 +239,6 @@
     enableEnumUnboxing = false;
     outline.enabled = false;
     enableEnumValueOptimization = false;
-    enableValuePropagation = false;
     enableSideEffectAnalysis = false;
     enableTreeShakingOfLibraryMethodOverrides = false;
     enableInitializedClassesAnalysis = false;
@@ -267,7 +266,6 @@
   public boolean libraryInterfacesMayHaveStaticInitialization = false;
 
   // Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
-  public boolean enableFieldAssignmentTracker = true;
   public boolean enableFieldBitAccessAnalysis =
       System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
   public boolean enableVerticalClassMerging = true;
@@ -317,7 +315,6 @@
   public final OutlineOptions outline = new OutlineOptions();
   public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
   public boolean enableRedundantFieldLoadElimination = true;
-  public boolean enableValuePropagation = true;
   // Currently disabled, see b/146957343.
   public boolean enableUninstantiatedTypeOptimizationForInterfaces = false;
   // TODO(b/138917494): Disable until we have numbers on potential performance penalties.
@@ -1207,6 +1204,7 @@
     public int minSize = 3;
     public int maxSize = 99;
     public int threshold = 20;
+    public int maxNumberOfInstructionsToBeConsidered = 100;
   }
 
   public static class KotlinOptimizationOptions {
@@ -1606,7 +1604,7 @@
     public BiConsumer<DexItemFactory, VerticallyMergedClasses> verticallyMergedClassesConsumer =
         ConsumerUtils.emptyBiConsumer();
 
-    public Consumer<Deque<SortedProgramMethodSet>> waveModifier = waves -> {};
+    public Consumer<Deque<ProgramMethodSet>> waveModifier = waves -> {};
 
     /**
      * If this flag is enabled, we will also compute the set of possible targets for invoke-
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 2a4db34..16af327 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -39,12 +39,18 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder;
+import com.android.tools.r8.ir.code.Position.OutlinePosition;
+import com.android.tools.r8.ir.code.Position.PositionBuilder;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNaming;
 import com.android.tools.r8.naming.ClassNaming.Builder;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
+import com.android.tools.r8.naming.MapVersion;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -53,6 +59,8 @@
 import com.android.tools.r8.naming.mappinginformation.CompilerSynthesizedMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.OutlineMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.RemoveInnerFramesAction;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition;
@@ -61,6 +69,10 @@
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.base.Suppliers;
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -84,6 +96,8 @@
 
     @Override
     public Pair<Position, Position> createRemappedPosition(Position position) {
+      // If we create outline calls we have to map them.
+      assert position.getOutlineCallee() == null;
       return new Pair<>(position, position);
     }
   }
@@ -103,12 +117,12 @@
 
     @Override
     public Pair<Position, Position> createRemappedPosition(Position position) {
-      assert position.method != null;
-      if (previousMethod == position.method) {
+      assert position.getMethod() != null;
+      if (previousMethod == position.getMethod()) {
         assert previousSourceLine >= 0;
-        if (position.line > previousSourceLine
-            && position.line - previousSourceLine <= maxLineDelta) {
-          nextOptimizedLineNumber += (position.line - previousSourceLine) - 1;
+        if (position.getLine() > previousSourceLine
+            && position.getLine() - previousSourceLine <= maxLineDelta) {
+          nextOptimizedLineNumber += (position.getLine() - previousSourceLine) - 1;
         }
       }
 
@@ -118,8 +132,8 @@
               .setLine(nextOptimizedLineNumber++)
               .setCallerPosition(null)
               .build();
-      previousSourceLine = position.line;
-      previousMethod = position.method;
+      previousSourceLine = position.getLine();
+      previousMethod = position.getMethod();
       return new Pair<>(position, newPosition);
     }
   }
@@ -149,8 +163,8 @@
     @Override
     public Pair<Position, Position> createRemappedPosition(Position position) {
       assert currentMethod != null;
-      int line = position.line;
-      Result parsedData = getAndParseSourceDebugExtension(position.method.holder);
+      int line = position.getLine();
+      Result parsedData = getAndParseSourceDebugExtension(position.getMethod().holder);
       if (parsedData == null) {
         return baseRemapper.createRemappedPosition(position);
       }
@@ -183,7 +197,7 @@
                 factory.createString(methodName),
                 factory.createString(returnTypeDescriptor),
                 argumentDexStringDescriptors);
-        if (!inlinee.equals(position.method)) {
+        if (!inlinee.equals(position.getMethod())) {
           // We have an inline from a different method than the current position.
           Entry<Integer, KotlinSourceDebugExtensionParser.Position> calleePosition =
               parsedData.lookupCalleePosition(line);
@@ -196,7 +210,7 @@
                     .build();
           }
           return baseRemapper.createRemappedPosition(
-              Position.builder()
+              SourcePosition.builder()
                   .setLine(originalInlineeLine)
                   .setMethod(inlinee)
                   .setCallerPosition(position)
@@ -256,9 +270,8 @@
 
     private void emitPositionEvents(int currentPc, Position currentPosition) {
       if (previousPosition == null) {
-        startLine = currentPosition.line;
-        previousPosition =
-            Position.builder().setLine(startLine).setFile(null).setMethod(method).build();
+        startLine = currentPosition.getLine();
+        previousPosition = SourcePosition.builder().setLine(startLine).setMethod(method).build();
       }
       DexDebugEventBuilder.emitAdvancementEvents(
           previousPc,
@@ -280,17 +293,34 @@
 
   // We will be remapping positional debug events and collect them as MappedPositions.
   private static class MappedPosition {
+
     private final DexMethod method;
     private final int originalLine;
     private final Position caller;
     private final int obfuscatedLine;
+    private final boolean isOutline;
+    private final DexMethod outlineCallee;
+    private final Int2StructuralItemArrayMap<Position> outlinePositions;
 
     private MappedPosition(
-        DexMethod method, int originalLine, Position caller, int obfuscatedLine) {
+        DexMethod method,
+        int originalLine,
+        Position caller,
+        int obfuscatedLine,
+        boolean isOutline,
+        DexMethod outlineCallee,
+        Int2StructuralItemArrayMap<Position> outlinePositions) {
       this.method = method;
       this.originalLine = originalLine;
       this.caller = caller;
       this.obfuscatedLine = obfuscatedLine;
+      this.isOutline = isOutline;
+      this.outlineCallee = outlineCallee;
+      this.outlinePositions = outlinePositions;
+    }
+
+    public boolean isOutlineCaller() {
+      return outlineCallee != null;
     }
   }
 
@@ -300,6 +330,9 @@
     // We create it here to ensure it is only reading class files once.
     CfLineToMethodMapper cfLineToMethodMapper = new CfLineToMethodMapper(inputApp);
     ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
+
+    Map<DexMethod, OutlineFixupBuilder> outlinesToFix = new IdentityHashMap<>();
+
     // Collect which files contain which classes that need to have their line numbers optimized.
     for (DexProgramClass clazz : application.classes()) {
       boolean isSyntheticClass = appView.getSyntheticItems().isSyntheticClass(clazz);
@@ -330,9 +363,9 @@
         }
       }
 
-      if (isSyntheticClass
-          && CompilerSynthesizedMappingInformation.isSupported(
-              appView.options().getMapFileVersion())) {
+      MapVersion mapFileVersion = appView.options().getMapFileVersion();
+
+      if (isSyntheticClass && CompilerSynthesizedMappingInformation.isSupported(mapFileVersion)) {
         onDemandClassNamingBuilder
             .get()
             .addMappingInformation(
@@ -382,9 +415,11 @@
           kotlinRemapper.currentMethod = method;
           List<MappedPosition> mappedPositions;
           Code code = method.getCode();
+          boolean canUseDexPc =
+              appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1;
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
-              if (appView.options().canUseDexPcAsDebugInformation() && methods.size() == 1) {
+              if (canUseDexPc) {
                 mappedPositions = optimizeDexCodePositionsForPc(method, appView, kotlinRemapper);
               } else {
                 mappedPositions =
@@ -412,8 +447,7 @@
 
           List<MappingInformation> methodMappingInfo = new ArrayList<>();
           if (method.isD8R8Synthesized()
-              && CompilerSynthesizedMappingInformation.isSupported(
-                  appView.options().getMapFileVersion())) {
+              && CompilerSynthesizedMappingInformation.isSupported(mapFileVersion)) {
             methodMappingInfo.add(CompilerSynthesizedMappingInformation.builder().build());
           }
 
@@ -446,6 +480,18 @@
                   signatures.computeIfAbsent(
                       m, key -> MethodSignature.fromDexMethod(m, m.holder != clazz.getType()));
 
+          // Check if mapped position is an outline
+          if (mappedPositions.get(0).isOutline
+              && OutlineMappingInformation.isSupported(mapFileVersion)) {
+            outlinesToFix
+                .computeIfAbsent(
+                    mappedPositions.get(0).method, ignored -> new OutlineFixupBuilder())
+                .setMappedPositionsOutline(mappedPositions);
+            methodMappingInfo.add(OutlineMappingInformation.builder().build());
+          }
+
+          int outlineCallersCounter = 0;
+
           // Update memberNaming with the collected positions, merging multiple positions into a
           // single region whenever possible.
           for (int i = 0; i < mappedPositions.size(); /* updated in body */ ) {
@@ -454,37 +500,40 @@
             MappedPosition lastPosition = firstPosition;
             for (; j < mappedPositions.size(); j++) {
               // Break if this position cannot be merged with lastPosition.
-              MappedPosition mp = mappedPositions.get(j);
+              MappedPosition currentPosition = mappedPositions.get(j);
               // We allow for ranges being mapped to the same line but not to other ranges:
               //   1:10:void foo():42:42 -> a
               // is OK since retrace(a(:7)) = 42, however, the following is not OK:
               //   1:10:void foo():42:43 -> a
               // since retrace(a(:7)) = 49, which is not correct.
-              boolean isSingleLine = mp.originalLine == firstPosition.originalLine;
+              boolean isSingleLine = currentPosition.originalLine == firstPosition.originalLine;
               boolean differentDelta =
-                  mp.originalLine - lastPosition.originalLine
-                      != mp.obfuscatedLine - lastPosition.obfuscatedLine;
+                  currentPosition.originalLine - lastPosition.originalLine
+                      != currentPosition.obfuscatedLine - lastPosition.obfuscatedLine;
               boolean isMappingRangeToSingleLine =
                   firstPosition.obfuscatedLine != lastPosition.obfuscatedLine
                       && firstPosition.originalLine == lastPosition.originalLine;
-              // Note that mp.caller and lastPosition.class must be deep-compared since multiple
-              // inlining passes lose the canonical property of the positions.
-              if (mp.method != lastPosition.method
+              // Note that currentPosition.caller and lastPosition.class must be deep-compared since
+              // multiple inlining passes lose the canonical property of the positions.
+              if (currentPosition.method != lastPosition.method
                   || (!isSingleLine && differentDelta)
                   || (!isSingleLine && isMappingRangeToSingleLine)
-                  || !Objects.equals(mp.caller, lastPosition.caller)) {
+                  || !Objects.equals(currentPosition.caller, lastPosition.caller)
+                  // Break when we see a mapped outline
+                  || currentPosition.outlineCallee != null
+                  // Ensure that we break when we start iterating with an outline caller again.
+                  || firstPosition.outlineCallee != null) {
                 break;
               }
               // The mapped positions are not guaranteed to be in order, so maintain first and last
               // position.
-              if (firstPosition.obfuscatedLine > mp.obfuscatedLine) {
-                firstPosition = mp;
+              if (firstPosition.obfuscatedLine > currentPosition.obfuscatedLine) {
+                firstPosition = currentPosition;
               }
-              if (lastPosition.obfuscatedLine < mp.obfuscatedLine) {
-                lastPosition = mp;
+              if (lastPosition.obfuscatedLine < currentPosition.obfuscatedLine) {
+                lastPosition = currentPosition;
               }
             }
-            Range originalRange = new Range(firstPosition.originalLine, lastPosition.originalLine);
             Range obfuscatedRange;
             if (method.getCode().isDexCode()
                 && method.getCode().asDexCode().getDebugInfo()
@@ -497,37 +546,49 @@
             }
             ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.get();
             MappedRange lastMappedRange =
-                classNamingBuilder.addMappedRange(
+                getMappedRangesForPosition(
+                    appView.options().dexItemFactory(),
+                    getOriginalMethodSignature,
+                    classNamingBuilder,
+                    firstPosition.method,
+                    obfuscatedName,
                     obfuscatedRange,
-                    getOriginalMethodSignature.apply(firstPosition.method),
-                    originalRange,
-                    obfuscatedName);
-            Position caller = firstPosition.caller;
-            int inlineFramesCount = 0;
-            while (caller != null) {
-              inlineFramesCount += 1;
-              lastMappedRange =
-                  classNamingBuilder.addMappedRange(
-                      obfuscatedRange,
-                      getOriginalMethodSignature.apply(caller.method),
-                      new Range(Math.max(caller.line, 0)), // Prevent against "no-position".
-                      obfuscatedName);
-              if (caller.removeInnerFrameIfThrowingNpe) {
-                lastMappedRange.addMappingInformation(
-                    RewriteFrameMappingInformation.builder()
-                        .addCondition(
-                            ThrowsCondition.create(
-                                Reference.classFromDescriptor(
-                                    appView.options().dexItemFactory().npeDescriptor.toString())))
-                        .addRewriteAction(RemoveInnerFramesAction.create(inlineFramesCount))
-                        .build(),
-                    Unreachable::raise);
-              }
-              caller = caller.callerPosition;
-            }
+                    new Range(firstPosition.originalLine, lastPosition.originalLine),
+                    firstPosition.caller);
             for (MappingInformation info : methodMappingInfo) {
               lastMappedRange.addMappingInformation(info, Unreachable::raise);
             }
+            // firstPosition will contain a potential outline caller.
+            if (firstPosition.outlineCallee != null
+                && OutlineCallsiteMappingInformation.isSupported(mapFileVersion)) {
+              Int2IntMap positionMap = new Int2IntArrayMap();
+              int maxPc = ListUtils.last(mappedPositions).obfuscatedLine;
+              firstPosition.outlinePositions.forEach(
+                  (line, position) -> {
+                    int placeHolderLineToBeFixed;
+                    if (canUseDexPc) {
+                      placeHolderLineToBeFixed = maxPc + line + 1;
+                    } else {
+                      placeHolderLineToBeFixed =
+                          positionRemapper.createRemappedPosition(position).getSecond().getLine();
+                    }
+                    positionMap.put((int) line, placeHolderLineToBeFixed);
+                    getMappedRangesForPosition(
+                        appView.options().dexItemFactory(),
+                        getOriginalMethodSignature,
+                        classNamingBuilder,
+                        position.getMethod(),
+                        obfuscatedName,
+                        new Range(placeHolderLineToBeFixed, placeHolderLineToBeFixed),
+                        new Range(position.getLine(), position.getLine()),
+                        position.getCallerPosition());
+                  });
+              outlinesToFix
+                  .computeIfAbsent(
+                      firstPosition.outlineCallee, ignored -> new OutlineFixupBuilder())
+                  .addMappedRangeForOutlineCallee(lastMappedRange, positionMap);
+              outlineCallersCounter += 1;
+            }
             i = j;
           }
           if (method.getCode().isDexCode()
@@ -538,9 +599,52 @@
         } // for each method of the group
       } // for each method group, grouped by name
     } // for each class
+
+    // Fixup all outline positions
+    outlinesToFix.values().forEach(OutlineFixupBuilder::fixup);
+
     return classNameMapperBuilder.build();
   }
 
+  private static MappedRange getMappedRangesForPosition(
+      DexItemFactory factory,
+      Function<DexMethod, MethodSignature> getOriginalMethodSignature,
+      Builder classNamingBuilder,
+      DexMethod method,
+      String obfuscatedName,
+      Range obfuscatedRange,
+      Range originalLine,
+      Position caller) {
+    MappedRange lastMappedRange =
+        classNamingBuilder.addMappedRange(
+            obfuscatedRange,
+            getOriginalMethodSignature.apply(method),
+            originalLine,
+            obfuscatedName);
+    int inlineFramesCount = 0;
+    while (caller != null) {
+      inlineFramesCount += 1;
+      lastMappedRange =
+          classNamingBuilder.addMappedRange(
+              obfuscatedRange,
+              getOriginalMethodSignature.apply(caller.getMethod()),
+              new Range(Math.max(caller.getLine(), 0)), // Prevent against "no-position".
+              obfuscatedName);
+      if (caller.isRemoveInnerFramesIfThrowingNpe()) {
+        lastMappedRange.addMappingInformation(
+            RewriteFrameMappingInformation.builder()
+                .addCondition(
+                    ThrowsCondition.create(
+                        Reference.classFromDescriptor(factory.npeDescriptor.toString())))
+                .addRewriteAction(RemoveInnerFramesAction.create(inlineFramesCount))
+                .build(),
+            Unreachable::raise);
+      }
+      caller = caller.getCallerPosition();
+    }
+    return lastMappedRange;
+  }
+
   private static boolean verifyMethodsAreKeptDirectlyOrIndirectly(
       AppView<?> appView, List<DexEncodedMethod> methods) {
     if (appView.options().isGeneratingClassFiles() || !appView.appInfo().hasClassHierarchy()) {
@@ -597,7 +701,7 @@
         if (!(instruction instanceof CfPosition)) {
           continue;
         }
-        return ((CfPosition) instruction).getPosition().line;
+        return ((CfPosition) instruction).getPosition().getLine();
       }
     }
     return 0;
@@ -747,19 +851,14 @@
           public void visit(Default defaultEvent) {
             super.visit(defaultEvent);
             assert getCurrentLine() >= 0;
-            Position position =
-                Position.builder()
-                    .setLine(getCurrentLine())
-                    .setFile(getCurrentFile())
-                    .setMethod(getCurrentMethod())
-                    .setCallerPosition(getCurrentCallerPosition())
-                    .build();
+            Position position = getPositionFromPositionState(this);
             Position currentPosition = remapAndAdd(position, positionRemapper, mappedPositions);
             positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
             if (currentPosition != position) {
               inlinedOriginalPosition.set(true);
             }
             emittedPc = getCurrentPc();
+            resetOutlineInformation();
           }
 
           // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame.
@@ -810,7 +909,8 @@
     if (mappedPositions.size() <= 1
         && !hasOverloads
         && !appView.options().debug
-        && appView.options().lineNumberOptimization != LineNumberOptimization.OFF) {
+        && appView.options().lineNumberOptimization != LineNumberOptimization.OFF
+        && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
       dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
       return mappedPositions;
     }
@@ -829,6 +929,27 @@
     return mappedPositions;
   }
 
+  private static Position getPositionFromPositionState(DexDebugPositionState state) {
+    PositionBuilder<?, ?> positionBuilder;
+    if (state.getOutlineCallee() != null) {
+      OutlineCallerPositionBuilder outlineCallerPositionBuilder =
+          OutlineCallerPosition.builder()
+              .setOutlineCallee(state.getOutlineCallee())
+              .setIsOutline(state.isOutline());
+      state.getOutlineCallerPositions().forEach(outlineCallerPositionBuilder::addOutlinePosition);
+      positionBuilder = outlineCallerPositionBuilder;
+    } else if (state.isOutline()) {
+      positionBuilder = OutlinePosition.builder();
+    } else {
+      positionBuilder = SourcePosition.builder();
+    }
+    return positionBuilder
+        .setLine(state.getCurrentLine())
+        .setMethod(state.getCurrentMethod())
+        .setCallerPosition(state.getCurrentCallerPosition())
+        .build();
+  }
+
   private static List<MappedPosition> optimizeDexCodePositionsForPc(
       DexEncodedMethod method, AppView<?> appView, PositionRemapper positionRemapper) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
@@ -855,13 +976,8 @@
                   mappedPositions);
             }
             lastPosition.setFirst(getCurrentPc());
-            lastPosition.setSecond(
-                Position.builder()
-                    .setLine(getCurrentLine())
-                    .setFile(getCurrentFile())
-                    .setMethod(getCurrentMethod())
-                    .setCallerPosition(getCurrentCallerPosition())
-                    .build());
+            lastPosition.setSecond(getPositionFromPositionState(this));
+            resetOutlineInformation();
           }
         };
 
@@ -932,7 +1048,13 @@
     Position newPosition = remappedPosition.getSecond();
     mappedPositions.add(
         new MappedPosition(
-            oldPosition.method, oldPosition.line, oldPosition.callerPosition, newPosition.line));
+            oldPosition.getMethod(),
+            oldPosition.getLine(),
+            oldPosition.getCallerPosition(),
+            newPosition.getLine(),
+            oldPosition.isOutline(),
+            oldPosition.getOutlineCallee(),
+            oldPosition.getOutlinePositions()));
     return newPosition;
   }
 
@@ -945,9 +1067,66 @@
     Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
     Position oldPosition = remappedPosition.getFirst();
     for (int currentPc = startPc; currentPc < endPc; currentPc++) {
+      boolean firstEntry = currentPc == startPc;
       mappedPositions.add(
           new MappedPosition(
-              oldPosition.method, oldPosition.line, oldPosition.callerPosition, currentPc));
+              oldPosition.getMethod(),
+              oldPosition.getLine(),
+              oldPosition.getCallerPosition(),
+              currentPc,
+              // Outline info is placed exactly on the positions that relate to it so we should
+              // only emit it for the first entry.
+              firstEntry && oldPosition.isOutline(),
+              firstEntry ? oldPosition.getOutlineCallee() : null,
+              firstEntry ? oldPosition.getOutlinePositions() : null));
+    }
+  }
+
+  private static class OutlineFixupBuilder {
+
+    private static int MINIFIED_POSITION_REMOVED = -1;
+
+    private List<MappedPosition> mappedOutlinePositions = null;
+    private final List<Pair<MappedRange, Int2IntMap>> mappedOutlineCalleePositions =
+        new ArrayList<>();
+
+    public void setMappedPositionsOutline(List<MappedPosition> mappedPositionsOutline) {
+      this.mappedOutlinePositions = mappedPositionsOutline;
+    }
+
+    public void addMappedRangeForOutlineCallee(
+        MappedRange mappedRangeForOutline, Int2IntMap calleePositions) {
+      mappedOutlineCalleePositions.add(Pair.create(mappedRangeForOutline, calleePositions));
+    }
+
+    public void fixup() {
+      assert mappedOutlinePositions != null;
+      assert !mappedOutlineCalleePositions.isEmpty();
+      for (Pair<MappedRange, Int2IntMap> mappingInfo : mappedOutlineCalleePositions) {
+        MappedRange mappedRange = mappingInfo.getFirst();
+        Int2IntMap positions = mappingInfo.getSecond();
+        Int2IntSortedMap map = new Int2IntLinkedOpenHashMap();
+        positions.forEach(
+            (outlinePosition, calleePosition) -> {
+              int minifiedLinePosition =
+                  getMinifiedLinePosition(outlinePosition, mappedOutlinePositions);
+              if (minifiedLinePosition != MINIFIED_POSITION_REMOVED) {
+                map.put(minifiedLinePosition, (int) calleePosition);
+              }
+            });
+        mappedRange.addMappingInformation(
+            OutlineCallsiteMappingInformation.create(map), Unreachable::raise);
+      }
+    }
+
+    private int getMinifiedLinePosition(
+        int originalPosition, List<MappedPosition> mappedPositions) {
+      for (MappedPosition mappedPosition : mappedPositions) {
+        if (mappedPosition.originalLine == originalPosition) {
+          return mappedPosition.obfuscatedLine;
+        }
+      }
+      return MINIFIED_POSITION_REMOVED;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index a7305a2..624fb10 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -34,14 +34,22 @@
     return result;
   }
 
+  @SafeVarargs
+  public static <T> HashSet<T> newHashSet(T... elements) {
+    HashSet<T> result = new HashSet<>(elements.length);
+    Collections.addAll(result, elements);
+    return result;
+  }
+
   public static <T> Set<T> newIdentityHashSet(T element) {
     Set<T> result = Sets.newIdentityHashSet();
     result.add(element);
     return result;
   }
 
-  public static <T> Set<T> newIdentityHashSet(T[] elements) {
-    Set<T> result = Sets.newIdentityHashSet();
+  @SafeVarargs
+  public static <T> Set<T> newIdentityHashSet(T... elements) {
+    Set<T> result = newIdentityHashSet(elements.length);
     Collections.addAll(result, elements);
     return result;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
index bdd70c8..0887329 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneRepresentativeHashMap.java
@@ -81,6 +81,17 @@
   }
 
   @Override
+  public void putAll(BidirectionalManyToOneRepresentativeMap<K, V> map) {
+    map.forEachManyToOneMapping(
+        (keys, value, representative) -> {
+          put(keys, value);
+          if (keys.size() > 1) {
+            setRepresentative(value, representative);
+          }
+        });
+  }
+
+  @Override
   public V remove(K key) {
     V value = super.remove(key);
     if (hasExplicitRepresentativeKey(value)) {
diff --git a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneRepresentativeMap.java b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneRepresentativeMap.java
index 24f91ac..b80cc5b 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneRepresentativeMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/MutableBidirectionalManyToOneRepresentativeMap.java
@@ -8,6 +8,8 @@
 public interface MutableBidirectionalManyToOneRepresentativeMap<K, V>
     extends MutableBidirectionalManyToOneMap<K, V>, BidirectionalManyToOneRepresentativeMap<K, V> {
 
+  void putAll(BidirectionalManyToOneRepresentativeMap<K, V> map);
+
   K removeRepresentativeFor(V value);
 
   void setRepresentative(V value, K representative);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
index d36d207..12deaa2 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.ForEachable;
 import com.google.common.collect.ImmutableMap;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
@@ -45,6 +46,12 @@
     return result;
   }
 
+  public static ProgramMethodSet create(ForEachable<ProgramMethod> methods) {
+    ProgramMethodSet result = create();
+    methods.forEach(result::add);
+    return result;
+  }
+
   public static ProgramMethodSet create(ProgramMethodSet methodSet) {
     ProgramMethodSet newMethodSet = create();
     newMethodSet.addAll(methodSet);
diff --git a/src/main/resources/api_database/api_database_ambiguous.txt b/src/main/resources/api_database/api_database_ambiguous.txt
index 3ffb464..5c4f00c 100644
--- a/src/main/resources/api_database/api_database_ambiguous.txt
+++ b/src/main/resources/api_database/api_database_ambiguous.txt
@@ -1,4 +1,171 @@
+ebf47efadc4c50002b13da0b1cef0646:18
+106de6ca4d0e49988f3328ce380ff517:18
+2f4aedd17528c30f8a9f16b030ff708b:18
+93aca156e3f15799914f868852723e47:18
+17cbc97604a781acb78c668b2437ffbd:18
+1709449e191fe131236bd0b8c1928b71:18
+a6cf882fc575147513b7b1b505fdd335:18
+1587f690fd5c0c886630334e89e397ff:18
+dc6ad4bf6892204b9fca73d1ee9cab04:18
+9ae673cfbb5d90688edafbf7ec2862e4:18
+acf250fcdbf54725219af8c6a8a614d9:18
+07319a58381faa784dda046d5e8fd8ae:18
+77e978f6318d01be894de8944fdc05b4:18
+81be9a78d26d25c6ff47c7e9af30a92e:18
+e87bf9d0084b39c443e421cdb5d3fc43:18
+9e973ee7f4f5ad967d2ba5bf5d88911c:18
+b47e7bbf50ad208599056f8d0f1cc5ba:18
+47a9e25289b4ece230adcc3b60797712:18
+42e4cc1882d334757afbe74f4d8cec8e:18
+024481748991b858bf7e03b0b87ce125:18
+b6ced7e04b527d83cd660894a0059ea7:18
+717e536c3a13f3602fbaf1f607ee2963:18
+18b540e31006aad6ae7a26d967ec0e0e:18
+f1483fe126d64bd7544dba7fdf0559f4:18
+3c4a0d8270a52e43316a711a2fe83268:18
+2e13bb1bfba423473a6efba65d9c8190:18
+47a5d9010bdeb561176cf070ef483e91:18
+4f2e7d32ed2c7ead316770fbe84d8aae:18
+c625e1090fc4d72afd0673839af5d46f:18
+4cc2dc623ee702db7a5899c5c401a98c:18
+780c7e0f80b5479d00e2c3679974644d:18
+eff51a62305642c720d163085061ed63:18
+69e416f1afc250d6239788b4b1fb9bb4:18
+d71a32697989404ae782e105a5eb5e4c:18
+6d60d78c1ce897131dcf45743a8d6e49:18
+e16e8dcca650cf79557cb76a86edb4e9:18
+bfebe4871ed9c4b5f3300fe8dfa2a0a3:18
+93e215f230901f20bdca460cfe8c42af:18
+8efacada80287ebb7607758a0a82b0a0:18
+487bc1228fd817b641056a1a008984f8:18
+dc6c2915cd6f0a6fd0ab01365349599d:18
+1a3dbf72e4bd135a621b8ad78a7151c1:18
+01fd60d48ba7e1434bb72696282c7113:18
+83c42b475763d64935a9ddfc6a374b64:18
+ed9a26aaf7ddb748bd1e624aeb4fdb81:18
+eeb05b41976f7bb96572330854aaab34:18
+3c17437ba337e7ce8edad092f745a363:18
+8d8fd1ffc3ae24cb43798f4eb41fc4de:18
+003f7ab7088710994a289bab815aacbf:18
+488d943cdcedfca2b076152769fc3c51:18
+cab779407e29ba3377e6a47a4f842ec5:18
+a0ddcf0f7bb4aa9d38db983941f6908d:18
+b64b41d26010783cae2dff88c7e21f1d:18
+aac6d054ad0f9bea1f64702aea698ee3:18
+90b4163293f9e35f7091692b31d3b5d7:18
+9d66fceb1054d5e2ff207591ec216b70:18
+93a65566b576af220207e30cc89ef490:18
+b3248e6a2d819bd8bb42a15179188c52:18
+f95f0545feb040d2d6bfcad6897364d0:18
+058ec3044e95ed7fcaff93a2f3105d6f:18
+39ef3516cb80752cac1c541b43a1c2fa:18
+c1295d79d79340152c6b98f1c9003e8c:18
+c4b4a2994711cb342b0c8c8ef409ce8b:18
+719c3b73816fa0db79747ea264abab5a:18
+1d7847b88e0feaecd299b3c5c18be763:18
+1211e656a8eaa4423e7678533a1e7e2a:18
+e0c7417a74df21b55367f9e1ea6c2d37:18
+e49b0a2bd830bb3b96934490b2bc6ebe:18
+91790d7c5cf146ab3936919184a70015:18
+ec36ea4bb9634f7ec5c4d3c1cc9dd71b:18
+860e7b1a68c02eef7d2e2d7b6a3e13b5:18
+458fda8d9863be24862bb62c910f427d:18
+e05ed0452c12e62ef74e9161159b1231:18
+9419764761c059b9c2f9cf640abfdeb0:18
+ea02175c4ef64b6370f619acd84f3c3c:18
+0b01d40e1ca37aa854e77c98cc153e77:18
+eb12f3e690b201aa171d794a8288af10:18
+b955e5418d490425f99d58f441ba4525:18
+b36bec8fd49158c020b6d8c09cc02baf:26
+e7ca6d1abe4cd55a491cdf58eb8f4602:17
+d8c43560e159bcb899c2180b8e01d8e3:24
+12233f83b06081671815562dce938d3b:24
+ae9c7aee12dfaaa56e43834dcea91f5f:24
+106252e3f31ee17239e1ee4184147deb:24
+1fb2cd38813bad6e5eeef74bcbcb133d:24
+c5bbd4174eee5cb68830801317071dd5:24
+b876313caea388f03200ac720ed528af:24
+dc32c09ecdd9bda4703aa944cf1eb4ca:24
+f4fa53a97c83899fd2da9f4112f35339:24
+07a59dae4166f6d504bb0793c5e75178:24
+efcb5024ce3514453d44735222903a55:24
+43445dc9e50e2c4fc327097d07ff749a:24
+bcb1787dd2def454fd3f31d1b2c0967a:24
+2cc65559141cece33ffdbc4e34a2ce3b:24
+cace738770550444ee1f91000c32c147:24
+b55a09768f05f29b15e1ff3a9feb7c92:24
+54282406a85f4410d4dfc72d5873ee96:24
+1c38913c447705e315ec4486da558c9d:24
+ca3f30645fa7b480b213f4228386a09a:24
+6d8800c5cc8ad9e4f3f28e777207a870:24
+b8dc6f9b9587159f978d643aeb8e4d07:24
+91eb697571f68f5850875286c5e36080:24
+44c0d27dc28d4da28655c3c62a0d7cac:24
+ae908f7abd7d347b0bf39fead3531c12:24
+145e730e204fdca9cf169521a3812ed1:24
+d27ceaaa92270c450aeee582cc17b985:24
+294141fdc8f17ac3c7e4d8687d8ad389:24
+48de820418038280f70eca64f878c67b:24
+8569110aa82eb44ff0b12819867c66f8:24
+cfde9ea95c663bed6d56df8778b01b79:24
+f389ac23dc4ef6641321adf43d712c32:24
+46de27224f4cf6fb745e30bbd6aad267:24
+ef4661e0074b431762177e10c2ac8570:24
+75c79913d4df6b467a67c40ce67a32fc:24
+2a000aca1e17b3b0343ddf151ee313f4:24
+e9d59e0325c6dd1dd78a998365435fac:24
+0a1d88442fc34d0081115d949f1c5fa9:24
+0a7ef5f8b75fa2dd79787b03dff923c9:24
+b247285b58ca44aad532b1c3b5a5dacf:24
+63f61697ff1319a76632338fe0c13ae9:24
+e8df9edc7e29252645d2b8a6a55c4e0f:24
+aaba729f45b1617d59cec26aa41937e3:24
+7e7ae6228d4ab54b90f0f580731941d7:24
+de1c84862deb139962240505133690f0:24
+9dbabf8883b7a4e120fcbceb00face40:24
+9660da0a81f885de5a3b16f1f07a1067:24
+c3f5202b30c04801da693013368a0da2:24
+b73113e1f7c2702cfd997dd84e5ac003:24
+9b5946e54278b49302da6ccd2a3a06e1:24
+c7a5a5244ef8d55b160e29df7e310b76:24
+74fa889a011613ee983f45aed7dcb656:24
+ccb57b751fd0ed3f7244a0e096b19076:24
+dcd61dd6bd1e0fec6888029656162559:24
+c2fa9ac71ea25473f3c384e5d3c3770b:24
+43de03bf819ac106a9a66f3d7b473bd1:24
+3ed4b5f668130ecec1aadb21bf0a671a:24
+ba4d4fc3c411bf5b477323a1329574b3:24
+4e82fa0797beeab61a7a0b9b3ba7505e:24
+1cff05a0b4291e8f289a1794f016eab5:24
+456c3251cceb566a82d3175390b6a663:24
+11e854ed754739797dbae75ff3159e82:24
+2daef0ca0e135ad904bf7e931690cf2b:24
+76fd080fdaa80390ca05399f4d0b49ae:24
+d49bee1da6282ff1efd4017e2c26ff55:24
+94bf636e74064a1f45c5e81777a8b541:24
+67395fd629dae7b237421aa91bc12150:24
+b69dd3d2f26fc766f3b4193b089fa71e:24
+b502fdd440592e2d637704ebef9baf01:24
+5a13a7fac55b3378629744099623199b:24
+6c21462d7f8529e6d017e054b02d96d3:24
+ef1a937ed3ed1277a46cbbfb589bcc7c:24
+a9446b05afd48f274f6469e9617dadde:24
+03b1c2c2b9e04b66ace80c8d891dad71:24
+5af3ef227d419594dc0316392c0fd762:24
+b4146bc53ba79957adf0bf7306b2212c:24
+f1136465059f10586c4fb8379126e010:24
+8796edfd2200b66560e180ecf2c8bd3a:24
+a4fc9c89f980d489cd69b5a9a5379279:28
+beef3d82791567a66852903d9acde2d8:14
+8f8e727dba622a81127060f53efbaf60:14
+a58ec4e669821ad885c5e539bea8f8c0:14
+c183aebc8596eae7dbaf32f831d4bf1a:14
+579a40bd08705acc7099b631706917cd:14
+c979623104ac1df2ff0b56f2eb8f40a3:14
+4a406313d3d1fc7c56f2e4ae88033ef5:14
+0534bb586552faf59923fcbb66c5c9a8:14
 828623182530e5b5c55265b6a20263be:9
+027b76c362f10b646fce08dd34457533:29
 850665aac5116ea7c5dd3ed4d672cc68:1
 d853b59bd420a046a715750d924360d0:1
 1a9e947630aeed44da4b6e668e8ac214:1
@@ -74,258 +241,8 @@
 83255b102f7327ffe0819cc9d48d764a:1
 a159e58a81a5d7e46b1d44f845486c25:1
 c9a7fe526ed238e56f029bdd9eadd4a8:1
-a5352e6f5efd89293cad68bde5359174:21
-0873c171286bf72adca073b8232909ad:21
-75fcc63a7299ec405cbbbae6c6f4124a:21
-4242f75bd579b5860e448896bd97161e:21
-f8e3f0c3200fb1dd44acfbd301d96bed:21
-3e972f384811f77fd62ce384910c7605:21
-746752a0385785d6c4a6af907b506ff8:21
-f2c67cccefcb8826a54b39122e002062:21
-e40adee8247464101fcb01d083025c31:21
-d96183e4ed9b9b2b075bd5f8c4364100:21
-0a8ab6673c7b4e87cb2faf2d7005362a:21
-810a68c672694a924abe0f0c59a9a394:21
-fe284dc96dc744beee1ddbb1a8e4c4ee:21
-d34d323d00595f4051cfe0621331317d:21
-711a52bc9e3eafe73e0c64c4ae6d5e6a:21
-542566d5040f1a84a95caf3c78e8d54f:21
-fadc0a0d51e6ccdc60992c058edd2711:21
-ccb12040fbbda66de180c11e6251049d:21
-052dc8ce89610356ae4c22dacb2668f6:21
-eb911a19cc8e2139a72a6bf025f0ef92:21
-8a9b11e0d4a3993f76485c069942489c:21
-b50ba38de9db0ecba8a7618f6138380b:21
-5a50c9965f9cd4ee5a321bbd530cc5b2:21
-301f4d37021f2c80999eedb2219554bd:21
-391832837fd0c5bd42b6d5d58dcd80f6:21
-535462c1df188932ba4375a241ac0a3b:21
-517cb4d902b3c235f31b41bcd012b184:21
-dd9d0f0182859fbb91bfe9cb3504becf:21
-4634339ec4a3dfef9d6a8a22e18c1354:21
-4015e477f56bfed839fc88a1ba749750:21
-be73d1320eb5c1229bc65e73f82039f3:21
-1c2ab72e50012527678e434999c9044a:21
-a09c3b287f292693c49b6ac6139f3aca:21
-67ce8616e53660f5898eaa146c7f3835:21
-5b728323f7668ae4ff0e895afaf15cea:21
-b070068bc98b0eafc27ab9bb38e7cfbb:21
-eadaf0fe065d92924ebd1cade7c57e35:21
-724567eb08f8a1a1dba97c82bd0a8d00:21
-06065f8f4550b0f3530468ae56e4437b:21
-eea2af8f17a87b391353b3878325bbd1:21
-aa462a0178a7561e7c756b157cbeeb38:21
-3af411c682f8264bb7c70794211c6248:21
-b972f761c2e0edfca60661ae2abf0218:21
-da13bac632b4f32422190d415f31bab8:21
-a7c7530bd46ed4e558e4d448f1ab9b7c:21
-e2c48e8e5e90a5dcc4cd070af2c1b20e:21
-0fc8da4c46369ca9dbcee59fb490681c:21
-5bb64f5d5c0764c9c1076e2c00ee3b3c:21
-f92f02539108f4c7034055e630ce5b1b:21
-21206168597de4cc003cf94bb2476441:21
-80ffcde01a4a187455c15c7b11ae7c33:21
-5adbafe8f34e077daeacc70305eed4f1:21
-d7d607c796c81475b0c57f2646b7e662:21
-4f89f7e86a6cb4ab329ea69918ae6f49:21
-0f88600ff268e2f32d56c63ce955aeba:21
-aeb824437b0a504c4a1a3109729a8817:21
-bd00bb3c1478ff33a781c1b48bfa9112:21
-eabb0f12ea319b61bdf53d9f4a6d7044:21
-b5653063b6061db848ef666076d4a44b:21
-1bf6b15eb573dea85120bcc84f419023:21
-71ae94c8959d23501b3a25269ed58f50:21
-c0673d423ec86b469092b2eedbf4e8db:21
-52986dd229c1677a8a8fea05023f7625:21
-364cf0eb88afb77fbc7209d2c1c7273e:21
-072b20ca146065654d29d72fa24fc89a:21
-50ddd3dc05b1198214cb57e9250d7076:21
-3eb1459c5b79cc1a562b145846a1b957:21
-d84c006146e1a54bba7a7122983ef085:21
-3a16ed65c968daf194834ce7a0225b8a:21
-6321e1881f171dc79e50d8e0cbc6ce7d:21
-5800fc71343c619b5e005bbab60912e0:21
-8e087b8bbcd92fe9389bf2f469f383f6:21
-e7e98eb2a51cc3cbf30bd532d8942294:21
-c173773ee22e94ab734c05bd504d3325:21
-5bef782b74608c4de25f4d053ae543a9:21
-ba7c51aed579c0f1b473ef2035b15a17:21
-169dfedc30ae5b95af848f3184206b4b:21
-549d9811413f0c4b84cd1e84290127f1:21
-668fa5f927950cbb34952cdb7e631eb8:21
-580fe13c6b95c05247c1770fff6d0fe9:21
-02d6383509108efe665de4a2aff24702:21
-68749591b1210c540ab306d416f49c68:21
-75269722fb78a1b61a850da9a73984fc:21
-58c819dd55c2558fed93faef88f45e89:21
-e479c8a303ccdaf45f673c78971d6a44:21
-cd94f79ea296e6126eb563a6e82a73b3:21
-ab652b1e99797dd3cc3bfb7e48db5d18:21
-4d3ab1362ff964f3947b437b16aba576:21
-53dea4d4defbb7512a31f4fa659437ae:21
-252e68c63ccea47b1d02a47837383c4e:21
-a9fff0b0f85b598b277aaf29fe537ad8:21
-caf732f52661f4c573b342a3d1da85bc:21
-9401285d5c41e4e0ee4581c62f4a2fc4:21
-5d0c7f0ad2c60ef78ae47f854b90a194:21
-983db0abe1d8976946a8a19eee412c9d:21
-e7ca6d1abe4cd55a491cdf58eb8f4602:17
-a4fc9c89f980d489cd69b5a9a5379279:28
 b63216a8584c12bf002f4ee8c3b0ac3e:19
 1ad1cfa76f9692958656f17b710a0ecc:19
-ebf47efadc4c50002b13da0b1cef0646:18
-106de6ca4d0e49988f3328ce380ff517:18
-2f4aedd17528c30f8a9f16b030ff708b:18
-93aca156e3f15799914f868852723e47:18
-17cbc97604a781acb78c668b2437ffbd:18
-1709449e191fe131236bd0b8c1928b71:18
-1587f690fd5c0c886630334e89e397ff:18
-a6cf882fc575147513b7b1b505fdd335:18
-dc6ad4bf6892204b9fca73d1ee9cab04:18
-9ae673cfbb5d90688edafbf7ec2862e4:18
-acf250fcdbf54725219af8c6a8a614d9:18
-07319a58381faa784dda046d5e8fd8ae:18
-77e978f6318d01be894de8944fdc05b4:18
-81be9a78d26d25c6ff47c7e9af30a92e:18
-e87bf9d0084b39c443e421cdb5d3fc43:18
-9e973ee7f4f5ad967d2ba5bf5d88911c:18
-b47e7bbf50ad208599056f8d0f1cc5ba:18
-47a9e25289b4ece230adcc3b60797712:18
-42e4cc1882d334757afbe74f4d8cec8e:18
-024481748991b858bf7e03b0b87ce125:18
-b6ced7e04b527d83cd660894a0059ea7:18
-717e536c3a13f3602fbaf1f607ee2963:18
-18b540e31006aad6ae7a26d967ec0e0e:18
-f1483fe126d64bd7544dba7fdf0559f4:18
-3c4a0d8270a52e43316a711a2fe83268:18
-2e13bb1bfba423473a6efba65d9c8190:18
-47a5d9010bdeb561176cf070ef483e91:18
-4f2e7d32ed2c7ead316770fbe84d8aae:18
-c625e1090fc4d72afd0673839af5d46f:18
-4cc2dc623ee702db7a5899c5c401a98c:18
-eff51a62305642c720d163085061ed63:18
-780c7e0f80b5479d00e2c3679974644d:18
-69e416f1afc250d6239788b4b1fb9bb4:18
-6d60d78c1ce897131dcf45743a8d6e49:18
-e16e8dcca650cf79557cb76a86edb4e9:18
-d71a32697989404ae782e105a5eb5e4c:18
-bfebe4871ed9c4b5f3300fe8dfa2a0a3:18
-93e215f230901f20bdca460cfe8c42af:18
-8efacada80287ebb7607758a0a82b0a0:18
-487bc1228fd817b641056a1a008984f8:18
-dc6c2915cd6f0a6fd0ab01365349599d:18
-1a3dbf72e4bd135a621b8ad78a7151c1:18
-01fd60d48ba7e1434bb72696282c7113:18
-83c42b475763d64935a9ddfc6a374b64:18
-ed9a26aaf7ddb748bd1e624aeb4fdb81:18
-eeb05b41976f7bb96572330854aaab34:18
-3c17437ba337e7ce8edad092f745a363:18
-8d8fd1ffc3ae24cb43798f4eb41fc4de:18
-003f7ab7088710994a289bab815aacbf:18
-488d943cdcedfca2b076152769fc3c51:18
-cab779407e29ba3377e6a47a4f842ec5:18
-a0ddcf0f7bb4aa9d38db983941f6908d:18
-b64b41d26010783cae2dff88c7e21f1d:18
-aac6d054ad0f9bea1f64702aea698ee3:18
-90b4163293f9e35f7091692b31d3b5d7:18
-9d66fceb1054d5e2ff207591ec216b70:18
-93a65566b576af220207e30cc89ef490:18
-b3248e6a2d819bd8bb42a15179188c52:18
-f95f0545feb040d2d6bfcad6897364d0:18
-058ec3044e95ed7fcaff93a2f3105d6f:18
-39ef3516cb80752cac1c541b43a1c2fa:18
-c4b4a2994711cb342b0c8c8ef409ce8b:18
-719c3b73816fa0db79747ea264abab5a:18
-c1295d79d79340152c6b98f1c9003e8c:18
-1211e656a8eaa4423e7678533a1e7e2a:18
-1d7847b88e0feaecd299b3c5c18be763:18
-e0c7417a74df21b55367f9e1ea6c2d37:18
-e49b0a2bd830bb3b96934490b2bc6ebe:18
-91790d7c5cf146ab3936919184a70015:18
-ec36ea4bb9634f7ec5c4d3c1cc9dd71b:18
-860e7b1a68c02eef7d2e2d7b6a3e13b5:18
-458fda8d9863be24862bb62c910f427d:18
-e05ed0452c12e62ef74e9161159b1231:18
-9419764761c059b9c2f9cf640abfdeb0:18
-ea02175c4ef64b6370f619acd84f3c3c:18
-0b01d40e1ca37aa854e77c98cc153e77:18
-eb12f3e690b201aa171d794a8288af10:18
-b955e5418d490425f99d58f441ba4525:18
-34305e28da7351c9fe8a69e055e1c250:4
-f9a8c4364b7598dbeca0186f60e71ea6:4
-6a6e8a76be9fd95bf68f4be6f6cf1bbf:4
-14a880d95891af72380815d1c91baf73:4
-a2fa13ef7ba19d24adf59f8ecc12f0b4:4
-acd568e2592b223aaec17240031ed688:4
-cf994432aef1a88d7b82ffeecbbb96dc:4
-31927400c1d74c4dbcefe4c8c787ab8a:4
-94961af3e92bca38554a63bef2e453a6:4
-c562088538d99567be8dfdc54f830661:4
-459a7fef5f29ed6bafedadf927e315ee:4
-5c9d5847e56faea367acff998cb9e02b:4
-766fb7e580b7f7211bee8e77b98859b7:4
-53dc80d117986033f21a0b5f8609c87c:4
-3d8114f2ba5b5b51c547181a89a3466f:4
-89418ba487f62fbae3843e5dff5b9040:4
-ebe40ec90a872c67a1c27ae2c1eea950:4
-35462fa6c34cbb3610497e0e7ec5604a:4
-4f7b3ab75c9ad8d5b10fec60265a3809:4
-5123777f5c011dbd46ae38689b72a7d4:4
-38c98f315f4b01c1aa53a76a214a919e:4
-25989076c985cb6136599803fbcb48d5:4
-a96139fddc921f9c720e39737bfc1e23:4
-5cc99cf3cb2f9f5b2f3da2068a3c3b14:4
-9d57682c086fd2c37173c59f7df7f7f1:4
-bae7808504fe1ce016d86125d969287f:4
-b0767396a0f7f31b48ac96880c9cbf8b:4
-e2babe17ce0f4f5b30fee91c06c0e8fa:4
-d1c3eecefff445a312cfee0fa59d1431:4
-d5702e33cc1c8a04039657bec033a3e8:4
-1449b4ca79da7eaf4a3e04a2aa7b764d:4
-f50415b6582d0e25f6949d844bf93ee8:4
-3fef2395a8a091e7dea7a3b99dfeaea6:4
-03e302c7776c88610dafb333323263af:4
-b7d9936f2c7c87d2edac782f5403001b:4
-affb389d45f01bc18197bf0f49f4b118:4
-a4eb5a999085dfe2424b2896e38b25d1:4
-9cc9248b4494b77d7b40bbbf4dcef8b3:4
-043d915c3e3d58cb001a8b32deaffea3:4
-f9d3d4bc8a5944b63f51ae472e86813d:4
-a5e0393e11eadd75dd36fb5379ae018c:4
-5980bcaa38c00e4b837f2e9785bda3d2:4
-cc63328c0b60b9a790f8a1457dfaa511:4
-afdf18c7f6d8a437d84e390721b9b8b3:4
-b08cfcf9149aa4e94b5fb9695271c575:4
-10a9f2ae427efa8d37d59f435e83d62e:4
-d689449577a88d1b20d321d4e5fd32d9:4
-75298a5c8b84105fd9b9df7184dc110a:4
-5c8ed88fdd5d5ccb1ad7a61ecdf1ef9e:4
-35a4cc8262e2a04a80fc506d1551c2b7:4
-7ef43aabb95d2f3ad79438accc09cb4c:4
-aa6a754d71d091d666fea53f8769dfe9:4
-c8341c7fe17e7ae1484fdc25aef13df9:4
-4b6c6d5caf6e3d47ffd3dfdb329842f2:4
-a93c08a489709b0b6402127f3287a0a2:4
-500d600d630b1130b906355e694caf25:4
-e2f20c930af14f873866419d98bb33c5:4
-4f67d44d8a9c73b20f9dccb0fe708910:4
-3207a78e01966b63f857a845d312c5db:4
-dc772adf9d92e1330e1e6c1bc8bbdbea:4
-9a0f97a242e5b5c8bf70dd24775d973a:4
-c51604fe4030e75ce4ed3f545e470eee:4
-e8627b4481475f494ae99206e4d614a4:4
-a3abcd010f778136d77b5c00575faa93:4
-d806274a83ce72b89939c7e1fcc7dbf1:4
-e42535765b64f8f96c6a5b13c5aa30e9:4
-0389b0ce442166bc4298bd49f8ecfbff:4
-e6c5da69c2396e1a4c404a82509edb1e:4
-d62e86b3047119ec0941418ab6365bd4:4
-3c957e719e0ffce99b544fcc9a97ab3a:4
-638115e7b634086640ad6b300846ad6d:4
-f42bda5821df4a01dc97cb7d611a98a2:4
-15884ea4526d7af7c6dd5c5c89b81c97:4
-23fb4c5f959189ae9c90b968647934f3:4
-6b538e97c482ee0993607820484dcb08:4
 e7e718f70ee49560800db2d60270603f:11
 61c32de62fbec72e56083dade766f0f9:11
 f9684960dbadc5206f33484afeba57e1:11
@@ -361,84 +278,6 @@
 13c8db7cad8e47068720c9f9b9d8213c:11
 d942884e00b9f288de96a8b0733372d9:11
 cd5c01104e0c46080a8d499f87de4991:11
-d8c43560e159bcb899c2180b8e01d8e3:24
-ae9c7aee12dfaaa56e43834dcea91f5f:24
-12233f83b06081671815562dce938d3b:24
-106252e3f31ee17239e1ee4184147deb:24
-1fb2cd38813bad6e5eeef74bcbcb133d:24
-c5bbd4174eee5cb68830801317071dd5:24
-b876313caea388f03200ac720ed528af:24
-f4fa53a97c83899fd2da9f4112f35339:24
-dc32c09ecdd9bda4703aa944cf1eb4ca:24
-07a59dae4166f6d504bb0793c5e75178:24
-efcb5024ce3514453d44735222903a55:24
-43445dc9e50e2c4fc327097d07ff749a:24
-bcb1787dd2def454fd3f31d1b2c0967a:24
-cace738770550444ee1f91000c32c147:24
-2cc65559141cece33ffdbc4e34a2ce3b:24
-b55a09768f05f29b15e1ff3a9feb7c92:24
-54282406a85f4410d4dfc72d5873ee96:24
-1c38913c447705e315ec4486da558c9d:24
-ca3f30645fa7b480b213f4228386a09a:24
-6d8800c5cc8ad9e4f3f28e777207a870:24
-b8dc6f9b9587159f978d643aeb8e4d07:24
-44c0d27dc28d4da28655c3c62a0d7cac:24
-91eb697571f68f5850875286c5e36080:24
-ae908f7abd7d347b0bf39fead3531c12:24
-d27ceaaa92270c450aeee582cc17b985:24
-145e730e204fdca9cf169521a3812ed1:24
-294141fdc8f17ac3c7e4d8687d8ad389:24
-cfde9ea95c663bed6d56df8778b01b79:24
-8569110aa82eb44ff0b12819867c66f8:24
-48de820418038280f70eca64f878c67b:24
-f389ac23dc4ef6641321adf43d712c32:24
-ef4661e0074b431762177e10c2ac8570:24
-46de27224f4cf6fb745e30bbd6aad267:24
-75c79913d4df6b467a67c40ce67a32fc:24
-2a000aca1e17b3b0343ddf151ee313f4:24
-e9d59e0325c6dd1dd78a998365435fac:24
-0a1d88442fc34d0081115d949f1c5fa9:24
-0a7ef5f8b75fa2dd79787b03dff923c9:24
-b247285b58ca44aad532b1c3b5a5dacf:24
-63f61697ff1319a76632338fe0c13ae9:24
-e8df9edc7e29252645d2b8a6a55c4e0f:24
-aaba729f45b1617d59cec26aa41937e3:24
-7e7ae6228d4ab54b90f0f580731941d7:24
-de1c84862deb139962240505133690f0:24
-9660da0a81f885de5a3b16f1f07a1067:24
-9dbabf8883b7a4e120fcbceb00face40:24
-c3f5202b30c04801da693013368a0da2:24
-b73113e1f7c2702cfd997dd84e5ac003:24
-9b5946e54278b49302da6ccd2a3a06e1:24
-c7a5a5244ef8d55b160e29df7e310b76:24
-74fa889a011613ee983f45aed7dcb656:24
-ccb57b751fd0ed3f7244a0e096b19076:24
-dcd61dd6bd1e0fec6888029656162559:24
-c2fa9ac71ea25473f3c384e5d3c3770b:24
-43de03bf819ac106a9a66f3d7b473bd1:24
-3ed4b5f668130ecec1aadb21bf0a671a:24
-ba4d4fc3c411bf5b477323a1329574b3:24
-4e82fa0797beeab61a7a0b9b3ba7505e:24
-1cff05a0b4291e8f289a1794f016eab5:24
-11e854ed754739797dbae75ff3159e82:24
-456c3251cceb566a82d3175390b6a663:24
-76fd080fdaa80390ca05399f4d0b49ae:24
-2daef0ca0e135ad904bf7e931690cf2b:24
-d49bee1da6282ff1efd4017e2c26ff55:24
-94bf636e74064a1f45c5e81777a8b541:24
-b502fdd440592e2d637704ebef9baf01:24
-67395fd629dae7b237421aa91bc12150:24
-b69dd3d2f26fc766f3b4193b089fa71e:24
-5a13a7fac55b3378629744099623199b:24
-6c21462d7f8529e6d017e054b02d96d3:24
-ef1a937ed3ed1277a46cbbfb589bcc7c:24
-a9446b05afd48f274f6469e9617dadde:24
-03b1c2c2b9e04b66ace80c8d891dad71:24
-5af3ef227d419594dc0316392c0fd762:24
-b4146bc53ba79957adf0bf7306b2212c:24
-f1136465059f10586c4fb8379126e010:24
-8796edfd2200b66560e180ecf2c8bd3a:24
-b36bec8fd49158c020b6d8c09cc02baf:26
 799e6e8b1fe3f2c45ec9703491bd67d5:8
 12011f7bb75e99008d3ef5bbe9087e6f:8
 53737c53af7f40961557a85fb4d11b55:8
@@ -446,8 +285,8 @@
 a85598686e913ff9a84c078990e2b74f:8
 9389bbac5d37c3655c2d112b5fb15d79:8
 c7143cf932c4620b38f241a67c698de7:8
-6bb42f291857bf93ef2407e3fe8910b3:8
 d5360a8c6fd960a52c125ad6ee65b369:8
+6bb42f291857bf93ef2407e3fe8910b3:8
 4c0513c3ea8aa4e0d1671697ade55dc0:8
 9ab999023c47266804e1c23912c49590:8
 c83ba14b99739545b5611680823c5e64:8
@@ -462,12 +301,173 @@
 2248103eacf690b2060d9f624dee5e41:8
 7aaff576c6f4f28ee8d80236c3943c3a:8
 62068b685c80a1da76ef1824af304e47:5
-beef3d82791567a66852903d9acde2d8:14
-8f8e727dba622a81127060f53efbaf60:14
-a58ec4e669821ad885c5e539bea8f8c0:14
-c183aebc8596eae7dbaf32f831d4bf1a:14
-579a40bd08705acc7099b631706917cd:14
-c979623104ac1df2ff0b56f2eb8f40a3:14
-4a406313d3d1fc7c56f2e4ae88033ef5:14
-0534bb586552faf59923fcbb66c5c9a8:14
-027b76c362f10b646fce08dd34457533:29
+a5352e6f5efd89293cad68bde5359174:21
+0873c171286bf72adca073b8232909ad:21
+75fcc63a7299ec405cbbbae6c6f4124a:21
+4242f75bd579b5860e448896bd97161e:21
+f8e3f0c3200fb1dd44acfbd301d96bed:21
+3e972f384811f77fd62ce384910c7605:21
+746752a0385785d6c4a6af907b506ff8:21
+e40adee8247464101fcb01d083025c31:21
+f2c67cccefcb8826a54b39122e002062:21
+d96183e4ed9b9b2b075bd5f8c4364100:21
+0a8ab6673c7b4e87cb2faf2d7005362a:21
+810a68c672694a924abe0f0c59a9a394:21
+fe284dc96dc744beee1ddbb1a8e4c4ee:21
+d34d323d00595f4051cfe0621331317d:21
+711a52bc9e3eafe73e0c64c4ae6d5e6a:21
+542566d5040f1a84a95caf3c78e8d54f:21
+fadc0a0d51e6ccdc60992c058edd2711:21
+ccb12040fbbda66de180c11e6251049d:21
+052dc8ce89610356ae4c22dacb2668f6:21
+eb911a19cc8e2139a72a6bf025f0ef92:21
+8a9b11e0d4a3993f76485c069942489c:21
+b50ba38de9db0ecba8a7618f6138380b:21
+5a50c9965f9cd4ee5a321bbd530cc5b2:21
+301f4d37021f2c80999eedb2219554bd:21
+391832837fd0c5bd42b6d5d58dcd80f6:21
+535462c1df188932ba4375a241ac0a3b:21
+517cb4d902b3c235f31b41bcd012b184:21
+dd9d0f0182859fbb91bfe9cb3504becf:21
+4634339ec4a3dfef9d6a8a22e18c1354:21
+4015e477f56bfed839fc88a1ba749750:21
+be73d1320eb5c1229bc65e73f82039f3:21
+1c2ab72e50012527678e434999c9044a:21
+a09c3b287f292693c49b6ac6139f3aca:21
+67ce8616e53660f5898eaa146c7f3835:21
+5b728323f7668ae4ff0e895afaf15cea:21
+eadaf0fe065d92924ebd1cade7c57e35:21
+b070068bc98b0eafc27ab9bb38e7cfbb:21
+eea2af8f17a87b391353b3878325bbd1:21
+724567eb08f8a1a1dba97c82bd0a8d00:21
+06065f8f4550b0f3530468ae56e4437b:21
+aa462a0178a7561e7c756b157cbeeb38:21
+3af411c682f8264bb7c70794211c6248:21
+b972f761c2e0edfca60661ae2abf0218:21
+da13bac632b4f32422190d415f31bab8:21
+a7c7530bd46ed4e558e4d448f1ab9b7c:21
+e2c48e8e5e90a5dcc4cd070af2c1b20e:21
+0fc8da4c46369ca9dbcee59fb490681c:21
+5bb64f5d5c0764c9c1076e2c00ee3b3c:21
+f92f02539108f4c7034055e630ce5b1b:21
+21206168597de4cc003cf94bb2476441:21
+80ffcde01a4a187455c15c7b11ae7c33:21
+5adbafe8f34e077daeacc70305eed4f1:21
+d7d607c796c81475b0c57f2646b7e662:21
+4f89f7e86a6cb4ab329ea69918ae6f49:21
+0f88600ff268e2f32d56c63ce955aeba:21
+aeb824437b0a504c4a1a3109729a8817:21
+bd00bb3c1478ff33a781c1b48bfa9112:21
+eabb0f12ea319b61bdf53d9f4a6d7044:21
+b5653063b6061db848ef666076d4a44b:21
+71ae94c8959d23501b3a25269ed58f50:21
+1bf6b15eb573dea85120bcc84f419023:21
+c0673d423ec86b469092b2eedbf4e8db:21
+52986dd229c1677a8a8fea05023f7625:21
+364cf0eb88afb77fbc7209d2c1c7273e:21
+072b20ca146065654d29d72fa24fc89a:21
+50ddd3dc05b1198214cb57e9250d7076:21
+3eb1459c5b79cc1a562b145846a1b957:21
+d84c006146e1a54bba7a7122983ef085:21
+3a16ed65c968daf194834ce7a0225b8a:21
+6321e1881f171dc79e50d8e0cbc6ce7d:21
+5800fc71343c619b5e005bbab60912e0:21
+8e087b8bbcd92fe9389bf2f469f383f6:21
+e7e98eb2a51cc3cbf30bd532d8942294:21
+ba7c51aed579c0f1b473ef2035b15a17:21
+c173773ee22e94ab734c05bd504d3325:21
+5bef782b74608c4de25f4d053ae543a9:21
+169dfedc30ae5b95af848f3184206b4b:21
+668fa5f927950cbb34952cdb7e631eb8:21
+549d9811413f0c4b84cd1e84290127f1:21
+580fe13c6b95c05247c1770fff6d0fe9:21
+68749591b1210c540ab306d416f49c68:21
+02d6383509108efe665de4a2aff24702:21
+75269722fb78a1b61a850da9a73984fc:21
+58c819dd55c2558fed93faef88f45e89:21
+e479c8a303ccdaf45f673c78971d6a44:21
+cd94f79ea296e6126eb563a6e82a73b3:21
+ab652b1e99797dd3cc3bfb7e48db5d18:21
+4d3ab1362ff964f3947b437b16aba576:21
+53dea4d4defbb7512a31f4fa659437ae:21
+252e68c63ccea47b1d02a47837383c4e:21
+a9fff0b0f85b598b277aaf29fe537ad8:21
+caf732f52661f4c573b342a3d1da85bc:21
+9401285d5c41e4e0ee4581c62f4a2fc4:21
+5d0c7f0ad2c60ef78ae47f854b90a194:21
+983db0abe1d8976946a8a19eee412c9d:21
+34305e28da7351c9fe8a69e055e1c250:4
+f9a8c4364b7598dbeca0186f60e71ea6:4
+6a6e8a76be9fd95bf68f4be6f6cf1bbf:4
+14a880d95891af72380815d1c91baf73:4
+a2fa13ef7ba19d24adf59f8ecc12f0b4:4
+acd568e2592b223aaec17240031ed688:4
+cf994432aef1a88d7b82ffeecbbb96dc:4
+31927400c1d74c4dbcefe4c8c787ab8a:4
+94961af3e92bca38554a63bef2e453a6:4
+c562088538d99567be8dfdc54f830661:4
+459a7fef5f29ed6bafedadf927e315ee:4
+5c9d5847e56faea367acff998cb9e02b:4
+766fb7e580b7f7211bee8e77b98859b7:4
+53dc80d117986033f21a0b5f8609c87c:4
+3d8114f2ba5b5b51c547181a89a3466f:4
+89418ba487f62fbae3843e5dff5b9040:4
+ebe40ec90a872c67a1c27ae2c1eea950:4
+35462fa6c34cbb3610497e0e7ec5604a:4
+4f7b3ab75c9ad8d5b10fec60265a3809:4
+5123777f5c011dbd46ae38689b72a7d4:4
+38c98f315f4b01c1aa53a76a214a919e:4
+25989076c985cb6136599803fbcb48d5:4
+a96139fddc921f9c720e39737bfc1e23:4
+5cc99cf3cb2f9f5b2f3da2068a3c3b14:4
+9d57682c086fd2c37173c59f7df7f7f1:4
+e2babe17ce0f4f5b30fee91c06c0e8fa:4
+d1c3eecefff445a312cfee0fa59d1431:4
+bae7808504fe1ce016d86125d969287f:4
+b0767396a0f7f31b48ac96880c9cbf8b:4
+d5702e33cc1c8a04039657bec033a3e8:4
+1449b4ca79da7eaf4a3e04a2aa7b764d:4
+f50415b6582d0e25f6949d844bf93ee8:4
+3fef2395a8a091e7dea7a3b99dfeaea6:4
+03e302c7776c88610dafb333323263af:4
+b7d9936f2c7c87d2edac782f5403001b:4
+affb389d45f01bc18197bf0f49f4b118:4
+a4eb5a999085dfe2424b2896e38b25d1:4
+9cc9248b4494b77d7b40bbbf4dcef8b3:4
+043d915c3e3d58cb001a8b32deaffea3:4
+f9d3d4bc8a5944b63f51ae472e86813d:4
+a5e0393e11eadd75dd36fb5379ae018c:4
+5980bcaa38c00e4b837f2e9785bda3d2:4
+cc63328c0b60b9a790f8a1457dfaa511:4
+afdf18c7f6d8a437d84e390721b9b8b3:4
+b08cfcf9149aa4e94b5fb9695271c575:4
+10a9f2ae427efa8d37d59f435e83d62e:4
+d689449577a88d1b20d321d4e5fd32d9:4
+75298a5c8b84105fd9b9df7184dc110a:4
+5c8ed88fdd5d5ccb1ad7a61ecdf1ef9e:4
+35a4cc8262e2a04a80fc506d1551c2b7:4
+7ef43aabb95d2f3ad79438accc09cb4c:4
+aa6a754d71d091d666fea53f8769dfe9:4
+c8341c7fe17e7ae1484fdc25aef13df9:4
+500d600d630b1130b906355e694caf25:4
+e2f20c930af14f873866419d98bb33c5:4
+4b6c6d5caf6e3d47ffd3dfdb329842f2:4
+a93c08a489709b0b6402127f3287a0a2:4
+4f67d44d8a9c73b20f9dccb0fe708910:4
+3207a78e01966b63f857a845d312c5db:4
+dc772adf9d92e1330e1e6c1bc8bbdbea:4
+9a0f97a242e5b5c8bf70dd24775d973a:4
+c51604fe4030e75ce4ed3f545e470eee:4
+e8627b4481475f494ae99206e4d614a4:4
+a3abcd010f778136d77b5c00575faa93:4
+d806274a83ce72b89939c7e1fcc7dbf1:4
+e42535765b64f8f96c6a5b13c5aa30e9:4
+0389b0ce442166bc4298bd49f8ecfbff:4
+e6c5da69c2396e1a4c404a82509edb1e:4
+d62e86b3047119ec0941418ab6365bd4:4
+3c957e719e0ffce99b544fcc9a97ab3a:4
+638115e7b634086640ad6b300846ad6d:4
+f42bda5821df4a01dc97cb7d611a98a2:4
+15884ea4526d7af7c6dd5c5c89b81c97:4
+23fb4c5f959189ae9c90b968647934f3:4
+6b538e97c482ee0993607820484dcb08:4
diff --git a/src/main/resources/api_database/api_database_api_level.ser b/src/main/resources/api_database/api_database_api_level.ser
index e9e539a..ab078a7 100644
--- a/src/main/resources/api_database/api_database_api_level.ser
+++ b/src/main/resources/api_database/api_database_api_level.ser
Binary files differ
diff --git a/src/main/resources/api_database/api_database_hash_lookup.ser b/src/main/resources/api_database/api_database_hash_lookup.ser
index 42a3b61..b332706 100644
--- a/src/main/resources/api_database/api_database_hash_lookup.ser
+++ b/src/main/resources/api_database/api_database_hash_lookup.ser
Binary files differ
diff --git a/src/test/examples/classmerging/ConflictInGeneratedNameTest.java b/src/test/examples/classmerging/ConflictInGeneratedNameTest.java
index 396e73c..4ef0b3e 100644
--- a/src/test/examples/classmerging/ConflictInGeneratedNameTest.java
+++ b/src/test/examples/classmerging/ConflictInGeneratedNameTest.java
@@ -11,7 +11,7 @@
   }
 
   public static class A {
-    private String name = "A";
+    @NeverPropagateValue private String name = "A";
 
     public A() {
       print("In A.<init>()");
@@ -56,8 +56,8 @@
   }
 
   public static class B extends A {
-    private String name = "B";
-    private String name$classmerging$ConflictInGeneratedNameTest$A = "C";
+    @NeverPropagateValue private String name = "B";
+    @NeverPropagateValue private String name$classmerging$ConflictInGeneratedNameTest$A = "C";
 
     public B() {
       print("In B.<init>()");
diff --git a/src/test/examples/classmerging/NeverPropagateValue.java b/src/test/examples/classmerging/NeverPropagateValue.java
new file mode 100644
index 0000000..f023635
--- /dev/null
+++ b/src/test/examples/classmerging/NeverPropagateValue.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, 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 classmerging;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.FIELD})
+public @interface NeverPropagateValue {}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 46fcb91..9c7188b 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -72,4 +72,5 @@
 -neverinline class * {
   @classmerging.NeverInline <methods>;
 }
+-neverpropagatevalue class * { @classmerging.NeverPropagateValue *; }
 -nohorizontalclassmerging @classmerging.NoHorizontalClassMerging class *
diff --git a/src/test/examplesJava11/com/android/tools/r8/NeverPropagateValue.java b/src/test/examplesJava11/com/android/tools/r8/NeverPropagateValue.java
new file mode 100644
index 0000000..b28eece
--- /dev/null
+++ b/src/test/examplesJava11/com/android/tools/r8/NeverPropagateValue.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.FIELD})
+public @interface NeverPropagateValue {}
diff --git a/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java b/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
index cdbe725..1e36fe1 100644
--- a/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
+++ b/src/test/examplesJava11/nesthostexample/NestPvtMethodCallInlined.java
@@ -5,6 +5,7 @@
 package nesthostexample;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 
 public class NestPvtMethodCallInlined {
 
@@ -15,6 +16,7 @@
     }
 
     @NeverInline
+    @NeverPropagateValue
     private String notInlinedPvtCall() {
       return "notInlinedPvtCallInner";
     }
@@ -31,6 +33,7 @@
     }
 
     @NeverInline
+    @NeverPropagateValue
     private String notInlinedPvtCall() {
       return "notInlinedPvtCallInnerInterface";
     }
@@ -44,6 +47,7 @@
     }
 
     @NeverInline
+    @NeverPropagateValue
     default String dispatchInlining(InnerSub iSub) {
       return iSub.dispatch(this);
     }
@@ -54,6 +58,7 @@
   public static class InnerSub extends Inner {
 
     @NeverInline
+    @NeverPropagateValue
     public String dispatchInlining(InnerInterface impl) {
       return impl.dispatch(this);
     }
@@ -63,6 +68,7 @@
     }
 
     @NeverInline
+    @NeverPropagateValue
     private String notInlinedPvtCall() {
       return "notInlinedPvtCallInnerSub";
     }
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index 2a8f1bb..accc8af 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -161,13 +161,10 @@
    * import java.util.concurrent.Flow.Subscriber;
    * import java.util.concurrent.Flow.Subscription;
    * import java.util.concurrent.SubmissionPublisher;
-   * import java.util.concurrent.locks.Condition;
-   * import java.util.concurrent.locks.Lock;
-   * import java.util.concurrent.locks.ReentrantLock;
+   * import java.util.concurrent.CountDownLatch;
    *
    * public class MySubscriber<T> implements Subscriber<T> {
-   *   final static Lock lock = new ReentrantLock();
-   *   final static Condition done  = lock.newCondition();
+   *   final static CountDownLatch done = new CountDownLatch(1);
    *
    *   private Subscription subscription;
    *
@@ -190,25 +187,7 @@
    *   @Override
    *   public void onComplete() {
    *     System.out.println("Done");
-   *     signalCondition(done);
-   *   }
-   *
-   *   public static void awaitCondition(Condition condition) throws Exception {
-   *     lock.lock();
-   *     try {
-   *       condition.await();
-   *     } finally {
-   *       lock.unlock();
-   *     }
-   *   }
-   *
-   *   public static void signalCondition(Condition condition) {
-   *     lock.lock();
-   *     try {
-   *       condition.signal();
-   *     } finally {
-   *       lock.unlock();
-   *     }
+   *     done.countDown();
    *   }
    *
    *   public static void main(String[] args) throws Exception {
@@ -220,7 +199,7 @@
    *     items.forEach(publisher::submit);
    *     publisher.close();
    *
-   *     awaitCondition(done);
+   *     done.await();
    *   }
    * }
    *
@@ -262,13 +241,7 @@
     {
       fieldVisitor =
           classWriter.visitField(
-              ACC_FINAL | ACC_STATIC, "lock", "Ljava/util/concurrent/locks/Lock;", null, null);
-      fieldVisitor.visitEnd();
-    }
-    {
-      fieldVisitor =
-          classWriter.visitField(
-              ACC_FINAL | ACC_STATIC, "done", "Ljava/util/concurrent/locks/Condition;", null, null);
+              ACC_FINAL | ACC_STATIC, "done", "Ljava/util/concurrent/CountDownLatch;", null, null);
       fieldVisitor.visitEnd();
     }
     {
@@ -282,7 +255,7 @@
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(10, label0);
+      methodVisitor.visitLineNumber(8, label0);
       methodVisitor.visitVarInsn(ALOAD, 0);
       methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
       methodVisitor.visitInsn(RETURN);
@@ -296,21 +269,21 @@
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(18, label0);
+      methodVisitor.visitLineNumber(15, label0);
       methodVisitor.visitVarInsn(ALOAD, 0);
       methodVisitor.visitVarInsn(ALOAD, 1);
       methodVisitor.visitFieldInsn(
           PUTFIELD, "MySubscriber", "subscription", "Ljava/util/concurrent/Flow$Subscription;");
       Label label1 = new Label();
       methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(19, label1);
+      methodVisitor.visitLineNumber(16, label1);
       methodVisitor.visitVarInsn(ALOAD, 1);
       methodVisitor.visitInsn(LCONST_1);
       methodVisitor.visitMethodInsn(
           INVOKEINTERFACE, "java/util/concurrent/Flow$Subscription", "request", "(J)V", true);
       Label label2 = new Label();
       methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(20, label2);
+      methodVisitor.visitLineNumber(17, label2);
       methodVisitor.visitInsn(RETURN);
       methodVisitor.visitMaxs(3, 2);
       methodVisitor.visitEnd();
@@ -321,7 +294,7 @@
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(24, label0);
+      methodVisitor.visitLineNumber(21, label0);
       methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
       methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
       methodVisitor.visitInsn(DUP);
@@ -347,7 +320,7 @@
           INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
       Label label1 = new Label();
       methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(25, label1);
+      methodVisitor.visitLineNumber(22, label1);
       methodVisitor.visitVarInsn(ALOAD, 0);
       methodVisitor.visitFieldInsn(
           GETFIELD, "MySubscriber", "subscription", "Ljava/util/concurrent/Flow$Subscription;");
@@ -356,7 +329,7 @@
           INVOKEINTERFACE, "java/util/concurrent/Flow$Subscription", "request", "(J)V", true);
       Label label2 = new Label();
       methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(26, label2);
+      methodVisitor.visitLineNumber(23, label2);
       methodVisitor.visitInsn(RETURN);
       methodVisitor.visitMaxs(3, 2);
       methodVisitor.visitEnd();
@@ -367,13 +340,13 @@
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(29, label0);
+      methodVisitor.visitLineNumber(26, label0);
       methodVisitor.visitVarInsn(ALOAD, 1);
       methodVisitor.visitMethodInsn(
           INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false);
       Label label1 = new Label();
       methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(30, label1);
+      methodVisitor.visitLineNumber(27, label1);
       methodVisitor.visitInsn(RETURN);
       methodVisitor.visitMaxs(1, 2);
       methodVisitor.visitEnd();
@@ -383,25 +356,21 @@
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(34, label0);
+      methodVisitor.visitLineNumber(31, label0);
       methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
       methodVisitor.visitLdcInsn("Done");
       methodVisitor.visitMethodInsn(
           INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
       Label label1 = new Label();
       methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(35, label1);
+      methodVisitor.visitLineNumber(32, label1);
       methodVisitor.visitFieldInsn(
-          GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+          GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/CountDownLatch;");
       methodVisitor.visitMethodInsn(
-          INVOKESTATIC,
-          "MySubscriber",
-          "signalCondition",
-          "(Ljava/util/concurrent/locks/Condition;)V",
-          false);
+          INVOKEVIRTUAL, "java/util/concurrent/CountDownLatch", "countDown", "()V", false);
       Label label2 = new Label();
       methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(36, label2);
+      methodVisitor.visitLineNumber(33, label2);
       methodVisitor.visitInsn(RETURN);
       methodVisitor.visitMaxs(2, 1);
       methodVisitor.visitEnd();
@@ -410,112 +379,6 @@
       methodVisitor =
           classWriter.visitMethod(
               ACC_PUBLIC | ACC_STATIC,
-              "awaitCondition",
-              "(Ljava/util/concurrent/locks/Condition;)V",
-              null,
-              new String[] {"java/lang/Exception"});
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      Label label1 = new Label();
-      Label label2 = new Label();
-      methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
-      Label label3 = new Label();
-      methodVisitor.visitLabel(label3);
-      methodVisitor.visitLineNumber(39, label3);
-      methodVisitor.visitFieldInsn(
-          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
-      methodVisitor.visitMethodInsn(
-          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V", true);
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(41, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(
-          INVOKEINTERFACE, "java/util/concurrent/locks/Condition", "await", "()V", true);
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(43, label1);
-      methodVisitor.visitFieldInsn(
-          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
-      methodVisitor.visitMethodInsn(
-          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
-      Label label4 = new Label();
-      methodVisitor.visitLabel(label4);
-      methodVisitor.visitLineNumber(44, label4);
-      Label label5 = new Label();
-      methodVisitor.visitJumpInsn(GOTO, label5);
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(43, label2);
-      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
-      methodVisitor.visitVarInsn(ASTORE, 1);
-      methodVisitor.visitFieldInsn(
-          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
-      methodVisitor.visitMethodInsn(
-          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitInsn(ATHROW);
-      methodVisitor.visitLabel(label5);
-      methodVisitor.visitLineNumber(45, label5);
-      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(1, 2);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor =
-          classWriter.visitMethod(
-              ACC_PUBLIC | ACC_STATIC,
-              "signalCondition",
-              "(Ljava/util/concurrent/locks/Condition;)V",
-              null,
-              null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      Label label1 = new Label();
-      Label label2 = new Label();
-      methodVisitor.visitTryCatchBlock(label0, label1, label2, null);
-      Label label3 = new Label();
-      methodVisitor.visitLabel(label3);
-      methodVisitor.visitLineNumber(48, label3);
-      methodVisitor.visitFieldInsn(
-          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
-      methodVisitor.visitMethodInsn(
-          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V", true);
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(50, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(
-          INVOKEINTERFACE, "java/util/concurrent/locks/Condition", "signal", "()V", true);
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(52, label1);
-      methodVisitor.visitFieldInsn(
-          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
-      methodVisitor.visitMethodInsn(
-          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
-      Label label4 = new Label();
-      methodVisitor.visitLabel(label4);
-      methodVisitor.visitLineNumber(53, label4);
-      Label label5 = new Label();
-      methodVisitor.visitJumpInsn(GOTO, label5);
-      methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(52, label2);
-      methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
-      methodVisitor.visitVarInsn(ASTORE, 1);
-      methodVisitor.visitFieldInsn(
-          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
-      methodVisitor.visitMethodInsn(
-          INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V", true);
-      methodVisitor.visitVarInsn(ALOAD, 1);
-      methodVisitor.visitInsn(ATHROW);
-      methodVisitor.visitLabel(label5);
-      methodVisitor.visitLineNumber(54, label5);
-      methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(1, 2);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor =
-          classWriter.visitMethod(
-              ACC_PUBLIC | ACC_STATIC,
               "main",
               "([Ljava/lang/String;)V",
               null,
@@ -523,7 +386,7 @@
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(57, label0);
+      methodVisitor.visitLineNumber(36, label0);
       methodVisitor.visitTypeInsn(NEW, "java/util/concurrent/SubmissionPublisher");
       methodVisitor.visitInsn(DUP);
       methodVisitor.visitMethodInsn(
@@ -531,14 +394,14 @@
       methodVisitor.visitVarInsn(ASTORE, 1);
       Label label1 = new Label();
       methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(58, label1);
+      methodVisitor.visitLineNumber(37, label1);
       methodVisitor.visitTypeInsn(NEW, "MySubscriber");
       methodVisitor.visitInsn(DUP);
       methodVisitor.visitMethodInsn(INVOKESPECIAL, "MySubscriber", "<init>", "()V", false);
       methodVisitor.visitVarInsn(ASTORE, 2);
       Label label2 = new Label();
       methodVisitor.visitLabel(label2);
-      methodVisitor.visitLineNumber(59, label2);
+      methodVisitor.visitLineNumber(38, label2);
       methodVisitor.visitVarInsn(ALOAD, 1);
       methodVisitor.visitVarInsn(ALOAD, 2);
       methodVisitor.visitMethodInsn(
@@ -549,7 +412,7 @@
           false);
       Label label3 = new Label();
       methodVisitor.visitLabel(label3);
-      methodVisitor.visitLineNumber(60, label3);
+      methodVisitor.visitLineNumber(39, label3);
       methodVisitor.visitLdcInsn("1");
       methodVisitor.visitLdcInsn("2");
       methodVisitor.visitLdcInsn("3");
@@ -562,7 +425,7 @@
       methodVisitor.visitVarInsn(ASTORE, 3);
       Label label4 = new Label();
       methodVisitor.visitLabel(label4);
-      methodVisitor.visitLineNumber(62, label4);
+      methodVisitor.visitLineNumber(41, label4);
       methodVisitor.visitVarInsn(ALOAD, 3);
       methodVisitor.visitVarInsn(ALOAD, 1);
       methodVisitor.visitInsn(DUP);
@@ -596,24 +459,20 @@
           INVOKEINTERFACE, "java/util/List", "forEach", "(Ljava/util/function/Consumer;)V", true);
       Label label5 = new Label();
       methodVisitor.visitLabel(label5);
-      methodVisitor.visitLineNumber(63, label5);
+      methodVisitor.visitLineNumber(42, label5);
       methodVisitor.visitVarInsn(ALOAD, 1);
       methodVisitor.visitMethodInsn(
           INVOKEVIRTUAL, "java/util/concurrent/SubmissionPublisher", "close", "()V", false);
       Label label6 = new Label();
       methodVisitor.visitLabel(label6);
-      methodVisitor.visitLineNumber(65, label6);
+      methodVisitor.visitLineNumber(44, label6);
       methodVisitor.visitFieldInsn(
-          GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+          GETSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/CountDownLatch;");
       methodVisitor.visitMethodInsn(
-          INVOKESTATIC,
-          "MySubscriber",
-          "awaitCondition",
-          "(Ljava/util/concurrent/locks/Condition;)V",
-          false);
+          INVOKEVIRTUAL, "java/util/concurrent/CountDownLatch", "await", "()V", false);
       Label label7 = new Label();
       methodVisitor.visitLabel(label7);
-      methodVisitor.visitLineNumber(66, label7);
+      methodVisitor.visitLineNumber(45, label7);
       methodVisitor.visitInsn(RETURN);
       methodVisitor.visitMaxs(3, 4);
       methodVisitor.visitEnd();
@@ -623,28 +482,16 @@
       methodVisitor.visitCode();
       Label label0 = new Label();
       methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(11, label0);
-      methodVisitor.visitTypeInsn(NEW, "java/util/concurrent/locks/ReentrantLock");
+      methodVisitor.visitLineNumber(9, label0);
+      methodVisitor.visitTypeInsn(NEW, "java/util/concurrent/CountDownLatch");
       methodVisitor.visitInsn(DUP);
+      methodVisitor.visitInsn(ICONST_1);
       methodVisitor.visitMethodInsn(
-          INVOKESPECIAL, "java/util/concurrent/locks/ReentrantLock", "<init>", "()V", false);
+          INVOKESPECIAL, "java/util/concurrent/CountDownLatch", "<init>", "(I)V", false);
       methodVisitor.visitFieldInsn(
-          PUTSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(12, label1);
-      methodVisitor.visitFieldInsn(
-          GETSTATIC, "MySubscriber", "lock", "Ljava/util/concurrent/locks/Lock;");
-      methodVisitor.visitMethodInsn(
-          INVOKEINTERFACE,
-          "java/util/concurrent/locks/Lock",
-          "newCondition",
-          "()Ljava/util/concurrent/locks/Condition;",
-          true);
-      methodVisitor.visitFieldInsn(
-          PUTSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/locks/Condition;");
+          PUTSTATIC, "MySubscriber", "done", "Ljava/util/concurrent/CountDownLatch;");
       methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(2, 0);
+      methodVisitor.visitMaxs(3, 0);
       methodVisitor.visitEnd();
     }
     classWriter.visitEnd();
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 2ada4dd..c988660 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -88,7 +89,7 @@
   @Override
   public D8TestBuilder enableCoreLibraryDesugaring(
       AndroidApiLevel minApiLevel,
-      StringConsumer keepRuleConsumer,
+      KeepRuleConsumer keepRuleConsumer,
       StringResource desugaredLibraryConfiguration) {
     if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
       super.enableCoreLibraryDesugaring(
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 73c4623..85e7ca6 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -181,7 +181,10 @@
 
       ProcessBuilder processBuilder = new ProcessBuilder(command);
       ProcessResult processResult = ToolHelper.runProcess(processBuilder, getStdoutForTesting());
-      assertEquals(processResult.stderr, 0, processResult.exitCode);
+      assertEquals(
+          "STDOUT\n:" + processResult.stdout + "\nSTDERR:\n" + processResult.stderr,
+          0,
+          processResult.exitCode);
       String proguardMap =
           proguardMapFile.toFile().exists()
               ? FileUtils.readTextFile(proguardMapFile, Charsets.UTF_8)
diff --git a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
index 396e9e0..111b040 100644
--- a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
+++ b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
@@ -137,13 +137,12 @@
       return this;
     }
 
-    public Builder setKeepRuleConsumer(StringConsumer keepRuleConsumer) {
+    public Builder setKeepRuleConsumer(KeepRuleConsumer keepRuleConsumer) {
       withKeepRuleConsumer = false;
       if (keepRuleConsumer == null) {
         this.keepRuleConsumer = null;
       } else {
-        assert keepRuleConsumer instanceof KeepRuleConsumer;
-        this.keepRuleConsumer = (KeepRuleConsumer) keepRuleConsumer;
+        this.keepRuleConsumer = keepRuleConsumer;
       }
       return this;
     }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index d30e214..2db1e9d 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
 import com.android.tools.r8.errors.Unreachable;
@@ -651,7 +652,7 @@
   @Override
   public T enableCoreLibraryDesugaring(
       AndroidApiLevel minApiLevel,
-      StringConsumer keepRuleConsumer,
+      KeepRuleConsumer keepRuleConsumer,
       StringResource desugaredLibraryConfiguration) {
     if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
       super.enableCoreLibraryDesugaring(
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 3702430..fbe6f27 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.testing.AndroidBuildVersion;
@@ -457,7 +458,7 @@
   }
 
   public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minApiLevel, StringConsumer keepRuleConsumer) {
+      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
     return enableCoreLibraryDesugaring(
         minApiLevel,
         keepRuleConsumer,
@@ -471,7 +472,7 @@
 
   public T enableCoreLibraryDesugaring(
       AndroidApiLevel minApiLevel,
-      StringConsumer keepRuleConsumer,
+      KeepRuleConsumer keepRuleConsumer,
       StringResource desugaredLibraryConfiguration) {
     assert minApiLevel.getLevel() < AndroidApiLevel.O.getLevel();
     return enableLibraryDesugaring(
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index fddd0d4..375f6f8 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.TestRuntime.NoneRuntime;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -62,10 +64,39 @@
     return withCfRuntimeFilter(vm -> vm == runtime);
   }
 
+  /**
+   * Test using the same runtime as the test is executing under.
+   *
+   * <p>This should only be used when there is an explicit dependency in the test that requires the
+   * host and test to be the same. For example, it could be to fork a subprocess using the same
+   * runtime.
+   */
   public TestParametersBuilder withSystemRuntime() {
     return withCfRuntimeFilter(TestParametersBuilder::isSystemJdk);
   }
 
+  /**
+   * Test using the default DEX VM.
+   *
+   * <p>Generally tests should rather use withDexRuntimes(), but if a test really only needs to be
+   * tested on a single DEX runtime this can be used instead. The test should not have any
+   * requirements as to which VM it is as the default will change and may not track latest.
+   */
+  public TestParametersBuilder withDefaultDexRuntime() {
+    return withDexRuntime(DexRuntime.getDefaultDexRuntime().getVersion());
+  }
+
+  /**
+   * Test using the default CF VM.
+   *
+   * <p>Generally tests should rather use withCfRuntimes(), but if a test really only needs to be
+   * tested on a single CF runtime this can be used instead. The test should not have any
+   * requirements as to which VM it is as the default will change and may not track latest.
+   */
+  public TestParametersBuilder withDefaultCfRuntime() {
+    return withCfRuntime(CfRuntime.getDefaultCfRuntime().getVm());
+  }
+
   /** Add all available CF runtimes. */
   public TestParametersBuilder withCfRuntimes() {
     return withCfRuntimeFilter(vm -> true);
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 20d71c6..a4b8697 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -32,6 +32,9 @@
     JDK17("jdk17", 61),
     ;
 
+    /** This should generally be the latest checked in CF runtime we fully support. */
+    private static final CfVm DEFAULT = JDK9;
+
     private final String name;
     private final int classfileVersion;
 
@@ -152,6 +155,14 @@
     return getCheckedInJdk9();
   }
 
+  public static CfRuntime getDefaultCfRuntime() {
+    return TestRuntime.getCheckedInJdk(CfVm.DEFAULT);
+  }
+
+  public static DexRuntime getDefaultDexRuntime() {
+    return new DexRuntime(DexVm.Version.NEW_DEFAULT);
+  }
+
   public static List<TestRuntime> getCheckedInRuntimes() {
     return ImmutableList.<TestRuntime>builder()
         .addAll(getCheckedInCfRuntimes())
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 033c3f4..11dfa2d 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -252,9 +252,9 @@
     ART_6_0_1_HOST(Version.V6_0_1, Kind.HOST),
     ART_7_0_0_TARGET(Version.V7_0_0, Kind.TARGET),
     ART_7_0_0_HOST(Version.V7_0_0, Kind.HOST),
+    ART_DEFAULT(Version.DEFAULT, Kind.HOST),
     ART_8_1_0_TARGET(Version.V8_1_0, Kind.TARGET),
     ART_8_1_0_HOST(Version.V8_1_0, Kind.HOST),
-    ART_DEFAULT(Version.DEFAULT, Kind.HOST),
     ART_9_0_0_TARGET(Version.V9_0_0, Kind.TARGET),
     ART_9_0_0_HOST(Version.V9_0_0, Kind.HOST),
     ART_10_0_0_TARGET(Version.V10_0_0, Kind.TARGET),
@@ -273,11 +273,16 @@
       V6_0_1("6.0.1"),
       V7_0_0("7.0.0"),
       V8_1_0("8.1.0"),
+      // TODO(b//204855476): Remove DEFAULT.
       DEFAULT("default"),
       V9_0_0("9.0.0"),
       V10_0_0("10.0.0"),
       V12_0_0("12.0.0");
 
+      /** This should generally be the latest DEX VM fully supported. */
+      // TODO(b/204855476): Rename to DEFAULT alias once the checked in VM is removed.
+      public static final Version NEW_DEFAULT = DEFAULT;
+
       Version(String shortName) {
         this.shortName = shortName;
       }
@@ -287,7 +292,7 @@
       }
 
       public boolean isDefault() {
-        return this == DEFAULT;
+        return this == NEW_DEFAULT;
       }
 
       public boolean isLatest() {
@@ -995,8 +1000,6 @@
 
   public static AndroidApiLevel getMinApiLevelForDexVm(DexVm dexVm) {
     switch (dexVm.version) {
-      case DEFAULT:
-        return AndroidApiLevel.O;
       case V12_0_0:
         return AndroidApiLevel.S;
       case V10_0_0:
@@ -1005,6 +1008,8 @@
         return AndroidApiLevel.P;
       case V8_1_0:
         return AndroidApiLevel.O_MR1;
+      case DEFAULT:
+        return AndroidApiLevel.O;
       case V7_0_0:
         return AndroidApiLevel.N;
       case V6_0_1:
@@ -1377,21 +1382,24 @@
     return pb;
   }
 
+  @Deprecated
   public static ProcessResult runJava(Class clazz) throws Exception {
     String main = clazz.getTypeName();
     Path path = getClassPathForTests();
     return runJava(path, main);
   }
 
+  @Deprecated
   public static ProcessResult runJava(Path classpath, String... args) throws IOException {
     return runJava(ImmutableList.of(classpath), args);
   }
 
+  @Deprecated
   public static ProcessResult runJava(List<Path> classpath, String... args) throws IOException {
     return runJava(ImmutableList.of(), classpath, args);
   }
 
-  public static ProcessResult runJava(List<String> vmArgs, List<Path> classpath, String... args)
+  private static ProcessResult runJava(List<String> vmArgs, List<Path> classpath, String... args)
       throws IOException {
     return runJava(TestRuntime.getSystemRuntime().asCf(), vmArgs, classpath, args);
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 19e3299..88d53c1 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -47,7 +47,7 @@
       Paths.get(ToolHelper.RESOURCES_DIR, "api_database", "api_database_api_level.ser");
   private static final Path API_DATABASE_AMBIGUOUS =
       Paths.get(ToolHelper.RESOURCES_DIR, "api_database", "api_database_ambiguous.txt");
-  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.R;
+  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.S;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -107,9 +107,9 @@
               }));
         });
     // These numbers will change when updating api-versions.xml
-    assertEquals(4742, parsedApiClasses.size());
-    assertEquals(25144, numberOfFields.get());
-    assertEquals(38661, numberOfMethods.get());
+    assertEquals(5037, parsedApiClasses.size());
+    assertEquals(26362, numberOfFields.get());
+    assertEquals(40416, numberOfMethods.get());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGeneratorTest.java
index 299a25a..40b57c5 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiObjectDatabaseBuilderGeneratorTest.java
@@ -55,7 +55,7 @@
       Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-versions", "api-versions.xml");
   private static final Path API_DATABASE_JAR =
       Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-database", "api-database.jar");
-  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.R;
+  private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.S;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -108,9 +108,9 @@
               }));
         });
     // These numbers will change when updating api-versions.xml
-    assertEquals(4742, parsedApiClasses.size());
-    assertEquals(25144, numberOfFields.get());
-    assertEquals(38661, numberOfMethods.get());
+    assertEquals(5037, parsedApiClasses.size());
+    assertEquals(26362, numberOfFields.get());
+    assertEquals(40416, numberOfMethods.get());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index 76a4d56..80e9aef 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -26,7 +26,7 @@
 
 public abstract class ApiModelingTestHelper {
 
-  static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
+  public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
       ThrowableConsumer<T> setMockApiLevelForMethod(Method method, AndroidApiLevel apiLevel) {
     return compilerBuilder -> {
       compilerBuilder.addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
index da83514..c5284c0 100644
--- a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
+++ b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.StringUtils;
@@ -80,19 +81,20 @@
 
   @Test
   public void testWithVersionUpgrade() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClassFileData(getDowngradedClass(Runner.class))
-        // Here the main class is not downgraded, thus the output may upgrade to that version.
-        .addProgramClasses(TestClass.class)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepMainRule(TestClass.class)
-        // We cannot keep class Runner, as that prohibits getClass optimization.
-        // Instead disable minification and inlining of the Runner class and method.
-        .noMinification()
-        .enableInliningAnnotations()
-        .enableNeverClassInliningAnnotations()
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED)
+    R8TestRunResult run =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(getDowngradedClass(Runner.class))
+            // Here the main class is not downgraded, thus the output may upgrade to that version.
+            .addProgramClasses(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepMainRule(TestClass.class)
+            // We cannot keep class Runner, as that prohibits getClass optimization.
+            // Instead disable minification and inlining of the Runner class and method.
+            .noMinification()
+            .enableInliningAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .run(parameters.getRuntime(), TestClass.class);
+    run.assertSuccessWithOutput(EXPECTED)
         .inspect(
             inspector -> {
               if (parameters.isCfRuntime()) {
@@ -101,7 +103,11 @@
                 assertTrue(CfVersion.V1_4.isLessThan(cfVersionForRuntime));
                 // Check that the downgraded class has been bumped to at least SE 1.5 (version 49).
                 CfVersion cfVersionAfterUpgrade = getVersion(inspector, Runner.class);
-                assertTrue(CfVersion.V1_4.isLessThan(cfVersionAfterUpgrade));
+                boolean lessThan = CfVersion.V1_4.isLessThan(cfVersionAfterUpgrade);
+                if (!lessThan) {
+                  run.disassemble();
+                }
+                assertTrue("Got version: " + cfVersionAfterUpgrade, lessThan);
               }
               // Check that the method uses a const class instruction.
               assertTrue(
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 d1dc197..cdecb76 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
@@ -64,8 +64,8 @@
   @BeforeClass
   public static void beforeAll() throws Exception {
     if (data().stream().count() > 0) {
-      r8R8Debug = compileR8(CompilationMode.DEBUG);
       r8R8Release = compileR8(CompilationMode.RELEASE);
+      r8R8Debug = compileR8(CompilationMode.DEBUG);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 699bb50..407165c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.utils.AndroidApp.Builder;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -241,6 +242,7 @@
     String main = "classmerging.ConflictInGeneratedNameTest";
     Path[] programFiles =
         new Path[] {
+          CF_DIR.resolve("NeverPropagateValue.class"),
           CF_DIR.resolve("ConflictInGeneratedNameTest.class"),
           CF_DIR.resolve("ConflictInGeneratedNameTest$A.class"),
           CF_DIR.resolve("ConflictInGeneratedNameTest$B.class")
@@ -254,7 +256,6 @@
                 testForR8(parameters.getBackend())
                     .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
                     .addOptionsModification(this::configure)
-                    .addOptionsModification(options -> options.enableValuePropagation = false)
                     .addOptionsModification(
                         options ->
                             options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
@@ -1065,15 +1066,17 @@
         };
     // SimpleInterface cannot be merged into SimpleInterfaceImpl because SimpleInterfaceImpl
     // is in a different package and is not public.
-    ImmutableSet<String> preservedClassNames =
-        ImmutableSet.of(
+    Set<String> preservedClassNames =
+        SetUtils.newHashSet(
             "classmerging.SimpleInterfaceAccessTest",
-            "classmerging.SimpleInterfaceAccessTest$1",
             "classmerging.SimpleInterfaceAccessTest$SimpleInterface",
             "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterface",
             "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl",
             "classmerging.pkg.SimpleInterfaceImplRetriever",
             "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
+    if (parameters.isCfRuntime()) {
+      preservedClassNames.add("classmerging.SimpleInterfaceAccessTest$1");
+    }
     runTest(
         testForR8(parameters.getBackend())
             .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index baa98a2..5232896 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -1072,8 +1072,7 @@
               artCommandBuilder.appendArtOption("-Xcompiler-option");
               artCommandBuilder.appendArtOption("--debuggable");
             }
-            if (ToolHelper.getDexVm().getVersion().isNewerThanOrEqual(DexVm.Version.V9_0_0)
-                && ToolHelper.getDexVm().getVersion() != DexVm.Version.DEFAULT) {
+            if (ToolHelper.getDexVm().getVersion().isNewerThanOrEqual(DexVm.Version.V9_0_0)) {
               artCommandBuilder.appendArtOption("-XjdwpProvider:internal");
             }
             if (DEBUG_TESTS && ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4)) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index f75e870..4ab080f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.L8Command;
 import com.android.tools.r8.L8TestBuilder;
 import com.android.tools.r8.LibraryDesugaringTestConfiguration;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.TestState;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfigurationParser;
 import com.android.tools.r8.tracereferences.TraceReferences;
@@ -318,6 +320,21 @@
   public interface KeepRuleConsumer extends StringConsumer {
 
     String get();
+
+    static KeepRuleConsumer emptyConsumer() {
+      return new KeepRuleConsumer() {
+
+        @Override
+        public String get() {
+          throw new Unreachable();
+        }
+
+        @Override
+        public void accept(String string, DiagnosticsHandler handler) {
+          // Intentionally empty.
+        }
+      };
+    }
   }
 
   protected static class ClassFileInfo {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index e1889a8..f86dacf 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingSupplier;
 import com.android.tools.r8.utils.codeinspector.CheckCastInstructionSubject;
@@ -98,15 +99,19 @@
     String expectedInstanceOfTypes;
     if (parameters.getApiLevel().getLevel() >= 26) {
       expectedInvokeHolders =
-          ImmutableSet.of(
-              "java.time.Clock", "java.time.LocalDate", "java.time.ZoneOffset", "java.time.ZoneId");
+          SetUtils.newHashSet("java.time.Clock", "java.time.LocalDate", "java.time.ZoneId");
+      if (!isR8) {
+        expectedInvokeHolders.add("java.time.ZoneOffset");
+      }
       expectedCatchGuards = ImmutableSet.of("java.time.format.DateTimeParseException");
       expectedCheckCastType = ImmutableSet.of("java.time.ZoneId");
       expectedInstanceOfTypes = "java.time.ZoneOffset";
     } else {
       expectedInvokeHolders =
-          ImmutableSet.of(
-              "j$.time.Clock", "j$.time.LocalDate", "j$.time.ZoneOffset", "j$.time.ZoneId");
+          SetUtils.newHashSet("j$.time.Clock", "j$.time.LocalDate", "j$.time.ZoneId");
+      if (!isR8) {
+        expectedInvokeHolders.add("j$.time.ZoneOffset");
+      }
       expectedCatchGuards = ImmutableSet.of("j$.time.format.DateTimeParseException");
       expectedCheckCastType = ImmutableSet.of("j$.time.ZoneId");
       expectedInstanceOfTypes = "j$.time.ZoneOffset";
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramInterfaceWithLibraryMethod.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramInterfaceWithLibraryMethod.java
new file mode 100644
index 0000000..089f18a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramInterfaceWithLibraryMethod.java
@@ -0,0 +1,167 @@
+// Copyright (c) 2021, 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.desugar.desugaredlibrary;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// See b/204518518.
+@RunWith(Parameterized.class)
+public class ProgramInterfaceWithLibraryMethod extends DesugaredLibraryTestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  private static final String EXPECTED_RESULT = StringUtils.lines("Hello, world!");
+  private static Path CUSTOM_LIB_DEX;
+  private static Path CUSTOM_LIB_CF;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  @BeforeClass
+  public static void compileCustomLib() throws Exception {
+    CUSTOM_LIB_DEX = getStaticTemp().newFolder().toPath().resolve("customLibDex.jar");
+    testForD8(getStaticTemp())
+        .addProgramClasses(LibraryClass.class)
+        .setMinApi(AndroidApiLevel.B)
+        .compile()
+        .writeToZip(CUSTOM_LIB_DEX);
+    CUSTOM_LIB_CF = getStaticTemp().newFolder().toPath().resolve("customLibCf.jar");
+    ZipBuilder.builder(CUSTOM_LIB_CF)
+        .addBytes(
+            DescriptorUtils.getPathFromJavaType(LibraryClass.class),
+            Files.readAllBytes(ToolHelper.getClassFileForTestClass(LibraryClass.class)))
+        .build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addLibraryClasses(LibraryClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class, ProgramInterface.class, ProgramClass.class)
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
+        .compile()
+        .addRunClasspathFiles(CUSTOM_LIB_DEX)
+        .run(parameters.getRuntime(), Executor.class)
+        .applyIf(
+            parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+            r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class));
+  }
+
+  @Test
+  public void testD8CfToCf() throws Exception {
+    Path jar =
+        testForD8(Backend.CF)
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+            .addLibraryClasses(LibraryClass.class)
+            .addProgramClasses(Executor.class, ProgramInterface.class, ProgramClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(
+                LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
+            .compile()
+            .writeToZip();
+    if (parameters.getRuntime().isDex()) {
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary, parameters.getApiLevel())
+          .addRunClasspathFiles(CUSTOM_LIB_DEX)
+          .run(parameters.getRuntime(), Executor.class)
+          .applyIf(
+              parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
+              r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+              r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class));
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
+          .addRunClasspathFiles(CUSTOM_LIB_CF)
+          .run(parameters.getRuntime(), Executor.class)
+          .applyIf(
+              parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
+              r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+              r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class));
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+        .addLibraryClasses(LibraryClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(Executor.class, ProgramInterface.class, ProgramClass.class)
+        .addKeepMainRule(Executor.class)
+        .enableCoreLibraryDesugaring(
+            LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
+        .compile()
+        .addRunClasspathFiles(parameters.isDexRuntime() ? CUSTOM_LIB_DEX : CUSTOM_LIB_CF)
+        .run(parameters.getRuntime(), Executor.class)
+        .applyIf(
+            parameters.isDexRuntime()
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+            r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      invoke(new ProgramClass());
+    }
+
+    static void invoke(ProgramInterface i) {
+      i.methodTakingConsumer(null);
+    }
+  }
+
+  interface ProgramInterface {
+    void methodTakingConsumer(Consumer<String> consumer);
+  }
+
+  static class ProgramClass extends LibraryClass implements ProgramInterface {
+    // TODO(b/204518518): Adding this forwarding method fixes the issue.
+    // public void methodTakingConsumer(Consumer<String> consumer) {
+    //   super.methodTakingConsumer(consumer);
+    // }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class LibraryClass {
+
+    public void methodTakingConsumer(Consumer<String> consumer) {
+      System.out.println("Hello, world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
index 58cf498..832e72d 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
@@ -154,6 +154,7 @@
         .addOptionsModification(options -> options.enableNestReduction = false)
         .addProgramFiles(JAR)
         .addInliningAnnotations()
+        .addMemberValuePropagationAnnotations()
         .setMinApi(minApi)
         .compile();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
index bc389c9..735e59c 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
@@ -136,7 +136,6 @@
         .addOptionsModification(
             options -> {
               // Disable optimizations else additional classes are removed since they become unused.
-              options.enableValuePropagation = false;
               options.enableClassInlining = false;
             })
         .addProgramFiles(classesMatching(outerNestName))
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java
index 3e8fc2a..c3806df 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestClassMergingTest.java
@@ -93,7 +93,6 @@
                 options -> {
                   // Disable optimizations else additional classes are removed since they become
                   // unused.
-                  options.enableValuePropagation = false;
                   options.enableClassInlining = false;
                   options.enableNestReduction = false;
                 })
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
index 726f391..8eb08f4 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
@@ -58,11 +58,11 @@
         .noMinification()
         .addOptionsModification(
             options -> {
-              options.enableValuePropagation = false;
               options.enableClassInlining = false;
               options.enableVerticalClassMerging = false;
             })
         .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
         .addProgramFiles(toCompile)
         .compile()
         .inspect(this::assertMethodsInlined)
diff --git a/src/test/java/com/android/tools/r8/examples/newarray/NewArray.java b/src/test/java/com/android/tools/r8/examples/newarray/NewArray.java
index b1f2693..2f57c56 100644
--- a/src/test/java/com/android/tools/r8/examples/newarray/NewArray.java
+++ b/src/test/java/com/android/tools/r8/examples/newarray/NewArray.java
@@ -16,7 +16,40 @@
   }
 
   public static void printArray(int[] array) {
-    for (int i : array) System.out.println(i);
+    System.out.print("[");
+    if (array.length > 0) {
+      System.out.print(array[0]);
+      for (int i = 1; i < array.length; i++) {
+        System.out.print(",");
+        System.out.print(i);
+      }
+    }
+    System.out.println("]");
+  }
+
+  static void printIntermediate(boolean b) {
+    System.out.print(b);
+    System.out.print(",");
+  }
+
+  static void printIntermediate(double d) {
+    System.out.print(d);
+    System.out.print(",");
+  }
+
+  static void printIntermediate(float f) {
+    System.out.print(f);
+    System.out.print(",");
+  }
+
+  static void printIntermediate(int i) {
+    System.out.print(i);
+    System.out.print(",");
+  }
+
+  static void printIntermediate(long l) {
+    System.out.print(l);
+    System.out.print(",");
   }
 
   public static void test() {
@@ -88,24 +121,24 @@
     int[][][][] i4 = new int[n][n][n][n];
     int[][][][][] i5 = new int[n][n][n][n][n];
     int[][][][][][] i6 = new int[n][n][n][n][n][n];
-    System.out.println(i2.length);
-    System.out.println(i3.length);
-    System.out.println(i4.length);
-    System.out.println(i5.length);
+    printIntermediate(i2.length);
+    printIntermediate(i3.length);
+    printIntermediate(i4.length);
+    printIntermediate(i5.length);
     System.out.println(i6.length);
   }
 
   public static void newMultiDimensionalArrays2(int n1, int n2, int n3, int n4, int n5, int n6) {
     int[][] i2 = new int[n1][n2];
-    System.out.println(i2.length);
+    printIntermediate(i2.length);
     int[][][] i3 = new int[n1][n2][n3];
-    System.out.println(i3.length);
+    printIntermediate(i3.length);
     int[][][][] i4 = new int[n1][n2][n3][n4];
-    System.out.println(i4.length);
+    printIntermediate(i4.length);
     int[][][][][] i5 = new int[n1][n2][n3][n4][n5];
-    System.out.println(i5.length);
+    printIntermediate(i5.length);
     int[][][][][][] i6 = new int[n1][n2][n3][n4][n5][n6];
-    System.out.println(i6.length);
+    printIntermediate(i6.length);
     int[][][][][][] i7 = new int[n1][n2][n1][n4][n5][n1];
     System.out.println(i7.length);
   }
@@ -115,9 +148,9 @@
     int[][][][] i4 = new int[n][n][][];
     int[][][][][][][] i7 = new int[n][n][n][n][n][n][];
     int[][][][][][][][] i8 = new int[n][n][n][n][n][n][][];
-    System.out.println(i3.length);
-    System.out.println(i4.length);
-    System.out.println(i7.length);
+    printIntermediate(i3.length);
+    printIntermediate(i4.length);
+    printIntermediate(i7.length);
     System.out.println(i8.length);
   }
 
@@ -130,21 +163,21 @@
     float[][] a6 = new float[11][12];
     double[][] a7 = new double[13][14];
     A[][] a8 = new A[15][16];
-    System.out.println(a1[0].length);
-    System.out.println(a2[0].length);
-    System.out.println(a3[0].length);
-    System.out.println(a4[0].length);
-    System.out.println(a5[0].length);
-    System.out.println(a6[0].length);
-    System.out.println(a7[0].length);
-    System.out.println(a8[0].length);
-    System.out.println(a1[0][0]);
-    System.out.println(a2[0][0]);
-    System.out.println(a3[0][0]);
-    System.out.println(a4[0][0]);
-    System.out.println(a5[0][0]);
-    System.out.println(a6[0][0]);
-    System.out.println(a7[0][0]);
+    printIntermediate(a1[0].length);
+    printIntermediate(a2[0].length);
+    printIntermediate(a3[0].length);
+    printIntermediate(a4[0].length);
+    printIntermediate(a5[0].length);
+    printIntermediate(a6[0].length);
+    printIntermediate(a7[0].length);
+    printIntermediate(a8[0].length);
+    printIntermediate(a1[0][0]);
+    printIntermediate(a2[0][0]);
+    printIntermediate(a3[0][0]);
+    printIntermediate(a4[0][0]);
+    printIntermediate(a5[0][0]);
+    printIntermediate(a6[0][0]);
+    printIntermediate(a7[0][0]);
     System.out.println(a8[0][0]);
   }
 
diff --git a/src/test/java/com/android/tools/r8/examples/newarray/NewArrayTestRunner.java b/src/test/java/com/android/tools/r8/examples/newarray/NewArrayTestRunner.java
index 676f7dd..d980c97 100644
--- a/src/test/java/com/android/tools/r8/examples/newarray/NewArrayTestRunner.java
+++ b/src/test/java/com/android/tools/r8/examples/newarray/NewArrayTestRunner.java
@@ -8,9 +8,8 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -23,7 +22,39 @@
   private final TestParameters parameters;
   private final CompilationMode mode;
 
-  private static String referenceOut;
+  private static final List<String> EXPECTED =
+      ImmutableList.of(
+          "[]",
+          "[0]",
+          "[0,1]",
+          "[0,1,2]",
+          "[0,1,2,3]",
+          "[0,1,2,3,4]",
+          "[0,1,2,3,4,5]",
+          "[0,1,2,3,4,5,6]",
+          "[0,1,2,3,4,5,6,7]",
+          "[0,1,2,3,4,5,6,7]",
+          "[]",
+          "[0]",
+          "[0,1]",
+          "[0,1,2]",
+          "[0,1,2,3]",
+          "[0,1,2,3,4]",
+          "[0,1,2,3,4,5]",
+          "[0,1,2,3,4,5,6,7,8,9,10]",
+          "[]",
+          "[0]",
+          "[0,1]",
+          "[0,1,2]",
+          "[0,1,2,3]",
+          "[0,1,2,3,4]",
+          "[0,1,2,3,4,5]",
+          "[0,1,2,3,4,5,6]",
+          "[0,1,2,3,4,5,6,7]",
+          "6,6,6,6,6",
+          "1,1,1,1,1,1",
+          "8,8,8,8",
+          "2,4,6,8,10,12,14,16,false,0,0,0,0,0.0,0.0,null");
 
   @Parameterized.Parameters(name = "{0}, {1}")
   public static List<Object[]> data() {
@@ -36,14 +67,13 @@
     this.mode = mode;
   }
 
-  @BeforeClass
-  public static void runReference() throws Exception {
-    referenceOut =
-        testForJvm(getStaticTemp())
-            .addProgramClassesAndInnerClasses(CLASS)
-            .run(TestRuntime.getDefaultJavaRuntime(), CLASS)
-            .assertSuccess()
-            .getStdOut();
+  @Test
+  public void runReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime() && mode == CompilationMode.DEBUG);
+    testForJvm(getStaticTemp())
+        .addProgramClassesAndInnerClasses(CLASS)
+        .run(parameters.getRuntime(), CLASS)
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   @Test
@@ -54,7 +84,7 @@
         .setMinApi(parameters.getApiLevel())
         .setMode(mode)
         .run(parameters.getRuntime(), CLASS)
-        .assertSuccessWithOutput(referenceOut);
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   @Test
@@ -65,6 +95,6 @@
         .setMinApi(parameters.getApiLevel())
         .setMode(mode)
         .run(parameters.getRuntime(), CLASS)
-        .assertSuccessWithOutput(referenceOut);
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java b/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
index 4f71913..9b85698 100644
--- a/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
@@ -8,7 +8,7 @@
 
 import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
 import com.android.tools.r8.graph.DexDebugEvent.Default;
-import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
@@ -133,9 +133,9 @@
     List<DexDebugEvent> events = new ArrayList<>();
     DexDebugEventBuilder.emitAdvancementEvents(
         pc,
-        Position.builder().setLine(line).setMethod(method).build(),
+        SourcePosition.builder().setLine(line).setMethod(method).build(),
         nextPc,
-        Position.builder().setLine(nextLine).setMethod(method).build(),
+        SourcePosition.builder().setLine(nextLine).setMethod(method).build(),
         events,
         factory,
         false);
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
index 3f5ad48..97d8b7a 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreLatestTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AssertionUtils;
 import com.google.common.collect.Sets;
@@ -39,10 +38,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntime(Version.DEFAULT)
-        .withApiLevel(AndroidApiLevel.L)
-        .build();
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.L).build();
   }
 
   public GMSCoreLatestTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
index 4c9b99f..9fe8e54 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreV10Test.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.Sets;
 import java.nio.file.Path;
@@ -42,10 +41,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntime(Version.DEFAULT)
-        .withApiLevel(AndroidApiLevel.L)
-        .build();
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.L).build();
   }
 
   public GMSCoreV10Test(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/internal/Regression127524985.java b/src/test/java/com/android/tools/r8/internal/Regression127524985.java
index e5e6dcd..a39814c 100644
--- a/src/test/java/com/android/tools/r8/internal/Regression127524985.java
+++ b/src/test/java/com/android/tools/r8/internal/Regression127524985.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -27,11 +26,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters()
-        .withCfRuntimes()
-        .withDexRuntime(Version.DEFAULT)
-        .withAllApiLevels()
-        .build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java
index b7ccb02..f1f134f 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java
@@ -20,13 +20,11 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -54,10 +52,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntime(Version.DEFAULT)
-        .withApiLevel(AndroidApiLevel.L)
-        .build();
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.L).build();
   }
 
   public YouTubeV1612Test(TestParameters parameters) {
@@ -85,7 +80,7 @@
   public void testProtoRewriting() throws Exception {
     assumeTrue(shouldRunSlowTests());
 
-    StringConsumer keepRuleConsumer = StringConsumer.emptyConsumer();
+    KeepRuleConsumer keepRuleConsumer = KeepRuleConsumer.emptyConsumer();
     R8TestCompileResult r8CompileResult =
         compileApplicationWithR8(
             keepRuleConsumer,
@@ -105,13 +100,13 @@
     reporter.failIfPendingErrors();
   }
 
-  private R8TestCompileResult compileApplicationWithR8(StringConsumer keepRuleConsumer)
+  private R8TestCompileResult compileApplicationWithR8(KeepRuleConsumer keepRuleConsumer)
       throws IOException, CompilationFailedException {
     return compileApplicationWithR8(keepRuleConsumer, ThrowableConsumer.empty());
   }
 
   private R8TestCompileResult compileApplicationWithR8(
-      StringConsumer keepRuleConsumer, ThrowableConsumer<R8FullTestBuilder> configuration)
+      KeepRuleConsumer keepRuleConsumer, ThrowableConsumer<R8FullTestBuilder> configuration)
       throws IOException, CompilationFailedException {
     return testForR8(parameters.getBackend())
         .addProgramFiles(getProgramFiles())
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
index 2030032..fc4fba0 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
@@ -20,13 +20,11 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -54,10 +52,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntime(Version.DEFAULT)
-        .withApiLevel(AndroidApiLevel.L)
-        .build();
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.L).build();
   }
 
   public YouTubeV1620Test(TestParameters parameters) {
@@ -85,7 +80,7 @@
   public void testProtoRewriting() throws Exception {
     assumeTrue(shouldRunSlowTests());
 
-    StringConsumer keepRuleConsumer = StringConsumer.emptyConsumer();
+    KeepRuleConsumer keepRuleConsumer = KeepRuleConsumer.emptyConsumer();
     R8TestCompileResult r8CompileResult =
         compileApplicationWithR8(
             keepRuleConsumer,
@@ -105,13 +100,13 @@
     reporter.failIfPendingErrors();
   }
 
-  private R8TestCompileResult compileApplicationWithR8(StringConsumer keepRuleConsumer)
+  private R8TestCompileResult compileApplicationWithR8(KeepRuleConsumer keepRuleConsumer)
       throws IOException, CompilationFailedException {
     return compileApplicationWithR8(keepRuleConsumer, ThrowableConsumer.empty());
   }
 
   private R8TestCompileResult compileApplicationWithR8(
-      StringConsumer keepRuleConsumer, ThrowableConsumer<R8FullTestBuilder> configuration)
+      KeepRuleConsumer keepRuleConsumer, ThrowableConsumer<R8FullTestBuilder> configuration)
       throws IOException, CompilationFailedException {
     return testForR8(parameters.getBackend())
         .addProgramFiles(getProgramFiles())
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
index d6a04df..c0f2478 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
@@ -13,7 +13,6 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -35,7 +34,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntime(Version.DEFAULT).withAllApiLevels().build();
+    return getTestParameters().withDefaultDexRuntime().withAllApiLevels().build();
   }
 
   public Proto2BuilderOnlyReferencedFromDynamicMethodTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index f8ae6c5..3f9e228 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -13,7 +13,6 @@
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.StringUtils;
@@ -58,7 +57,7 @@
                 "proto2.BuilderWithProtoSetterTestClass",
                 "proto2.BuilderWithReusedSettersTestClass",
                 "proto2.HasFlaggedOffExtensionBuilderTestClass")),
-        getTestParameters().withDexRuntime(Version.DEFAULT).withAllApiLevels().build());
+        getTestParameters().withDefaultDexRuntime().withAllApiLevels().build());
   }
 
   public Proto2BuilderShrinkingTest(List<String> mains, TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 5eda02a..fcc6f54 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -65,7 +64,7 @@
     return buildParameters(
         BooleanUtils.values(),
         BooleanUtils.values(),
-        getTestParameters().withDexRuntime(Version.DEFAULT).withAllApiLevels().build());
+        getTestParameters().withDefaultDexRuntime().withAllApiLevels().build());
   }
 
   public Proto2ShrinkingTest(
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index f641510..e2b8e51 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -13,7 +13,6 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -42,7 +41,7 @@
     return buildParameters(
         BooleanUtils.values(),
         BooleanUtils.values(),
-        getTestParameters().withDexRuntime(Version.DEFAULT).withAllApiLevels().build());
+        getTestParameters().withDefaultDexRuntime().withAllApiLevels().build());
   }
 
   public Proto3ShrinkingTest(
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index 252b11d..82bc605 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodProcessorWithWave;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
@@ -85,7 +86,7 @@
     OptimizationFeedbackMock feedback = new OptimizationFeedbackMock();
     FieldBitAccessAnalysis fieldBitAccessAnalysis = new FieldBitAccessAnalysis();
     FieldAccessAnalysis fieldAccessAnalysis =
-        new FieldAccessAnalysis(appView, null, fieldBitAccessAnalysis);
+        new FieldAccessAnalysis(appView, null, fieldBitAccessAnalysis, null);
 
     DexProgramClass clazz = appView.appInfo().classes().iterator().next();
     assertEquals(TestClass.class.getTypeName(), clazz.type.toSourceString());
@@ -93,7 +94,8 @@
     clazz.forEachProgramMethod(
         method -> {
           IRCode code = method.buildIR(appView);
-          fieldAccessAnalysis.recordFieldAccesses(code, feedback, new PrimaryMethodProcessorMock());
+          fieldAccessAnalysis.recordFieldAccesses(
+              code, BytecodeMetadataProvider.builder(), feedback, new PrimaryMethodProcessorMock());
         });
 
     int bitsReadInBitField = feedback.bitsReadPerField.getInt(uniqueFieldByName(clazz, "bitField"));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 2485f20..e4ee9bc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -75,7 +76,7 @@
     block.setNumber(basicBlockNumberGenerator.next());
 
     IRMetadata metadata = IRMetadata.unknown();
-    Position position = Position.testingPosition();
+    Position position = SyntheticPosition.builder().disableMethodCheck().setLine(0).build();
 
     Value v3 = new Value(3, TypeElement.getLong(), null);
     v3.setNeedsRegister(true);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 9fc16f8..868036c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
@@ -63,7 +64,7 @@
     // block2:
     //   return
     final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
-    Position position = Position.testingPosition();
+    Position position = SyntheticPosition.builder().setLine(0).disableMethodCheck().build();
     BasicBlock block2 = new BasicBlock();
     BasicBlock block0 =
         BasicBlock.createGotoBlock(basicBlockNumberGenerator.next(), position, metadata, block2);
@@ -127,7 +128,7 @@
     // block3:
     //   goto block3
     final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
-    Position position = Position.testingPosition();
+    Position position = SyntheticPosition.builder().setLine(0).disableMethodCheck().build();
     BasicBlock block0 = new BasicBlock();
     block0.setNumber(basicBlockNumberGenerator.next());
     BasicBlock block2 = new BasicBlock();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java
index be5c5a2..c9cc17b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java
@@ -30,7 +30,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public B135918413(TestParameters parameters) {
@@ -43,7 +43,7 @@
         .addInnerClasses(B135918413.class)
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
new file mode 100644
index 0000000..0c4e336
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2021, 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.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.FieldReadForWriteTest.R.anim;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FieldReadForWriteTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject animClassSubject = inspector.clazz(anim.class);
+              if (parameters.isCfRuntime()) {
+                assertThat(animClassSubject, isPresent());
+                assertThat(animClassSubject.uniqueFieldWithName("abc_fade_in"), isPresent());
+              } else {
+                assertThat(animClassSubject, isAbsent());
+              }
+            });
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      int packageId = args.length;
+      R.onResourcesLoaded(packageId);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class R {
+
+    static boolean sResourcesDidLoad;
+
+    @NoHorizontalClassMerging
+    static class anim {
+      public static int abc_fade_in = 0x7f010039;
+    }
+
+    static void onResourcesLoaded(int packageId) {
+      if (sResourcesDidLoad) {
+        return;
+      }
+      sResourcesDidLoad = true;
+      int packageIdTransform = (packageId ^ 0x7f) << 24;
+      onResourcesLoadedAnim(packageIdTransform);
+    }
+
+    static void onResourcesLoadedAnim(int packageIdTransform) {
+      // Arithmethic binop (add, div, mul, rem, sub).
+      anim.abc_fade_in += packageIdTransform;
+      anim.abc_fade_in /= packageIdTransform;
+      anim.abc_fade_in *= packageIdTransform;
+      anim.abc_fade_in %= packageIdTransform;
+      anim.abc_fade_in -= packageIdTransform;
+      // Logical binop (and, or, shl, shr, ush, xor).
+      anim.abc_fade_in &= packageIdTransform;
+      anim.abc_fade_in |= packageIdTransform;
+      anim.abc_fade_in <<= packageIdTransform;
+      anim.abc_fade_in >>= packageIdTransform;
+      anim.abc_fade_in >>>= packageIdTransform;
+      anim.abc_fade_in ^= packageIdTransform;
+      // Unop (number conversion, but also: inc, neg, not).
+      anim.abc_fade_in = (int) ((long) anim.abc_fade_in);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
index faf62b7..8943139 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import java.util.Deque;
 import java.util.Optional;
 import org.junit.Test;
@@ -46,7 +45,7 @@
         .assertSuccessWithOutputLines("42");
   }
 
-  private void waveModifier(Deque<SortedProgramMethodSet> waves) {
+  private void waveModifier(Deque<ProgramMethodSet> waves) {
     ProgramMethodSet initialWave = waves.getFirst();
     Optional<ProgramMethod> printFieldMethod =
         initialWave.stream()
@@ -55,7 +54,7 @@
     assertTrue(printFieldMethod.isPresent());
     initialWave.remove(printFieldMethod.get().getDefinition());
 
-    SortedProgramMethodSet lastWave = SortedProgramMethodSet.create();
+    ProgramMethodSet lastWave = ProgramMethodSet.create();
     lastWave.add(printFieldMethod.get());
     waves.addLast(lastWave);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
index f5b13eb..b493f1d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import org.junit.Test;
@@ -49,7 +49,7 @@
             options -> {
               options.testing.waveModifier =
                   (waves) -> {
-                    Function<String, Predicate<SortedProgramMethodSet>> wavePredicate =
+                    Function<String, Predicate<ProgramMethodSet>> wavePredicate =
                         methodName ->
                             wave ->
                                 wave.stream()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
index 1979d34..8a540ba 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineMappingInformationTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.ir.optimize.outliner;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
@@ -75,6 +74,7 @@
             HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
+        .enableExperimentalMapFileVersion()
         .compile()
         .run(
             parameters.getRuntime(),
@@ -90,12 +90,7 @@
               // and one for
               //   new ArrayStoreException("Foo")
               assertEquals(5, inspector.allClasses().size());
-              // TODO(b/201397823): Should always be equal.
-              if (throwInFirstOutline && !throwOnFirstCall) {
-                assertNotEquals(expectedStackTrace, stackTrace);
-              } else {
-                assertEquals(expectedStackTrace, stackTrace);
-              }
+              assertEquals(expectedStackTrace, stackTrace);
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java
new file mode 100644
index 0000000..fddf01c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineWithInlineMappingInformationTest.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2021, 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.outliner;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class OutlineWithInlineMappingInformationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameter(1)
+  public boolean throwInFirstOutline;
+
+  @Parameter(2)
+  public boolean throwOnFirstCall;
+
+  @Parameterized.Parameters(name = "{0}, throwInFirstOutline: {1}, throwOnFirstCall: {2}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        BooleanUtils.values(),
+        BooleanUtils.values());
+  }
+
+  StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    expectedStackTrace =
+        testForRuntime(parameters)
+            .addProgramClasses(TestClass.class, TestClass2.class, Greeter.class)
+            .run(
+                parameters.getRuntime(),
+                TestClass.class,
+                throwInFirstOutline ? "0" : "1",
+                throwOnFirstCall ? "0" : "1")
+            .assertFailureWithErrorThatThrows(ArrayStoreException.class)
+            .getStackTrace();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, TestClass2.class, Greeter.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options -> {
+              options.outline.threshold = 2;
+              options.outline.minSize = 2;
+            })
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .enableNoHorizontalClassMergingAnnotations()
+        .noHorizontalClassMergingOfSynthetics()
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .enableExperimentalMapFileVersion()
+        .compile()
+        .run(
+            parameters.getRuntime(),
+            TestClass.class,
+            throwInFirstOutline ? "0" : "1",
+            throwOnFirstCall ? "0" : "1")
+        .assertFailureWithErrorThatThrows(ArrayStoreException.class)
+        .inspectStackTrace(
+            (stackTrace, inspector) -> {
+              // Two outlines are created, one for
+              //   Greeter.throwExceptionFirst();
+              //   Greeter.throwExceptionSecond();
+              // and one for
+              //   new ArrayStoreException("Foo")
+              assertEquals(5, inspector.allClasses().size());
+              assertEquals(expectedStackTrace, stackTrace);
+            });
+  }
+
+  @NoHorizontalClassMerging
+  static class TestClass {
+
+    public static boolean shouldThrowInGreeter;
+    public static boolean throwOnFirst;
+
+    public static void main(String... args) {
+      shouldThrowInGreeter = args[0].equals("0");
+      throwOnFirst = args[1].equals("0");
+      greet();
+      shouldThrowInGreeter = true;
+      TestClass2.greet();
+    }
+
+    @NeverInline
+    static void greet() {
+      Greeter.throwExceptionFirst();
+      inlinee();
+    }
+
+    static void inlinee() {
+      Greeter.throwExceptionSecond();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class TestClass2 {
+
+    @NeverInline
+    static void greet() {
+      // Keep on same line
+      inlinee(); Greeter.throwExceptionSecond();
+    }
+
+    static void inlinee() {
+      Greeter.throwExceptionFirst();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  public static class Greeter {
+
+    @NeverInline
+    public static void throwExceptionFirst() {
+      if (TestClass.shouldThrowInGreeter && TestClass.throwOnFirst) {
+        throw new ArrayStoreException("Foo");
+      }
+    }
+
+    @NeverInline
+    public static void throwExceptionSecond() {
+      if (TestClass.shouldThrowInGreeter && !TestClass.throwOnFirst) {
+        throw new ArrayStoreException("Foo");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 80863b5..441b85f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -53,10 +53,12 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
@@ -870,7 +872,7 @@
         ProgramMethod programMethod = new ProgramMethod(programClass, method);
         IRCode ir = code.buildIR(programMethod, appView, Origin.unknown());
         RegisterAllocator allocator = new LinearScanRegisterAllocator(appView, ir);
-        method.setCode(ir, allocator, appView);
+        method.setCode(ir, BytecodeMetadataProvider.empty(), allocator, appView);
         directMethods[i] = method;
       }
       programClass.getMethodCollection().addDirectMethods(Arrays.asList(directMethods));
@@ -902,7 +904,12 @@
     private final Position position;
 
     public ReturnVoidCode(DexMethod method, Position callerPosition) {
-      this.position = Position.synthetic(0, method, callerPosition);
+      this.position =
+          SyntheticPosition.builder()
+              .setLine(0)
+              .setMethod(method)
+              .setCallerPosition(callerPosition)
+              .build();
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java
index 7df0c10..29a2fce 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java
@@ -4,9 +4,17 @@
 
 package com.android.tools.r8.memberrebinding;
 
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static java.util.Collections.emptyList;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -68,15 +76,54 @@
 
   private void test(Path compileTimeLibrary, Path runtimeLibrary) throws Exception {
     testForR8(parameters.getBackend())
-        .addProgramClasses(TestClass.class)
-        .addKeepClassAndMembersRules(TestClass.class)
+        .addProgramClasses(Main.class)
+        .addKeepClassAndMembersRules(Main.class)
         .addLibraryFiles(compileTimeLibrary)
         .addDefaultRuntimeLibrary(parameters)
+        .apply(setMockApiLevelForMethod(LibraryI.class.getDeclaredMethod("m"), AndroidApiLevel.B))
+        .apply(setMockApiLevelForMethod(LibraryC.class.getDeclaredMethod("m"), AndroidApiLevel.B))
+        .apply(setMockApiLevelForMethod(LibraryA.class.getDeclaredMethod("m"), AndroidApiLevel.N))
         .setMinApi(parameters.getApiLevel())
         .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject testMethodSubject =
+                  inspector.clazz(Main.class).uniqueMethodWithName("test");
+              assertThat(testMethodSubject, isPresent());
+              assertThat(
+                  testMethodSubject,
+                  invokesMethod(
+                      "int",
+                      getExpectedMemberRebindingTarget(compileTimeLibrary).getTypeName(),
+                      "m",
+                      emptyList()));
+            })
         .addRunClasspathFiles(buildOnDexRuntime(parameters, runtimeLibrary))
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("42");
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            // Compiling to an API level above the API where LibraryA.m() was defined with a new
+            // android.jar, and running this on an older runtime (correctly) results in a NSME.
+            parameters.isDexRuntime()
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
+                && compileTimeLibrary == newRuntimeJar
+                && runtimeLibrary == oldRuntimeJar,
+            runResult -> runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            runResult -> runResult.assertSuccessWithOutputLines("42"));
+  }
+
+  private Class<?> getExpectedMemberRebindingTarget(Path compileTimeLibrary) {
+    if (compileTimeLibrary == newRuntimeJar) {
+      // If we are compiling to a new runtime with a new android.jar, we should rebind to LibraryA.
+      if (parameters.isDexRuntime()
+          && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
+        return LibraryA.class;
+      }
+      // If we are compiling to an old runtime with a new android.jar, we should rebind to LibraryB.
+      return LibraryB.class;
+    }
+    // Otherwise, we are compiling to an old android.jar, in which case we should rebind to
+    // LibraryI.
+    return LibraryI.class;
   }
 
   private static Path createJar(Collection<Class<?>> programClasses, byte[]... programClassFileData)
@@ -97,33 +144,33 @@
         .transform();
   }
 
-  static class TestClass {
+  public static class Main {
 
     public static void main(String[] args) {
       test(new LibraryC());
     }
 
-    static void test(LibraryB b) {
+    private static void test(LibraryB b) {
       System.out.println(b.m());
     }
   }
 
-  interface LibraryI {
+  public interface LibraryI {
 
     int m();
   }
 
-  static class LibraryA {
+  public static class LibraryA {
 
-    // Added in API level X, so we can't rebind to this in APIs < X.
+    // Added in API N, so we can't rebind to this in APIs < N.
     public int m() {
       return 42;
     }
   }
 
-  abstract static class LibraryB extends LibraryA implements LibraryI {}
+  public abstract static class LibraryB extends LibraryA implements LibraryI {}
 
-  static class LibraryC extends LibraryB {
+  public static class LibraryC extends LibraryB {
 
     @Override
     public int m() {
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 48c9501..605ddf7 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -160,9 +160,9 @@
     assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
     assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsLibraryClass"));
     // For the next three - test that we re-bind to library methods (holder is java.util.ArrayList).
-    assertTrue(iterator.next().holder().is("java.util.ArrayList"));
-    assertTrue(iterator.next().holder().is("java.util.ArrayList"));
-    assertTrue(iterator.next().holder().is("java.util.ArrayList"));
+    assertTrue(iterator.next().holder().is("java.util.AbstractList"));
+    assertTrue(iterator.next().holder().is("java.util.AbstractList"));
+    assertTrue(iterator.next().holder().is("java.util.AbstractList"));
     assertTrue(iterator.next().holder().is("memberrebinding.subpackage.PublicClassInTheMiddle"));
     assertTrue(iterator.next().holder().is("memberrebinding.subpackage.PublicClassInTheMiddle"));
     // For the next three - test that we re-bind to the lowest library class.
diff --git a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
index 90617f7..cc7900e 100644
--- a/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b150400371/DebuginfoForInlineFrameRegressionTest.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import it.unimi.dsi.fastutil.ints.IntArraySet;
 import it.unimi.dsi.fastutil.ints.IntSet;
@@ -25,7 +24,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntime(Version.DEFAULT).withAllApiLevels().build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   public DebuginfoForInlineFrameRegressionTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index 7138094..01d1c30 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -62,14 +62,13 @@
 
     List<FoundClassSubject> classes = inspector.allClasses();
 
-    // Check that the synthetic class is still present.
-    assertEquals(3, classes.size());
+    // Check that the synthetic class is still present when generating class files.
+    assertEquals(parameters.isCfRuntime() ? 3 : 2, classes.size());
     assertEquals(
-        1,
+        parameters.isCfRuntime(),
         classes.stream()
             .map(FoundClassSubject::getOriginalName)
-            .filter(name -> name.endsWith("$1"))
-            .count());
+            .anyMatch(name -> name.endsWith("$1")));
   }
 
   @Test
@@ -94,13 +93,12 @@
 
     List<FoundClassSubject> classes = inspector.allClasses();
 
-    // Check that the synthetic class is still present.
-    assertEquals(3, classes.size());
+    // The synthetic class is still present when generating class files.
+    assertEquals(parameters.isCfRuntime() ? 3 : 2, classes.size());
     assertEquals(
-        1,
+        parameters.isCfRuntime(),
         classes.stream()
             .map(FoundClassSubject::getOriginalName)
-            .filter(name -> name.endsWith("$1"))
-            .count());
+            .anyMatch(name -> name.endsWith("$1")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java
index af4995f..19138f1 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,6 +54,7 @@
         .addDontWarn(MissingSuperType.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .compile()
         .inspect(
             inspector -> {
@@ -77,6 +79,7 @@
   }
 
   @NeverClassInline
+  @NoHorizontalClassMerging
   public static class ClassWithSuperCall extends MissingSuperType {
 
     @Override
@@ -88,6 +91,7 @@
   }
 
   @NeverClassInline
+  @NoHorizontalClassMerging
   public static class ClassWithoutSuperCall extends MissingSuperType {
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/StringBuildersAfterAssumenosideeffectsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/StringBuildersAfterAssumenosideeffectsTest.java
index 2787932..b80febc 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/StringBuildersAfterAssumenosideeffectsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/StringBuildersAfterAssumenosideeffectsTest.java
@@ -28,7 +28,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -51,7 +51,7 @@
             "  void info(...);",
             "}")
         .noMinification()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(EXPECTED)
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java b/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
index 9afdd92..0468d27 100644
--- a/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/negatedrules/NegatedKeepRulesTest.java
@@ -14,9 +14,7 @@
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.TestShrinkerBuilder;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.Subject;
 import org.hamcrest.Matcher;
@@ -32,7 +30,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withCfRuntime(CfVm.JDK9).withDexRuntime(Version.DEFAULT).build();
+    return getTestParameters().withDefaultCfRuntime().withDefaultDexRuntime().build();
   }
 
   public NegatedKeepRulesTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 242d5d2..042bec1 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -1649,6 +1649,7 @@
   @Test
   public void b113145696_superClassUseFirst() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+    builder.addDefaultConstructor();
 
     // Code where the outline argument is first used as java.lang.Object and afterwards
     // java.util.ArrayList.
@@ -1727,6 +1728,7 @@
   @Test
   public void b113145696_superInterfaceUseFirst() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+    builder.addDefaultConstructor();
 
     List<String> codeToOutline = ImmutableList.of(
         "    invoke-interface      { v1 }, Ljava/lang/Iterable;->iterator()Ljava/util/Iterator;",
@@ -1801,6 +1803,7 @@
   @Test
   public void b113145696_interfaceUseFirst() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+    builder.addDefaultConstructor();
 
     // Code where the outline argument is first used as java.lang.Iterable and afterwards
     // java.util.ArrayList.
@@ -1877,6 +1880,7 @@
   @Test
   public void b113145696_classUseFirst() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+    builder.addDefaultConstructor();
 
     // Code where the outline argument is first used as java.lang.Iterable and afterwards
     // java.util.ArrayList.
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 7c882b4..40fcc0f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -258,7 +258,7 @@
         new Object2IntOpenHashMap<>(code.getInstructions().size());
     for (CfInstruction insn : code.getInstructions()) {
       if (insn instanceof CfPosition) {
-        currentLine = ((CfPosition) insn).getPosition().line;
+        currentLine = ((CfPosition) insn).getPosition().getLine();
       }
       if (currentLine != -1) {
         lineNumberTable.put(new CfInstructionSubject(insn, this), currentLine);
diff --git a/third_party/android_jar/api-versions.tar.gz.sha1 b/third_party/android_jar/api-versions.tar.gz.sha1
index 849abf3..0c98c4f 100644
--- a/third_party/android_jar/api-versions.tar.gz.sha1
+++ b/third_party/android_jar/api-versions.tar.gz.sha1
@@ -1 +1 @@
-021b38c29b9be789f4312f95543a3f08baf66a19
\ No newline at end of file
+788aea6588610917a910af5d0e658fb13e8f7baf
\ No newline at end of file
diff --git a/third_party/chrome/chrome_180917_ffbaa8.tar.gz.sha1 b/third_party/chrome/chrome_180917_ffbaa8.tar.gz.sha1
index 3ac92ca..b716149 100644
--- a/third_party/chrome/chrome_180917_ffbaa8.tar.gz.sha1
+++ b/third_party/chrome/chrome_180917_ffbaa8.tar.gz.sha1
@@ -1 +1 @@
-c32dc7f70022946c04bfe9316e35e60e8d2ee0cb
\ No newline at end of file
+ac82e65cc398a3dff8f3fccf43f8a21ebc8609ef
\ No newline at end of file
diff --git a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
index 4e9a38b..4011aa4 100644
--- a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
+++ b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
@@ -1 +1 @@
-ae9eb01960d396ab603c611960f456d14ce7a1a8
\ No newline at end of file
+4523245af5a856bb5b27b7a26795775ce4d54591
\ No newline at end of file
diff --git a/tools/test.py b/tools/test.py
index 4a5d79c..7235b6e 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -460,7 +460,10 @@
 def print_jstacks():
   processes = subprocess.check_output(['ps', 'aux'])
   for l in processes.splitlines():
+    if 'art' in l or 'dalvik' in l:
+      print('Running art of dalvik process: \n%s' % l)
     if 'java' in l and 'openjdk' in l:
+      print('Running jstack on process: \n%s' % l)
       # Example line:
       # ricow    184313  2.6  0.0 36839068 31808 ?      Sl   09:53   0:00 /us..
       columns = l.split()
diff --git a/tools/trigger.py b/tools/trigger.py
index 984cc08..33b44a7 100755
--- a/tools/trigger.py
+++ b/tools/trigger.py
@@ -17,12 +17,12 @@
 import utils
 
 LUCI_SCHEDULE = os.path.join(utils.REPO_ROOT, 'infra', 'config', 'global',
-                             'luci-scheduler.cfg')
+                             'generated', 'luci-scheduler.cfg')
 # Trigger lines have the format:
 #   triggers: "BUILDER_NAME"
 TRIGGERS_RE = r'^  triggers: "(\w.*)"'
 
-DESUGAR_BOT = 'archive_lib_desugar'
+DESUGAR_BOT = 'lib_desugar-archive'
 
 def ParseOptions():
   result = optparse.OptionParser()
@@ -49,20 +49,20 @@
     for line in lines:
       if 'branch-gitiles-trigger' in line:
         is_release = True
+      if 'main-gitiles-trigger' in line:
+        is_release = False
       match = re.match(TRIGGERS_RE, line)
       if match:
         builder = match.group(1)
         if is_release:
-          assert 'release' in builder
+          assert 'release' in builder, builder
           release_builders.append(builder)
         else:
-          assert 'release' not in builder
+          assert 'release' not in builder, builder
           main_builders.append(builder)
-  assert DESUGAR_BOT in main_builders
-  print 'Desugar builder:\n  ' + DESUGAR_BOT
-  main_builders.remove(DESUGAR_BOT)
-  print 'Main builders:\n  ' + '\n  '.join(main_builders)
-  print 'Release builders:\n  ' + '\n  '.join(release_builders)
+  print('Desugar builder:\n  ' + DESUGAR_BOT)
+  print('Main builders:\n  ' + '\n  '.join(main_builders))
+  print('Release builders:\n  ' + '\n  '.join(release_builders))
   return (main_builders, release_builders)
 
 def sanity_check_url(url):