Merge commit 'e505f1ebab5a9f698aa94bb583b77eafe93baa6e' into dev-release
diff --git a/build.gradle b/build.gradle
index 433b39a..323256b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1939,6 +1939,10 @@
         systemProperty 'slow_tests', project.property('slow_tests')
     }
 
+
+    if (project.hasProperty('desugar_jdk_json_dir')) {
+        systemProperty 'desugar_jdk_json_dir', project.property('desugar_jdk_json_dir')
+    }
     if (project.hasProperty('desugar_jdk_libs')) {
         systemProperty 'desugar_jdk_libs', project.property('desugar_jdk_libs')
     }
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index a4e4210..65521a9 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -425,6 +425,7 @@
       name: "kotlin-builder"
       mixins: "linux"
       mixins: "normal"
+      priority: 27
       recipe {
         properties_j: "test_options:[\"--not_used\"]"
         properties: "test_wrapper:google-scripts/build.py"
diff --git a/src/library_desugar/desugar_jdk_libs_alternative_3.json b/src/library_desugar/desugar_jdk_libs_alternative_3.json
index e23a9b8..2b0ffd9 100644
--- a/src/library_desugar/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/desugar_jdk_libs_alternative_3.json
@@ -77,12 +77,6 @@
         "java.time.": "j$.time.",
         "java.util.Desugar": "j$.util.Desugar"
       },
-      "backport": {
-        "java.lang.Double8": "java.lang.Double",
-        "java.lang.Integer8": "java.lang.Integer",
-        "java.lang.Long8": "java.lang.Long",
-        "java.lang.Math8": "java.lang.Math"
-      },
       "retarget_lib_member": {
         "java.util.Date#toInstant": "java.util.DesugarDate",
         "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
new file mode 100644
index 0000000..b0c751c
--- /dev/null
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -0,0 +1,257 @@
+{
+  "configuration_format_version": 3,
+  "group_id" : "com.tools.android",
+  "artifact_id" : "desugar_jdk_libs",
+  "version": "2.0.0",
+  "required_compilation_api_level": 26,
+  "synthesized_library_classes_package_prefix": "j$.",
+  "support_all_callbacks_from_library": true,
+  "common_flags": [
+    {
+      "api_level_below_or_equal": 25,
+      "wrapper_conversion": [
+        "java.time.Clock"
+      ]
+    },
+    {
+      "api_level_below_or_equal": 23,
+      "wrapper_conversion": [
+        "java.util.PrimitiveIterator$OfDouble",
+        "java.util.PrimitiveIterator$OfInt",
+        "java.util.PrimitiveIterator$OfLong",
+        "java.util.Spliterator",
+        "java.util.Spliterator$OfDouble",
+        "java.util.Spliterator$OfInt",
+        "java.util.Spliterator$OfLong",
+        "java.util.Spliterator$OfPrimitive",
+        "java.util.function.BiConsumer",
+        "java.util.function.BiFunction",
+        "java.util.function.BiPredicate",
+        "java.util.function.BinaryOperator",
+        "java.util.function.Consumer",
+        "java.util.function.DoubleBinaryOperator",
+        "java.util.function.DoubleConsumer",
+        "java.util.function.DoubleFunction",
+        "java.util.function.DoublePredicate",
+        "java.util.function.DoubleToIntFunction",
+        "java.util.function.DoubleToLongFunction",
+        "java.util.function.DoubleUnaryOperator",
+        "java.util.function.Function",
+        "java.util.function.IntBinaryOperator",
+        "java.util.function.IntConsumer",
+        "java.util.function.IntFunction",
+        "java.util.function.IntPredicate",
+        "java.util.function.IntToDoubleFunction",
+        "java.util.function.IntToLongFunction",
+        "java.util.function.IntUnaryOperator",
+        "java.util.function.LongBinaryOperator",
+        "java.util.function.LongConsumer",
+        "java.util.function.LongFunction",
+        "java.util.function.LongPredicate",
+        "java.util.function.LongToDoubleFunction",
+        "java.util.function.LongToIntFunction",
+        "java.util.function.LongUnaryOperator",
+        "java.util.function.ObjDoubleConsumer",
+        "java.util.function.ObjIntConsumer",
+        "java.util.function.ObjLongConsumer",
+        "java.util.function.Predicate",
+        "java.util.function.Supplier",
+        "java.util.function.ToDoubleFunction",
+        "java.util.function.ToIntFunction",
+        "java.util.function.ToLongFunction",
+        "java.util.function.UnaryOperator",
+        "java.util.stream.BaseStream",
+        "java.util.stream.Collector",
+        "java.util.stream.DoubleStream",
+        "java.util.stream.IntStream",
+        "java.util.stream.LongStream",
+        "java.util.stream.Stream"
+      ]
+    }
+  ],
+  "library_flags": [
+    {
+      "api_level_below_or_equal": 25,
+      "rewrite_prefix": {
+        "j$.time.": "java.time.",
+        "java.time.": "j$.time.",
+        "java.util.Desugar": "j$.util.Desugar",
+        "sun.misc.Desugar": "j$.sun.misc.Desugar",
+        "jdk.internal.util.Preconditions": "j$.jdk.internal.util.Preconditions"
+      },
+      "retarget_lib_member": {
+        "java.util.Date#toInstant": "java.util.DesugarDate",
+        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
+        "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone"
+      },
+      "custom_conversion": {
+        "java.time.ZonedDateTime": "java.time.TimeConversions",
+        "java.time.LocalDate": "java.time.TimeConversions",
+        "java.time.Duration": "java.time.TimeConversions",
+        "java.time.ZoneId": "java.time.TimeConversions",
+        "java.time.MonthDay": "java.time.TimeConversions",
+        "java.time.Instant": "java.time.TimeConversions"
+      }
+    },
+    {
+      "api_level_below_or_equal": 23,
+      "rewrite_prefix": {
+        "j$.util.Optional": "java.util.Optional",
+        "j$.util.LongSummaryStatistics": "java.util.LongSummaryStatistics",
+        "j$.util.IntSummaryStatistics": "java.util.IntSummaryStatistics",
+        "j$.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatistics",
+        "java.util.stream.": "j$.util.stream.",
+        "java.util.function.": "j$.util.function.",
+        "java.util.Comparators": "j$.util.Comparators",
+        "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
+        "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
+        "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
+        "java.util.Objects": "j$.util.Objects",
+        "java.util.Optional": "j$.util.Optional",
+        "java.util.PrimitiveIterator": "j$.util.PrimitiveIterator",
+        "java.util.SortedSet$1": "j$.util.SortedSet$1",
+        "java.util.Spliterator": "j$.util.Spliterator",
+        "java.util.StringJoiner": "j$.util.StringJoiner",
+        "java.util.Tripwire": "j$.util.Tripwire",
+        "java.util.concurrent.Helpers": "j$.util.concurrent.Helpers",
+        "java.util.concurrent.ThreadLocalRandom": "j$.util.concurrent.ThreadLocalRandom",
+        "java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic",
+        "java.util.concurrent.ConcurrentHashMap": "j$.util.concurrent.ConcurrentHashMap",
+        "java.util.AbstractList": "j$.util.AbstractList",
+        "java.util.CollSer": "j$.util.CollSer",
+        "java.util.ImmutableCollections": "j$.util.ImmutableCollections",
+        "java.util.KeyValueHolder": "j$.util.KeyValueHolder"
+      },
+      "retarget_lib_member": {
+        "java.util.Arrays#stream": "java.util.DesugarArrays",
+        "java.util.Arrays#spliterator": "java.util.DesugarArrays",
+        "java.util.LinkedHashSet#spliterator": "java.util.DesugarLinkedHashSet"
+      },
+      "dont_rewrite": [
+        "java.util.Iterator#remove"
+      ],
+      "emulate_interface": {
+        "java.lang.Iterable": "j$.lang.Iterable",
+        "java.util.Map$Entry": "j$.util.Map$Entry",
+        "java.util.Collection": "j$.util.Collection",
+        "java.util.Map": "j$.util.Map",
+        "java.util.Iterator": "j$.util.Iterator",
+        "java.util.Comparator": "j$.util.Comparator",
+        "java.util.List": "j$.util.List",
+        "java.util.SortedSet": "j$.util.SortedSet",
+        "java.util.Set": "j$.util.Set",
+        "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
+      },
+      "custom_conversion": {
+        "java.util.Optional": "java.util.OptionalConversions",
+        "java.util.OptionalDouble": "java.util.OptionalConversions",
+        "java.util.OptionalInt": "java.util.OptionalConversions",
+        "java.util.OptionalLong": "java.util.OptionalConversions",
+        "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions",
+        "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
+        "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
+      }
+    }
+  ],
+  "program_flags": [
+    {
+      "api_level_below_or_equal": 25,
+      "rewrite_prefix": {
+        "java.time.": "j$.time.",
+        "java.util.Desugar": "j$.util.Desugar"
+      },
+      "retarget_lib_member": {
+        "java.util.Calendar#toInstant": "java.util.DesugarCalendar",
+        "java.util.Date#from": "java.util.DesugarDate",
+        "java.util.Date#toInstant": "java.util.DesugarDate",
+        "java.util.GregorianCalendar#from": "java.util.DesugarGregorianCalendar",
+        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
+        "java.util.TimeZone#getTimeZone": "java.util.DesugarTimeZone",
+        "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone"
+      },
+      "custom_conversion": {
+        "java.time.ZonedDateTime": "java.time.TimeConversions",
+        "java.time.LocalDate": "java.time.TimeConversions",
+        "java.time.Duration": "java.time.TimeConversions",
+        "java.time.ZoneId": "java.time.TimeConversions",
+        "java.time.MonthDay": "java.time.TimeConversions",
+        "java.time.Instant": "java.time.TimeConversions"
+      }
+    },
+    {
+      "api_level_below_or_equal": 23,
+      "rewrite_prefix": {
+        "java.util.stream.": "j$.util.stream.",
+        "java.util.function.": "j$.util.function.",
+        "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
+        "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
+        "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
+        "java.util.Optional": "j$.util.Optional",
+        "java.util.PrimitiveIterator": "j$.util.PrimitiveIterator",
+        "java.util.Spliterator": "j$.util.Spliterator",
+        "java.util.StringJoiner": "j$.util.StringJoiner",
+        "java.util.concurrent.ThreadLocalRandom": "j$.util.concurrent.ThreadLocalRandom",
+        "java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic",
+        "java.util.concurrent.ConcurrentHashMap": "j$.util.concurrent.ConcurrentHashMap"
+      },
+      "retarget_lib_member": {
+        "java.util.Arrays#stream": "java.util.DesugarArrays",
+        "java.util.Arrays#spliterator": "java.util.DesugarArrays",
+        "java.util.LinkedHashSet#spliterator": "java.util.DesugarLinkedHashSet",
+        "java.util.concurrent.atomic.AtomicInteger#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicInteger",
+        "java.util.concurrent.atomic.AtomicInteger#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicInteger",
+        "java.util.concurrent.atomic.AtomicInteger#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicInteger",
+        "java.util.concurrent.atomic.AtomicInteger#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicInteger",
+        "java.util.concurrent.atomic.AtomicLong#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicLong",
+        "java.util.concurrent.atomic.AtomicLong#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicLong",
+        "java.util.concurrent.atomic.AtomicLong#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicLong",
+        "java.util.concurrent.atomic.AtomicLong#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicLong",
+        "java.util.concurrent.atomic.AtomicReference#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicReference",
+        "java.util.concurrent.atomic.AtomicReference#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference",
+        "java.util.concurrent.atomic.AtomicReference#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicReference",
+        "java.util.concurrent.atomic.AtomicReference#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference",
+        "java.util.Collections#synchronizedMap": "java.util.DesugarCollections",
+        "java.util.Collections#synchronizedSortedMap": "java.util.DesugarCollections"
+      },
+      "dont_rewrite": [
+        "java.util.Iterator#remove"
+      ],
+      "emulate_interface": {
+        "java.lang.Iterable": "j$.lang.Iterable",
+        "java.util.Map$Entry": "j$.util.Map$Entry",
+        "java.util.Collection": "j$.util.Collection",
+        "java.util.Map": "j$.util.Map",
+        "java.util.Iterator": "j$.util.Iterator",
+        "java.util.Comparator": "j$.util.Comparator",
+        "java.util.List": "j$.util.List",
+        "java.util.SortedSet": "j$.util.SortedSet",
+        "java.util.Set": "j$.util.Set",
+        "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
+      },
+      "custom_conversion": {
+        "java.util.Optional": "java.util.OptionalConversions",
+        "java.util.OptionalDouble": "java.util.OptionalConversions",
+        "java.util.OptionalInt": "java.util.OptionalConversions",
+        "java.util.OptionalLong": "java.util.OptionalConversions",
+        "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions",
+        "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
+        "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
+      }
+    }
+  ],
+  "shrinker_config": [
+    "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$TreeBin { int lockState; }",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { private void readObject(java.io.ObjectInputStream); private void writeObject(java.io.ObjectOutputStream); private void readObjectNoData(); private static final java.io.ObjectStreamField[] serialPersistentFields; private static final long serialVersionUID;}",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }",
+    "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); public static final !synthetic <fields>; }",
+    "-keeppackagenames j$.**",
+    "-keepclassmembers class j$.util.IntSummaryStatistics { long count; long sum; int min; int max; }",
+    "-keepclassmembers class j$.util.LongSummaryStatistics { long count; long sum; long min; long max; }",
+    "-keepclassmembers class j$.util.DoubleSummaryStatistics { long count; double sum; double min; double max; }",
+    "-keepattributes Signature",
+    "-keepattributes EnclosingMethod",
+    "-keepattributes InnerClasses",
+    "-dontwarn sun.misc.Unsafe"
+  ]
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
new file mode 100644
index 0000000..66f3500
--- /dev/null
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
@@ -0,0 +1,260 @@
+{
+  "configuration_format_version": 3,
+  "group_id" : "com.tools.android",
+  "artifact_id" : "desugar_jdk_libs_alternative_3",
+  "version": "2.0.0",
+  "required_compilation_api_level": 26,
+  "synthesized_library_classes_package_prefix": "j$.",
+  "support_all_callbacks_from_library": false,
+  "common_flags": [
+    {
+      "api_level_below_or_equal": 25,
+      "wrapper_conversion": [
+        "java.time.Clock"
+      ]
+    },
+    {
+      "api_level_below_or_equal": 23,
+      "wrapper_conversion": [
+        "java.util.PrimitiveIterator$OfDouble",
+        "java.util.PrimitiveIterator$OfInt",
+        "java.util.PrimitiveIterator$OfLong",
+        "java.util.Spliterator",
+        "java.util.Spliterator$OfDouble",
+        "java.util.Spliterator$OfInt",
+        "java.util.Spliterator$OfLong",
+        "java.util.Spliterator$OfPrimitive",
+        "java.util.function.BiConsumer",
+        "java.util.function.BiFunction",
+        "java.util.function.BiPredicate",
+        "java.util.function.BinaryOperator",
+        "java.util.function.Consumer",
+        "java.util.function.DoubleBinaryOperator",
+        "java.util.function.DoubleConsumer",
+        "java.util.function.DoubleFunction",
+        "java.util.function.DoublePredicate",
+        "java.util.function.DoubleToIntFunction",
+        "java.util.function.DoubleToLongFunction",
+        "java.util.function.DoubleUnaryOperator",
+        "java.util.function.Function",
+        "java.util.function.IntBinaryOperator",
+        "java.util.function.IntConsumer",
+        "java.util.function.IntFunction",
+        "java.util.function.IntPredicate",
+        "java.util.function.IntToDoubleFunction",
+        "java.util.function.IntToLongFunction",
+        "java.util.function.IntUnaryOperator",
+        "java.util.function.LongBinaryOperator",
+        "java.util.function.LongConsumer",
+        "java.util.function.LongFunction",
+        "java.util.function.LongPredicate",
+        "java.util.function.LongToDoubleFunction",
+        "java.util.function.LongToIntFunction",
+        "java.util.function.LongUnaryOperator",
+        "java.util.function.ObjDoubleConsumer",
+        "java.util.function.ObjIntConsumer",
+        "java.util.function.ObjLongConsumer",
+        "java.util.function.Predicate",
+        "java.util.function.Supplier",
+        "java.util.function.ToDoubleFunction",
+        "java.util.function.ToIntFunction",
+        "java.util.function.ToLongFunction",
+        "java.util.function.UnaryOperator",
+        "java.util.stream.BaseStream",
+        "java.util.stream.Collector",
+        "java.util.stream.DoubleStream",
+        "java.util.stream.IntStream",
+        "java.util.stream.LongStream",
+        "java.util.stream.Stream"
+      ]
+    }
+  ],
+  "library_flags": [
+    {
+      "api_level_below_or_equal": 25,
+      "rewrite_prefix": {
+        "j$.time.": "java.time.",
+        "java.time.": "j$.time.",
+        "java.util.Desugar": "j$.util.Desugar",
+        "sun.misc.Desugar": "j$.sun.misc.Desugar",
+        "jdk.internal.util.Preconditions": "j$.jdk.internal.util.Preconditions"
+      },
+      "retarget_lib_member": {
+        "java.util.Date#toInstant": "java.util.DesugarDate",
+        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
+        "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone"
+      },
+      "custom_conversion": {
+        "java.time.ZonedDateTime": "java.time.TimeConversions",
+        "java.time.LocalDate": "java.time.TimeConversions",
+        "java.time.Duration": "java.time.TimeConversions",
+        "java.time.ZoneId": "java.time.TimeConversions",
+        "java.time.MonthDay": "java.time.TimeConversions",
+        "java.time.Instant": "java.time.TimeConversions"
+      }
+    },
+    {
+      "api_level_below_or_equal": 23,
+      "rewrite_prefix": {
+        "j$.util.Optional": "java.util.Optional",
+        "j$.util.LongSummaryStatistics": "java.util.LongSummaryStatistics",
+        "j$.util.IntSummaryStatistics": "java.util.IntSummaryStatistics",
+        "j$.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatistics",
+        "java.util.stream.": "j$.util.stream.",
+        "java.util.function.": "j$.util.function.",
+        "java.util.Comparators": "j$.util.Comparators",
+        "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
+        "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
+        "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
+        "java.util.Objects": "j$.util.Objects",
+        "java.util.Optional": "j$.util.Optional",
+        "java.util.PrimitiveIterator": "j$.util.PrimitiveIterator",
+        "java.util.SortedSet$1": "j$.util.SortedSet$1",
+        "java.util.Spliterator": "j$.util.Spliterator",
+        "java.util.StringJoiner": "j$.util.StringJoiner",
+        "java.util.Tripwire": "j$.util.Tripwire",
+        "java.util.concurrent.Helpers": "j$.util.concurrent.Helpers",
+        "java.util.concurrent.ThreadLocalRandom": "j$.util.concurrent.ThreadLocalRandom",
+        "java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic",
+        "java.util.concurrent.ConcurrentHashMap": "j$.util.concurrent.ConcurrentHashMap",
+        "java.util.AbstractList": "j$.util.AbstractList",
+        "java.util.CollSer": "j$.util.CollSer",
+        "java.util.ImmutableCollections": "j$.util.ImmutableCollections",
+        "java.util.KeyValueHolder": "j$.util.KeyValueHolder",
+        "java.io.DesugarBufferedReader": "j$.io.DesugarBufferedReader",
+        "java.io.UncheckedIOException": "j$.io.UncheckedIOException"
+      },
+      "retarget_lib_member": {
+        "java.util.Arrays#stream": "java.util.DesugarArrays",
+        "java.util.Arrays#spliterator": "java.util.DesugarArrays",
+        "java.util.LinkedHashSet#spliterator": "java.util.DesugarLinkedHashSet",
+        "java.io.BufferedReader#lines": "java.io.DesugarBufferedReader"
+      },
+      "dont_rewrite": [
+        "java.util.Iterator#remove"
+      ],
+      "emulate_interface": {
+        "java.util.Map$Entry": "j$.util.Map$Entry",
+        "java.util.Collection": "j$.util.Collection",
+        "java.util.Map": "j$.util.Map",
+        "java.util.Iterator": "j$.util.Iterator",
+        "java.util.Comparator": "j$.util.Comparator",
+        "java.util.List": "j$.util.List",
+        "java.util.SortedSet": "j$.util.SortedSet",
+        "java.util.Set": "j$.util.Set",
+        "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
+      },
+      "custom_conversion": {
+        "java.util.Optional": "java.util.OptionalConversions",
+        "java.util.OptionalDouble": "java.util.OptionalConversions",
+        "java.util.OptionalInt": "java.util.OptionalConversions",
+        "java.util.OptionalLong": "java.util.OptionalConversions",
+        "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions",
+        "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
+        "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
+      }
+    }
+  ],
+  "program_flags": [
+    {
+      "api_level_below_or_equal": 25,
+      "rewrite_prefix": {
+        "java.time.": "j$.time.",
+        "java.util.Desugar": "j$.util.Desugar"
+      },
+      "retarget_lib_member": {
+        "java.util.Calendar#toInstant": "java.util.DesugarCalendar",
+        "java.util.Date#from": "java.util.DesugarDate",
+        "java.util.Date#toInstant": "java.util.DesugarDate",
+        "java.util.GregorianCalendar#from": "java.util.DesugarGregorianCalendar",
+        "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
+        "java.util.TimeZone#getTimeZone": "java.util.DesugarTimeZone",
+        "java.util.TimeZone#toZoneId": "java.util.DesugarTimeZone"
+      },
+      "custom_conversion": {
+        "java.time.ZonedDateTime": "java.time.TimeConversions",
+        "java.time.LocalDate": "java.time.TimeConversions",
+        "java.time.Duration": "java.time.TimeConversions",
+        "java.time.ZoneId": "java.time.TimeConversions",
+        "java.time.MonthDay": "java.time.TimeConversions",
+        "java.time.Instant": "java.time.TimeConversions"
+      }
+    },
+    {
+      "api_level_below_or_equal": 23,
+      "rewrite_prefix": {
+        "java.util.stream.": "j$.util.stream.",
+        "java.util.function.": "j$.util.function.",
+        "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
+        "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
+        "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
+        "java.util.Objects": "j$.util.Objects",
+        "java.util.Optional": "j$.util.Optional",
+        "java.util.PrimitiveIterator": "j$.util.PrimitiveIterator",
+        "java.util.Spliterator": "j$.util.Spliterator",
+        "java.util.StringJoiner": "j$.util.StringJoiner",
+        "java.util.concurrent.ThreadLocalRandom": "j$.util.concurrent.ThreadLocalRandom",
+        "java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic",
+        "java.util.concurrent.ConcurrentHashMap": "j$.util.concurrent.ConcurrentHashMap",
+        "java.io.UncheckedIOException": "j$.io.UncheckedIOException"
+      },
+      "retarget_lib_member": {
+        "java.util.Arrays#stream": "java.util.DesugarArrays",
+        "java.util.Arrays#spliterator": "java.util.DesugarArrays",
+        "java.util.LinkedHashSet#spliterator": "java.util.DesugarLinkedHashSet",
+        "java.util.concurrent.atomic.AtomicInteger#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicInteger",
+        "java.util.concurrent.atomic.AtomicInteger#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicInteger",
+        "java.util.concurrent.atomic.AtomicInteger#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicInteger",
+        "java.util.concurrent.atomic.AtomicInteger#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicInteger",
+        "java.util.concurrent.atomic.AtomicLong#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicLong",
+        "java.util.concurrent.atomic.AtomicLong#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicLong",
+        "java.util.concurrent.atomic.AtomicLong#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicLong",
+        "java.util.concurrent.atomic.AtomicLong#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicLong",
+        "java.util.concurrent.atomic.AtomicReference#getAndUpdate": "java.util.concurrent.atomic.DesugarAtomicReference",
+        "java.util.concurrent.atomic.AtomicReference#updateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference",
+        "java.util.concurrent.atomic.AtomicReference#getAndAccumulate": "java.util.concurrent.atomic.DesugarAtomicReference",
+        "java.util.concurrent.atomic.AtomicReference#accumulateAndGet": "java.util.concurrent.atomic.DesugarAtomicReference",
+        "java.util.Collections#synchronizedMap": "java.util.DesugarCollections",
+        "java.util.Collections#synchronizedSortedMap": "java.util.DesugarCollections",
+        "java.io.BufferedReader#lines": "java.io.DesugarBufferedReader"
+      },
+      "dont_rewrite": [
+        "java.util.Iterator#remove"
+      ],
+      "emulate_interface": {
+        "java.util.Map$Entry": "j$.util.Map$Entry",
+        "java.util.Collection": "j$.util.Collection",
+        "java.util.Map": "j$.util.Map",
+        "java.util.Iterator": "j$.util.Iterator",
+        "java.util.Comparator": "j$.util.Comparator",
+        "java.util.List": "j$.util.List",
+        "java.util.SortedSet": "j$.util.SortedSet",
+        "java.util.Set": "j$.util.Set",
+        "java.util.concurrent.ConcurrentMap": "j$.util.concurrent.ConcurrentMap"
+      },
+      "custom_conversion": {
+        "java.util.Optional": "java.util.OptionalConversions",
+        "java.util.OptionalDouble": "java.util.OptionalConversions",
+        "java.util.OptionalInt": "java.util.OptionalConversions",
+        "java.util.OptionalLong": "java.util.OptionalConversions",
+        "java.util.LongSummaryStatistics": "java.util.LongSummaryStatisticsConversions",
+        "java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
+        "java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions"
+      }
+    }
+  ],
+  "shrinker_config": [
+    "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$TreeBin { int lockState; }",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }",
+    "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); public static final !synthetic <fields>; }",
+    "-keeppackagenames j$",
+    "-keepclassmembers class j$.util.IntSummaryStatistics { long count; long sum; int min; int max; }",
+    "-keepclassmembers class j$.util.LongSummaryStatistics { long count; long sum; long min; long max; }",
+    "-keepclassmembers class j$.util.DoubleSummaryStatistics { long count; double sum; double min; double max; }",
+    "-keepattributes Signature",
+    "-keepattributes EnclosingMethod",
+    "-keepattributes InnerClasses",
+    "-dontwarn sun.misc.Unsafe"
+  ]
+}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index b12b467..f0cb3e8 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -618,8 +618,9 @@
      *
      * @param inspection Inspection callback receiving inspectors denoting parts of the output.
      */
-    public void addOutputInspection(Consumer<Inspector> inspection) {
+    public B addOutputInspection(Consumer<Inspector> inspection) {
       outputInspections.add(inspection);
+      return self();
     }
 
     List<Consumer<Inspector>> getOutputInspections() {
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 740984c..a47844f 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -150,11 +150,11 @@
       assert method.getHolderType() == clazz.type;
       CfCode code = null;
       if (!method.accessFlags.isAbstract() /*&& !method.accessFlags.isNative()*/) {
-        code = buildEmptyThrowingCfCode(method.method);
+        code = buildEmptyThrowingCfCode(method.getReference());
       }
       DexEncodedMethod throwingMethod =
           new DexEncodedMethod(
-              method.method,
+              method.getReference(),
               method.accessFlags,
               MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
@@ -246,7 +246,7 @@
               continue;
             }
             ProgramMethod implementationMethod =
-                implementationClass.lookupProgramMethod(method.method);
+                implementationClass.lookupProgramMethod(method.getReference());
             // Don't include methods which are not implemented by the desugared library.
             if (supported.test(method) && implementationMethod != null) {
               supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
@@ -265,10 +265,10 @@
         if (desugaredLibraryConfiguration
             .getRetargetCoreLibMember()
             .keySet()
-            .contains(method.method.name)) {
+            .contains(method.getReference().name)) {
           if (desugaredLibraryConfiguration
               .getRetargetCoreLibMember()
-              .get(method.method.name)
+              .get(method.getReference().name)
               .containsKey(clazz.type)) {
             if (supported.test(method)) {
               supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method);
@@ -333,8 +333,8 @@
               desugaredApisSignatures.add(
                   classBinaryName
                       + '#'
-                      + method.method.name
-                      + method.method.proto.toDescriptorString());
+                      + method.getReference().name
+                      + method.getReference().proto.toDescriptorString());
             }
           } else {
             desugaredApisSignatures.add(classBinaryName);
@@ -405,7 +405,7 @@
             return true;
           }
           assert minApiLevel == AndroidApiLevel.B;
-          return !parallelMethods.contains(method.method);
+          return !parallelMethods.contains(method.getReference());
         });
   }
 
@@ -585,7 +585,7 @@
     }
 
     public String arguments(DexEncodedMethod method) {
-      DexProto proto = method.method.proto;
+      DexProto proto = method.getReference().proto;
       StringBuilder argsBuilder = new StringBuilder();
       boolean firstArg = true;
       int argIndex = method.isVirtualMethod() || method.accessFlags.isConstructor() ? 1 : 0;
@@ -685,9 +685,9 @@
           builder.appendLiCode(
               accessFlags(field.accessFlags)
                   + " "
-                  + typeInPackage(field.field.type)
+                  + typeInPackage(field.getReference().type)
                   + " "
-                  + field.field.name);
+                  + field.getReference().name);
         }
       }
       if (!constructors.isEmpty()) {
@@ -705,12 +705,12 @@
           builder.appendLiCode(
               accessFlags(method.accessFlags)
                   + " "
-                  + typeInPackage(method.method.proto.returnType)
+                  + typeInPackage(method.getReference().proto.returnType)
                   + " "
-                  + method.method.name
+                  + method.getReference().name
                   + arguments(method));
-          if (parallelMethods.contains(method.method)) {
-            parallelM.add(method.method.name.toString());
+          if (parallelMethods.contains(method.getReference())) {
+            parallelM.add(method.getReference().name.toString());
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/JarDiff.java b/src/main/java/com/android/tools/r8/JarDiff.java
index a268496..446791f 100644
--- a/src/main/java/com/android/tools/r8/JarDiff.java
+++ b/src/main/java/com/android/tools/r8/JarDiff.java
@@ -145,23 +145,23 @@
   private void compareMethods(DexProgramClass class1, DexProgramClass class2) {
     class1.forEachMethod(
         method1 -> {
-          DexEncodedMethod method2 = class2.lookupMethod(method1.method);
+          DexEncodedMethod method2 = class2.lookupMethod(method1.getReference());
           compareMethods(method1, method2);
         });
     class2.forEachMethod(
         method2 -> {
-          DexEncodedMethod method1 = class1.lookupMethod(method2.method);
+          DexEncodedMethod method1 = class1.lookupMethod(method2.getReference());
           compareMethods(method1, method2);
         });
   }
 
   private void compareMethods(DexEncodedMethod m1, DexEncodedMethod m2) {
     if (m1 == null) {
-      System.out.println("Only in " + path2 + ": " + m2.method.toSourceString());
+      System.out.println("Only in " + path2 + ": " + m2.getReference().toSourceString());
       return;
     }
     if (m2 == null) {
-      System.out.println("Only in " + path1 + ": " + m1.method.toSourceString());
+      System.out.println("Only in " + path1 + ": " + m1.getReference().toSourceString());
       return;
     }
     List<String> code1 = getInstructionStrings(m1);
@@ -176,12 +176,19 @@
     int before = Math.min(i, this.before);
     int after = Math.min(j, this.after);
     int context = before + after;
-    System.out.println("--- " + path1 + "/" + m1.method.toSmaliString());
-    System.out.println("+++ " + path2 + "/" + m2.method.toSmaliString());
+    System.out.println("--- " + path1 + "/" + m1.getReference().toSmaliString());
+    System.out.println("+++ " + path2 + "/" + m2.getReference().toSmaliString());
     System.out.println(
-        "@@ -" + (i - before) + "," + (length1 + context)
-            + " +" + (i - before) + "," + (length2 + context) + " @@ "
-            + m1.method.toSourceString());
+        "@@ -"
+            + (i - before)
+            + ","
+            + (length1 + context)
+            + " +"
+            + (i - before)
+            + ","
+            + (length2 + context)
+            + " @@ "
+            + m1.getReference().toSourceString());
     for (int k = 0; k < before; k++) {
       System.out.println(" " + code1.get(i - before + k));
     }
diff --git a/src/main/java/com/android/tools/r8/JarSizeCompare.java b/src/main/java/com/android/tools/r8/JarSizeCompare.java
index 15e29fa..e520d1b 100644
--- a/src/main/java/com/android/tools/r8/JarSizeCompare.java
+++ b/src/main/java/com/android/tools/r8/JarSizeCompare.java
@@ -332,8 +332,9 @@
             MethodSignature originalSignature =
                 proguardMap == null
                     ? null
-                    : proguardMap.originalSignatureOf(dexEncodedMethod.method);
-            MethodSignature signature = MethodSignature.fromDexMethod(dexEncodedMethod.method);
+                    : proguardMap.originalSignatureOf(dexEncodedMethod.getReference());
+            MethodSignature signature =
+                MethodSignature.fromDexMethod(dexEncodedMethod.getReference());
             consumer.accept(
                 originalSignature == null ? signature : originalSignature, dexEncodedMethod);
           });
@@ -346,8 +347,10 @@
       programClass.forEachField(
           dexEncodedField -> {
             FieldSignature originalSignature =
-                proguardMap == null ? null : proguardMap.originalSignatureOf(dexEncodedField.field);
-            FieldSignature signature = FieldSignature.fromDexField(dexEncodedField.field);
+                proguardMap == null
+                    ? null
+                    : proguardMap.originalSignatureOf(dexEncodedField.getReference());
+            FieldSignature signature = FieldSignature.fromDexField(dexEncodedField.getReference());
             consumer.accept(
                 originalSignature == null ? signature : originalSignature, dexEncodedField);
           });
diff --git a/src/main/java/com/android/tools/r8/PrintClassList.java b/src/main/java/com/android/tools/r8/PrintClassList.java
index 73cb04d..1530251 100644
--- a/src/main/java/com/android/tools/r8/PrintClassList.java
+++ b/src/main/java/com/android/tools/r8/PrintClassList.java
@@ -57,7 +57,7 @@
   }
 
   private static void printMethod(DexEncodedMethod encodedMethod, ClassNameMapper map) {
-    DexMethod method = encodedMethod.method;
+    DexMethod method = encodedMethod.getReference();
     if (map != null) {
       System.out.println(map.originalNameOf(method));
     } else {
@@ -68,7 +68,7 @@
   }
 
   private static void printField(DexEncodedField encodedField, ClassNameMapper map) {
-    DexField field = encodedField.field;
+    DexField field = encodedField.getReference();
     if (map != null) {
       System.out.println(map.originalNameOf(field));
     } else {
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 80b5ec7..926577f 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -108,9 +108,9 @@
       ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
       DexEncodedMethod target =
           resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
-      if (target != null && target.method != method) {
+      if (target != null && target.getReference() != method) {
         addType(method.holder);
-        addMethod(target.method);
+        addMethod(target.getReference());
       } else {
         addMethod(method);
       }
@@ -124,9 +124,9 @@
     @Override
     public void registerInvokeStatic(DexMethod method) {
       DexEncodedMethod target = appInfo.unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
-      if (target != null && target.method != method) {
+      if (target != null && target.getReference() != method) {
         addType(method.holder);
-        addMethod(target.method);
+        addMethod(target.getReference());
       } else {
         addMethod(method);
       }
@@ -204,7 +204,7 @@
       addType(field.type);
       DexEncodedField baseField = appInfo.resolveField(field).getResolvedField();
       if (baseField != null && baseField.getHolderType() != field.holder) {
-        field = baseField.field;
+        field = baseField.getReference();
       }
       addType(field.holder);
       Set<DexField> typeFields = fields.get(field.holder);
@@ -245,7 +245,7 @@
     }
 
     private void registerField(DexEncodedField field) {
-      registerTypeReference(field.field.type);
+      registerTypeReference(field.getReference().type);
     }
 
     private void registerMethod(ProgramMethod method) {
@@ -277,10 +277,11 @@
       clazz.forEachMethod(
           method -> {
             ResolutionResult resolutionResult =
-                appInfo.resolveMethodOn(superType, method.method, superType != clazz.superType);
+                appInfo.resolveMethodOn(
+                    superType, method.getReference(), superType != clazz.superType);
             DexEncodedMethod dexEncodedMethod = resolutionResult.getSingleTarget();
             if (dexEncodedMethod != null) {
-              addMethod(dexEncodedMethod.method);
+              addMethod(dexEncodedMethod.getReference());
             }
           });
     }
@@ -428,7 +429,7 @@
       if (encodedMethod.accessFlags.isConstructor()) {
         printConstructorName(encodedMethod);
       } else {
-        DexMethod method = encodedMethod.method;
+        DexMethod method = encodedMethod.getReference();
         append(method.proto.returnType.toSourceString());
         append(" ");
         append(method.name.toSourceString());
@@ -467,7 +468,7 @@
           }
           methodDefinitions.add(encodedMethod);
         }
-        methodDefinitions.sort(Comparator.comparing(x -> x.method.name.toSourceString()));
+        methodDefinitions.sort(Comparator.comparing(x -> x.getReference().name.toSourceString()));
         for (DexEncodedMethod encodedMethod : methodDefinitions) {
           printMethod(encodedMethod, dexClass.type.toSourceString());
         }
@@ -506,7 +507,7 @@
     void printMethod(DexEncodedMethod encodedMethod, String typeName) {
       append(typeName + ": ");
       printNameAndReturn(encodedMethod);
-      printArguments(encodedMethod.method);
+      printArguments(encodedMethod.getReference());
       appendLine("");
     }
 
@@ -578,13 +579,13 @@
         append("static ");
       }
       printNameAndReturn(encodedMethod);
-      printArguments(encodedMethod.method);
+      printArguments(encodedMethod.getReference());
       appendLine(";");
     }
 
     @Override
     void printPackageNames(List<String> packageNames) {
-      append("-keeppackagenames " + StringUtils.join(packageNames, ",") + "\n");
+      append("-keeppackagenames " + StringUtils.join(",", packageNames) + "\n");
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index b77c8ed..e6cc1f5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.RecordRewriter;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.ir.optimize.NestReducer;
@@ -283,7 +284,7 @@
       {
         ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
         DirectMappedDexApplication application = applicationReader.read(executorService).toDirect();
-        MainDexInfo mainDexInfo = applicationReader.readMainDexClasses(application);
+        MainDexInfo mainDexInfo = applicationReader.readMainDexClassesForR8(application);
 
         // Now that the dex-application is fully loaded, close any internal archive providers.
         inputApp.closeInternalArchiveProviders();
@@ -309,6 +310,9 @@
       if (options.enableEnumUnboxing) {
         EnumUnboxingCfMethods.registerSynthesizedCodeReferences(appView.dexItemFactory());
       }
+      if (options.shouldDesugarRecords()) {
+        RecordRewriter.registerSynthesizedCodeReferences(appView.dexItemFactory());
+      }
       CfUtilityMethodsForCodeOptimizations.registerSynthesizedCodeReferences(
           appView.dexItemFactory());
 
@@ -496,7 +500,7 @@
           timing.begin("HorizontalClassMerger");
           HorizontalClassMerger merger = new HorizontalClassMerger(appViewWithLiveness);
           HorizontalClassMergerResult horizontalClassMergerResult =
-              merger.run(runtimeTypeCheckInfo);
+              merger.run(runtimeTypeCheckInfo, timing);
           if (horizontalClassMergerResult != null) {
             // Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that
             // allocations sites, fields accesses, etc. are correctly transferred to the target
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 d56b873..bc57c25 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -452,7 +452,7 @@
 
   @Override
   public void print(CfFrame frame) {
-    String keys = join(frame.getLocals().keySet(), ",");
+    String keys = join(",", frame.getLocals().keySet());
     String values = join(",", frame.getLocals().values(), this::frameTypeType);
     String stack = join(",", frame.getStack(), this::frameTypeType);
     printNewInstruction(
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 9862f27..64dd9a5 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -140,7 +140,7 @@
     }
     if (method != null) {
       builder.append(".method ");
-      appendMethod(method.method);
+      appendMethod(method.getReference());
       newline();
     }
     builder.append(".limit stack ").append(code.getMaxStack());
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index ec4b22f..90fc9ee 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -242,7 +242,8 @@
                   : createInitializedType(code.method().getHolderType());
         } else {
           argumentType =
-              createInitializedType(code.method().method.proto.parameters.values[argumentIndex]);
+              createInitializedType(
+                  code.method().getReference().proto.parameters.values[argumentIndex]);
         }
         Value outValue = instruction.outValue();
         if (outValue.outType().isObject()) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 21de6ac..df1a88b 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
 import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -29,6 +30,7 @@
 import com.android.tools.r8.graph.JarClassFileReader;
 import com.android.tools.r8.graph.LazyLoadedDexApplication;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -65,6 +67,9 @@
   private final Timing timing;
   private final AndroidApp inputApp;
 
+  private boolean hasReadProgramResourcesFromCf = false;
+  private boolean hasReadProgramResourcesFromDex = false;
+
   public interface ProgramClassConflictResolver {
     DexProgramClass resolveClassConflict(DexProgramClass a, DexProgramClass b);
   }
@@ -151,7 +156,9 @@
       // TODO: try and preload less classes.
       readProguardMap(proguardMap, builder, executorService, futures);
       ClassReader classReader = new ClassReader(executorService, futures);
-      JarClassFileReader<DexProgramClass> jcf = classReader.readSources();
+      classReader.readSources();
+      hasReadProgramResourcesFromCf = classReader.hasReadProgramResourceFromCf;
+      hasReadProgramResourcesFromDex = classReader.hasReadProgramResourceFromDex;
       ThreadUtils.awaitFutures(futures);
       classReader.initializeLazyClassCollection(builder);
       for (ProgramResourceProvider provider : inputApp.getProgramResourceProviders()) {
@@ -199,17 +206,35 @@
   }
 
   public MainDexInfo readMainDexClasses(DexApplication app) {
+    return readMainDexClasses(app, hasReadProgramResourcesFromCf);
+  }
+
+  public MainDexInfo readMainDexClassesForR8(DexApplication app) {
+    // Officially R8 only support reading CF program inputs, thus we always generate a deprecated
+    // diagnostic if main-dex list is used.
+    return readMainDexClasses(app, true);
+  }
+
+  private MainDexInfo readMainDexClasses(DexApplication app, boolean emitDeprecatedDiagnostics) {
     MainDexInfo.Builder builder = MainDexInfo.none().builder();
     if (inputApp.hasMainDexList()) {
       for (StringResource resource : inputApp.getMainDexListResources()) {
+        if (emitDeprecatedDiagnostics) {
+          options.reporter.warning(new UnsupportedMainDexListUsageDiagnostic(resource.getOrigin()));
+        }
         addToMainDexClasses(app, builder, MainDexListParser.parseList(resource, itemFactory));
       }
-      addToMainDexClasses(
-          app,
-          builder,
-          inputApp.getMainDexClasses().stream()
-              .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
-              .collect(Collectors.toList()));
+      if (!inputApp.getMainDexClasses().isEmpty()) {
+        if (emitDeprecatedDiagnostics) {
+          options.reporter.warning(new UnsupportedMainDexListUsageDiagnostic(Origin.unknown()));
+        }
+        addToMainDexClasses(
+            app,
+            builder,
+            inputApp.getMainDexClasses().stream()
+                .map(clazz -> itemFactory.createType(DescriptorUtils.javaTypeToDescriptor(clazz)))
+                .collect(Collectors.toList()));
+      }
     }
     return builder.buildList();
   }
@@ -291,6 +316,13 @@
     // Jar application reader to share across all class readers.
     private final JarApplicationReader application = new JarApplicationReader(options);
 
+    // Flag of which input resource types have flowen into the program classes.
+    // Note that this is just at the level of the resources having been given.
+    // It is possible to have, e.g., an empty dex file, so no classes, but this will still be true
+    // as there was a dex resource.
+    private boolean hasReadProgramResourceFromCf = false;
+    private boolean hasReadProgramResourceFromDex = false;
+
     ClassReader(ExecutorService executorService, List<Future<?>> futures) {
       this.executorService = executorService;
       this.futures = futures;
@@ -298,36 +330,42 @@
 
     private void readDexSources(List<ProgramResource> dexSources, Queue<DexProgramClass> classes)
         throws IOException, ResourceException {
-      if (dexSources.size() > 0) {
-        List<DexParser<DexProgramClass>> dexParsers = new ArrayList<>(dexSources.size());
-        int computedMinApiLevel = options.minApiLevel;
-        for (ProgramResource input : dexSources) {
-          DexReader dexReader = new DexReader(input);
-          if (options.passthroughDexCode) {
-            computedMinApiLevel = validateOrComputeMinApiLevel(computedMinApiLevel, dexReader);
-          }
-          dexParsers.add(new DexParser<>(dexReader, PROGRAM, options));
+      if (dexSources.isEmpty()) {
+        return;
+      }
+      hasReadProgramResourceFromDex = true;
+      List<DexParser<DexProgramClass>> dexParsers = new ArrayList<>(dexSources.size());
+      int computedMinApiLevel = options.minApiLevel;
+      for (ProgramResource input : dexSources) {
+        DexReader dexReader = new DexReader(input);
+        if (options.passthroughDexCode) {
+          computedMinApiLevel = validateOrComputeMinApiLevel(computedMinApiLevel, dexReader);
         }
+        dexParsers.add(new DexParser<>(dexReader, PROGRAM, options));
+      }
 
-        options.minApiLevel = computedMinApiLevel;
+      options.minApiLevel = computedMinApiLevel;
+      for (DexParser<DexProgramClass> dexParser : dexParsers) {
+        dexParser.populateIndexTables();
+      }
+      // Read the DexCode items and DexProgramClass items in parallel.
+      if (!options.skipReadingDexCode) {
         for (DexParser<DexProgramClass> dexParser : dexParsers) {
-          dexParser.populateIndexTables();
-        }
-        // Read the DexCode items and DexProgramClass items in parallel.
-        if (!options.skipReadingDexCode) {
-          for (DexParser<DexProgramClass> dexParser : dexParsers) {
-            futures.add(
-                executorService.submit(
-                    () -> {
-                      dexParser.addClassDefsTo(classes::add); // Depends on Methods, Code items etc.
-                    }));
-          }
+          futures.add(
+              executorService.submit(
+                  () -> {
+                    dexParser.addClassDefsTo(classes::add); // Depends on Methods, Code items etc.
+                  }));
         }
       }
     }
 
-    private JarClassFileReader<DexProgramClass> readClassSources(
+    private void readClassSources(
         List<ProgramResource> classSources, Queue<DexProgramClass> classes) {
+      if (classSources.isEmpty()) {
+        return;
+      }
+      hasReadProgramResourceFromCf = true;
       JarClassFileReader<DexProgramClass> reader =
           new JarClassFileReader<>(application, classes::add, PROGRAM);
       // Read classes in parallel.
@@ -341,10 +379,9 @@
                   return null;
                 }));
       }
-      return reader;
     }
 
-    JarClassFileReader<DexProgramClass> readSources() throws IOException, ResourceException {
+    void readSources() throws IOException, ResourceException {
       Collection<ProgramResource> resources = inputApp.computeAllProgramResources();
       List<ProgramResource> dexResources = new ArrayList<>(resources.size());
       List<ProgramResource> cfResources = new ArrayList<>(resources.size());
@@ -357,7 +394,7 @@
         }
       }
       readDexSources(dexResources, programClasses);
-      return readClassSources(cfResources, programClasses);
+      readClassSources(cfResources, programClasses);
     }
 
     private <T extends DexClass> ClassProvider<T> buildClassProvider(
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index cd78995..26bbc5e 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InstructionFactory;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.ApplicationReaderMap;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -79,6 +80,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -992,9 +994,12 @@
   private void populateTypes() {
     DexSection dexSection = lookupSection(Constants.TYPE_TYPE_ID_ITEM);
     assert verifyOrderOfTypeIds(dexSection);
+    Map<DexType, DexType> typeMap = ApplicationReaderMap.getTypeMap(options);
     indexedItems.initializeTypes(dexSection.length);
     for (int i = 0; i < dexSection.length; i++) {
-      indexedItems.setType(i, typeAt(i));
+      DexType type = typeAt(i);
+      DexType actualType = typeMap.getOrDefault(type, type);
+      indexedItems.setType(i, actualType);
     }
   }
 
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 adfab44..889fc27 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -275,25 +275,25 @@
   //     static methods, as well as public non-abstract (default)
   //     and private instance methods.
   private void checkInterfaceMethod(DexEncodedMethod method) {
-    if (application.dexItemFactory.isClassConstructor(method.method)) {
+    if (application.dexItemFactory.isClassConstructor(method.getReference())) {
       return; // Class constructor is always OK.
     }
     if (method.accessFlags.isStatic()) {
       if (!options.canUseDefaultAndStaticInterfaceMethods()
           && !options.testing.allowStaticInterfaceMethodsForPreNApiLevel) {
         throw options.reporter.fatalError(
-            new StaticInterfaceMethodDiagnostic(new MethodPosition(method.method)));
+            new StaticInterfaceMethodDiagnostic(new MethodPosition(method.getReference())));
       }
 
     } else {
       if (method.isInstanceInitializer()) {
         throw new CompilationError(
-            "Interface must not have constructors: " + method.method.toSourceString());
+            "Interface must not have constructors: " + method.getReference().toSourceString());
       }
       if (!method.accessFlags.isAbstract() && !method.accessFlags.isPrivate() &&
           !options.canUseDefaultAndStaticInterfaceMethods()) {
         throw options.reporter.fatalError(
-            new DefaultInterfaceMethodDiagnostic(new MethodPosition(method.method)));
+            new DefaultInterfaceMethodDiagnostic(new MethodPosition(method.getReference())));
       }
     }
 
@@ -302,12 +302,14 @@
         return;
       }
       throw options.reporter.fatalError(
-          new PrivateInterfaceMethodDiagnostic(new MethodPosition(method.method)));
+          new PrivateInterfaceMethodDiagnostic(new MethodPosition(method.getReference())));
     }
 
     if (!method.accessFlags.isPublic()) {
-      throw new CompilationError("Interface methods must not be "
-          + "protected or package private: " + method.method.toSourceString());
+      throw new CompilationError(
+          "Interface methods must not be "
+              + "protected or package private: "
+              + method.getReference().toSourceString());
     }
   }
 
@@ -636,32 +638,36 @@
 
   private void writeEncodedFields(List<DexEncodedField> unsortedFields) {
     List<DexEncodedField> fields = new ArrayList<>(unsortedFields);
-    fields.sort((a, b) -> a.field.acceptCompareTo(b.field, mapping.getCompareToVisitor()));
+    fields.sort(
+        (a, b) ->
+            a.getReference().acceptCompareTo(b.getReference(), mapping.getCompareToVisitor()));
     int currentOffset = 0;
     for (DexEncodedField field : fields) {
       assert field.validateDexValue(application.dexItemFactory);
-      int nextOffset = mapping.getOffsetFor(field.field);
+      int nextOffset = mapping.getOffsetFor(field.getReference());
       assert nextOffset - currentOffset >= 0;
       dest.putUleb128(nextOffset - currentOffset);
       currentOffset = nextOffset;
       dest.putUleb128(field.accessFlags.getAsDexAccessFlags());
-      desugaredLibraryCodeToKeep.recordField(field.field);
+      desugaredLibraryCodeToKeep.recordField(field.getReference());
     }
   }
 
   private void writeEncodedMethods(
       Iterable<DexEncodedMethod> unsortedMethods, boolean isSharedSynthetic) {
     List<DexEncodedMethod> methods = IterableUtils.toNewArrayList(unsortedMethods);
-    methods.sort((a, b) -> a.method.acceptCompareTo(b.method, mapping.getCompareToVisitor()));
+    methods.sort(
+        (a, b) ->
+            a.getReference().acceptCompareTo(b.getReference(), mapping.getCompareToVisitor()));
     int currentOffset = 0;
     for (DexEncodedMethod method : methods) {
-      int nextOffset = mapping.getOffsetFor(method.method);
+      int nextOffset = mapping.getOffsetFor(method.getReference());
       assert nextOffset - currentOffset >= 0;
       dest.putUleb128(nextOffset - currentOffset);
       currentOffset = nextOffset;
       dest.putUleb128(method.accessFlags.getAsDexAccessFlags());
       DexCode code = codeMapping.getCode(method);
-      desugaredLibraryCodeToKeep.recordMethod(method.method);
+      desugaredLibraryCodeToKeep.recordMethod(method.getReference());
       if (code == null) {
         assert method.shouldNotHaveCode();
         dest.putUleb128(0);
diff --git a/src/main/java/com/android/tools/r8/errors/DuplicateTypesDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DuplicateTypesDiagnostic.java
index 1c35883..6aa7234 100644
--- a/src/main/java/com/android/tools/r8/errors/DuplicateTypesDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/DuplicateTypesDiagnostic.java
@@ -64,6 +64,6 @@
   @Override
   public String getDiagnosticMessage() {
     String typeName = DescriptorUtils.descriptorToJavaType(type.getDescriptor());
-    return "Type " + typeName + " is defined multiple times: " + StringUtils.join(origins, ", ");
+    return "Type " + typeName + " is defined multiple times: " + StringUtils.join(", ", origins);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/errors/UnsupportedMainDexListUsageDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnsupportedMainDexListUsageDiagnostic.java
new file mode 100644
index 0000000..9bcbcba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/UnsupportedMainDexListUsageDiagnostic.java
@@ -0,0 +1,40 @@
+// 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+/**
+ * Diagnostic to issue warnings/errors for unsupported usage of main-dex list.
+ *
+ * <p>See b/181858113 for context.
+ */
+@Keep
+public class UnsupportedMainDexListUsageDiagnostic implements Diagnostic {
+  private final Origin origin;
+
+  public UnsupportedMainDexListUsageDiagnostic(Origin origin) {
+    this.origin = origin;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "Unsupported usage of main-dex list. "
+        + "The usage of main-dex-list content for the compilation of non-DEX inputs is deprecated. "
+        + "See issue https://issuetracker.google.com/181858113 for context.";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index d96643d..c8c462b 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -159,6 +159,11 @@
 
   public boolean isInBaseOrSameFeatureAs(
       DexProgramClass clazz, ProgramDefinition context, SyntheticItems syntheticItems) {
+    return isInBaseOrSameFeatureAs(clazz.getContextType(), context, syntheticItems);
+  }
+
+  public boolean isInBaseOrSameFeatureAs(
+      DexType clazz, ProgramDefinition context, SyntheticItems syntheticItems) {
     FeatureSplit split = getFeatureSplit(clazz, syntheticItems);
     return split.isBase() || split == getFeatureSplit(context, syntheticItems);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 51ff1e2..cf2c789 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -25,15 +25,16 @@
 
   public static OptionalBool isClassAccessible(
       DexClass clazz,
-      ProgramDefinition context,
+      Definition context,
       ClassToFeatureSplitMap classToFeatureSplitMap,
       SyntheticItems syntheticItems) {
     if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
       return OptionalBool.FALSE;
     }
     if (clazz.isProgramClass()
+        && context.isProgramDefinition()
         && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
-            clazz.asProgramClass(), context, syntheticItems)) {
+            clazz.asProgramClass(), context.asProgramDefinition(), syntheticItems)) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
@@ -47,7 +48,7 @@
     return isMemberAccessible(
         resolutionResult.getResolutionPair(),
         resolutionResult.getInitialResolutionHolder(),
-        context,
+        context.getContextClass(),
         appInfo);
   }
 
@@ -56,13 +57,14 @@
       DexClass initialResolutionHolder,
       ProgramDefinition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isMemberAccessible(member, initialResolutionHolder, context, appView.appInfo());
+    return isMemberAccessible(
+        member, initialResolutionHolder, context.getContextClass(), appView.appInfo());
   }
 
-  public static OptionalBool isMemberAccessible(
+  static OptionalBool isMemberAccessible(
       DexClassAndMember<?, ?> member,
       DexClass initialResolutionHolder,
-      ProgramDefinition context,
+      DexClass context,
       AppInfoWithClassHierarchy appInfo) {
     AccessFlags<?> memberFlags = member.getDefinition().getAccessFlags();
     OptionalBool classAccessibility =
@@ -78,25 +80,28 @@
       return classAccessibility;
     }
     if (memberFlags.isPrivate()) {
-      if (!isNestMate(member.getHolder(), context.getContextClass())) {
+      if (!isNestMate(member.getHolder(), context)) {
         return OptionalBool.FALSE;
       }
       return classAccessibility;
     }
-    if (member.getHolderType().isSamePackage(context.getContextType())) {
+    if (member.getHolderType().isSamePackage(context.getType())) {
       return classAccessibility;
     }
-    if (memberFlags.isProtected()
-        && appInfo.isSubtype(context.getContextType(), member.getHolderType())) {
+    if (memberFlags.isProtected() && appInfo.isSubtype(context.getType(), member.getHolderType())) {
       return classAccessibility;
     }
     return OptionalBool.FALSE;
   }
 
-  private static boolean isNestMate(DexClass clazz, DexProgramClass context) {
+  private static boolean isNestMate(DexClass clazz, DexClass context) {
     if (clazz == context) {
       return true;
     }
+    if (context == null) {
+      assert false : "context should not be null";
+      return false;
+    }
     if (!clazz.isInANest() || !context.isInANest()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index c8d9834..2ee3013 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -455,7 +455,7 @@
     }
     assert potentialHolder.isInterface();
     for (DexEncodedMethod virtualMethod : potentialHolder.virtualMethods()) {
-      if (virtualMethod.method.hasSameProtoAndName(method.method)
+      if (virtualMethod.getReference().hasSameProtoAndName(method.getReference())
           && virtualMethod.accessFlags.isSameVisibility(method.accessFlags)) {
         return true;
       }
@@ -711,7 +711,7 @@
       // allowed because of nests, a NoSuchMethodError. Which error cannot be determined without
       // knowing the calling context.
       if (result.isPrivateMethod() && clazz != initialResolutionHolder) {
-        return new IllegalAccessOrNoSuchMethodResult(result);
+        return new IllegalAccessOrNoSuchMethodResult(initialResolutionHolder, result);
       }
       return new SingleResolutionResult(initialResolutionHolder, clazz, result);
     }
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 7622121..7321cc0 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -588,11 +588,11 @@
     if (!options().isGeneratingClassFiles()) {
       return false;
     }
-    if (cfByteCodePassThrough.contains(method.method)) {
+    if (cfByteCodePassThrough.contains(method.getReference())) {
       return true;
     }
     return options().testing.cfByteCodePassThrough != null
-        && options().testing.cfByteCodePassThrough.test(method.method);
+        && options().testing.cfByteCodePassThrough.test(method.getReference());
   }
 
   public boolean hasCfByteCodePassThroughMethods() {
diff --git a/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java b/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
new file mode 100644
index 0000000..82b9d7f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
@@ -0,0 +1,31 @@
+// 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.utils.InternalOptions;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public class ApplicationReaderMap {
+
+  public static Map<String, String> getDescriptorMap(InternalOptions options) {
+    ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+    if (options.shouldDesugarRecords()) {
+      builder.put(DexItemFactory.recordTagDescriptorString, DexItemFactory.recordDescriptorString);
+    }
+    return builder.build();
+  }
+
+  public static Map<DexType, DexType> getTypeMap(InternalOptions options) {
+    DexItemFactory factory = options.dexItemFactory();
+    ImmutableMap.Builder<DexType, DexType> builder = ImmutableMap.builder();
+    getDescriptorMap(options)
+        .forEach(
+            (k, v) -> {
+              builder.put(factory.createType(k), factory.createType(v));
+            });
+    return builder.build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index 187402a..a05960f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -27,7 +27,7 @@
 public final class AppliedGraphLens extends NonIdentityGraphLens {
 
   private final MutableBidirectionalManyToOneRepresentativeMap<DexType, DexType> renamedTypeNames =
-      new BidirectionalManyToOneRepresentativeHashMap<>();
+      BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
   private final BiMap<DexField, DexField> originalFieldSignatures = HashBiMap.create();
   private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
 
@@ -50,7 +50,7 @@
 
       // Record original field signatures.
       for (DexEncodedField encodedField : clazz.fields()) {
-        DexField field = encodedField.field;
+        DexField field = encodedField.getReference();
         DexField original = appView.graphLens().getOriginalFieldSignature(field);
         if (original != field) {
           DexField existing = originalFieldSignatures.forcePut(field, original);
@@ -60,7 +60,7 @@
 
       // Record original method signatures.
       for (DexEncodedMethod encodedMethod : clazz.methods()) {
-        DexMethod method = encodedMethod.method;
+        DexMethod method = encodedMethod.getReference();
         DexMethod original = appView.graphLens().getOriginalMethodSignature(method);
         DexMethod existing = originalMethodSignatures.inverse().get(original);
         if (existing == null) {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 1c30bbf..4ed66d8 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -128,9 +128,10 @@
   void writeField(DexEncodedField field, PrintStream ps) {
     if (writeFields) {
       ClassNameMapper naming = application.getProguardMap();
-      FieldSignature fieldSignature = naming != null
-          ? naming.originalSignatureOf(field.field)
-          : FieldSignature.fromDexField(field.field);
+      FieldSignature fieldSignature =
+          naming != null
+              ? naming.originalSignatureOf(field.getReference())
+              : FieldSignature.fromDexField(field.getReference());
       writeAnnotations(null, field.annotations(), ps);
       ps.print(field.accessFlags + " ");
       ps.print(fieldSignature);
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 71b938f..315bb3e 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -300,7 +300,7 @@
     }
     // If tree shaking, only keep annotations on kept methods.
     if (appView.appInfo().hasLiveness()
-        && !appView.appInfo().withLiveness().isPinned(method.method)) {
+        && !appView.appInfo().withLiveness().isPinned(method.getReference())) {
       return false;
     }
     return true;
@@ -575,7 +575,7 @@
     // The enqueuer might build IR to trace reflective behaviour. At that point liveness is not
     // known, so be conservative with collection parameter name information.
     if (appView.appInfo().hasLiveness()
-        && !appView.appInfo().withLiveness().isPinned(encodedMethod.method)) {
+        && !appView.appInfo().withLiveness().isPinned(encodedMethod.getReference())) {
       return DexEncodedMethod.NO_PARAMETER_INFO;
     }
 
@@ -585,7 +585,7 @@
     if (!encodedMethod.isStatic()) {
       localSlotsForParameters.set(nextLocalSlotsForParameters++);
     }
-    for (DexType type : encodedMethod.method.proto.parameters.values) {
+    for (DexType type : encodedMethod.getReference().proto.parameters.values) {
       localSlotsForParameters.set(nextLocalSlotsForParameters);
       nextLocalSlotsForParameters += type.isLongType() || type.isDoubleType() ? 2 : 1;
     }
@@ -607,7 +607,7 @@
 
   @Override
   public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) {
-    DexProto proto = method.method.proto;
+    DexProto proto = method.getReference().proto;
     boolean isStatic = method.accessFlags.isStatic();
     int argumentCount = proto.parameters.values.length + (isStatic ? 0 : 1);
     Int2IntArrayMap indexToNumber = new Int2IntArrayMap(argumentCount);
@@ -728,7 +728,7 @@
     if (!method.isInstanceInitializer()
         && appView
             .graphLens()
-            .getOriginalMethodSignature(method.method)
+            .getOriginalMethodSignature(method.getReference())
             .isInstanceInitializer(appView.dexItemFactory())) {
       // We cannot verify instance initializers if they are moved.
       return StackMapStatus.NOT_PRESENT;
@@ -746,7 +746,7 @@
               return reportStackMapError(
                   CfCodeStackMapValidatingException.multipleFramesForLabel(
                       origin,
-                      appView.graphLens().getOriginalMethodSignature(method.method),
+                      appView.graphLens().getOriginalMethodSignature(method.getReference()),
                       appView),
                   appView);
             }
@@ -756,7 +756,9 @@
           // From b/168212806, it is possible that the first instruction is a frame.
           return reportStackMapError(
               CfCodeStackMapValidatingException.unexpectedStackMapFrame(
-                  origin, appView.graphLens().getOriginalMethodSignature(method.method), appView),
+                  origin,
+                  appView.graphLens().getOriginalMethodSignature(method.getReference()),
+                  appView),
               appView);
         }
       }
@@ -777,15 +779,17 @@
     if (requireStackMapFrame && stateMap.isEmpty()) {
       return reportStackMapError(
           CfCodeStackMapValidatingException.noFramesForMethodWithJumps(
-              origin, appView.graphLens().getOriginalMethodSignature(method.method), appView),
+              origin,
+              appView.graphLens().getOriginalMethodSignature(method.getReference()),
+              appView),
           appView);
     }
     DexType context = appView.graphLens().lookupType(method.getHolderType());
-    DexType returnType = appView.graphLens().lookupType(method.method.getReturnType());
+    DexType returnType = appView.graphLens().lookupType(method.getReference().getReturnType());
     RewrittenPrototypeDescription rewrittenDescription = RewrittenPrototypeDescription.none();
     if (applyProtoTypeChanges) {
       rewrittenDescription =
-          appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.method);
+          appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference());
       if (!rewrittenDescription.isEmpty()
           && rewrittenDescription.getRewrittenReturnInfo() != null) {
         returnType = rewrittenDescription.getRewrittenReturnInfo().getOldType();
@@ -824,7 +828,7 @@
         return reportStackMapError(
             CfCodeStackMapValidatingException.toDiagnostics(
                 origin,
-                appView.graphLens().getOriginalMethodSignature(method.method),
+                appView.graphLens().getOriginalMethodSignature(method.getReference()),
                 i,
                 instruction,
                 ex.getMessage(),
@@ -889,7 +893,7 @@
       initialLocals.put(index++, FrameType.initialized(context));
     }
     ArgumentInfoCollection argumentsInfo = protoTypeChanges.getArgumentInfoCollection();
-    DexType[] parameters = method.method.proto.parameters.values;
+    DexType[] parameters = method.getReference().proto.parameters.values;
     int originalNumberOfArguments =
         parameters.length
             + argumentsInfo.numberOfRemovedArguments()
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index ad9bf23..d2d763e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -46,17 +46,18 @@
   }
 
   public List<DexEncodedMethod> sortMethodAnnotations(CompareToVisitor visitor) {
-    methodAnnotations.sort((a, b) -> a.method.acceptCompareTo(b.method, visitor));
+    methodAnnotations.sort((a, b) -> a.getReference().acceptCompareTo(b.getReference(), visitor));
     return methodAnnotations;
   }
 
   public List<DexEncodedMethod> sortParameterAnnotations(CompareToVisitor visitor) {
-    parameterAnnotations.sort((a, b) -> a.method.acceptCompareTo(b.method, visitor));
+    parameterAnnotations.sort(
+        (a, b) -> a.getReference().acceptCompareTo(b.getReference(), visitor));
     return parameterAnnotations;
   }
 
   public List<DexEncodedField> sortFieldAnnotations(CompareToVisitor visitor) {
-    fieldAnnotations.sort((a, b) -> a.field.acceptCompareTo(b.field, visitor));
+    fieldAnnotations.sort((a, b) -> a.getReference().acceptCompareTo(b.getReference(), visitor));
     return fieldAnnotations;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 130dbcf..b2bc948 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -10,6 +10,9 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
@@ -20,6 +23,7 @@
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
@@ -31,6 +35,7 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -257,7 +262,9 @@
     if (options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() && !isAbstract()) {
       for (DexEncodedMethod method : methods) {
         assert !method.isAbstract()
-            : "Non-abstract method on abstract class: `" + method.method.toSourceString() + "`";
+            : "Non-abstract method on abstract class: `"
+                + method.getReference().toSourceString()
+                + "`";
       }
     }
     return true;
@@ -363,7 +370,7 @@
 
   public boolean definesStaticField(DexField field) {
     for (DexEncodedField encodedField : staticFields()) {
-      if (encodedField.field == field) {
+      if (encodedField.getReference() == field) {
         return true;
       }
     }
@@ -427,7 +434,7 @@
   private boolean verifyCorrectnessOfFieldHolder(DexEncodedField field) {
     assert field.getHolderType() == type
         : "Expected field `"
-            + field.field.toSourceString()
+            + field.getReference().toSourceString()
             + "` to have holder `"
             + type.toSourceString()
             + "`";
@@ -444,8 +451,8 @@
   private boolean verifyNoDuplicateFields() {
     Set<DexField> unique = Sets.newIdentityHashSet();
     for (DexEncodedField field : fields()) {
-      boolean changed = unique.add(field.field);
-      assert changed : "Duplicate field `" + field.field.toSourceString() + "`";
+      boolean changed = unique.add(field.getReference());
+      assert changed : "Duplicate field `" + field.getReference().toSourceString() + "`";
     }
     return true;
   }
@@ -463,11 +470,11 @@
   public DexField lookupUniqueInstanceFieldWithName(DexString name) {
     DexField field = null;
     for (DexEncodedField encodedField : instanceFields()) {
-      if (encodedField.field.name == name) {
+      if (encodedField.getReference().name == name) {
         if (field != null) {
           return null;
         }
-        field = encodedField.field;
+        field = encodedField.getReference();
       }
     }
     return field;
@@ -544,7 +551,7 @@
     DexEncodedMethod matchingName = null;
     DexEncodedMethod signaturePolymorphicMethod = null;
     for (DexEncodedMethod method : virtualMethods()) {
-      if (method.method.name == methodName) {
+      if (method.getReference().name == methodName) {
         if (matchingName != null) {
           // The jvm spec, section 5.4.3.3 details that there must be exactly one method with the
           // given name only.
@@ -564,8 +571,8 @@
         || method.getHolderType() == factory.varHandleType;
     return method.accessFlags.isVarargs()
         && method.accessFlags.isNative()
-        && method.method.proto.parameters.size() == 1
-        && method.method.proto.parameters.values[0] != factory.objectArrayType;
+        && method.getReference().proto.parameters.size() == 1
+        && method.getReference().proto.parameters.values[0] != factory.objectArrayType;
   }
 
   private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> D lookupTarget(
@@ -704,7 +711,7 @@
   public DexEncodedMethod getInitializer(DexType[] parameters) {
     for (DexEncodedMethod method : directMethods()) {
       if (method.isInstanceInitializer()
-          && Arrays.equals(method.method.proto.parameters.values, parameters)) {
+          && Arrays.equals(method.getReference().proto.parameters.values, parameters)) {
         return method;
       }
     }
@@ -778,13 +785,125 @@
       Predicate<DexType> ignore,
       Set<DexType> seen);
 
+  public void forEachImmediateInterface(Consumer<DexType> fn) {
+    for (DexType iface : interfaces.values) {
+      fn.accept(iface);
+    }
+  }
+
   public void forEachImmediateSupertype(Consumer<DexType> fn) {
     if (superType != null) {
       fn.accept(superType);
     }
-    for (DexType iface : interfaces.values) {
-      fn.accept(iface);
+    forEachImmediateInterface(fn);
+  }
+
+  public boolean validInterfaceSignatures() {
+    return getClassSignature().superInterfaceSignatures().isEmpty()
+        || interfaces.values.length == getClassSignature().superInterfaceSignatures.size();
+  }
+
+  public void forEachImmediateInterface(BiConsumer<DexType, ClassTypeSignature> consumer) {
+    assert validInterfaceSignatures();
+
+    // If there is no generic signature information don't pass any type arguments.
+    if (getClassSignature().superInterfaceSignatures().isEmpty()) {
+      forEachImmediateInterface(
+          superInterface ->
+              consumer.accept(superInterface, new ClassTypeSignature(superInterface)));
+      return;
     }
+
+    Iterator<DexType> interfaceIterator = Arrays.asList(interfaces.values).iterator();
+    Iterator<ClassTypeSignature> interfaceSignatureIterator =
+        getClassSignature().superInterfaceSignatures().iterator();
+
+    while (interfaceIterator.hasNext()) {
+      assert interfaceSignatureIterator.hasNext();
+      DexType superInterface = interfaceIterator.next();
+      ClassTypeSignature superInterfaceSignatures = interfaceSignatureIterator.next();
+      consumer.accept(superInterface, superInterfaceSignatures);
+    }
+  }
+
+  public void forEachImmediateSupertype(BiConsumer<DexType, ClassTypeSignature> consumer) {
+    if (superType != null) {
+      consumer.accept(superType, classSignature.superClassSignature);
+    }
+    forEachImmediateInterface(consumer);
+  }
+
+  public void forEachImmediateInterfaceWithAppliedTypeArguments(
+      List<FieldTypeSignature> typeArguments,
+      BiConsumer<DexType, List<FieldTypeSignature>> consumer) {
+    assert validInterfaceSignatures();
+
+    // If there is no generic signature information don't pass any type arguments.
+    if (getClassSignature().superInterfaceSignatures().size() == 0) {
+      forEachImmediateInterface(
+          superInterface -> consumer.accept(superInterface, ImmutableList.of()));
+      return;
+    }
+
+    Iterator<DexType> interfaceIterator = Arrays.asList(interfaces.values).iterator();
+    Iterator<ClassTypeSignature> interfaceSignatureIterator =
+        getClassSignature().superInterfaceSignatures().iterator();
+
+    while (interfaceIterator.hasNext()) {
+      assert interfaceSignatureIterator.hasNext();
+      DexType superInterface = interfaceIterator.next();
+      ClassTypeSignature superInterfaceSignatures = interfaceSignatureIterator.next();
+
+      // With no type arguments erase the signatures.
+      if (typeArguments.isEmpty() && superInterfaceSignatures.hasTypeVariableArguments()) {
+        consumer.accept(superInterface, ImmutableList.of());
+        continue;
+      }
+
+      consumer.accept(superInterface, applyTypeArguments(superInterfaceSignatures, typeArguments));
+    }
+    assert !interfaceSignatureIterator.hasNext();
+  }
+
+  public void forEachImmediateSupertypeWithAppliedTypeArguments(
+      List<FieldTypeSignature> typeArguments,
+      BiConsumer<DexType, List<FieldTypeSignature>> consumer) {
+    if (superType != null) {
+      consumer.accept(
+          superType, applyTypeArguments(getClassSignature().superClassSignature, typeArguments));
+    }
+    forEachImmediateInterfaceWithAppliedTypeArguments(typeArguments, consumer);
+  }
+
+  private List<FieldTypeSignature> applyTypeArguments(
+      ClassTypeSignature superInterfaceSignatures, List<FieldTypeSignature> appliedTypeArguments) {
+    ImmutableList.Builder<FieldTypeSignature> superTypeArgumentsBuilder = ImmutableList.builder();
+    if (superInterfaceSignatures.type.toSourceString().equals("java.util.Map")) {
+      System.currentTimeMillis();
+    }
+    superInterfaceSignatures
+        .typeArguments()
+        .forEach(
+            typeArgument -> {
+              if (typeArgument.isTypeVariableSignature()) {
+                for (int i = 0; i < getClassSignature().getFormalTypeParameters().size(); i++) {
+                  FormalTypeParameter formalTypeParameter =
+                      getClassSignature().getFormalTypeParameters().get(i);
+                  if (formalTypeParameter
+                      .getName()
+                      .equals(typeArgument.asTypeVariableSignature().typeVariable())) {
+                    if (i >= appliedTypeArguments.size()) {
+                      assert false;
+                    } else {
+                      superTypeArgumentsBuilder.add(appliedTypeArguments.get(i));
+                    }
+                  }
+                }
+              } else {
+                superTypeArgumentsBuilder.add(typeArgument);
+              }
+            });
+    return superTypeArgumentsBuilder.build();
   }
 
   @Override
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 32743f0..09bc3b7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -63,11 +63,11 @@
   }
 
   public DexDebugEntryBuilder(DexEncodedMethod method, DexItemFactory factory) {
-    assert method != null && method.method != null;
-    this.method = method.method;
+    assert method != null && method.getReference() != null;
+    this.method = method.getReference();
     positionState =
         new DexDebugPositionState(
-            method.getCode().asDexCode().getDebugInfo().startLine, method.method);
+            method.getCode().asDexCode().getDebugInfo().startLine, method.getReference());
     DexCode code = method.getCode().asDexCode();
     DexDebugInfo info = code.getDebugInfo();
     int argumentRegister = code.registerSize - code.incomingRegisterSize;
@@ -77,7 +77,7 @@
       startArgument(argumentRegister, name, type);
       argumentRegister += ValueType.fromDexType(type).requiredRegisters();
     }
-    DexType[] types = method.method.proto.parameters.values;
+    DexType[] types = method.getReference().proto.parameters.values;
     DexString[] names = info.parameters;
     for (int i = 0; i < types.length; i++) {
       // If null, the parameter has a parameterized type and the local is introduced in the stream.
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 f166e3c..bdfe991 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -110,7 +110,7 @@
     if (startLine == NO_LINE_INFO) {
       return null;
     }
-    DexString[] params = new DexString[method.method.getArity()];
+    DexString[] params = new DexString[method.getReference().getArity()];
     if (arguments != null) {
       assert params.length == arguments.size();
       for (int i = 0; i < arguments.size(); i++) {
@@ -158,7 +158,7 @@
 
   private void startArgument(Argument argument) {
     if (arguments == null) {
-      arguments = new ArrayList<>(method.method.getArity());
+      arguments = new ArrayList<>(method.getReference().getArity());
     }
     if (!argument.outValue().isThis()) {
       arguments.add(argument.getLocalInfo());
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 1a2b411..65bc4fb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -31,7 +31,6 @@
     implements StructuralItem<DexEncodedField> {
   public static final DexEncodedField[] EMPTY_ARRAY = {};
 
-  public final DexField field;
   public final FieldAccessFlags accessFlags;
   private DexValue staticValue;
   private final boolean deprecated;
@@ -42,10 +41,10 @@
   private KotlinFieldLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
 
   private static void specify(StructuralSpecification<DexEncodedField, ?> spec) {
-    spec.withItem(f -> f.field)
-        .withItem(f -> f.accessFlags)
+    spec.withItem(DexEncodedField::getReference)
+        .withItem(DexEncodedField::getAccessFlags)
         .withNullableItem(f -> f.staticValue)
-        .withBool(f -> f.deprecated)
+        .withBool(DexEncodedField::isDeprecated)
         // TODO(b/171867022): The generic signature should be part of the definition.
         .withAssert(f -> f.genericSignature.hasNoSignature());
     // TODO(b/171867022): Should the optimization info and member info be part of the definition?
@@ -82,8 +81,7 @@
       DexValue staticValue,
       boolean deprecated,
       boolean d8R8Synthesized) {
-    super(annotations, d8R8Synthesized);
-    this.field = field;
+    super(field, annotations, d8R8Synthesized);
     this.accessFlags = accessFlags;
     this.staticValue = staticValue;
     this.deprecated = deprecated;
@@ -103,7 +101,7 @@
   }
 
   public DexType type() {
-    return field.type;
+    return getReference().type;
   }
 
   public boolean isDeprecated() {
@@ -111,8 +109,8 @@
   }
 
   public boolean isProgramField(DexDefinitionSupplier definitions) {
-    if (field.holder.isClassType()) {
-      DexClass clazz = definitions.definitionFor(field.holder);
+    if (getReference().holder.isClassType()) {
+      DexClass clazz = definitions.definitionFor(getReference().holder);
       return clazz != null && clazz.isProgramClass();
     }
     return false;
@@ -158,22 +156,17 @@
 
   @Override
   public String toString() {
-    return "Encoded field " + field;
+    return "Encoded field " + getReference();
   }
 
   @Override
   public String toSmaliString() {
-    return field.toSmaliString();
+    return getReference().toSmaliString();
   }
 
   @Override
   public String toSourceString() {
-    return field.toSourceString();
-  }
-
-  @Override
-  public DexField getReference() {
-    return field;
+    return getReference().toSourceString();
   }
 
   public DexType getType() {
@@ -201,7 +194,7 @@
 
   public ProgramField asProgramField(DexDefinitionSupplier definitions) {
     assert getHolderType().isClassType();
-    DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(field));
+    DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(getReference()));
     if (clazz != null) {
       return new ProgramField(clazz, this);
     }
@@ -264,7 +257,7 @@
 
   public DexValue getStaticValue() {
     assert accessFlags.isStatic();
-    return staticValue == null ? DexValue.defaultForType(field.type) : staticValue;
+    return staticValue == null ? DexValue.defaultForType(getReference().type) : staticValue;
   }
 
   /**
@@ -277,7 +270,7 @@
     boolean isWritten = appView.appInfo().isFieldWrittenByFieldPutInstruction(this);
     if (!isWritten) {
       // Since the field is not written, we can simply return the default value for the type.
-      DexValue value = isStatic() ? getStaticValue() : DexValue.defaultForType(field.type);
+      DexValue value = isStatic() ? getStaticValue() : DexValue.defaultForType(getReference().type);
       return value.asConstInstruction(appView, code, local);
     }
 
@@ -286,11 +279,11 @@
     if (abstractValue.isSingleValue()) {
       SingleValue singleValue = abstractValue.asSingleValue();
       if (singleValue.isSingleFieldValue()
-          && singleValue.asSingleFieldValue().getField() == field) {
+          && singleValue.asSingleFieldValue().getField() == getReference()) {
         return null;
       }
       if (singleValue.isMaterializableInContext(appView, code.context())) {
-        TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
+        TypeElement type = TypeElement.fromDexType(getReference().type, maybeNull(), appView);
         return singleValue.createMaterializingInstruction(
             appView, code, TypeAndLocalInfoSupplier.create(type, local));
       }
@@ -299,12 +292,12 @@
     // The only way to figure out whether the static value contains the final value is ensure the
     // value is not the default or check that <clinit> is not present.
     if (accessFlags.isFinal() && isStatic()) {
-      DexClass clazz = appView.definitionFor(field.holder);
+      DexClass clazz = appView.definitionFor(getReference().holder);
       if (clazz == null || clazz.hasClassInitializer()) {
         return null;
       }
       DexValue staticValue = getStaticValue();
-      if (!staticValue.isDefault(field.type)) {
+      if (!staticValue.isDefault(getReference().type)) {
         return staticValue.asConstInstruction(appView, code, local);
       }
     }
@@ -317,7 +310,7 @@
   }
 
   public DexEncodedField toTypeSubstitutedField(DexField field, Consumer<Builder> consumer) {
-    if (this.field == field) {
+    if (this.getReference() == field) {
       return this;
     }
     return builder(this).setField(field).apply(consumer).build();
@@ -327,12 +320,13 @@
     if (!accessFlags.isStatic() || staticValue == null) {
       return true;
     }
-    if (field.type.isPrimitiveType()) {
-      assert staticValue.getType(factory) == field.type
-          : "Static " + field + " has invalid static value " + staticValue + ".";
+    if (getReference().type.isPrimitiveType()) {
+      assert staticValue.getType(factory) == getReference().type
+          : "Static " + getReference() + " has invalid static value " + staticValue + ".";
     }
     if (staticValue.isDexValueNull()) {
-      assert field.type.isReferenceType() : "Static " + field + " has invalid null static value.";
+      assert getReference().type.isReferenceType()
+          : "Static " + getReference() + " has invalid null static value.";
     }
     // TODO(b/150593449): Support non primitive DexValue (String, enum) and add assertions.
     return true;
@@ -369,7 +363,7 @@
 
     Builder(DexEncodedField from) {
       // Copy all the mutable state of a DexEncodedField here.
-      field = from.field;
+      field = from.getReference();
       accessFlags = from.accessFlags.copy();
       // TODO(b/169923358): Consider removing the fieldSignature here.
       genericSignature = from.getGenericSignature();
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index 7d66bb6..0074842 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -16,8 +16,11 @@
   // set.
   private final boolean d8R8Synthesized;
 
-  public DexEncodedMember(DexAnnotationSet annotations, boolean d8R8Synthesized) {
+  private final R reference;
+
+  public DexEncodedMember(R reference, DexAnnotationSet annotations, boolean d8R8Synthesized) {
     super(annotations);
+    this.reference = reference;
     this.d8R8Synthesized = d8R8Synthesized;
   }
 
@@ -32,7 +35,9 @@
   }
 
   @Override
-  public abstract R getReference();
+  public R getReference() {
+    return reference;
+  }
 
   public boolean isD8R8Synthesized() {
     return d8R8Synthesized;
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 470bee9..d387d77 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -150,7 +150,6 @@
   public static final Int2ReferenceMap<DebugLocalInfo> NO_PARAMETER_INFO =
       new Int2ReferenceArrayMap<>(0);
 
-  public final DexMethod method;
   public final MethodAccessFlags accessFlags;
   public final boolean deprecated;
   public ParameterAnnotationsList parameterAnnotationsList;
@@ -311,8 +310,7 @@
       boolean d8R8Synthesized,
       CfVersion classFileVersion,
       boolean deprecated) {
-    super(annotations, d8R8Synthesized);
-    this.method = method;
+    super(method, annotations, d8R8Synthesized);
     this.accessFlags = accessFlags;
     this.deprecated = deprecated;
     this.genericSignature = genericSignature;
@@ -383,12 +381,6 @@
     return getReference().getProto();
   }
 
-  @Override
-  public DexMethod getReference() {
-    checkIfObsolete();
-    return method;
-  }
-
   public DexType getParameter(int index) {
     return getReference().getParameter(index);
   }
@@ -398,11 +390,11 @@
   }
 
   public DexMethodSignature getSignature() {
-    return new DexMethodSignature(method);
+    return new DexMethodSignature(getReference());
   }
 
   public DexType returnType() {
-    return method.proto.returnType;
+    return getReference().proto.returnType;
   }
 
   public ParameterAnnotationsList liveParameterAnnotations(AppView<AppInfoWithLiveness> appView) {
@@ -420,19 +412,19 @@
     assert isLibraryMethodOverride.isPossiblyFalse()
             || this.isLibraryMethodOverride.isPossiblyTrue()
         : "Method `"
-            + method.toSourceString()
+            + getReference().toSourceString()
             + "` went from not overriding a library method to overriding a library method";
     assert isLibraryMethodOverride.isPossiblyTrue()
             || this.isLibraryMethodOverride.isPossiblyFalse()
         : "Method `"
-            + method.toSourceString()
+            + getReference().toSourceString()
             + "` went from overriding a library method to not overriding a library method";
     this.isLibraryMethodOverride = isLibraryMethodOverride;
   }
 
   public boolean isProgramMethod(DexDefinitionSupplier definitions) {
-    if (method.holder.isClassType()) {
-      DexClass clazz = definitions.definitionFor(method.holder);
+    if (getReference().holder.isClassType()) {
+      DexClass clazz = definitions.definitionFor(getReference().holder);
       return clazz != null && clazz.isProgramClass();
     }
     return false;
@@ -444,8 +436,8 @@
   }
 
   public DexClassAndMethod asDexClassAndMethod(DexDefinitionSupplier definitions) {
-    assert method.holder.isClassType();
-    DexClass clazz = definitions.definitionForHolder(method);
+    assert getReference().holder.isClassType();
+    DexClass clazz = definitions.definitionForHolder(getReference());
     if (clazz != null) {
       return DexClassAndMethod.create(clazz, this);
     }
@@ -453,8 +445,8 @@
   }
 
   public ProgramMethod asProgramMethod(DexDefinitionSupplier definitions) {
-    assert method.holder.isClassType();
-    DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(method));
+    assert getReference().holder.isClassType();
+    DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(getReference()));
     if (clazz != null) {
       return new ProgramMethod(clazz, this);
     }
@@ -530,7 +522,7 @@
 
   public boolean isDefaultInitializer() {
     checkIfObsolete();
-    return isInstanceInitializer() && method.proto.parameters.isEmpty();
+    return isInstanceInitializer() && getReference().proto.parameters.isEmpty();
   }
 
   public boolean isClassInitializer() {
@@ -698,28 +690,28 @@
         return true;
 
       case PROCESSED_INLINING_CANDIDATE_SUBCLASS:
-        if (appInfo.isSubtype(containerType, method.holder)) {
+        if (appInfo.isSubtype(containerType, getReference().holder)) {
           return true;
         }
         whyAreYouNotInliningReporter.reportCallerNotSubtype();
         return false;
 
       case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
-        if (containerType.isSamePackage(method.holder)) {
+        if (containerType.isSamePackage(getReference().holder)) {
           return true;
         }
         whyAreYouNotInliningReporter.reportCallerNotSamePackage();
         return false;
 
       case PROCESSED_INLINING_CANDIDATE_SAME_NEST:
-        if (NestUtils.sameNest(containerType, method.holder, appInfo)) {
+        if (NestUtils.sameNest(containerType, getReference().holder, appInfo)) {
           return true;
         }
         whyAreYouNotInliningReporter.reportCallerNotSameNest();
         return false;
 
       case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
-        if (containerType == method.holder) {
+        if (containerType == getReference().holder) {
           return true;
         }
         whyAreYouNotInliningReporter.reportCallerNotSameClass();
@@ -808,7 +800,7 @@
   @Override
   public String toString() {
     checkIfObsolete();
-    return "Encoded method " + method;
+    return "Encoded method " + getReference();
   }
 
   @Override
@@ -869,7 +861,7 @@
 
   public String qualifiedName() {
     checkIfObsolete();
-    return method.qualifiedName();
+    return getReference().qualifiedName();
   }
 
   public String descriptor() {
@@ -881,11 +873,11 @@
     checkIfObsolete();
     StringBuilder builder = new StringBuilder();
     builder.append("(");
-    for (DexType type : method.proto.parameters.values) {
+    for (DexType type : getReference().proto.parameters.values) {
       builder.append(namingLens.lookupDescriptor(type).toString());
     }
     builder.append(")");
-    builder.append(namingLens.lookupDescriptor(method.proto.returnType).toString());
+    builder.append(namingLens.lookupDescriptor(getReference().proto.returnType).toString());
     return builder.toString();
   }
 
@@ -903,8 +895,8 @@
     builder.append(".method ");
     builder.append(accessFlags.toSmaliString());
     builder.append(" ");
-    builder.append(method.name.toSmaliString());
-    builder.append(method.proto.toSmaliString());
+    builder.append(getReference().name.toSmaliString());
+    builder.append(getReference().proto.toSmaliString());
     builder.append("\n");
     if (code != null) {
       DexCode dexCode = code.asDexCode();
@@ -920,7 +912,7 @@
   @Override
   public String toSourceString() {
     checkIfObsolete();
-    return method.toSourceString();
+    return getReference().toSourceString();
   }
 
   public DexEncodedMethod toAbstractMethod() {
@@ -946,7 +938,7 @@
       offset += instruction.getSize();
     }
     int requiredArgRegisters = accessFlags.isStatic() ? 0 : 1;
-    for (DexType type : method.proto.parameters.values) {
+    for (DexType type : getReference().proto.parameters.values) {
       requiredArgRegisters += ValueType.fromDexType(type).requiredRegisters();
     }
     return new DexCode(
@@ -998,7 +990,7 @@
   }
 
   public CfCode buildEmptyThrowingCfCode() {
-    return buildEmptyThrowingCfCode(method);
+    return buildEmptyThrowingCfCode(getReference());
   }
 
   public static CfCode buildEmptyThrowingCfCode(DexMethod method) {
@@ -1034,9 +1026,9 @@
     }
     instructions[i] = new CfReturn(ValueType.INT);
     return new CfCode(
-        method.holder,
+        getReference().holder,
         1 + BooleanUtils.intValue(negate),
-        method.getArity() + 1,
+        getReference().getArity() + 1,
         Arrays.asList(instructions),
         Collections.emptyList(),
         Collections.emptyList());
@@ -1076,10 +1068,13 @@
   
   private DexEncodedMethod toMethodThatLogsErrorDexCode(DexItemFactory itemFactory) {
     checkIfObsolete();
-    Signature signature = MethodSignature.fromDexMethod(method);
+    Signature signature = MethodSignature.fromDexMethod(getReference());
     DexString message =
         itemFactory.createString(
-            CONFIGURATION_DEBUGGING_PREFIX + method.holder.toSourceString() + ": " + signature);
+            CONFIGURATION_DEBUGGING_PREFIX
+                + getReference().holder.toSourceString()
+                + ": "
+                + signature);
     DexString tag = itemFactory.createString("[R8]");
     DexType[] args = {itemFactory.stringType, itemFactory.stringType};
     DexProto proto = itemFactory.createProto(itemFactory.intType, args);
@@ -1110,10 +1105,13 @@
 
   private DexEncodedMethod toMethodThatLogsErrorCfCode(DexItemFactory itemFactory) {
     checkIfObsolete();
-    Signature signature = MethodSignature.fromDexMethod(method);
+    Signature signature = MethodSignature.fromDexMethod(getReference());
     DexString message =
         itemFactory.createString(
-            CONFIGURATION_DEBUGGING_PREFIX + method.holder.toSourceString() + ": " + signature);
+            CONFIGURATION_DEBUGGING_PREFIX
+                + getReference().holder.toSourceString()
+                + ": "
+                + signature);
     DexString tag = itemFactory.createString("[R8]");
     DexType logger = itemFactory.javaUtilLoggingLoggerType;
     DexMethod getLogger =
@@ -1132,7 +1130,7 @@
             exceptionType,
             itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
             itemFactory.constructorMethodName);
-    int locals = method.proto.parameters.size() + 1;
+    int locals = getReference().proto.parameters.size() + 1;
     if (!isStaticMember()) {
       // Consider `this` pointer
       locals++;
@@ -1152,7 +1150,7 @@
         .add(new CfThrow());
     CfCode code =
         new CfCode(
-            method.holder,
+            getReference().holder,
             3,
             locals,
             instructionBuilder.build(),
@@ -1171,7 +1169,7 @@
 
   public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method, Consumer<Builder> consumer) {
     checkIfObsolete();
-    if (this.method == method) {
+    if (this.getReference() == method) {
       return this;
     }
     Builder builder = builder(this);
@@ -1243,7 +1241,7 @@
 
   public DexEncodedMethod toRenamedHolderMethod(DexType newHolderType, DexItemFactory factory) {
     DexEncodedMethod.Builder builder = DexEncodedMethod.builder(this);
-    builder.setMethod(method.withHolder(newHolderType, factory));
+    builder.setMethod(getReference().withHolder(newHolderType, factory));
     return builder.build();
   }
 
@@ -1318,7 +1316,7 @@
 
   public DexEncodedMethod toForwardingMethod(
       DexClass newHolder, DexDefinitionSupplier definitions) {
-    DexMethod newMethod = method.withHolder(newHolder, definitions.dexItemFactory());
+    DexMethod newMethod = getReference().withHolder(newHolder, definitions.dexItemFactory());
     checkIfObsolete();
 
     // Clear the final flag, as this method is now overwritten. Do this before creating the builder
@@ -1347,13 +1345,17 @@
                                         .setStaticSource(newMethod)
                                         .setStaticTarget(
                                             getReference(),
-                                            method.getHolderType().isInterface(definitions)),
+                                            getReference()
+                                                .getHolderType()
+                                                .isInterface(definitions)),
                                 codeBuilder ->
                                     codeBuilder
                                         .setNonStaticSource(newMethod)
                                         .setSuperTarget(
                                             getReference(),
-                                            method.getHolderType().isInterface(definitions)))
+                                            getReference()
+                                                .getHolderType()
+                                                .isInterface(definitions)))
                             .build())
                     .modifyAccessFlags(MethodAccessFlags::setBridge))
         .build();
@@ -1435,7 +1437,7 @@
   }
 
   public MethodPosition getPosition() {
-    return new MethodPosition(method.asMethodReference());
+    return new MethodPosition(getReference().asMethodReference());
   }
 
   @Override
@@ -1456,7 +1458,7 @@
   }
 
   public static int slowCompare(DexEncodedMethod m1, DexEncodedMethod m2) {
-    return m1.method.compareTo(m2.method);
+    return m1.getReference().compareTo(m2.getReference());
   }
 
   public MethodOptimizationInfo getOptimizationInfo() {
@@ -1543,7 +1545,7 @@
 
     private Builder(DexEncodedMethod from, boolean d8R8Synthesized) {
       // Copy all the mutable state of a DexEncodedMethod here.
-      method = from.method;
+      method = from.getReference();
       accessFlags = from.accessFlags.copy();
       genericSignature = from.getGenericSignature();
       annotations = from.annotations();
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index ef99d01..fd2c110 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -175,7 +175,7 @@
 
   @Override
   public boolean match(DexEncodedField encodedField) {
-    return match(encodedField.field);
+    return match(encodedField.getReference());
   }
 
   public String qualifiedName() {
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 2ce742a..8ba291b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -64,6 +64,8 @@
 
   public static final String throwableDescriptorString = "Ljava/lang/Throwable;";
   public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
+  public static final String recordTagDescriptorString = "Lcom/android/tools/r8/RecordTag;";
+  public static final String recordDescriptorString = "Ljava/lang/Record;";
 
   /** Set of types that may be synthesized during compilation. */
   private final Set<DexType> possibleCompilerSynthesizedTypes = Sets.newIdentityHashSet();
@@ -215,8 +217,8 @@
   public final DexString stringDescriptor = createString("Ljava/lang/String;");
   public final DexString stringArrayDescriptor = createString("[Ljava/lang/String;");
   public final DexString objectDescriptor = createString("Ljava/lang/Object;");
-  public final DexString recordDescriptor = createString("Ljava/lang/Record;");
-  public final DexString r8RecordDescriptor = createString("Lcom/android/tools/r8/RecordTag;");
+  public final DexString recordDescriptor = createString(recordDescriptorString);
+  public final DexString recordTagDescriptor = createString(recordTagDescriptorString);
   public final DexString objectArrayDescriptor = createString("[Ljava/lang/Object;");
   public final DexString classDescriptor = createString("Ljava/lang/Class;");
   public final DexString classLoaderDescriptor = createString("Ljava/lang/ClassLoader;");
@@ -348,7 +350,7 @@
   public final DexType stringArrayType = createStaticallyKnownType(stringArrayDescriptor);
   public final DexType objectType = createStaticallyKnownType(objectDescriptor);
   public final DexType recordType = createStaticallyKnownType(recordDescriptor);
-  public final DexType r8RecordType = createStaticallyKnownType(r8RecordDescriptor);
+  public final DexType recordTagType = createStaticallyKnownType(recordTagDescriptor);
   public final DexType objectArrayType = createStaticallyKnownType(objectArrayDescriptor);
   public final DexType classArrayType = createStaticallyKnownType(classArrayDescriptor);
   public final DexType enumType = createStaticallyKnownType(enumDescriptor);
@@ -631,6 +633,10 @@
   public final DexType callSiteType = createStaticallyKnownType("Ljava/lang/invoke/CallSite;");
   public final DexType lookupType =
       createStaticallyKnownType("Ljava/lang/invoke/MethodHandles$Lookup;");
+  public final DexType objectMethodsType =
+      createStaticallyKnownType("Ljava/lang/runtime/ObjectMethods;");
+  public final DexType typeDescriptorType =
+      createStaticallyKnownType("Ljava/lang/invoke/TypeDescriptor;");
   public final DexType iteratorType = createStaticallyKnownType("Ljava/util/Iterator;");
   public final DexType listIteratorType = createStaticallyKnownType("Ljava/util/ListIterator;");
   public final DexType enumerationType = createStaticallyKnownType("Ljava/util/Enumeration;");
@@ -642,6 +648,7 @@
       createStaticallyKnownType("Ljava/lang/invoke/StringConcatFactory;");
   public final DexType unsafeType = createStaticallyKnownType("Lsun/misc/Unsafe;");
 
+  public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
   public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
   public final StringConcatFactoryMembers stringConcatFactoryMembers =
       new StringConcatFactoryMembers();
@@ -1215,6 +1222,21 @@
     public final DexMethod toString = createMethod(recordType, createProto(stringType), "toString");
   }
 
+  public class ObjectMethodsMembers {
+    public final DexMethod bootstrap =
+        createMethod(
+            objectMethodsType,
+            createProto(
+                objectType,
+                lookupType,
+                stringType,
+                typeDescriptorType,
+                classType,
+                stringType,
+                createArrayType(1, methodHandleType)),
+            "bootstrap");
+  }
+
   public class ObjectMembers {
 
     /**
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 8484a94..c96dc4a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -223,7 +223,7 @@
 
   @Override
   public boolean match(DexEncodedMethod encodedMethod) {
-    return match(encodedMethod.method);
+    return match(encodedMethod.getReference());
   }
 
   public String qualifiedName() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index a4ccb8e..881b0e9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -559,7 +559,8 @@
       return null;
     }
     DexEncodedField[] fields = staticFields;
-    Arrays.sort(fields, (a, b) -> a.field.compareToWithNamingLens(b.field, namingLens));
+    Arrays.sort(
+        fields, (a, b) -> a.getReference().compareToWithNamingLens(b.getReference(), namingLens));
     int length = 0;
     List<DexValue> values = new ArrayList<>(fields.length);
     for (int i = 0; i < fields.length; i++) {
@@ -567,7 +568,7 @@
       DexValue staticValue = field.getStaticValue();
       assert staticValue != null;
       values.add(staticValue);
-      if (!staticValue.isDefault(field.field.type)) {
+      if (!staticValue.isDefault(field.getReference().type)) {
         length = i + 1;
       }
     }
@@ -579,7 +580,7 @@
   private boolean hasNonDefaultStaticFieldValues() {
     for (DexEncodedField field : staticFields) {
       DexValue value = field.getStaticValue();
-      if (value != null && !value.isDefault(field.field.type)) {
+      if (value != null && !value.isDefault(field.getReference().type)) {
         return true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 75b592b..a38770a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -315,6 +315,14 @@
     return isDoubleType() || isLongType();
   }
 
+  public boolean isSynthesizedTypeAllowedDuplication() {
+    // If we are desugaring Records, then the r8Record type is mapped back to java.lang.Record, and
+    // java.lang.Record can be duplicated.
+    // If we are not desugaring Records, then the r8Record type can be duplicated instead.
+    return descriptor.toString().equals(DexItemFactory.recordDescriptorString)
+        || descriptor.toString().equals(DexItemFactory.recordTagDescriptorString);
+  }
+
   public boolean isLegacySynthesizedTypeAllowedDuplication() {
     return oldSynthesizedName(toSourceString());
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 1dc1f4f..2fa08c8 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -542,6 +542,15 @@
         visitor.visitSimpleClass(innerTypeSignature);
       }
     }
+
+    public boolean hasTypeVariableArguments() {
+      for (FieldTypeSignature typeArgument : typeArguments) {
+        if (typeArgument.isTypeVariableSignature()) {
+          return true;
+        }
+      }
+      return false;
+    }
   }
 
   public static class ArrayTypeSignature extends FieldTypeSignature {
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 7135cfc..610208f 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -13,12 +13,9 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
 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.MutableBidirectionalManyToOneRepresentativeMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -33,7 +30,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 /**
  * A GraphLens implements a virtual view on top of the graph, used to delay global rewrites until
@@ -233,18 +229,16 @@
     }
   }
 
-  public static class Builder {
+  public abstract static class Builder {
+
+    protected final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap =
+        BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
+    protected final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap =
+        BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
+    protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
 
     protected Builder() {}
 
-    protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
-    protected final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
-    protected final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap =
-        new BidirectionalManyToOneRepresentativeHashMap<>();
-
-    protected final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures =
-        new BidirectionalOneToOneHashMap<>();
-
     public void map(DexType from, DexType to) {
       if (from == to) {
         return;
@@ -252,19 +246,11 @@
       typeMap.put(from, to);
     }
 
-    public void map(DexMethod from, DexMethod to) {
-      if (from == to) {
-        return;
-      }
-      methodMap.put(from, to);
-    }
-
     public void move(DexMethod from, DexMethod to) {
       if (from == to) {
         return;
       }
-      map(from, to);
-      originalMethodSignatures.put(to, from);
+      methodMap.put(from, to);
     }
 
     public void move(DexField from, DexField to) {
@@ -274,22 +260,7 @@
       fieldMap.put(from, to);
     }
 
-    public GraphLens build(DexItemFactory dexItemFactory) {
-      return build(dexItemFactory, getIdentityLens());
-    }
-
-    public GraphLens build(DexItemFactory dexItemFactory, GraphLens previousLens) {
-      if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
-        return previousLens;
-      }
-      return new NestedGraphLens(
-          typeMap,
-          methodMap,
-          fieldMap,
-          originalMethodSignatures,
-          previousLens,
-          dexItemFactory);
-    }
+    public abstract GraphLens build(AppView<?> appView);
   }
 
   /**
@@ -334,7 +305,7 @@
       DexDefinitionSupplier definitions,
       GraphLens applied) {
     assert originalEncodedMethod != DexEncodedMethod.SENTINEL;
-    DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method, applied);
+    DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.getReference(), applied);
     // Note that:
     // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
     //   up, since `originalEncodedMethod` may be obsolete.
@@ -602,10 +573,10 @@
     Set<DexMethod> originalMethods = Sets.newIdentityHashSet();
     for (DexProgramClass clazz : originalApplication.classes()) {
       for (DexEncodedField field : clazz.fields()) {
-        originalFields.add(field.field);
+        originalFields.add(field.getReference());
       }
       for (DexEncodedMethod method : clazz.methods()) {
-        originalMethods.add(method.method);
+        originalMethods.add(method.getReference());
       }
     }
 
@@ -631,7 +602,7 @@
           // Methods synthesized by D8/R8 may not be mapped.
           continue;
         }
-        DexMethod originalMethod = getOriginalMethodSignature(method.method);
+        DexMethod originalMethod = getOriginalMethodSignature(method.getReference());
         assert originalMethods.contains(originalMethod);
       }
     }
@@ -959,288 +930,4 @@
       return getIdentityLens().isContextFreeForMethods();
     }
   }
-
-  /**
-   * GraphLens implementation with a parent lens using a simple mapping for type, method and field
-   * mapping.
-   *
-   * <p>Subclasses can override the lookup methods.
-   *
-   * <p>For method mapping where invocation type can change just override {@link
-   * #mapInvocationType(DexMethod, DexMethod, Type)} if the default name mapping applies, and only
-   * invocation type might need to change.
-   */
-  public static class NestedGraphLens extends NonIdentityGraphLens {
-
-    protected final DexItemFactory dexItemFactory;
-
-    protected final Map<DexType, DexType> typeMap;
-    protected final Map<DexMethod, DexMethod> methodMap;
-    protected final BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap;
-
-    // Map that store the original signature of methods that have been affected, for example, by
-    // vertical class merging. Needed to generate a correct Proguard map in the end.
-    protected BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
-        originalMethodSignatures;
-
-    // Overrides this if the sub type needs to be a nested lens while it doesn't have any mappings
-    // at all, e.g., publicizer lens that changes invocation type only.
-    protected boolean isLegitimateToHaveEmptyMappings() {
-      return false;
-    }
-
-    public NestedGraphLens(
-        Map<DexType, DexType> typeMap,
-        Map<DexMethod, DexMethod> methodMap,
-        BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
-        BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures,
-        GraphLens previousLens,
-        DexItemFactory dexItemFactory) {
-      super(dexItemFactory, previousLens);
-      assert !typeMap.isEmpty()
-          || !methodMap.isEmpty()
-          || !fieldMap.isEmpty()
-          || isLegitimateToHaveEmptyMappings();
-      this.typeMap = typeMap.isEmpty() ? null : typeMap;
-      this.methodMap = methodMap;
-      this.fieldMap = fieldMap;
-      this.originalMethodSignatures = originalMethodSignatures;
-      this.dexItemFactory = dexItemFactory;
-    }
-
-    public static Builder builder() {
-      return new Builder();
-    }
-
-    protected DexType internalGetOriginalType(DexType previous) {
-      return previous;
-    }
-
-    protected Iterable<DexType> internalGetOriginalTypes(DexType previous) {
-      return IterableUtils.singleton(internalGetOriginalType(previous));
-    }
-
-    @Override
-    public DexType getOriginalType(DexType type) {
-      return getPrevious().getOriginalType(internalGetOriginalType(type));
-    }
-
-    @Override
-    public Iterable<DexType> getOriginalTypes(DexType type) {
-      return IterableUtils.flatMap(internalGetOriginalTypes(type), getPrevious()::getOriginalTypes);
-    }
-
-    @Override
-    public DexField getOriginalFieldSignature(DexField field) {
-      DexField originalField = fieldMap.getRepresentativeKeyOrDefault(field, field);
-      return getPrevious().getOriginalFieldSignature(originalField);
-    }
-
-    @Override
-    public DexMethod getOriginalMethodSignature(DexMethod method) {
-      DexMethod originalMethod = internalGetPreviousMethodSignature(method);
-      return getPrevious().getOriginalMethodSignature(originalMethod);
-    }
-
-    @Override
-    public DexField getRenamedFieldSignature(DexField originalField) {
-      DexField renamedField = getPrevious().getRenamedFieldSignature(originalField);
-      return internalGetNextFieldSignature(renamedField);
-    }
-
-    @Override
-    public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
-      if (this == applied) {
-        return originalMethod;
-      }
-      DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied);
-      return internalGetNextMethodSignature(renamedMethod);
-    }
-
-    @Override
-    protected DexType internalDescribeLookupClassType(DexType previous) {
-      return typeMap != null ? typeMap.getOrDefault(previous, previous) : previous;
-    }
-
-    @Override
-    protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
-      if (previous.hasReboundReference()) {
-        // Rewrite the rebound reference and then "fixup" the non-rebound reference.
-        DexField rewrittenReboundReference = previous.getRewrittenReboundReference(fieldMap);
-        DexField rewrittenNonReboundReference =
-            previous.getReference() == previous.getReboundReference()
-                ? rewrittenReboundReference
-                : rewrittenReboundReference.withHolder(
-                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
-                    dexItemFactory);
-        return FieldLookupResult.builder(this)
-            .setReboundReference(rewrittenReboundReference)
-            .setReference(rewrittenNonReboundReference)
-            .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType))
-            .build();
-      } else {
-        // TODO(b/168282032): We should always have the rebound reference, so this should become
-        //  unreachable.
-        DexField rewrittenReference = previous.getRewrittenReference(fieldMap);
-        return FieldLookupResult.builder(this)
-            .setReference(rewrittenReference)
-            .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType))
-            .build();
-      }
-    }
-
-    @Override
-    public MethodLookupResult internalDescribeLookupMethod(
-        MethodLookupResult previous, DexMethod context) {
-      if (previous.hasReboundReference()) {
-        // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
-        //  that only subclasses which are known to need it actually do it?
-        DexMethod rewrittenReboundReference = previous.getRewrittenReboundReference(methodMap);
-        DexMethod rewrittenReference =
-            previous.getReference() == previous.getReboundReference()
-                ? rewrittenReboundReference
-                : // This assumes that the holder will always be moved in lock-step with the method!
-                rewrittenReboundReference.withHolder(
-                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
-                    dexItemFactory);
-        return MethodLookupResult.builder(this)
-            .setReference(rewrittenReference)
-            .setReboundReference(rewrittenReboundReference)
-            .setPrototypeChanges(
-                internalDescribePrototypeChanges(
-                    previous.getPrototypeChanges(), rewrittenReboundReference))
-            .setType(
-                mapInvocationType(
-                    rewrittenReboundReference, previous.getReference(), previous.getType()))
-            .build();
-      } else {
-        // TODO(b/168282032): We should always have the rebound reference, so this should become
-        //  unreachable.
-        DexMethod newMethod = methodMap.get(previous.getReference());
-        if (newMethod == null) {
-          return previous;
-        }
-        // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
-        //  that only subclasses which are known to need it actually do it?
-        return MethodLookupResult.builder(this)
-            .setReference(newMethod)
-            .setPrototypeChanges(
-                internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
-            .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
-            .build();
-      }
-    }
-
-    @Override
-    public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
-        DexMethod method) {
-      DexMethod previous = internalGetPreviousMethodSignature(method);
-      RewrittenPrototypeDescription lookup =
-          getPrevious().lookupPrototypeChangesForMethodDefinition(previous);
-      return internalDescribePrototypeChanges(lookup, method);
-    }
-
-    protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-        RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
-      return prototypeChanges;
-    }
-
-    protected DexField internalGetNextFieldSignature(DexField field) {
-      return fieldMap.getOrDefault(field, field);
-    }
-
-    @Override
-    protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
-      return originalMethodSignatures.getRepresentativeValueOrDefault(method, method);
-    }
-
-    protected DexMethod internalGetNextMethodSignature(DexMethod method) {
-      return originalMethodSignatures.getRepresentativeKeyOrDefault(method, method);
-    }
-
-    @Override
-    public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
-      return getPrevious().lookupGetFieldForMethod(field, context);
-    }
-
-    @Override
-    public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
-      return getPrevious().lookupPutFieldForMethod(field, context);
-    }
-
-    /**
-     * Default invocation type mapping.
-     *
-     * <p>This is an identity mapping. If a subclass need invocation type mapping either override
-     * this method or {@link #lookupMethod(DexMethod, DexMethod, Type)}
-     */
-    protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
-      return type;
-    }
-
-    /**
-     * Standard mapping between interface and virtual invoke type.
-     *
-     * <p>Handle methods moved from interface to class or class to interface.
-     */
-    public static Type mapVirtualInterfaceInvocationTypes(
-        DexDefinitionSupplier definitions,
-        DexMethod newMethod,
-        DexMethod originalMethod,
-        Type type) {
-      if (type == Type.VIRTUAL || type == Type.INTERFACE) {
-        // Get the invoke type of the actual definition.
-        DexClass newTargetClass = definitions.definitionFor(newMethod.getHolderType());
-        if (newTargetClass == null) {
-          return type;
-        }
-        DexClass originalTargetClass = definitions.definitionFor(originalMethod.getHolderType());
-        if (originalTargetClass != null
-            && (originalTargetClass.isInterface() ^ (type == Type.INTERFACE))) {
-          // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
-          // the IncompatibleClassChangeError the original invoke would have triggered.
-          return newTargetClass.accessFlags.isInterface() ? Type.VIRTUAL : Type.INTERFACE;
-        }
-        return newTargetClass.accessFlags.isInterface() ? Type.INTERFACE : Type.VIRTUAL;
-      }
-      return type;
-    }
-
-    @Override
-    public boolean isContextFreeForMethods() {
-      return getPrevious().isContextFreeForMethods();
-    }
-
-    @Override
-    public boolean verifyIsContextFreeForMethod(DexMethod method) {
-      assert getPrevious().verifyIsContextFreeForMethod(method);
-      return true;
-    }
-
-    @Override
-    public String toString() {
-      StringBuilder builder = new StringBuilder();
-      if (typeMap != null) {
-        for (Map.Entry<DexType, DexType> entry : typeMap.entrySet()) {
-          builder.append(entry.getKey().toSourceString()).append(" -> ");
-          builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
-        }
-      }
-      for (Map.Entry<DexMethod, DexMethod> entry : methodMap.entrySet()) {
-        builder.append(entry.getKey().toSourceString()).append(" -> ");
-        builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
-      }
-      fieldMap.forEachManyToOneMapping(
-          (keys, value) -> {
-            builder.append(
-                keys.stream()
-                    .map(DexField::toSourceString)
-                    .collect(Collectors.joining("," + System.lineSeparator())));
-            builder.append(" -> ");
-            builder.append(value.toSourceString()).append(System.lineSeparator());
-          });
-      builder.append(getPrevious().toString());
-      return builder.toString();
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 8b5977f..67e7b5b 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import org.objectweb.asm.Type;
 
@@ -23,9 +24,11 @@
   private final ConcurrentHashMap<String, Type> asmObjectTypeCache = new ConcurrentHashMap<>();
   private final ConcurrentHashMap<String, Type> asmTypeCache = new ConcurrentHashMap<>();
   private final ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>();
+  private final Map<String, String> typeDescriptorMap;
 
   public JarApplicationReader(InternalOptions options) {
     this.options = options;
+    typeDescriptorMap = ApplicationReaderMap.getDescriptorMap(options);
   }
 
   public Type getAsmObjectType(String name) {
@@ -55,7 +58,8 @@
 
   public DexType getTypeFromDescriptor(String desc) {
     assert isValidDescriptor(desc);
-    return options.itemFactory.createType(getString(desc));
+    String actualDesc = typeDescriptorMap.getOrDefault(desc, desc);
+    return options.itemFactory.createType(getString(actualDesc));
   }
 
   public DexTypeList getTypeListFromNames(String[] names) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index d8815c7..4def125 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -510,9 +510,9 @@
         throw new CompilationError(message, origin);
       }
       for (DexEncodedField instanceField : instanceFields) {
-        if (!recordComponents.contains(instanceField.field)) {
+        if (!recordComponents.contains(instanceField.getReference())) {
           throw new CompilationError(
-              message + " Unmatched field " + instanceField.field + ".", origin);
+              message + " Unmatched field " + instanceField.getReference() + ".", origin);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
index 3c7b2d8..4a99071 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
@@ -34,7 +34,7 @@
       if (pinnedMethods == null) {
         pinnedMethods = Sets.newIdentityHashSet();
       }
-      pinnedMethods.add(method.method);
+      pinnedMethods.add(method.getReference());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 7c38464..17072ef 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -26,8 +26,8 @@
     Set<DexMethod> unique = Sets.newIdentityHashSet();
     forEachMethod(
         method -> {
-          boolean changed = unique.add(method.method);
-          assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
+          boolean changed = unique.add(method.getReference());
+          assert changed : "Duplicate method `" + method.getReference().toSourceString() + "`";
         });
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index ccf33aa..35e0541 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -137,7 +137,7 @@
   public List<DexEncodedMethod> allMethodsSorted() {
     List<DexEncodedMethod> sorted = new ArrayList<>(size());
     forEachMethod(sorted::add);
-    sorted.sort((a, b) -> a.method.compareTo(b.method));
+    sorted.sort((a, b) -> a.getReference().compareTo(b.getReference()));
     return sorted;
   }
 
@@ -344,7 +344,7 @@
   private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) {
     assert method.getHolderType() == holder.type
         : "Expected method `"
-            + method.method.toSourceString()
+            + method.getReference().toSourceString()
             + "` to have holder `"
             + holder.type.toSourceString()
             + "`";
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index f3385af..a7d3c45 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -57,7 +57,7 @@
       methodMap.put(existingKey, method);
     } else {
       methodMap.remove(existingKey);
-      methodMap.put(wrap(method.method), method);
+      methodMap.put(wrap(method.getReference()), method);
     }
   }
 
@@ -165,7 +165,7 @@
 
   @Override
   void addMethod(DexEncodedMethod method) {
-    Wrapper<DexMethod> key = wrap(method.method);
+    Wrapper<DexMethod> key = wrap(method.getReference());
     DexEncodedMethod old = methodMap.put(key, method);
     assert old == null;
   }
@@ -229,12 +229,12 @@
     forEachMethod(
         method -> {
           if (belongsToVirtualPool(method)) {
-            newMap.put(wrap(method.method), method);
+            newMap.put(wrap(method.getReference()), method);
           }
         });
     for (DexEncodedMethod method : methods) {
       assert belongsToDirectPool(method);
-      newMap.put(wrap(method.method), method);
+      newMap.put(wrap(method.getReference()), method);
     }
     methodMap = newMap;
   }
@@ -252,12 +252,12 @@
     forEachMethod(
         method -> {
           if (belongsToDirectPool(method)) {
-            newMap.put(wrap(method.method), method);
+            newMap.put(wrap(method.getReference()), method);
           }
         });
     for (DexEncodedMethod method : methods) {
       assert belongsToVirtualPool(method);
-      newMap.put(wrap(method.method), method);
+      newMap.put(wrap(method.getReference()), method);
     }
     methodMap = newMap;
   }
@@ -271,7 +271,7 @@
     for (DexEncodedMethod method : initialValues) {
       DexEncodedMethod newMethod = replacement.apply(method);
       if (newMethod != method) {
-        removeMethod(method.method);
+        removeMethod(method.getReference());
         addMethod(newMethod);
       }
     }
@@ -359,7 +359,7 @@
   private boolean verifyVirtualizedMethods(Set<DexEncodedMethod> methods) {
     for (DexEncodedMethod method : methods) {
       assert belongsToVirtualPool(method);
-      assert methodMap.get(wrap(method.method)) == method;
+      assert methodMap.get(wrap(method.getReference())) == method;
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
new file mode 100644
index 0000000..8eaa46f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
@@ -0,0 +1,320 @@
+// 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.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
+import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * GraphLens implementation with a parent lens using a simple mapping for type, method and field
+ * mapping.
+ *
+ * <p>Subclasses can override the lookup methods.
+ *
+ * <p>For method mapping where invocation type can change just override {@link
+ * #mapInvocationType(DexMethod, DexMethod, Type)} if the default name mapping applies, and only
+ * invocation type might need to change.
+ */
+public class NestedGraphLens extends NonIdentityGraphLens {
+
+  protected static final EmptyBidirectionalOneToOneMap<DexField, DexField> EMPTY_FIELD_MAP =
+      new EmptyBidirectionalOneToOneMap<>();
+  protected static final EmptyBidirectionalOneToOneMap<DexMethod, DexMethod> EMPTY_METHOD_MAP =
+      new EmptyBidirectionalOneToOneMap<>();
+  protected static final Map<DexType, DexType> EMPTY_TYPE_MAP = Collections.emptyMap();
+
+  protected final BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap;
+  protected final Map<DexMethod, DexMethod> methodMap;
+  protected final Map<DexType, DexType> typeMap;
+
+  // Map that stores the new signature of methods that have been affected by class merging, unused
+  // argument removal, repackaging, synthetic finalization, etc. This is needed to generate a
+  // correct mapping file in the end.
+  //
+  // The difference to `methodMap` is that `methodMap` specifies how invoke-method instructions
+  // should be rewritten, whereas this map specifies where methods have been moved. In most cases
+  // the two maps are the same, but in a few cases we move a method m1 to m2 and rewrite invokes to
+  // m1 to m3.
+  //
+  // The static type of this map is a many-to-many map to facilitate both one-to-many mappings and
+  // many-to-one mappings. Currently, the concrete map is always *either* a one-to-many or a
+  // many-to-one map, and not a many-to-many map.
+  //
+  // One-to-many mappings are generally found when methods are split into two. This could be due to
+  // the code object being moved elsewhere (e.g., interface desugaring).
+  //
+  // Many-to-one mappings are generally found when methods are shared, e.g., due to horizontal class
+  // merging of synthetic finalization.
+  protected BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures;
+
+  // Overrides this if the sub type needs to be a nested lens while it doesn't have any mappings
+  // at all, e.g., publicizer lens that changes invocation type only.
+  protected boolean isLegitimateToHaveEmptyMappings() {
+    return false;
+  }
+
+  public NestedGraphLens(
+      AppView<?> appView,
+      BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
+      BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap,
+      Map<DexType, DexType> typeMap) {
+    this(appView, fieldMap, methodMap.getForwardMap(), typeMap, methodMap);
+  }
+
+  public NestedGraphLens(
+      AppView<?> appView,
+      BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
+      Map<DexMethod, DexMethod> methodMap,
+      Map<DexType, DexType> typeMap,
+      BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) {
+    super(appView);
+    assert !typeMap.isEmpty()
+        || !methodMap.isEmpty()
+        || !fieldMap.isEmpty()
+        || isLegitimateToHaveEmptyMappings();
+    this.fieldMap = fieldMap;
+    this.methodMap = methodMap;
+    this.typeMap = typeMap;
+    this.newMethodSignatures = newMethodSignatures;
+  }
+
+  protected DexType internalGetOriginalType(DexType previous) {
+    return previous;
+  }
+
+  protected Iterable<DexType> internalGetOriginalTypes(DexType previous) {
+    return IterableUtils.singleton(internalGetOriginalType(previous));
+  }
+
+  @Override
+  public DexType getOriginalType(DexType type) {
+    return getPrevious().getOriginalType(internalGetOriginalType(type));
+  }
+
+  @Override
+  public Iterable<DexType> getOriginalTypes(DexType type) {
+    return IterableUtils.flatMap(internalGetOriginalTypes(type), getPrevious()::getOriginalTypes);
+  }
+
+  @Override
+  public DexField getOriginalFieldSignature(DexField field) {
+    DexField originalField = fieldMap.getRepresentativeKeyOrDefault(field, field);
+    return getPrevious().getOriginalFieldSignature(originalField);
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    DexMethod originalMethod = internalGetPreviousMethodSignature(method);
+    return getPrevious().getOriginalMethodSignature(originalMethod);
+  }
+
+  @Override
+  public DexField getRenamedFieldSignature(DexField originalField) {
+    DexField renamedField = getPrevious().getRenamedFieldSignature(originalField);
+    return internalGetNextFieldSignature(renamedField);
+  }
+
+  @Override
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    if (this == applied) {
+      return originalMethod;
+    }
+    DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied);
+    return internalGetNextMethodSignature(renamedMethod);
+  }
+
+  @Override
+  protected DexType internalDescribeLookupClassType(DexType previous) {
+    return typeMap.getOrDefault(previous, previous);
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    if (previous.hasReboundReference()) {
+      // Rewrite the rebound reference and then "fixup" the non-rebound reference.
+      DexField rewrittenReboundReference = previous.getRewrittenReboundReference(fieldMap);
+      DexField rewrittenNonReboundReference =
+          previous.getReference() == previous.getReboundReference()
+              ? rewrittenReboundReference
+              : rewrittenReboundReference.withHolder(
+                  internalDescribeLookupClassType(previous.getReference().getHolderType()),
+                  dexItemFactory());
+      return FieldLookupResult.builder(this)
+          .setReboundReference(rewrittenReboundReference)
+          .setReference(rewrittenNonReboundReference)
+          .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType))
+          .build();
+    } else {
+      // TODO(b/168282032): We should always have the rebound reference, so this should become
+      //  unreachable.
+      DexField rewrittenReference = previous.getRewrittenReference(fieldMap);
+      return FieldLookupResult.builder(this)
+          .setReference(rewrittenReference)
+          .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType))
+          .build();
+    }
+  }
+
+  @Override
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    if (previous.hasReboundReference()) {
+      // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
+      //  that only subclasses which are known to need it actually do it?
+      DexMethod rewrittenReboundReference = previous.getRewrittenReboundReference(methodMap);
+      DexMethod rewrittenReference =
+          previous.getReference() == previous.getReboundReference()
+              ? rewrittenReboundReference
+              : // This assumes that the holder will always be moved in lock-step with the method!
+              rewrittenReboundReference.withHolder(
+                  internalDescribeLookupClassType(previous.getReference().getHolderType()),
+                  dexItemFactory());
+      return MethodLookupResult.builder(this)
+          .setReference(rewrittenReference)
+          .setReboundReference(rewrittenReboundReference)
+          .setPrototypeChanges(
+              internalDescribePrototypeChanges(
+                  previous.getPrototypeChanges(), rewrittenReboundReference))
+          .setType(
+              mapInvocationType(
+                  rewrittenReboundReference, previous.getReference(), previous.getType()))
+          .build();
+    } else {
+      // TODO(b/168282032): We should always have the rebound reference, so this should become
+      //  unreachable.
+      DexMethod newMethod = methodMap.get(previous.getReference());
+      if (newMethod == null) {
+        return previous;
+      }
+      // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
+      //  that only subclasses which are known to need it actually do it?
+      return MethodLookupResult.builder(this)
+          .setReference(newMethod)
+          .setPrototypeChanges(
+              internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
+          .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
+          .build();
+    }
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
+    DexMethod previous = internalGetPreviousMethodSignature(method);
+    RewrittenPrototypeDescription lookup =
+        getPrevious().lookupPrototypeChangesForMethodDefinition(previous);
+    return internalDescribePrototypeChanges(lookup, method);
+  }
+
+  protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+    return prototypeChanges;
+  }
+
+  protected DexField internalGetNextFieldSignature(DexField field) {
+    return fieldMap.getOrDefault(field, field);
+  }
+
+  @Override
+  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    return newMethodSignatures.getRepresentativeKeyOrDefault(method, method);
+  }
+
+  protected DexMethod internalGetNextMethodSignature(DexMethod method) {
+    return newMethodSignatures.getRepresentativeValueOrDefault(method, method);
+  }
+
+  @Override
+  public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
+    return getPrevious().lookupGetFieldForMethod(field, context);
+  }
+
+  @Override
+  public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
+    return getPrevious().lookupPutFieldForMethod(field, context);
+  }
+
+  /**
+   * Default invocation type mapping.
+   *
+   * <p>This is an identity mapping. If a subclass need invocation type mapping either override this
+   * method or {@link #lookupMethod(DexMethod, DexMethod, Type)}
+   */
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+    return type;
+  }
+
+  /**
+   * Standard mapping between interface and virtual invoke type.
+   *
+   * <p>Handle methods moved from interface to class or class to interface.
+   */
+  public static Type mapVirtualInterfaceInvocationTypes(
+      DexDefinitionSupplier definitions, DexMethod newMethod, DexMethod originalMethod, Type type) {
+    if (type == Type.VIRTUAL || type == Type.INTERFACE) {
+      // Get the invoke type of the actual definition.
+      DexClass newTargetClass = definitions.definitionFor(newMethod.getHolderType());
+      if (newTargetClass == null) {
+        return type;
+      }
+      DexClass originalTargetClass = definitions.definitionFor(originalMethod.getHolderType());
+      if (originalTargetClass != null
+          && (originalTargetClass.isInterface() ^ (type == Type.INTERFACE))) {
+        // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
+        // the IncompatibleClassChangeError the original invoke would have triggered.
+        return newTargetClass.accessFlags.isInterface() ? Type.VIRTUAL : Type.INTERFACE;
+      }
+      return newTargetClass.accessFlags.isInterface() ? Type.INTERFACE : Type.VIRTUAL;
+    }
+    return type;
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return getPrevious().isContextFreeForMethods();
+  }
+
+  @Override
+  public boolean verifyIsContextFreeForMethod(DexMethod method) {
+    assert getPrevious().verifyIsContextFreeForMethod(method);
+    return true;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    typeMap.forEach(
+        (from, to) ->
+            builder
+                .append(from.getTypeName())
+                .append(" -> ")
+                .append(to.getTypeName())
+                .append(System.lineSeparator()));
+    methodMap.forEach(
+        (from, to) ->
+            builder
+                .append(from.toSourceString())
+                .append(" -> ")
+                .append(to.toSourceString())
+                .append(System.lineSeparator()));
+    fieldMap.forEachManyToOneMapping(
+        (fromSet, to) -> {
+          builder.append(
+              fromSet.stream()
+                  .map(DexField::toSourceString)
+                  .collect(Collectors.joining("," + System.lineSeparator())));
+          builder.append(" -> ");
+          builder.append(to.toSourceString()).append(System.lineSeparator());
+        });
+    builder.append(getPrevious().toString());
+    return builder.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 041033e..e3558a2 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.InstantiatedObject;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.OptionalBool;
 import java.util.ArrayList;
@@ -60,6 +61,14 @@
     return false;
   }
 
+  public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+    return false;
+  }
+
+  public boolean isIllegalAccessErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+    return false;
+  }
+
   /** Returns non-null if isFailedResolution() is true, otherwise null. */
   public FailedResolutionResult asFailedResolution() {
     return null;
@@ -346,7 +355,7 @@
         return null;
       }
       // 1-3. Search the initial class and its supers in order for a matching instance method.
-      DexMethod method = getResolvedMethod().method;
+      DexMethod method = getResolvedMethod().getReference();
       DexClassAndMethod target = null;
       DexClass current = initialType;
       while (current != null) {
@@ -446,7 +455,7 @@
       return LookupResult.createResult(
           methodTargets,
           lambdaTargets,
-          incompleteness.computeCollectionState(resolvedMethod.method, appInfo));
+          incompleteness.computeCollectionState(resolvedMethod.getReference(), appInfo));
     }
 
     @Override
@@ -626,12 +635,12 @@
 
     private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
         DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
-      return appInfo.lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.method);
+      return appInfo.lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.getReference());
     }
 
     private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
         LambdaDescriptor lambdaDescriptor, AppInfoWithClassHierarchy appInfo) {
-      return appInfo.lookupMaximallySpecificMethod(lambdaDescriptor, resolvedMethod.method);
+      return appInfo.lookupMaximallySpecificMethod(lambdaDescriptor, resolvedMethod.getReference());
     }
 
     /**
@@ -642,7 +651,7 @@
      */
     private static DexEncodedMethod lookupOverrideCandidate(
         DexEncodedMethod method, DexClass clazz) {
-      DexEncodedMethod candidate = clazz.lookupVirtualMethod(method.method);
+      DexEncodedMethod candidate = clazz.lookupVirtualMethod(method.getReference());
       assert candidate == null || !candidate.isPrivateMethod();
       if (candidate != null) {
         return isOverriding(method, candidate) ? candidate : DexEncodedMethod.SENTINEL;
@@ -659,7 +668,7 @@
         if (clazz == null) {
           return resolvedMethod;
         }
-        DexEncodedMethod otherOverride = clazz.lookupVirtualMethod(resolvedMethod.method);
+        DexEncodedMethod otherOverride = clazz.lookupVirtualMethod(resolvedMethod.getReference());
         if (otherOverride != null
             && isOverriding(resolvedMethod, otherOverride)
             && (otherOverride.accessFlags.isPublic() || otherOverride.accessFlags.isProtected())) {
@@ -680,7 +689,7 @@
      */
     public static boolean isOverriding(
         DexEncodedMethod resolvedMethod, DexEncodedMethod candidate) {
-      assert resolvedMethod.method.match(candidate.method);
+      assert resolvedMethod.getReference().match(candidate.getReference());
       assert !candidate.isPrivateMethod();
       if (resolvedMethod.accessFlags.isPublic() || resolvedMethod.accessFlags.isProtected()) {
         return true;
@@ -814,6 +823,10 @@
     public boolean isVirtualTarget() {
       return false;
     }
+
+    public boolean hasMethodsCausingError() {
+      return false;
+    }
   }
 
   public static class ClassNotFoundResult extends FailedResolutionResult {
@@ -824,7 +837,7 @@
     }
   }
 
-  abstract static class FailedResolutionWithCausingMethods extends FailedResolutionResult {
+  public abstract static class FailedResolutionWithCausingMethods extends FailedResolutionResult {
 
     private final Collection<DexEncodedMethod> methodsCausingError;
 
@@ -836,6 +849,11 @@
     public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
       this.methodsCausingError.forEach(methodCausingFailureConsumer);
     }
+
+    @Override
+    public boolean hasMethodsCausingError() {
+      return methodsCausingError.size() > 0;
+    }
   }
 
   public static class IncompatibleClassResult extends FailedResolutionWithCausingMethods {
@@ -861,13 +879,70 @@
   public static class NoSuchMethodResult extends FailedResolutionResult {
 
     static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
+
+    @Override
+    public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+      return true;
+    }
   }
 
-  public static class IllegalAccessOrNoSuchMethodResult extends FailedResolutionWithCausingMethods {
+  static class IllegalAccessOrNoSuchMethodResult extends FailedResolutionWithCausingMethods {
 
-    public IllegalAccessOrNoSuchMethodResult(DexEncodedMethod methodCausingError) {
-      super(Collections.singletonList(methodCausingError));
+    private final DexClass initialResolutionHolder;
+
+    public IllegalAccessOrNoSuchMethodResult(
+        DexClass initialResolutionHolder, Collection<DexEncodedMethod> methodsCausingError) {
+      super(methodsCausingError);
+      this.initialResolutionHolder = initialResolutionHolder;
+    }
+
+    public IllegalAccessOrNoSuchMethodResult(
+        DexClass initialResolutionHolder, DexEncodedMethod methodCausingError) {
+      this(initialResolutionHolder, Collections.singletonList(methodCausingError));
       assert methodCausingError != null;
     }
+
+    @Override
+    public boolean isIllegalAccessErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+      if (!hasMethodsCausingError()) {
+        return false;
+      }
+      BooleanBox seenNoAccess = new BooleanBox(false);
+      forEachFailureDependency(
+          method -> {
+            DexClassAndMethod classAndMethod =
+                DexClassAndMethod.create(appInfo.definitionFor(method.getHolderType()), method);
+            seenNoAccess.or(
+                AccessControl.isMemberAccessible(
+                        classAndMethod, initialResolutionHolder, context, appInfo)
+                    .isFalse());
+          });
+      return seenNoAccess.get();
+    }
+
+    @Override
+    public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+      if (!hasMethodsCausingError()) {
+        return true;
+      }
+      if (isIllegalAccessErrorResult(context, appInfo)) {
+        return false;
+      }
+      // At this point we know we have methods causing errors but we have access to them. To be
+      // certain that this is the case where we have nest access but we are invoking a method with
+      // an incorrect symbolic reference, we directly test for it by having an assert.
+      assert verifyInvalidSymbolicReference();
+      return true;
+    }
+
+    private boolean verifyInvalidSymbolicReference() {
+      BooleanBox invalidSymbolicReference = new BooleanBox(true);
+      forEachFailureDependency(
+          method -> {
+            invalidSymbolicReference.and(
+                method.getHolderType() != initialResolutionHolder.getType());
+          });
+      return invalidSymbolicReference.get();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
index add54d6..c016016 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescription.java
@@ -133,8 +133,8 @@
     private final DexType oldType;
     private final DexType newType;
 
-    static RewrittenTypeInfo toVoid(DexType oldReturnType, AppView<?> appView) {
-      return new RewrittenTypeInfo(oldReturnType, appView.dexItemFactory().voidType);
+    static RewrittenTypeInfo toVoid(DexType oldReturnType, DexItemFactory dexItemFactory) {
+      return new RewrittenTypeInfo(oldReturnType, dexItemFactory.voidType);
     }
 
     public RewrittenTypeInfo(DexType oldType, DexType newType) {
@@ -150,8 +150,8 @@
       return oldType;
     }
 
-    boolean hasBeenChangedToReturnVoid(AppView<?> appView) {
-      return newType == appView.dexItemFactory().voidType;
+    boolean hasBeenChangedToReturnVoid(DexItemFactory dexItemFactory) {
+      return newType == dexItemFactory.voidType;
     }
 
     @Override
@@ -252,7 +252,7 @@
       // Currently not allowed to remove the receiver of an instance method. This would involve
       // changing invoke-direct/invoke-virtual into invoke-static.
       assert encodedMethod.isStatic() || !getArgumentInfo(0).isRemovedArgumentInfo();
-      DexType[] params = encodedMethod.method.proto.parameters.values;
+      DexType[] params = encodedMethod.getReference().proto.parameters.values;
       if (isEmpty()) {
         return params;
       }
@@ -367,7 +367,9 @@
       ArgumentInfoCollection removedArgumentsInfo) {
     DexType returnType = method.proto.returnType;
     RewrittenTypeInfo returnInfo =
-        returnType.isAlwaysNull(appView) ? RewrittenTypeInfo.toVoid(returnType, appView) : null;
+        returnType.isAlwaysNull(appView)
+            ? RewrittenTypeInfo.toVoid(returnType, appView.dexItemFactory())
+            : null;
     return create(Collections.emptyList(), returnInfo, removedArgumentsInfo);
   }
 
@@ -394,8 +396,9 @@
     return extraParameters.size();
   }
 
-  public boolean hasBeenChangedToReturnVoid(AppView<?> appView) {
-    return rewrittenReturnInfo != null && rewrittenReturnInfo.hasBeenChangedToReturnVoid(appView);
+  public boolean hasBeenChangedToReturnVoid(DexItemFactory dexItemFactory) {
+    return rewrittenReturnInfo != null
+        && rewrittenReturnInfo.hasBeenChangedToReturnVoid(dexItemFactory);
   }
 
   public ArgumentInfoCollection getArgumentInfoCollection() {
@@ -433,23 +436,23 @@
 
   public DexProto rewriteProto(DexEncodedMethod encodedMethod, DexItemFactory dexItemFactory) {
     if (isEmpty()) {
-      return encodedMethod.method.proto;
+      return encodedMethod.getReference().proto;
     }
     DexType newReturnType =
         rewrittenReturnInfo != null
             ? rewrittenReturnInfo.newType
-            : encodedMethod.method.proto.returnType;
+            : encodedMethod.getReference().proto.returnType;
     DexType[] newParameters = argumentInfoCollection.rewriteParameters(encodedMethod);
     return dexItemFactory.createProto(newReturnType, newParameters);
   }
 
   public RewrittenPrototypeDescription withConstantReturn(
-      DexType oldReturnType, AppView<?> appView) {
+      DexType oldReturnType, DexItemFactory dexItemFactory) {
     assert rewrittenReturnInfo == null;
-    return !hasBeenChangedToReturnVoid(appView)
+    return !hasBeenChangedToReturnVoid(dexItemFactory)
         ? new RewrittenPrototypeDescription(
             extraParameters,
-            RewrittenTypeInfo.toVoid(oldReturnType, appView),
+            RewrittenTypeInfo.toVoid(oldReturnType, dexItemFactory),
             argumentInfoCollection)
         : this;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 27f3251..5bbc1ee 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -43,7 +43,7 @@
   }
 
   private DexEncodedField recordFieldChange(DexEncodedField from, DexEncodedField to) {
-    recordFieldChange(from.field, to.field);
+    recordFieldChange(from.getReference(), to.getReference());
     return to;
   }
 
@@ -77,7 +77,7 @@
 
   /** Callback to allow custom handling when an encoded method changes. */
   public DexEncodedMethod recordMethodChange(DexEncodedMethod from, DexEncodedMethod to) {
-    recordMethodChange(from.method, to.method);
+    recordMethodChange(from.getReference(), to.getReference());
     return to;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 0e7d164..a47acd6 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -63,7 +63,6 @@
   private final DexItemFactory dexItemFactory;
   private final ClassInitializerSynthesizedCode classInitializerSynthesizedCode;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
-  private final HorizontallyMergedClasses.Builder mergedClassesBuilder;
 
   private final ClassMethodsBuilder classMethodsBuilder = new ClassMethodsBuilder();
   private final Reference2IntMap<DexType> classIdentifiers = new Reference2IntOpenHashMap<>();
@@ -75,14 +74,12 @@
   private ClassMerger(
       AppView<AppInfoWithLiveness> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
-      HorizontallyMergedClasses.Builder mergedClassesBuilder,
       MergeGroup group,
       Collection<VirtualMethodMerger> virtualMethodMergers,
       Collection<ConstructorMerger> constructorMergers,
       ClassInitializerSynthesizedCode classInitializerSynthesizedCode) {
     this.appView = appView;
     this.lensBuilder = lensBuilder;
-    this.mergedClassesBuilder = mergedClassesBuilder;
     this.group = group;
     this.virtualMethodMergers = virtualMethodMergers;
     this.constructorMergers = constructorMergers;
@@ -95,10 +92,6 @@
     buildClassIdentifierMap();
   }
 
-  MergeGroup getGroup() {
-    return group;
-  }
-
   void buildClassIdentifierMap() {
     classIdentifiers.put(group.getTarget().getType(), 0);
     group.forEachSource(clazz -> classIdentifiers.put(clazz.getType(), classIdentifiers.size()));
@@ -174,7 +167,7 @@
   DexMethod renameDirectMethod(ProgramMethod method) {
     assert method.getDefinition().belongsToDirectPool();
     return dexItemFactory.createFreshMethodName(
-        method.getDefinition().method.name.toSourceString(),
+        method.getDefinition().getReference().name.toSourceString(),
         method.getHolderType(),
         method.getDefinition().getProto(),
         group.getTarget().getType(),
@@ -279,8 +272,6 @@
 
     mergeStaticFields();
     mergeInstanceFields();
-
-    mergedClassesBuilder.addMergeGroup(group);
   }
 
   public static class Builder {
@@ -351,7 +342,6 @@
     }
 
     public ClassMerger build(
-        HorizontallyMergedClasses.Builder mergedClassesBuilder,
         HorizontalClassMergerGraphLens.Builder lensBuilder) {
       setup();
       List<VirtualMethodMerger> virtualMethodMergers =
@@ -377,7 +367,6 @@
       return new ClassMerger(
           appView,
           lensBuilder,
-          mergedClassesBuilder,
           group,
           virtualMethodMergers,
           constructorMergers,
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 05e91a4..7085879 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -40,8 +40,8 @@
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -56,14 +56,13 @@
     assert appView.options().enableInlining;
   }
 
-  public HorizontalClassMergerResult run(
-      RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+  public HorizontalClassMergerResult run(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
     MergeGroup initialGroup = new MergeGroup(appView.appInfo().classesWithDeterministicOrder());
 
     // Run the policies on all program classes to produce a final grouping.
     List<Policy> policies = getPolicies(runtimeTypeCheckInfo);
     Collection<MergeGroup> groups =
-        new SimplePolicyExecutor().run(Collections.singletonList(initialGroup), policies);
+        new PolicyExecutor().run(Collections.singletonList(initialGroup), policies, timing);
 
     // If there are no groups, then end horizontal class merging.
     if (groups.isEmpty()) {
@@ -71,25 +70,18 @@
       return null;
     }
 
-    HorizontallyMergedClasses.Builder mergedClassesBuilder =
-        new HorizontallyMergedClasses.Builder();
     HorizontalClassMergerGraphLens.Builder lensBuilder =
         new HorizontalClassMergerGraphLens.Builder();
 
-    // Set up a class merger for each group.
-    List<ClassMerger> classMergers =
-        initializeClassMergers(mergedClassesBuilder, lensBuilder, groups);
-    Iterable<DexProgramClass> allMergeClasses =
-        Iterables.concat(
-            Iterables.transform(classMergers, classMerger -> classMerger.getGroup().getClasses()));
-
     // Merge the classes.
+    List<ClassMerger> classMergers = initializeClassMergers(lensBuilder, groups);
     SyntheticArgumentClass syntheticArgumentClass =
-        new SyntheticArgumentClass.Builder(appView).build(allMergeClasses);
+        new SyntheticArgumentClass.Builder(appView).build(groups);
     applyClassMergers(classMergers, syntheticArgumentClass);
 
     // Generate the graph lens.
-    HorizontallyMergedClasses mergedClasses = mergedClassesBuilder.build();
+    HorizontallyMergedClasses mergedClasses =
+        HorizontallyMergedClasses.builder().addMergeGroups(groups).build();
     appView.setHorizontallyMergedClasses(mergedClasses);
     HorizontalClassMergerGraphLens lens =
         createLens(mergedClasses, lensBuilder, syntheticArgumentClass);
@@ -163,7 +155,6 @@
    * be merged and how the merging should be performed.
    */
   private List<ClassMerger> initializeClassMergers(
-      HorizontallyMergedClasses.Builder mergedClassesBuilder,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Collection<MergeGroup> groups) {
     List<ClassMerger> classMergers = new ArrayList<>();
@@ -171,8 +162,7 @@
     // TODO(b/166577694): Replace Collection<DexProgramClass> with MergeGroup
     for (MergeGroup group : groups) {
       assert !group.isEmpty();
-      ClassMerger merger =
-          new ClassMerger.Builder(appView, group).build(mergedClassesBuilder, lensBuilder);
+      ClassMerger merger = new ClassMerger.Builder(appView, group).build(lensBuilder);
       classMergers.add(merger);
     }
 
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 f5fdd6f..4358dc8 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -8,14 +8,12 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Streams;
@@ -36,14 +34,8 @@
       Map<DexMethod, List<ExtraParameter>> methodExtraParameters,
       BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
       Map<DexMethod, DexMethod> methodMap,
-      BidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures) {
-    super(
-        mergedClasses.getForwardMap(),
-        methodMap,
-        fieldMap,
-        originalMethodSignatures,
-        appView.graphLens(),
-        appView.dexItemFactory());
+      BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> newMethodSignatures) {
+    super(appView, fieldMap, methodMap, mergedClasses.getForwardMap(), newMethodSignatures);
     this.methodExtraParameters = methodExtraParameters;
     this.mergedClasses = mergedClasses;
   }
@@ -91,32 +83,33 @@
   public static class Builder {
 
     private final MutableBidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap =
-        new BidirectionalManyToOneRepresentativeHashMap<>();
+        BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
     private final BidirectionalManyToOneHashMap<DexMethod, DexMethod> methodMap =
-        new BidirectionalManyToOneHashMap<>();
-    private final BidirectionalOneToManyRepresentativeHashMap<DexMethod, DexMethod>
-        originalMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>();
+        BidirectionalManyToOneHashMap.newIdentityHashMap();
+    private final BidirectionalManyToOneRepresentativeHashMap<DexMethod, DexMethod>
+        newMethodSignatures = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
     private final Map<DexMethod, List<ExtraParameter>> methodExtraParameters =
         new IdentityHashMap<>();
 
     private final BidirectionalManyToOneHashMap<DexMethod, DexMethod> pendingMethodMapUpdates =
-        new BidirectionalManyToOneHashMap<>();
-    private final BidirectionalOneToManyRepresentativeHashMap<DexMethod, DexMethod>
-        pendingOriginalMethodSignatureUpdates = new BidirectionalOneToManyRepresentativeHashMap<>();
+        BidirectionalManyToOneHashMap.newIdentityHashMap();
+    private final BidirectionalManyToOneRepresentativeHashMap<DexMethod, DexMethod>
+        pendingNewMethodSignatureUpdates =
+            BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
 
     Builder() {}
 
     HorizontalClassMergerGraphLens build(
         AppView<?> appView, HorizontallyMergedClasses mergedClasses) {
       assert pendingMethodMapUpdates.isEmpty();
-      assert pendingOriginalMethodSignatureUpdates.isEmpty();
+      assert pendingNewMethodSignatureUpdates.isEmpty();
       return new HorizontalClassMergerGraphLens(
           appView,
           mergedClasses,
           methodExtraParameters,
           fieldMap,
           methodMap.getForwardMap(),
-          originalMethodSignatures);
+          newMethodSignatures);
     }
 
     void recordNewFieldSignature(DexField oldFieldSignature, DexField newFieldSignature) {
@@ -161,7 +154,7 @@
     }
 
     void recordNewMethodSignature(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
-      originalMethodSignatures.put(newMethodSignature, oldMethodSignature);
+      newMethodSignatures.put(oldMethodSignature, newMethodSignature);
     }
 
     void fixupMethod(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
@@ -182,12 +175,12 @@
 
     private void fixupOriginalMethodSignatures(
         DexMethod oldMethodSignature, DexMethod newMethodSignature) {
-      Set<DexMethod> oldMethodSignatures = originalMethodSignatures.getValues(oldMethodSignature);
+      Set<DexMethod> oldMethodSignatures = newMethodSignatures.getKeys(oldMethodSignature);
       if (oldMethodSignatures.isEmpty()) {
-        pendingOriginalMethodSignatureUpdates.put(newMethodSignature, oldMethodSignature);
+        pendingNewMethodSignatureUpdates.put(oldMethodSignature, newMethodSignature);
       } else {
         for (DexMethod originalMethodSignature : oldMethodSignatures) {
-          pendingOriginalMethodSignatureUpdates.put(newMethodSignature, originalMethodSignature);
+          pendingNewMethodSignatureUpdates.put(originalMethodSignature, newMethodSignature);
         }
       }
     }
@@ -199,9 +192,9 @@
       pendingMethodMapUpdates.clear();
 
       // Commit pending original method signatures updates.
-      originalMethodSignatures.removeAll(pendingOriginalMethodSignatureUpdates.keySet());
-      pendingOriginalMethodSignatureUpdates.forEachOneToManyMapping(originalMethodSignatures::put);
-      pendingOriginalMethodSignatureUpdates.clear();
+      newMethodSignatures.removeAll(pendingNewMethodSignatureUpdates.keySet());
+      pendingNewMethodSignatureUpdates.forEachManyToOneMapping(newMethodSignatures::put);
+      pendingNewMethodSignatureUpdates.clear();
     }
 
     /**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
index 93f9d3f..927e629 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -24,6 +24,10 @@
     this.mergedClasses = mergedClasses;
   }
 
+  static Builder builder() {
+    return new Builder();
+  }
+
   public static HorizontallyMergedClasses empty() {
     return new HorizontallyMergedClasses(new EmptyBidirectionalOneToOneMap<>());
   }
@@ -79,15 +83,21 @@
   }
 
   public static class Builder {
-    private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses =
-        new BidirectionalManyToOneHashMap<>();
 
-    public HorizontallyMergedClasses build() {
-      return new HorizontallyMergedClasses(mergedClasses);
+    private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses =
+        BidirectionalManyToOneHashMap.newIdentityHashMap();
+
+    void addMergeGroup(MergeGroup group) {
+      group.forEachSource(clazz -> mergedClasses.put(clazz.getType(), group.getTarget().getType()));
     }
 
-    public void addMergeGroup(MergeGroup group) {
-      group.forEachSource(clazz -> mergedClasses.put(clazz.getType(), group.getTarget().getType()));
+    Builder addMergeGroups(Iterable<MergeGroup> groups) {
+      groups.forEach(this::addMergeGroup);
+      return this;
+    }
+
+    HorizontallyMergedClasses build() {
+      return new HorizontallyMergedClasses(mergedClasses);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
index 4642229..b984ed5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
@@ -14,6 +14,8 @@
 
   public void clear() {}
 
+  public abstract String getName();
+
   public boolean shouldSkipPolicy() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
index b94adf8..33b6f7f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
@@ -4,15 +4,88 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.Timing;
 import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
 
-public abstract class PolicyExecutor {
+/**
+ * This is a simple policy executor that ensures regular sequential execution of policies. It should
+ * primarily be readable and correct. The SimplePolicyExecutor should be a reference implementation,
+ * against which more efficient policy executors can be compared.
+ */
+public class PolicyExecutor {
+
+  // TODO(b/165506334): if performing mutable operation ensure that linked lists are used
+  private void applySingleClassPolicy(SingleClassPolicy policy, LinkedList<MergeGroup> groups) {
+    Iterator<MergeGroup> i = groups.iterator();
+    while (i.hasNext()) {
+      MergeGroup group = i.next();
+      int previousNumberOfClasses = group.size();
+      group.removeIf(clazz -> !policy.canMerge(clazz));
+      policy.numberOfRemovedClasses += previousNumberOfClasses - group.size();
+      if (group.size() < 2) {
+        i.remove();
+      }
+    }
+  }
+
+  private LinkedList<MergeGroup> applyMultiClassPolicy(
+      MultiClassPolicy policy, LinkedList<MergeGroup> groups) {
+    // For each group apply the multi class policy and add all the new groups together.
+    LinkedList<MergeGroup> newGroups = new LinkedList<>();
+    groups.forEach(
+        group -> {
+          int previousNumberOfClasses = group.size();
+          Collection<MergeGroup> policyGroups = policy.apply(group);
+          policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
+          policy.numberOfRemovedClasses +=
+              previousNumberOfClasses - IterableUtils.sumInt(policyGroups, MergeGroup::size);
+          newGroups.addAll(policyGroups);
+        });
+    return newGroups;
+  }
 
   /**
    * Given an initial collection of class groups which can potentially be merged, run all of the
    * policies registered to this policy executor on the class groups yielding a new collection of
    * class groups.
    */
-  public abstract Collection<MergeGroup> run(
-      Collection<MergeGroup> classes, Collection<Policy> policies);
+  public Collection<MergeGroup> run(
+      Collection<MergeGroup> inputGroups, Collection<Policy> policies, Timing timing) {
+    LinkedList<MergeGroup> linkedGroups;
+
+    if (inputGroups instanceof LinkedList) {
+      linkedGroups = (LinkedList<MergeGroup>) inputGroups;
+    } else {
+      linkedGroups = new LinkedList<>(inputGroups);
+    }
+
+    for (Policy policy : policies) {
+      if (policy.shouldSkipPolicy()) {
+        continue;
+      }
+
+      timing.begin(policy.getName());
+      if (policy instanceof SingleClassPolicy) {
+        applySingleClassPolicy((SingleClassPolicy) policy, linkedGroups);
+      } else {
+        assert policy instanceof MultiClassPolicy;
+        linkedGroups = applyMultiClassPolicy((MultiClassPolicy) policy, linkedGroups);
+      }
+      timing.end();
+
+      policy.clear();
+
+      if (linkedGroups.isEmpty()) {
+        break;
+      }
+
+      // Any policy should not return any trivial groups.
+      assert linkedGroups.stream().allMatch(group -> group.size() >= 2);
+    }
+
+    return linkedGroups;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
deleted file mode 100644
index 3b96a76..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SimplePolicyExecutor.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging;
-
-import com.android.tools.r8.utils.IterableUtils;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedList;
-
-/**
- * This is a simple policy executor that ensures regular sequential execution of policies. It should
- * primarily be readable and correct. The SimplePolicyExecutor should be a reference implementation,
- * against which more efficient policy executors can be compared.
- */
-public class SimplePolicyExecutor extends PolicyExecutor {
-
-  // TODO(b/165506334): if performing mutable operation ensure that linked lists are used
-  private LinkedList<MergeGroup> applySingleClassPolicy(
-      SingleClassPolicy policy, LinkedList<MergeGroup> groups) {
-    Iterator<MergeGroup> i = groups.iterator();
-    while (i.hasNext()) {
-      MergeGroup group = i.next();
-      int previousNumberOfClasses = group.size();
-      group.removeIf(clazz -> !policy.canMerge(clazz));
-      policy.numberOfRemovedClasses += previousNumberOfClasses - group.size();
-      if (group.size() < 2) {
-        i.remove();
-      }
-    }
-    return groups;
-  }
-
-  private LinkedList<MergeGroup> applyMultiClassPolicy(
-      MultiClassPolicy policy, LinkedList<MergeGroup> groups) {
-    // For each group apply the multi class policy and add all the new groups together.
-    LinkedList<MergeGroup> newGroups = new LinkedList<>();
-    groups.forEach(
-        group -> {
-          int previousNumberOfClasses = group.size();
-          Collection<MergeGroup> policyGroups = policy.apply(group);
-          policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
-          policy.numberOfRemovedClasses +=
-              previousNumberOfClasses - IterableUtils.sumInt(policyGroups, MergeGroup::size);
-          newGroups.addAll(policyGroups);
-        });
-    return newGroups;
-  }
-
-  @Override
-  public Collection<MergeGroup> run(
-      Collection<MergeGroup> inputGroups, Collection<Policy> policies) {
-    LinkedList<MergeGroup> linkedGroups;
-
-    if (inputGroups instanceof LinkedList) {
-      linkedGroups = (LinkedList<MergeGroup>) inputGroups;
-    } else {
-      linkedGroups = new LinkedList<>(inputGroups);
-    }
-
-    for (Policy policy : policies) {
-      if (policy.shouldSkipPolicy()) {
-        continue;
-      }
-
-      if (policy instanceof SingleClassPolicy) {
-        linkedGroups = applySingleClassPolicy((SingleClassPolicy) policy, linkedGroups);
-      } else if (policy instanceof MultiClassPolicy) {
-        linkedGroups = applyMultiClassPolicy((MultiClassPolicy) policy, linkedGroups);
-      }
-
-      policy.clear();
-
-      if (linkedGroups.isEmpty()) {
-        break;
-      }
-
-      // Any policy should not return any trivial groups.
-      assert linkedGroups.stream().allMatch(group -> group.size() >= 2);
-    }
-
-    return linkedGroups;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
index ca0b752..c8cc19e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -55,9 +56,8 @@
           .createFixedClass(syntheticKind, context, appView.dexItemFactory(), builder -> {});
     }
 
-    public SyntheticArgumentClass build(Iterable<DexProgramClass> mergeClasses) {
-      // Find a fresh name in an existing package.
-      DexProgramClass context = mergeClasses.iterator().next();
+    public SyntheticArgumentClass build(Collection<MergeGroup> mergeGroups) {
+      DexProgramClass context = getDeterministicContext(mergeGroups);
       List<DexType> syntheticArgumentTypes = new ArrayList<>();
       syntheticArgumentTypes.add(
           synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1).getType());
@@ -67,5 +67,12 @@
           synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_3).getType());
       return new SyntheticArgumentClass(syntheticArgumentTypes);
     }
+
+    private static DexProgramClass getDeterministicContext(Collection<MergeGroup> mergeGroups) {
+      // Relies on the determinism of the merge groups.
+      MergeGroup mergeGroup = mergeGroups.iterator().next();
+      assert mergeGroup.hasTarget();
+      return mergeGroup.getTarget();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index e8130ed..c144128 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -112,7 +112,7 @@
     flags.setPrivate();
     classMethodsBuilder.addDirectMethod(encodedMethod);
 
-    return encodedMethod.method;
+    return encodedMethod.getReference();
   }
 
   private MethodAccessFlags getAccessFlags() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java
index 75d61d9..38150db 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AllInstantiatedOrUninstantiated.java
@@ -21,4 +21,9 @@
   public Boolean getMergeKey(DexProgramClass clazz) {
     return appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
   }
+
+  @Override
+  public String getName() {
+    return "AllInstantiatedOrUninstantiated";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
index 6e5c711..e78b196 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
@@ -25,6 +25,11 @@
   }
 
   @Override
+  public String getName() {
+    return "CheckAbstractClasses";
+  }
+
+  @Override
   public boolean shouldSkipPolicy() {
     // We can just make the target class non-abstract if one of the classes in the group
     // is non-abstract.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
index f7d121a..e6e67e0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontInlinePolicy.java
@@ -12,16 +12,14 @@
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexInfo;
 import com.google.common.collect.Iterables;
 
 public class DontInlinePolicy extends SingleClassPolicy {
+
   private final AppView<AppInfoWithLiveness> appView;
-  private final MainDexInfo mainDexInfo;
 
   public DontInlinePolicy(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.mainDexInfo = appView.appInfo().getMainDexInfo();
   }
 
   private boolean disallowInlining(ProgramMethod method) {
@@ -53,4 +51,9 @@
         program.directProgramMethods(),
         method -> method.getDefinition().isInstanceInitializer() && disallowInlining(method));
   }
+
+  @Override
+  public String getName() {
+    return "DontInlinePolicy";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java
index 4372869..e1cb9a7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/DontMergeSynchronizedClasses.java
@@ -56,4 +56,9 @@
 
     return synchronizedGroups;
   }
+
+  @Override
+  public String getName() {
+    return "DontMergeSynchronizedClasses";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java
index 33de8f3..80e1292 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java
@@ -52,4 +52,9 @@
     newGroups.add(newGroup);
     return newGroup;
   }
+
+  @Override
+  public String getName() {
+    return "LimitGroups";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java
index 73e0786..1ef2f6a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeFieldCasts.java
@@ -70,4 +70,9 @@
     }
     return fieldTypes;
   }
+
+  @Override
+  public String getName() {
+    return "MinimizeFieldCasts";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
index 1effb8b..9943010 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoAnnotationClasses.java
@@ -12,4 +12,9 @@
   public boolean canMerge(DexProgramClass program) {
     return !program.isAnnotation();
   }
+
+  @Override
+  public String getName() {
+    return "NoAnnotationClasses";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
index 9d3d730..1a96901 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassAnnotationCollisions.java
@@ -41,4 +41,9 @@
     }
     return removeTrivialGroups(newGroups);
   }
+
+  @Override
+  public String getName() {
+    return "NoClassAnnotationCollisions";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerWithObservableSideEffects.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerWithObservableSideEffects.java
index f10d78c..b156a2e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerWithObservableSideEffects.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerWithObservableSideEffects.java
@@ -28,4 +28,9 @@
     return program.getKotlinInfo().isSyntheticClass()
         && program.getKotlinInfo().asSyntheticClass().isLambda();
   }
+
+  @Override
+  public String getName() {
+    return "NoClassInitializerWithObservableSideEffects";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
index b0f3766..17659f8 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
@@ -19,4 +19,9 @@
   public boolean canMerge(DexProgramClass clazz) {
     return !runtimeTypeCheckInfo.isRuntimeCheckType(clazz);
   }
+
+  @Override
+  public String getName() {
+    return "NoDirectRuntimeTypeChecks";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java
index c8a0aab..8bea8a4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java
@@ -27,6 +27,11 @@
   }
 
   @Override
+  public String getName() {
+    return "NoEnums";
+  }
+
+  @Override
   public boolean canMerge(DexProgramClass program) {
     if (program.isEnum()) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
index 7446787..8de67d6 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
@@ -65,4 +65,9 @@
     }
     return false;
   }
+
+  @Override
+  public String getName() {
+    return "NoIndirectRuntimeTypeChecks";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java
index cc9f61c..1d26825 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInnerClasses.java
@@ -14,4 +14,9 @@
     // TODO(b/179018501): allow merging classes with inner/outer classes.
     return program.getInnerClasses().isEmpty();
   }
+
+  @Override
+  public String getName() {
+    return "NoInnerClasses";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
index 610af7e..229c062 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceFieldAnnotations.java
@@ -19,4 +19,9 @@
     }
     return true;
   }
+
+  @Override
+  public String getName() {
+    return "NoInstanceFieldAnnotations";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
index 63df0de..d6032e7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
@@ -13,4 +13,9 @@
   public boolean canMerge(DexProgramClass program) {
     return !program.isInterface();
   }
+
+  @Override
+  public String getName() {
+    return "NoInterfaces";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
index e0a39eb..ec8a5aa 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
@@ -49,4 +49,9 @@
   public boolean canMerge(DexProgramClass program) {
     return !dontMergeTypes.contains(program.getType());
   }
+
+  @Override
+  public String getName() {
+    return "NoKeepRules";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
index 1acad7a..7783f20 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKotlinMetadata.java
@@ -29,4 +29,9 @@
         .allMatch(member -> member.getKotlinMemberInfo().isNoKotlinInformation());
     return true;
   }
+
+  @Override
+  public String getName() {
+    return "NoKotlinMetadata";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNativeMethods.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNativeMethods.java
index f1a278c..a32d083 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNativeMethods.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNativeMethods.java
@@ -14,4 +14,9 @@
   public boolean canMerge(DexProgramClass program) {
     return !Iterables.any(program.methods(), DexEncodedMethod::isNative);
   }
+
+  @Override
+  public String getName() {
+    return "NoNativeMethods";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java
index 1c6ae77..b1f0d99 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java
@@ -26,4 +26,9 @@
     return !appView.appServices().allServiceTypes().contains(program.getType())
         && !allServiceImplementations.contains(program.getType());
   }
+
+  @Override
+  public String getName() {
+    return "NoServiceLoaders";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
index bfe0f2a..8751afb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
@@ -30,4 +30,9 @@
     return !deadEnumLiteMaps.contains(program.getType())
         && !appView.appInfo().isNoHorizontalClassMergingOfType(program.getType());
   }
+
+  @Override
+  public String getName() {
+    return "NotMatchedByNoHorizontalClassMerging";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
index 67afbc3..92d50d1 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
@@ -23,4 +23,9 @@
     }
     return !appView.verticallyMergedClasses().hasBeenMergedIntoSubtype(program.type);
   }
+
+  @Override
+  public String getName() {
+    return "NotVerticallyMergedIntoSubtype";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index 4a789ba..c8bb5c6 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -28,6 +28,11 @@
  */
 public class PreserveMethodCharacteristics extends MultiClassPolicy {
 
+  @Override
+  public String getName() {
+    return "PreserveMethodCharacteristics";
+  }
+
   static class MethodCharacteristics {
 
     private final MethodAccessFlags accessFlags;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
index 41c240f..38df5cf 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
@@ -29,4 +29,9 @@
         ? mainDexInfo.getMergeKey(clazz, synthetics)
         : ineligibleForClassMerging();
   }
+
+  @Override
+  public String getName() {
+    return "PreventMergeIntoDifferentMainDexGroups";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
index c7c57c8..b182330 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
@@ -59,6 +59,11 @@
   private final ReservedInterfaceSignaturesFor reservedInterfaceSignaturesFor =
       new ReservedInterfaceSignaturesFor();
 
+  @Override
+  public String getName() {
+    return "PreventMethodImplementation";
+  }
+
   private abstract static class SignaturesCache<C extends DexClass> {
     private final Map<DexClass, DexMethodSignatureSet> memoizedSignatures = new IdentityHashMap<>();
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
index 914fc57..78cfe13 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -96,4 +96,9 @@
     groups.addAll(restrictedClasses.values());
     return groups;
   }
+
+  @Override
+  public String getName() {
+    return "RespectPackageBoundaries";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
index 20caa50..b3ec017 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
@@ -24,4 +24,9 @@
         .getClassToFeatureSplitMap()
         .getFeatureSplit(clazz, appView.getSyntheticItems());
   }
+
+  @Override
+  public String getName() {
+    return "SameFeatureSplit";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
index 13e0388..291aae5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
@@ -34,6 +34,11 @@
     return fields;
   }
 
+  @Override
+  public String getName() {
+    return "SameInstanceFields";
+  }
+
   public static class InstanceFieldInfo {
 
     private final FieldAccessFlags accessFlags;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
index 88d2984..fc8e39c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
@@ -23,4 +23,9 @@
   public DexType getMergeKey(DexProgramClass clazz) {
     return clazz.isInANest() ? clazz.getNestHost() : dexItemFactory.objectType;
   }
+
+  @Override
+  public String getName() {
+    return "SameNestHost";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
index 12c8e31..3afa656 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameParentClass.java
@@ -14,4 +14,9 @@
   public DexType getMergeKey(DexProgramClass clazz) {
     return clazz.superType;
   }
+
+  @Override
+  public String getName() {
+    return "SameParentClass";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
index b012897..cbc9b3d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
@@ -48,4 +48,9 @@
     // Java lambda merging is disabled.
     return ineligibleForClassMerging();
   }
+
+  @Override
+  public String getName() {
+    return "SyntheticItemsPolicy";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java
index 056d652e..c7e0cbb 100644
--- a/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java
+++ b/src/main/java/com/android/tools/r8/inspector/internal/FieldInspectorImpl.java
@@ -26,8 +26,8 @@
       reference =
           Reference.field(
               parent.getClassReference(),
-              field.field.name.toString(),
-              Reference.typeFromDescriptor(field.field.type.toDescriptorString()));
+              field.getReference().name.toString(),
+              Reference.typeFromDescriptor(field.getReference().type.toDescriptorString()));
     }
     return reference;
   }
@@ -45,7 +45,7 @@
   @Override
   public Optional<ValueInspector> getInitialValue() {
     if (field.isStatic() && field.getStaticValue() != null) {
-      return Optional.of(new ValueInspectorImpl(field.getStaticValue(), field.field.type));
+      return Optional.of(new ValueInspectorImpl(field.getStaticValue(), field.getReference().type));
     }
     return Optional.empty();
   }
diff --git a/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java b/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java
index 1d868be..3c27924 100644
--- a/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java
+++ b/src/main/java/com/android/tools/r8/inspector/internal/MethodInspectorImpl.java
@@ -27,14 +27,14 @@
       reference =
           Reference.method(
               parent.getClassReference(),
-              method.method.name.toString(),
+              method.getReference().name.toString(),
               ListUtils.map(
-                  Arrays.asList(method.method.proto.parameters.values),
+                  Arrays.asList(method.getReference().proto.parameters.values),
                   param -> Reference.typeFromDescriptor(param.toDescriptorString())),
-              method.method.proto.returnType.isVoidType()
+              method.getReference().proto.returnType.isVoidType()
                   ? null
                   : Reference.typeFromDescriptor(
-                      method.method.proto.returnType.toDescriptorString()));
+                      method.getReference().proto.returnType.toDescriptorString()));
     }
     return reference;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index 5926ecc..b2b6111 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -72,7 +72,8 @@
     }
     TypeElement valueType = instruction.returnValue().getType();
     TypeElement returnType =
-        TypeElement.fromDexType(method.method.proto.returnType, Nullability.maybeNull(), appView);
+        TypeElement.fromDexType(
+            method.getReference().proto.returnType, Nullability.maybeNull(), appView);
     if (verifyTypesHelper.isAssignable(valueType, returnType)) {
       return true;
     }
@@ -80,7 +81,7 @@
     if (returnType.isClassType() && valueType.isReferenceType()) {
       // Interface types are treated like Object according to the JVM spec.
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
-      DexClass clazz = appView.definitionFor(method.method.proto.returnType);
+      DexClass clazz = appView.definitionFor(method.getReference().proto.returnType);
       return clazz != null && clazz.isInterface();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 2b63089..d1b411a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -102,7 +102,7 @@
           Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
               new IdentityHashMap<>();
           for (DexEncodedField field : clazz.instanceFields()) {
-            FieldAccessInfo fieldAccessInfo = fieldAccessInfos.get(field.field);
+            FieldAccessInfo fieldAccessInfo = fieldAccessInfos.get(field.getReference());
             if (fieldAccessInfo != null && !fieldAccessInfo.hasReflectiveAccess()) {
               abstractInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
             }
@@ -112,14 +112,14 @@
   }
 
   private boolean isAlwaysZero(DexEncodedField field) {
-    return !appView.appInfo().isPinned(field.field) && !nonZeroFields.contains(field);
+    return !appView.appInfo().isPinned(field.getReference()) && !nonZeroFields.contains(field);
   }
 
   void acceptClassInitializerDefaultsResult(
       ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     classInitializerDefaultsResult.forEachOptimizedField(
         (field, value) -> {
-          if (!value.isDefault(field.field.type)) {
+          if (!value.isDefault(field.getReference().type)) {
             nonZeroFields.add(field);
           }
         });
@@ -223,11 +223,6 @@
                 >= initialAbstractValue.getAbstractionSize()) {
               abstractValue = UnknownValue.getInstance();
             }
-          } else {
-            assert false
-                : "Expected abstract value of "
-                    + field.toSourceString()
-                    + " to be instance of NonConstantNumberValue";
           }
         }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessAnalysis.java
index a178a69..a1e3eb5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessAnalysis.java
@@ -17,7 +17,7 @@
 
   public void recordFieldAccess(
       FieldInstruction instruction, DexEncodedField field, OptimizationFeedback feedback) {
-    if (!field.field.type.isIntType()) {
+    if (!field.getReference().type.isIntType()) {
       return;
     }
 
@@ -79,7 +79,7 @@
     }
     if (user.isFieldPut()) {
       FieldInstruction fieldInstruction = user.asFieldInstruction();
-      if (fieldInstruction.getField() == encodedField.field) {
+      if (fieldInstruction.getField() == encodedField.getReference()) {
         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 05259ca..b11431e 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
@@ -134,7 +134,7 @@
   private void clearReadsAndWritesFromFieldsOfInterest(AppInfoWithLiveness appInfo) {
     FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
     for (DexEncodedField field : constantFields) {
-      fieldAccessInfoCollection.get(field.field).asMutable().clearReads();
+      fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads();
     }
     for (DexEncodedField field : readFields.keySet()) {
       fieldAccessInfoCollection.get(field.getReference()).asMutable().clearWrites();
@@ -165,7 +165,7 @@
   private static FieldClassification classifyField(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView) {
     FieldAccessInfo fieldAccessInfo =
-        appView.appInfo().getFieldAccessInfoCollection().get(field.field);
+        appView.appInfo().getFieldAccessInfoCollection().get(field.getReference());
     if (fieldAccessInfo == null
         || fieldAccessInfo.hasReflectiveAccess()
         || fieldAccessInfo.isAccessedFromMethodHandle()
@@ -184,7 +184,7 @@
       if (singleValue.isSingleFieldValue()) {
         SingleFieldValue singleFieldValue = singleValue.asSingleFieldValue();
         DexField singleField = singleFieldValue.getField();
-        if (singleField != field.field
+        if (singleField != field.getReference()
             && !singleFieldValue.mayHaveFinalizeMethodDirectlyOrIndirectly(appView)) {
           return FieldClassification.CONSTANT;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index 5bf0c61..f84e13d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -73,7 +73,7 @@
     assert !isEmpty();
     ConcreteMutableFieldSet rewrittenSet = new ConcreteMutableFieldSet();
     for (DexEncodedField field : fields) {
-      DexField rewrittenFieldReference = lens.lookupField(field.field);
+      DexField rewrittenFieldReference = lens.lookupField(field.getReference());
       DexClass holder = appView.definitionForHolder(rewrittenFieldReference);
       DexEncodedField rewrittenField = rewrittenFieldReference.lookupOnClass(holder);
       if (rewrittenField == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index fc01602..13d03d5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -150,7 +150,7 @@
 
     // Dynamic upper bound type.
     TypeElement fieldType =
-        TypeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
+        TypeElement.fromDexType(field.getReference().type, Nullability.maybeNull(), appView);
     TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
     if (dynamicUpperBoundType.strictlyLessThan(fieldType, appView)) {
       if (maybeNull && dynamicUpperBoundType.isDefinitelyNotNull()) {
@@ -201,7 +201,7 @@
     }
     return appView
         .abstractValueFactory()
-        .createSingleFieldValue(field.field, computeObjectState(value));
+        .createSingleFieldValue(field.getReference(), computeObjectState(value));
   }
 
   /**
@@ -330,7 +330,7 @@
 
     return appView
         .abstractValueFactory()
-        .createSingleFieldValue(valuesField.field, new EnumValuesObjectState(valuesState));
+        .createSingleFieldValue(valuesField.getReference(), new EnumValuesObjectState(valuesState));
   }
 
   private ObjectState computeEnumInstanceObjectState(Value value) {
@@ -428,7 +428,7 @@
 
     return appView
         .abstractValueFactory()
-        .createSingleFieldValue(enumField.field, computeObjectState(value));
+        .createSingleFieldValue(enumField.getReference(), computeObjectState(value));
   }
 
   private ObjectState computeObjectState(Value value) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index 65e1ea3..ec51f91 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -63,11 +63,13 @@
             assert valuesCandidateAbstractValue == null
                 || valuesCandidateAbstractValue.equals(value);
             valuesCandidateAbstractValue = value;
-            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+            enumObjectStateBuilder.put(
+                staticField.getReference(), value.asSingleFieldValue().getState());
           }
         } else if (factory.enumMembers.isEnumField(staticField, staticField.getHolderType())) {
           if (value.isSingleFieldValue() && !value.asSingleFieldValue().getState().isEmpty()) {
-            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+            enumObjectStateBuilder.put(
+                staticField.getReference(), value.asSingleFieldValue().getState());
           }
         }
       }
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 64f5bea..f0e63e9 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
@@ -113,7 +113,7 @@
     }
     DexType enumLiteCandidate = null;
     for (DexEncodedMethod virtualMethod : enumLiteMap.virtualMethods()) {
-      if (!matchesFindValueByNumberMethod(virtualMethod.method)) {
+      if (!matchesFindValueByNumberMethod(virtualMethod.getReference())) {
         return null;
       }
       if (virtualMethod.returnType() == references.enumLiteType) {
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 f15ad69..3cb884a 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
@@ -109,7 +109,8 @@
         @Override
         public boolean isReachableOrReferencedField(
             AppInfoWithLiveness appInfo, DexEncodedField field) {
-          return !wasRemoved(field.field) && super.isReachableOrReferencedField(appInfo, field);
+          return !wasRemoved(field.getReference())
+              && super.isReachableOrReferencedField(appInfo, field);
         }
       };
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index c5f72a3..da0ed41 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -440,9 +440,9 @@
         if (clazz != null) {
           DexEncodedMethod newBuilderMethod =
               clazz.lookupDirectMethod(
-                  method -> method.method.name == references.newBuilderMethodName);
+                  method -> method.getReference().name == references.newBuilderMethodName);
           if (newBuilderMethod != null) {
-            bypassClinitforInlining.add(newBuilderMethod.method);
+            bypassClinitforInlining.add(newBuilderMethod.getReference());
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
index 5c3692c..f8c2bf0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
@@ -64,7 +64,8 @@
       return null;
     }
     ObjectState state =
-        enumStaticFieldValues.getObjectStateForPossiblyPinnedField(enumInstanceField.field);
+        enumStaticFieldValues.getObjectStateForPossiblyPinnedField(
+            enumInstanceField.getReference());
     if (state == null) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index b25f946..030fe3d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -147,7 +147,7 @@
   }
 
   public boolean isDynamicMethod(DexEncodedMethod encodedMethod) {
-    return isDynamicMethod(encodedMethod.method);
+    return isDynamicMethod(encodedMethod.getReference());
   }
 
   public boolean isDynamicMethod(ProgramMethod method) {
@@ -160,7 +160,7 @@
   }
 
   public boolean isDynamicMethodBridge(DexEncodedMethod method) {
-    return isDynamicMethodBridge(method.method);
+    return isDynamicMethodBridge(method.getReference());
   }
 
   public boolean isDynamicMethodBridge(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
index 6aacc3d..567449e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NonEmptyObjectState.java
@@ -31,7 +31,7 @@
 
   @Override
   public AbstractValue getAbstractFieldValue(DexEncodedField field) {
-    return state.getOrDefault(field.field, UnknownValue.getInstance());
+    return state.getOrDefault(field.getReference(), UnknownValue.getInstance());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
index 4db63a6..012a204 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/ObjectState.java
@@ -70,8 +70,8 @@
 
     public void recordFieldHasValue(DexEncodedField field, AbstractValue abstractValue) {
       if (!abstractValue.isUnknown()) {
-        assert !state.containsKey(field.field);
-        state.put(field.field, abstractValue);
+        assert !state.containsKey(field.getReference());
+        state.put(field.getReference(), abstractValue);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index fa42fcd..6c773a6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -113,7 +113,7 @@
       // Only check for <clinit> side effects if there is no -assumenosideeffects rule.
       if (appView.appInfo().hasLiveness()) {
         AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
-        if (appInfoWithLiveness.noSideEffects.containsKey(resolvedField.field)) {
+        if (appInfoWithLiveness.noSideEffects.containsKey(resolvedField.getReference())) {
           return false;
         }
       }
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 506e8af..202df23 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
@@ -973,7 +973,7 @@
                   || !v.hasDebugUsers()
                   || v.debugUsers().stream().anyMatch(i -> !i.isAssume())
                   || v.numberOfPhiUsers() > 0
-              : StringUtils.join(v.uniqueUsers(), System.lineSeparator());
+              : StringUtils.join(System.lineSeparator(), v.uniqueUsers());
           return true;
         };
     return verifySSATypeLattice(wrapSSAVerifierWithStackValueHandling(verifyValue));
@@ -1091,7 +1091,7 @@
       }
     }
     assert arguments.size()
-        == method().method.getArity()
+        == method().getReference().getArity()
             + ((method().accessFlags.isStatic() || ignoreReceiver) ? 0 : 1);
     return arguments;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 39f2aee..ae02e8b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -185,7 +185,7 @@
             .appInfo()
             .resolveMethodOnClass(dexItemFactory.objectMembers.finalize, clazz);
     if (finalizeResolutionResult.isSingleResolution()) {
-      DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().method;
+      DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().getReference();
       if (finalizeMethod != dexItemFactory.enumMembers.finalize
           && finalizeMethod != dexItemFactory.objectMembers.finalize) {
         return true;
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 1017f53..9b68a81 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
@@ -111,7 +111,7 @@
 
       boolean isDeadProtoExtensionField =
           appView.withGeneratedExtensionRegistryShrinker(
-              shrinker -> shrinker.isDeadProtoExtensionField(encodedField.field), false);
+              shrinker -> shrinker.isDeadProtoExtensionField(encodedField.getReference()), false);
       if (isDeadProtoExtensionField) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
index e6f7cb6..4be3d83 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
@@ -38,7 +38,7 @@
   boolean verifyAllMethodsWithCodeExists() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedMethod method : clazz.methods()) {
-        assert method.hasCode() == (nodes.get(method.method) != null);
+        assert method.hasCode() == (nodes.get(method.getReference()) != null);
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 967fee0..32be662 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -195,7 +195,7 @@
 
     private void processInvokeWithDynamicDispatch(
         Invoke.Type type, DexEncodedMethod encodedTarget, ProgramMethod context) {
-      DexMethod target = encodedTarget.method;
+      DexMethod target = encodedTarget.getReference();
       DexClass clazz = appView.definitionFor(target.holder);
       if (clazz == null) {
         assert false : "Unable to lookup holder of `" + target.toSourceString() + "`";
@@ -258,7 +258,7 @@
       }
 
       DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
-      if (encodedField == null || appView.appInfo().isPinned(encodedField.field)) {
+      if (encodedField == null || appView.appInfo().isPinned(encodedField.getReference())) {
         return;
       }
 
@@ -273,7 +273,7 @@
         addClassInitializerTarget(clazz);
       }
 
-      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(encodedField.field);
+      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(encodedField.getReference());
       if (fieldAccessInfo != null && fieldAccessInfo.hasKnownWriteContexts()) {
         if (fieldAccessInfo.getNumberOfWriteContexts() == 1) {
           fieldAccessInfo.forEachWriteContext(this::addFieldReadEdge);
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 8a63d63..682ca02 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
@@ -187,7 +187,7 @@
   public DexField resolveField(DexField field) {
     DexEncodedField resolvedField =
         appView.appInfoForDesugaring().resolveField(field).getResolvedField();
-    return resolvedField == null ? field : resolvedField.field;
+    return resolvedField == null ? field : resolvedField.getReference();
   }
 
   private void computeInitializers() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index bb8e12a..4ba4555 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -95,6 +95,9 @@
       // The non-synthetic holder is not scheduled. It will be processed once holder is scheduled.
       return;
     }
+    if (method.getDefinition().isAbstract()) {
+      return;
+    }
     terminalFutures.add(
         ThreadUtils.processAsynchronously(
             () ->
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 93c61ab..e6bc084 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
@@ -542,7 +542,7 @@
     }
 
     int originalNumberOfArguments =
-        method.method.proto.parameters.values.length
+        method.getReference().proto.parameters.values.length
             + argumentsInfo.numberOfRemovedArguments()
             + (method.isStatic() ? 0 : 1)
             - prototypeChanges.numberOfExtraParameters();
@@ -562,14 +562,14 @@
         DexType argType;
         if (argumentInfo.isRewrittenTypeInfo()) {
           RewrittenTypeInfo argumentRewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo();
-          assert method.method.proto.getParameter(usedArgumentIndex)
+          assert method.getReference().proto.getParameter(usedArgumentIndex)
               == argumentRewrittenTypeInfo.getNewType();
           // The old type is used to prevent that a changed value from reference to primitive
           // type breaks IR building. Rewriting from the old to the new type will be done in the
           // IRConverter (typically through the lensCodeRewriter).
           argType = argumentRewrittenTypeInfo.getOldType();
         } else {
-          argType = method.method.proto.getParameter(usedArgumentIndex);
+          argType = method.getReference().proto.getParameter(usedArgumentIndex);
         }
         usedArgumentIndex++;
         writeCallback.accept(register, argType);
@@ -585,7 +585,7 @@
     }
 
     for (ExtraParameter extraParameter : prototypeChanges.getExtraParameters()) {
-      DexType argType = method.method.proto.getParameter(usedArgumentIndex);
+      DexType argType = method.getReference().proto.getParameter(usedArgumentIndex);
       TypeElement type = extraParameter.getTypeElement(appView, argType);
       register += type.requiredRegisters();
       usedArgumentIndex++;
@@ -1827,7 +1827,7 @@
   public void addReturn(int value) {
     DexType returnType = method.getDefinition().returnType();
     if (returnType.isVoidType()) {
-      assert prototypeChanges.hasBeenChangedToReturnVoid(appView);
+      assert prototypeChanges.hasBeenChangedToReturnVoid(appView.dexItemFactory());
       addReturn();
     } else {
       ValueTypeConstraint returnTypeConstraint =
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 54b0a66..0530046 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
@@ -1196,7 +1196,7 @@
             + ExceptionUtils.getMainStackTrace();
     assert !method.isProcessed()
             || !appView.enableWholeProgramOptimizations()
-            || !appView.appInfo().withLiveness().isNeverReprocessMethod(method.method)
+            || !appView.appInfo().withLiveness().isNeverReprocessMethod(method.getReference())
         : "Illegal reprocessing due to -neverreprocess rule: " + context.toSourceString();
 
     if (typeChecker != null && !typeChecker.check(code)) {
@@ -1621,7 +1621,7 @@
       timing.end();
     }
 
-    if (appView.appInfo().withLiveness().isPinned(code.method().method)) {
+    if (appView.appInfo().withLiveness().isPinned(code.method().getReference())) {
       return;
     }
 
@@ -1785,7 +1785,7 @@
       return;
     }
     // Only constructors with certain signatures.
-    DexTypeList paramTypes = code.method().method.proto.parameters;
+    DexTypeList paramTypes = code.method().getReference().proto.parameters;
     if (paramTypes.size() != 3 ||
         paramTypes.values[0] != options.itemFactory.doubleType ||
         paramTypes.values[1] != options.itemFactory.doubleType ||
@@ -1967,7 +1967,7 @@
       printer.end("cfg");
     }
     if (options.extensiveLoggingFilter.size() > 0
-        && options.extensiveLoggingFilter.contains(code.method().method.toSourceString())) {
+        && options.extensiveLoggingFilter.contains(code.method().getReference().toSourceString())) {
       String current = code.toString();
       System.out.println();
       System.out.println("-----------------------------------------------------------------------");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index b6609b8..66e7ff7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -280,7 +280,7 @@
                 }
 
                 ConstInstruction constantReturnMaterializingInstruction = null;
-                if (prototypeChanges.hasBeenChangedToReturnVoid(appView)
+                if (prototypeChanges.hasBeenChangedToReturnVoid(appView.dexItemFactory())
                     && invoke.outValue() != null) {
                   constantReturnMaterializingInstruction =
                       prototypeChanges.getConstantReturn(code, invoke.getPosition());
@@ -297,7 +297,7 @@
                 }
 
                 Value newOutValue =
-                    prototypeChanges.hasBeenChangedToReturnVoid(appView)
+                    prototypeChanges.hasBeenChangedToReturnVoid(appView.dexItemFactory())
                         ? null
                         : makeOutValue(invoke, code);
 
@@ -596,7 +596,7 @@
               if (ret.isReturnVoid()) {
                 break;
               }
-              DexType returnType = code.method().method.proto.returnType;
+              DexType returnType = code.method().getReference().proto.returnType;
               Value retValue = ret.returnValue();
               DexType initialType =
                   retValue.getType().isPrimitiveType()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
index 4c073e1..d9f8625 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -152,7 +152,7 @@
                       + ", its imprecise type is: "
                       + stillImprecise.get(0).getType(),
                   code.origin,
-                  new MethodPosition(code.method().method.asMethodReference())));
+                  new MethodPosition(code.method().getReference().asMethodReference())));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 5cd32f2..2fc5c3a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1059,10 +1059,15 @@
               factory.intType);
       DexMethod method = factory.createMethod(type, proto, name);
       addProvider(
-          new MethodGenerator(
-              method,
-              BackportedMethods::IntegerMethods_parseIntSubsequenceWithRadix,
-              "parseIntSubsequenceWithRadix"));
+          appView.options().canParseNumbersWithPlusPrefix()
+              ? new MethodGenerator(
+                  method,
+                  BackportedMethods::IntegerMethods_parseIntSubsequenceWithRadix,
+                  "parseIntSubsequenceWithRadix")
+              : new MethodGenerator(
+                  method,
+                  BackportedMethods::IntegerMethods_parseIntSubsequenceWithRadixDalvik,
+                  "parseIntSubsequenceWithRadix"));
 
       // Long
       type = factory.boxedLongType;
@@ -1077,10 +1082,15 @@
               factory.intType);
       method = factory.createMethod(type, proto, name);
       addProvider(
-          new MethodGenerator(
-              method,
-              BackportedMethods::LongMethods_parseLongSubsequenceWithRadix,
-              "parseLongSubsequenceWithRadix"));
+          appView.options().canParseNumbersWithPlusPrefix()
+              ? new MethodGenerator(
+                  method,
+                  BackportedMethods::LongMethods_parseLongSubsequenceWithRadix,
+                  "parseLongSubsequenceWithRadix")
+              : new MethodGenerator(
+                  method,
+                  BackportedMethods::LongMethods_parseLongSubsequenceWithRadixDalvik,
+                  "parseLongSubsequenceWithRadix"));
 
       // long Long.parseUnsignedLong(CharSequence s, int beginIndex, int endIndex, int radix)
       name = factory.createString("parseUnsignedLong");
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
index d5acaa7..9b02ab5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassDesugaringEventConsumer.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 
 public abstract class CfClassDesugaringEventConsumer implements RecordDesugaringEventConsumer {
@@ -25,6 +26,11 @@
     public void acceptRecordClass(DexProgramClass recordClass) {
       methodProcessor.scheduleDesugaredMethodsForProcessing(recordClass.programMethods());
     }
+
+    @Override
+    public void acceptRecordMethod(ProgramMethod method) {
+      assert false;
+    }
   }
 
   // TODO(b/): Implement R8CfClassDesugaringEventConsumer
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index a95569e..a0c4c64 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -64,6 +64,11 @@
       }
 
       @Override
+      public void acceptRecordMethod(ProgramMethod method) {
+        assert false;
+      }
+
+      @Override
       public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
         assert false;
       }
@@ -119,6 +124,11 @@
     }
 
     @Override
+    public void acceptRecordMethod(ProgramMethod method) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+    }
+
+    @Override
     public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
       synchronized (pendingInvokeSpecialBridges) {
         assert !pendingInvokeSpecialBridges.containsKey(info.getNewDirectMethod().getReference());
@@ -242,6 +252,11 @@
     }
 
     @Override
+    public void acceptRecordMethod(ProgramMethod method) {
+      assert false : "TODO(b/179146128): To be implemented";
+    }
+
+    @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
       // Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
       // present in the input code, and thus nothing needs to be done.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 6200603..6ef420d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.WorkList;
@@ -125,7 +126,7 @@
         ClassInfo parent,
         ImmutableList<DexClassAndMethod> forwardedMethodTargets,
         EmulatedInterfaceInfo emulatedInterfaceInfo) {
-      return forwardedMethodTargets.isEmpty()
+      return forwardedMethodTargets.isEmpty() && emulatedInterfaceInfo.isEmpty()
           ? parent
           : new ClassInfo(parent, forwardedMethodTargets, emulatedInterfaceInfo);
     }
@@ -204,6 +205,40 @@
     }
   }
 
+  // Emulated interfaces together with the generic signatures.
+  static class EmulatedInterfaces {
+    static EmulatedInterfaces EMPTY = new EmulatedInterfaces(ImmutableSet.of());
+
+    final Set<DexType> emulatedInterfaces;
+
+    EmulatedInterfaces(DexType emulatedInterface) {
+      this.emulatedInterfaces = ImmutableSet.of(emulatedInterface);
+    }
+
+    private EmulatedInterfaces(Set<DexType> emulatedInterfaces) {
+      this.emulatedInterfaces = emulatedInterfaces;
+    }
+
+    boolean isEmpty() {
+      return emulatedInterfaces.isEmpty();
+    }
+
+    boolean contains(DexType type) {
+      return emulatedInterfaces.contains(type);
+    }
+
+    Set<DexType> getEmulatedInterfaces() {
+      return emulatedInterfaces;
+    }
+
+    EmulatedInterfaces merge(EmulatedInterfaces other) {
+      ImmutableSet.Builder<DexType> newEmulatedInterfaces = ImmutableSet.builder();
+      newEmulatedInterfaces.addAll(emulatedInterfaces);
+      newEmulatedInterfaces.addAll(other.emulatedInterfaces);
+      return new EmulatedInterfaces(newEmulatedInterfaces.build());
+    }
+  }
+
   // List of emulated interfaces and corresponding signatures which may require forwarding methods.
   // If one of the signatures has an override, then the class holding the override is required to
   // add the forwarding methods for all signatures, and introduce the corresponding emulated
@@ -213,13 +248,13 @@
   private static class EmulatedInterfaceInfo {
 
     static final EmulatedInterfaceInfo EMPTY =
-        new EmulatedInterfaceInfo(MethodSignatures.EMPTY, ImmutableSet.of());
+        new EmulatedInterfaceInfo(MethodSignatures.EMPTY, EmulatedInterfaces.EMPTY);
 
     final MethodSignatures signatures;
-    final ImmutableSet<DexType> emulatedInterfaces;
+    final EmulatedInterfaces emulatedInterfaces;
 
     private EmulatedInterfaceInfo(
-        MethodSignatures methodsToForward, ImmutableSet<DexType> emulatedInterfaces) {
+        MethodSignatures methodsToForward, EmulatedInterfaces emulatedInterfaces) {
       this.signatures = methodsToForward;
       this.emulatedInterfaces = emulatedInterfaces;
     }
@@ -231,17 +266,18 @@
       if (other.isEmpty()) {
         return this;
       }
-      ImmutableSet.Builder<DexType> newEmulatedInterfaces = ImmutableSet.builder();
-      newEmulatedInterfaces.addAll(emulatedInterfaces);
-      newEmulatedInterfaces.addAll(other.emulatedInterfaces);
       return new EmulatedInterfaceInfo(
-          signatures.merge(other.signatures), newEmulatedInterfaces.build());
+          signatures.merge(other.signatures), emulatedInterfaces.merge(other.emulatedInterfaces));
     }
 
     public boolean isEmpty() {
       assert !emulatedInterfaces.isEmpty() || signatures.isEmpty();
       return emulatedInterfaces.isEmpty();
     }
+
+    boolean contains(DexType type) {
+      return emulatedInterfaces.contains(type);
+    }
   }
 
   // Helper to keep track of the direct active subclass and nearest program subclass for reporting.
@@ -375,7 +411,7 @@
     assert needsLibraryInfo();
     MethodSignatures signatures = getDefaultMethods(iface);
     EmulatedInterfaceInfo emulatedInterfaceInfo =
-        new EmulatedInterfaceInfo(signatures, ImmutableSet.of(iface.type));
+        new EmulatedInterfaceInfo(signatures, new EmulatedInterfaces(iface.type));
     return interfaceInfo.withEmulatedInterfaceInfo(emulatedInterfaceInfo);
   }
 
@@ -384,7 +420,7 @@
     Set<Wrapper<DexMethod>> defaultMethods =
         new HashSet<>(iface.getMethodCollection().numberOfVirtualMethods());
     for (DexEncodedMethod method : iface.virtualMethods(DexEncodedMethod::isDefaultMethod)) {
-      defaultMethods.add(equivalence.wrap(method.method));
+      defaultMethods.add(equivalence.wrap(method.getReference()));
     }
     return MethodSignatures.create(defaultMethods);
   }
@@ -422,14 +458,13 @@
   // implement the interface and the emulated one for correct emulated dispatch.
   // The class signature won't include the correct type parameters for the duplicated interfaces,
   // i.e., there will be foo.A instead of foo.A<K,V>, but such parameters are unused.
-  private void duplicateEmulatedInterfaces(
-      DexClass clazz, ImmutableSet<DexType> emulatedInterfaces) {
+  private void duplicateEmulatedInterfaces(DexClass clazz, EmulatedInterfaces emulatedInterfaces) {
     if (clazz.isNotProgramClass()) {
       return;
     }
-    Set<DexType> filtered = new HashSet<>(emulatedInterfaces);
+    Set<DexType> filtered = new HashSet<>(emulatedInterfaces.getEmulatedInterfaces());
     WorkList<DexType> workList = WorkList.newIdentityWorkList();
-    for (DexType emulatedInterface : emulatedInterfaces) {
+    for (DexType emulatedInterface : emulatedInterfaces.getEmulatedInterfaces()) {
       DexClass iface = appView.definitionFor(emulatedInterface);
       if (iface != null) {
         assert iface.isLibraryClass()
@@ -447,7 +482,7 @@
       workList.addIfNotSeen(iface.getInterfaces());
     }
 
-    for (DexType emulatedInterface : emulatedInterfaces) {
+    for (DexType emulatedInterface : emulatedInterfaces.getEmulatedInterfaces()) {
       DexClass s = appView.definitionFor(emulatedInterface);
       if (s != null) {
         s = appView.definitionFor(s.superType);
@@ -458,13 +493,17 @@
       }
     }
 
+    // Collect the signatures for the emulated interfaces to add.
+    Map<DexType, GenericSignature.ClassTypeSignature> signatures = new IdentityHashMap<>();
+    collectEmulatedInterfaces(clazz, filtered, signatures);
     // We need to introduce them in deterministic order for deterministic compilation.
     ArrayList<DexType> sortedEmulatedInterfaces = new ArrayList<>(filtered);
     Collections.sort(sortedEmulatedInterfaces);
     List<GenericSignature.ClassTypeSignature> extraInterfaceSignatures = new ArrayList<>();
     for (DexType extraInterface : sortedEmulatedInterfaces) {
-      extraInterfaceSignatures.add(
-          new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(extraInterface)));
+      GenericSignature.ClassTypeSignature signature = signatures.get(extraInterface);
+      assert signature != null;
+      extraInterfaceSignatures.add(signature);
     }
     // The emulated interface might already be implemented if the input class has gone through
     // library desugaring already.
@@ -489,6 +528,75 @@
     clazz.asProgramClass().addExtraInterfaces(extraInterfaceSignatures);
   }
 
+  private void collectEmulatedInterfaces(
+      DexClass clazz,
+      Set<DexType> emulatesInterfaces,
+      Map<DexType, GenericSignature.ClassTypeSignature> extraInterfaceSignatures) {
+    // TODO(b/182329331): Only handle type arguments for Cf to Cf desugar.
+    if (appView.options().cfToCfDesugar && clazz.validInterfaceSignatures()) {
+      clazz.forEachImmediateSupertype(
+          (type, signature) -> {
+            if (emulatesInterfaces.contains(type)) {
+              extraInterfaceSignatures.put(
+                  type,
+                  new GenericSignature.ClassTypeSignature(
+                      rewriter.getEmulatedInterface(type), signature.typeArguments()));
+            }
+            collectEmulatedInterfacesWithPropagatedTypeArguments(
+                type, signature.typeArguments(), emulatesInterfaces, extraInterfaceSignatures);
+          });
+    } else {
+      clazz.forEachImmediateSupertype(
+          (type) -> {
+            if (emulatesInterfaces.contains(type)) {
+              extraInterfaceSignatures.put(
+                  type,
+                  new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(type)));
+            }
+            collectEmulatedInterfacesWithPropagatedTypeArguments(
+                type, null, emulatesInterfaces, extraInterfaceSignatures);
+          });
+    }
+  }
+
+  private void collectEmulatedInterfacesWithPropagatedTypeArguments(
+      DexType type,
+      List<GenericSignature.FieldTypeSignature> typeArguments,
+      Set<DexType> emulatesInterfaces,
+      Map<DexType, GenericSignature.ClassTypeSignature> extraInterfaceSignatures) {
+    DexClass clazz = appView.definitionFor(type);
+    if (clazz == null) {
+      return;
+    }
+    // TODO(b/182329331): Only handle type arguments for Cf to Cf desugar.
+    if (appView.options().cfToCfDesugar && clazz.validInterfaceSignatures()) {
+      assert typeArguments != null;
+      clazz.forEachImmediateSupertypeWithAppliedTypeArguments(
+          typeArguments,
+          (iface, signature) -> {
+            if (emulatesInterfaces.contains(iface)) {
+              extraInterfaceSignatures.put(
+                  iface,
+                  new GenericSignature.ClassTypeSignature(
+                      rewriter.getEmulatedInterface(iface), signature));
+            }
+            collectEmulatedInterfacesWithPropagatedTypeArguments(
+                iface, signature, emulatesInterfaces, extraInterfaceSignatures);
+          });
+    } else {
+      assert typeArguments == null;
+      clazz.forEachImmediateSupertype(
+          iface -> {
+            if (emulatesInterfaces.contains(iface)) {
+              extraInterfaceSignatures.put(
+                  iface,
+                  new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(iface)));
+            }
+            collectEmulatedInterfacesWithPropagatedTypeArguments(
+                iface, null, emulatesInterfaces, extraInterfaceSignatures);
+          });
+    }
+  }
   // If any of the signature would lead to a different behavior than the default method on the
   // emulated interface, we need to resolve the forwarding methods.
   private boolean shouldResolveForwardingMethodsForEmulatedInterfaces(
@@ -501,7 +609,7 @@
       }
       DexClass resolvedHolder = resolutionResult.asSingleResolution().getResolvedHolder();
       if (!resolvedHolder.isLibraryClass()
-          && !emulatedInterfaceInfo.emulatedInterfaces.contains(resolvedHolder.type)) {
+          && !emulatedInterfaceInfo.contains(resolvedHolder.type)) {
         return true;
       }
     }
@@ -533,27 +641,44 @@
   // the 'addForward' call-back is called with the target of the forward.
   private void resolveForwardForSignature(
       DexClass clazz, DexMethod method, Consumer<DexClassAndMethod> addForward) {
-    // Resolve the default method with base type as the symbolic holder as call sites are not known.
-    // The dispatch target is then looked up from the possible "instance" class.
-    // Doing so can cause an invalid invoke to become valid (at runtime resolution at a subtype
-    // might have failed which is hidden by the insertion of the forward method). However, not doing
-    // so could cause valid dispatches to become invalid by resolving to private overrides.
     AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
-    DexClassAndMethod virtualDispatchTarget =
-        appInfo
-            .resolveMethodOnInterface(method.holder, method)
-            .lookupVirtualDispatchTarget(clazz, appInfo);
-    if (virtualDispatchTarget == null) {
-      // If no target is found due to multiple default method targets, preserve ICCE behavior.
-      ResolutionResult resolutionFromSubclass = appInfo.resolveMethodOn(clazz, method);
-      if (resolutionFromSubclass.isIncompatibleClassChangeErrorResult()) {
-        addICCEThrowingMethod(method, clazz);
+    ResolutionResult resolutionResult = appInfo.resolveMethodOn(clazz, method);
+    if (resolutionResult.isFailedResolution()
+        || resolutionResult.asSuccessfulMemberResolutionResult().getResolvedMember().isStatic()) {
+      // When doing resolution we may find a static or private targets and bubble up the failed
+      // resolution to preserve ICCE even though the resolution actually succeeded, ie. finding a
+      // method with the same name and descriptor. For invoke-virtual and invoke-interface, the
+      // selected method will only consider instance methods.
+      BooleanBox staticTarget = new BooleanBox(true);
+      if (resolutionResult.isFailedResolution()) {
+        resolutionResult
+            .asFailedResolution()
+            .forEachFailureDependency(target -> staticTarget.and(target.isStatic()));
+      } else if (resolutionResult.isSuccessfulMemberResolutionResult()) {
+        staticTarget.set(
+            resolutionResult.asSuccessfulMemberResolutionResult().getResolvedMember().isStatic());
+      }
+      if (staticTarget.isAssigned() && staticTarget.isTrue()) {
+        resolutionResult = appInfo.resolveMethodOnInterface(method.holder, method);
+      }
+      if (resolutionResult.isFailedResolution()) {
+        if (resolutionResult.isIncompatibleClassChangeErrorResult()) {
+          addICCEThrowingMethod(method, clazz);
+          return;
+        }
+        if (resolutionResult.isNoSuchMethodErrorResult(clazz, appInfo)) {
+          addNoSuchMethodErrorThrowingMethod(method, clazz);
+          return;
+        }
+        assert resolutionResult.isIllegalAccessErrorResult(clazz, appInfo);
+        addIllegalAccessErrorThrowingMethod(method, clazz);
         return;
       }
-      assert resolutionFromSubclass.isFailedResolution()
-          || resolutionFromSubclass.getSingleTarget().isPrivateMethod();
-      return;
     }
+    assert resolutionResult.isSuccessfulMemberResolutionResult();
+    DexClassAndMethod virtualDispatchTarget =
+        resolutionResult.lookupVirtualDispatchTarget(clazz, appInfo);
+    assert virtualDispatchTarget != null;
 
     // Don't forward if the target is explicitly marked as 'dont-rewrite'
     if (dontRewrite(virtualDispatchTarget)) {
@@ -612,6 +737,18 @@
   }
 
   private void addICCEThrowingMethod(DexMethod method, DexClass clazz) {
+    addThrowingMethod(method, clazz, dexItemFactory.icceType);
+  }
+
+  private void addIllegalAccessErrorThrowingMethod(DexMethod method, DexClass clazz) {
+    addThrowingMethod(method, clazz, dexItemFactory.illegalAccessErrorType);
+  }
+
+  private void addNoSuchMethodErrorThrowingMethod(DexMethod method, DexClass clazz) {
+    addThrowingMethod(method, clazz, dexItemFactory.noSuchMethodErrorType);
+  }
+
+  private void addThrowingMethod(DexMethod method, DexClass clazz, DexType errorType) {
     if (!clazz.isProgramClass()) {
       return;
     }
@@ -625,8 +762,7 @@
             ParameterAnnotationsList.empty(),
             new SynthesizedCode(
                 callerPosition ->
-                    new ExceptionThrowingSourceCode(
-                        clazz.type, method, callerPosition, dexItemFactory.icceType)),
+                    new ExceptionThrowingSourceCode(clazz.type, method, callerPosition, errorType)),
             true);
     addSyntheticMethod(clazz.asProgramClass(), newEncodedMethod);
   }
@@ -644,7 +780,7 @@
           "Attempt to add forwarding method that conflicts with existing method.",
           null,
           clazz.getOrigin(),
-          new MethodPosition(methodOnSelf.method.asMethodReference()));
+          new MethodPosition(methodOnSelf.getReference().asMethodReference()));
     }
 
     // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
@@ -696,8 +832,8 @@
     SignaturesInfo signatures = visitLibraryClassInfo(clazz.superType);
     // The class may inherit emulated interface info from its program superclass if the latter
     // did not require to resolve the forwarding methods for emualted interfaces.
-    signatures = signatures.withEmulatedInterfaceInfo(superInfo.emulatedInterfaceInfo);
     assert superInfo.isEmpty() || signatures.isEmpty();
+    signatures = signatures.withEmulatedInterfaceInfo(superInfo.emulatedInterfaceInfo);
     for (DexType iface : clazz.interfaces.values) {
       signatures = signatures.merge(visitInterfaceInfo(iface, thisContext));
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index fd4abda..5760bd2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -267,7 +267,7 @@
 
   private static boolean hasVirtualMethodWithSignature(DexClass clazz, DexEncodedMethod method) {
     for (DexEncodedMethod existingMethod : clazz.virtualMethods()) {
-      if (existingMethod.method.equals(method.method)) {
+      if (existingMethod.getReference().equals(method.getReference())) {
         return true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
index 07277bc..838374d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
@@ -44,7 +44,7 @@
     DexMethod getSingleCandidate(DexMethod method) {
       DexMethod candidate = null;
       for (DexEncodedMethod encodedMethod : live) {
-        DexMethod current = encodedMethod.method;
+        DexMethod current = encodedMethod.getReference();
         if (current.proto == method.proto && current.name == method.name) {
           if (candidate != null) {
             return null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index f2f255a..4f05b2e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -341,10 +341,10 @@
   private ProgramMethod generateCallbackMethod(
       DexEncodedMethod originalMethod, DexProgramClass clazz) {
     DexMethod methodToInstall =
-        methodWithVivifiedTypeInSignature(originalMethod.method, clazz.type, appView);
+        methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
     CfCode cfCode =
         new APIConverterWrapperCfCodeProvider(
-                appView, originalMethod.method, null, this, clazz.isInterface())
+                appView, originalMethod.getReference(), null, this, clazz.isInterface())
             .generateCfCode();
     DexEncodedMethod newMethod =
         wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 8a6624e..4f4673a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -264,7 +264,7 @@
     if (!appView.options().encodeChecksums) {
       return DexProgramClass::invalidChecksumRequest;
     }
-    return c -> method.method.hashCode();
+    return c -> method.getReference().hashCode();
   }
 
   // Used by the ListOfBackportedMethods utility.
@@ -331,7 +331,7 @@
       }
       DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
       assert singleTarget != null;
-      retarget = getRetargetLibraryMember(singleTarget.method);
+      retarget = getRetargetLibraryMember(singleTarget.getReference());
     }
     return retarget;
   }
@@ -622,7 +622,8 @@
         // Dispatch holder.
         DexType holderType = dispatchHolderTypeFor(emulatedDispatchMethod);
         DexEncodedMethod dispatchMethod =
-            generateHolderDispatchMethod(emulatedDispatchMethod, holderType, itfMethod.method);
+            generateHolderDispatchMethod(
+                emulatedDispatchMethod, holderType, itfMethod.getReference());
         synthesizeClassWithUniqueMethod(
             builder,
             holderAccessFlags,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index ce4f61a..f5845d4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -239,7 +239,9 @@
         DexAnnotationSet.empty(),
         DexEncodedField.EMPTY_ARRAY, // No static fields.
         new DexEncodedField[] {wrapperField},
-        new DexEncodedMethod[] {synthesizeConstructor(wrapperField.field), conversionMethod},
+        new DexEncodedMethod[] {
+          synthesizeConstructor(wrapperField.getReference()), conversionMethod
+        },
         virtualMethods,
         factory.getSkipNameValidationForTesting(),
         DexProgramClass::checksumFromType);
@@ -275,21 +277,17 @@
       DexMethod methodToInstall =
           factory.createMethod(
               wrapperField.getHolderType(),
-              dexEncodedMethod.method.proto,
-              dexEncodedMethod.method.name);
+              dexEncodedMethod.getReference().proto,
+              dexEncodedMethod.getReference().name);
       CfCode cfCode;
       if (dexEncodedMethod.isFinal()) {
         invalidWrappers.add(wrapperField.getHolderType());
-        finalMethods.add(dexEncodedMethod.method);
+        finalMethods.add(dexEncodedMethod.getReference());
         continue;
       } else {
         cfCode =
             new APIConverterVivifiedWrapperCfCodeProvider(
-                    appView,
-                    methodToInstall,
-                    wrapperField.field,
-                    converter,
-                isInterface)
+                    appView, methodToInstall, wrapperField.getReference(), converter, isInterface)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -320,16 +318,20 @@
       boolean isInterface = holderClass == null || holderClass.isInterface();
       DexMethod methodToInstall =
           DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-              dexEncodedMethod.method, wrapperField.getHolderType(), appView);
+              dexEncodedMethod.getReference(), wrapperField.getHolderType(), appView);
       CfCode cfCode;
       if (dexEncodedMethod.isFinal()) {
         invalidWrappers.add(wrapperField.getHolderType());
-        finalMethods.add(dexEncodedMethod.method);
+        finalMethods.add(dexEncodedMethod.getReference());
         continue;
       } else {
         cfCode =
             new APIConverterWrapperCfCodeProvider(
-                    appView, dexEncodedMethod.method, wrapperField.field, converter, isInterface)
+                    appView,
+                    dexEncodedMethod.getReference(),
+                    wrapperField.getReference(),
+                    converter,
+                    isInterface)
                 .generateCfCode();
       }
       DexEncodedMethod newDexEncodedMethod =
@@ -398,7 +400,7 @@
           // This looks quadratic but given the size of the collections met in practice for
           // desugared libraries (Max ~15) it does not matter.
           for (DexEncodedMethod alreadyImplementedMethod : implementedMethods) {
-            if (alreadyImplementedMethod.method.match(virtualMethod.method)) {
+            if (alreadyImplementedMethod.getReference().match(virtualMethod.getReference())) {
               alreadyAdded = true;
               break;
             }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index a1c0d58..f6a653b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -198,8 +198,10 @@
       clazz.forEachMethod(
           m -> {
             if (m.isDefaultMethod()) {
-              appInfo.dexItemFactory().registerTypeNeededForDesugaring(m.method.proto.returnType);
-              for (DexType param : m.method.proto.parameters.values) {
+              appInfo
+                  .dexItemFactory()
+                  .registerTypeNeededForDesugaring(m.getReference().proto.returnType);
+              for (DexType param : m.getReference().proto.parameters.values) {
                 appInfo.dexItemFactory().registerTypeNeededForDesugaring(param);
               }
             }
@@ -216,7 +218,7 @@
       if (emulatedInterfaceClass != null) {
         for (DexEncodedMethod encodedMethod :
             emulatedInterfaceClass.methods(DexEncodedMethod::isDefaultMethod)) {
-          emulatedMethods.add(encodedMethod.method.name);
+          emulatedMethods.add(encodedMethod.getReference().name);
         }
       }
     }
@@ -1243,8 +1245,7 @@
         InterfaceProcessorNestedGraphLens.builder();
     Map<DexClass, DexProgramClass> classMapping =
         processInterfaces(builder, flavour, graphLensBuilder, synthesizedMethods::add);
-    InterfaceProcessorNestedGraphLens graphLens =
-        graphLensBuilder.build(appView.dexItemFactory(), appView.graphLens());
+    InterfaceProcessorNestedGraphLens graphLens = graphLensBuilder.build(appView);
     if (appView.enableWholeProgramOptimizations() && graphLens != null) {
       appView.setGraphLens(graphLens);
     }
@@ -1476,7 +1477,7 @@
 
     // Hide by virtual methods of this interface.
     for (DexEncodedMethod virtual : definedInterface.virtualMethods()) {
-      helper.hideMatches(virtual.method);
+      helper.hideMatches(virtual.getReference());
     }
 
     // Add all default methods of this interface.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java
index ed8077a..bb0de67 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriterFixup.java
@@ -53,8 +53,7 @@
       return null;
     }
     // Map default methods to their companion methods.
-    DexMethod mappedMethod =
-        graphLens.getExtraOriginalMethodSignatures().getRepresentativeKey(method);
+    DexMethod mappedMethod = graphLens.getExtraNewMethodSignatures().getRepresentativeValue(method);
     if (mappedMethod != null) {
       return mappedMethod;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index d20eee7..0199730 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -37,9 +37,9 @@
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodCollection;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -256,7 +256,8 @@
         implMethod.copyMetadata(virtual);
         virtual.setDefaultInterfaceMethodImplementation(implMethod);
         companionMethods.add(implMethod);
-        graphLensBuilder.recordCodeMovedToCompanionClass(method.getReference(), implMethod.method);
+        graphLensBuilder.recordCodeMovedToCompanionClass(
+            method.getReference(), implMethod.getReference());
       }
 
       // Remove bridge methods.
@@ -324,7 +325,8 @@
       newFlags.promoteToStatic();
 
       DexMethod companionMethod =
-          rewriter.privateAsMethodOfCompanionClass(oldMethod, appView.dexItemFactory());
+          InterfaceMethodRewriter.privateAsMethodOfCompanionClass(
+              oldMethod, appView.dexItemFactory());
 
       Code code = definition.getCode();
       if (code == null) {
@@ -397,7 +399,7 @@
   // also be kept (such a situation can happen if the vertical class merger merges two interfaces).
   private boolean interfaceMethodRemovalChangesApi(DexEncodedMethod method, DexClass iface) {
     if (appView.enableWholeProgramOptimizations()) {
-      if (appView.appInfo().withLiveness().isPinned(method.method)) {
+      if (appView.appInfo().withLiveness().isPinned(method.getReference())) {
         return true;
       }
     }
@@ -411,7 +413,7 @@
         if (clazz == null || !seenBefore.add(clazz.type)) {
           continue;
         }
-        if (clazz.lookupVirtualMethod(method.method) != null) {
+        if (clazz.lookupVirtualMethod(method.getReference()) != null) {
           return false;
         }
         addSuperTypes(clazz, worklist);
@@ -433,32 +435,24 @@
     if (method.accessFlags.isNative()) {
       throw new Unimplemented("Native interface methods are not yet supported.");
     }
-    return method.accessFlags.isStatic() && !rewriter.factory.isClassConstructor(method.method);
+    return method.accessFlags.isStatic()
+        && !rewriter.factory.isClassConstructor(method.getReference());
   }
 
   // Specific lens which remaps invocation types to static since all rewrites performed here
   // are to static companion methods.
   public static class InterfaceProcessorNestedGraphLens extends NestedGraphLens {
 
-    private BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
-        extraOriginalMethodSignatures;
+    private BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> extraNewMethodSignatures;
 
     public InterfaceProcessorNestedGraphLens(
-        Map<DexType, DexType> typeMap,
-        Map<DexMethod, DexMethod> methodMap,
+        AppView<?> appView,
         BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
-        BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures,
-        BidirectionalOneToOneMap<DexMethod, DexMethod> extraOriginalMethodSignatures,
-        GraphLens previousLens,
-        DexItemFactory dexItemFactory) {
-      super(
-          typeMap,
-          methodMap,
-          fieldMap,
-          originalMethodSignatures,
-          previousLens,
-          dexItemFactory);
-      this.extraOriginalMethodSignatures = extraOriginalMethodSignatures;
+        BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap,
+        Map<DexType, DexType> typeMap,
+        BidirectionalOneToOneMap<DexMethod, DexMethod> extraNewMethodSignatures) {
+      super(appView, fieldMap, methodMap, typeMap);
+      this.extraNewMethodSignatures = extraNewMethodSignatures;
     }
 
     public static InterfaceProcessorNestedGraphLens find(GraphLens lens) {
@@ -476,14 +470,14 @@
     }
 
     public void toggleMappingToExtraMethods() {
-      BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> tmp = originalMethodSignatures;
-      this.originalMethodSignatures = extraOriginalMethodSignatures;
-      this.extraOriginalMethodSignatures = tmp;
+      BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> tmp = newMethodSignatures;
+      this.newMethodSignatures = extraNewMethodSignatures;
+      this.extraNewMethodSignatures = tmp;
     }
 
     public BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
-        getExtraOriginalMethodSignatures() {
-      return extraOriginalMethodSignatures;
+        getExtraNewMethodSignatures() {
+      return extraNewMethodSignatures;
     }
 
     @Override
@@ -503,14 +497,14 @@
 
     @Override
     protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
-      return extraOriginalMethodSignatures.getRepresentativeValueOrDefault(
-          method, originalMethodSignatures.getRepresentativeValueOrDefault(method, method));
+      return extraNewMethodSignatures.getRepresentativeKeyOrDefault(
+          method, newMethodSignatures.getRepresentativeKeyOrDefault(method, method));
     }
 
     @Override
     protected DexMethod internalGetNextMethodSignature(DexMethod method) {
-      return originalMethodSignatures.getRepresentativeKeyOrDefault(
-          method, extraOriginalMethodSignatures.getRepresentativeKeyOrDefault(method, method));
+      return newMethodSignatures.getRepresentativeValueOrDefault(
+          method, extraNewMethodSignatures.getRepresentativeValueOrDefault(method, method));
     }
 
     @Override
@@ -522,33 +516,24 @@
       return new Builder();
     }
 
-    public static class Builder extends NestedGraphLens.Builder {
+    public static class Builder extends GraphLens.Builder {
 
-      private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod>
-          extraOriginalMethodSignatures = new BidirectionalOneToOneHashMap<>();
+      private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> extraNewMethodSignatures =
+          new BidirectionalOneToOneHashMap<>();
 
       public void recordCodeMovedToCompanionClass(DexMethod from, DexMethod to) {
         assert from != to;
-        originalMethodSignatures.put(from, from);
-        extraOriginalMethodSignatures.put(to, from);
+        methodMap.put(from, from);
+        extraNewMethodSignatures.put(from, to);
       }
 
       @Override
-      public InterfaceProcessorNestedGraphLens build(
-          DexItemFactory dexItemFactory, GraphLens previousLens) {
-        if (fieldMap.isEmpty()
-            && originalMethodSignatures.isEmpty()
-            && extraOriginalMethodSignatures.isEmpty()) {
+      public InterfaceProcessorNestedGraphLens build(AppView<?> appView) {
+        if (fieldMap.isEmpty() && methodMap.isEmpty() && extraNewMethodSignatures.isEmpty()) {
           return null;
         }
         return new InterfaceProcessorNestedGraphLens(
-            typeMap,
-            methodMap,
-            fieldMap,
-            originalMethodSignatures,
-            extraOriginalMethodSignatures,
-            previousLens,
-            dexItemFactory);
+            appView, fieldMap, methodMap, typeMap, extraNewMethodSignatures);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index d54264c..a264d65 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -575,7 +575,7 @@
                             true);
                     newMethod.copyMetadata(encodedMethod);
                     forcefullyMovedLambdaMethodConsumer.acceptForcefullyMovedLambdaMethod(
-                        encodedMethod.method, callTarget);
+                        encodedMethod.getReference(), callTarget);
 
                     DexEncodedMethod.setDebugInfoWithFakeThisParameter(
                         newMethod.getCode(), callTarget.getArity(), appView);
@@ -659,7 +659,7 @@
                             true);
                     newMethod.copyMetadata(encodedMethod);
                     forcefullyMovedLambdaMethodConsumer.acceptForcefullyMovedLambdaMethod(
-                        encodedMethod.method, callTarget);
+                        encodedMethod.getReference(), callTarget);
                     return newMethod;
                   });
       if (replacement != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordCfMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordCfMethods.java
new file mode 100644
index 0000000..85a7982
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordCfMethods.java
@@ -0,0 +1,454 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateRecordMethods.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+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;
+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.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+
+public final class RecordCfMethods {
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType(
+        "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;");
+    factory.createSynthesizedType("Ljava/lang/Record;");
+    factory.createSynthesizedType("Ljava/util/Arrays;");
+    factory.createSynthesizedType("[Ljava/lang/Object;");
+    factory.createSynthesizedType("[Ljava/lang/String;");
+  }
+
+  public static CfCode RecordMethods_equals(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.objectType,
+                    options.itemFactory.createProto(options.itemFactory.classType),
+                    options.itemFactory.createString("getClass")),
+                false),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.objectType,
+                    options.itemFactory.createProto(options.itemFactory.classType),
+                    options.itemFactory.createString("getClass")),
+                false),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label3),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfCheckCast(options.itemFactory.createType("Ljava/lang/Record;")),
+            label1,
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Record;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("$record$getFieldsAsObjects")),
+                false),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Record;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("$record$getFieldsAsObjects")),
+                false),
+            label2,
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.booleanType,
+                        options.itemFactory.createType("[Ljava/lang/Object;"),
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("equals")),
+                false),
+            new CfIf(If.Type.EQ, ValueType.INT, label3),
+            new CfConstNumber(1, ValueType.INT),
+            new CfGoto(label4),
+            label3,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.objectType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfConstNumber(0, ValueType.INT),
+            label4,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.objectType)
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.intType)))),
+            new CfReturn(ValueType.INT),
+            label5),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode RecordMethods_hashCode(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        1,
+        ImmutableList.of(
+            label0,
+            new CfConstNumber(31, ValueType.INT),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Record;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("$record$getFieldsAsObjects")),
+                false),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.intType,
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("hashCode")),
+                false),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT),
+            new CfLoad(ValueType.OBJECT, 0),
+            label1,
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.objectType,
+                    options.itemFactory.createProto(options.itemFactory.classType),
+                    options.itemFactory.createString("getClass")),
+                false),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.objectType,
+                    options.itemFactory.createProto(options.itemFactory.intType),
+                    options.itemFactory.createString("hashCode")),
+                false),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            label2,
+            new CfReturn(ValueType.INT),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode RecordMethods_toString(InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    CfLabel label10 = new CfLabel();
+    CfLabel label11 = new CfLabel();
+    CfLabel label12 = new CfLabel();
+    CfLabel label13 = new CfLabel();
+    CfLabel label14 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        7,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringType,
+                    options.itemFactory.createProto(options.itemFactory.booleanType),
+                    options.itemFactory.createString("isEmpty")),
+                false),
+            new CfIf(If.Type.EQ, ValueType.INT, label1),
+            new CfConstNumber(0, ValueType.INT),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/String;")),
+            new CfGoto(label2),
+            label1,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfConstString(options.itemFactory.createString(";")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/String;"),
+                        options.itemFactory.stringType),
+                    options.itemFactory.createString("split")),
+                false),
+            label2,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType)
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(
+                        FrameType.initialized(
+                            options.itemFactory.createType("[Ljava/lang/String;"))))),
+            new CfStore(ValueType.OBJECT, 3),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Record;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
+                    options.itemFactory.createString("$record$getFieldsAsObjects")),
+                false),
+            new CfStore(ValueType.OBJECT, 4),
+            label4,
+            new CfNew(options.itemFactory.stringBuilderType),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfStore(ValueType.OBJECT, 5),
+            label5,
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfConstString(options.itemFactory.createString("[")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label6,
+            new CfConstNumber(0, ValueType.INT),
+            new CfStore(ValueType.INT, 6),
+            label7,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5, 6},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initialized(options.itemFactory.stringBuilderType),
+                      FrameType.initialized(options.itemFactory.intType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.INT, 6),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfArrayLength(),
+            new CfIfCmp(If.Type.GE, ValueType.INT, label12),
+            label8,
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfLoad(ValueType.INT, 6),
+            new CfArrayLoad(MemberType.OBJECT),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfConstString(options.itemFactory.createString("=")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfLoad(ValueType.OBJECT, 4),
+            new CfLoad(ValueType.INT, 6),
+            new CfArrayLoad(MemberType.OBJECT),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.objectType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label9,
+            new CfLoad(ValueType.INT, 6),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfArrayLength(),
+            new CfConstNumber(1, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfIfCmp(If.Type.EQ, ValueType.INT, label11),
+            label10,
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfConstString(options.itemFactory.createString(", ")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label11,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5, 6},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initialized(options.itemFactory.stringBuilderType),
+                      FrameType.initialized(options.itemFactory.intType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfIinc(6, 1),
+            new CfGoto(label7),
+            label12,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;")),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.stringType),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/String;")),
+                      FrameType.initialized(options.itemFactory.createType("[Ljava/lang/Object;")),
+                      FrameType.initialized(options.itemFactory.stringBuilderType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfConstString(options.itemFactory.createString("]")),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.stringBuilderType, options.itemFactory.stringType),
+                    options.itemFactory.createString("append")),
+                false),
+            new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+            label13,
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.stringBuilderType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label14),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
index 19d9ae3..61eb249 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordDesugaringEventConsumer.java
@@ -5,8 +5,11 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 
 public interface RecordDesugaringEventConsumer {
 
   void acceptRecordClass(DexProgramClass recordClass);
+
+  void acceptRecordMethod(ProgramMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
index 67e2c90..469f119 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
@@ -4,50 +4,78 @@
 
 package com.android.tools.r8.ir.desugar;
 
-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.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
 import com.android.tools.r8.cf.code.CfTypeInstruction;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+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.DexAnnotationSet;
+import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.synthetic.CallObjectInitCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.RecordGetFieldsAsObjectsCfCodeProvider;
+import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
 import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
+import java.util.function.BiFunction;
+import org.objectweb.asm.Opcodes;
 
 public class RecordRewriter implements CfInstructionDesugaring, CfClassDesugaring {
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
+  private final DexProto recordToStringHelperProto;
+  private final DexProto recordEqualsHelperProto;
+  private final DexProto recordHashCodeHelperProto;
+
+  public static final String GET_FIELDS_AS_OBJECTS_METHOD_NAME = "$record$getFieldsAsObjects";
 
   public static RecordRewriter create(AppView<?> appView) {
     return appView.options().shouldDesugarRecords() ? new RecordRewriter(appView) : null;
   }
 
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    RecordCfMethods.registerSynthesizedCodeReferences(factory);
+    RecordGetFieldsAsObjectsCfCodeProvider.registerSynthesizedCodeReferences(factory);
+  }
+
   private RecordRewriter(AppView<?> appView) {
     this.appView = appView;
     factory = appView.dexItemFactory();
+    recordToStringHelperProto =
+        factory.createProto(
+            factory.stringType, factory.recordType, factory.stringType, factory.stringType);
+    recordEqualsHelperProto =
+        factory.createProto(factory.booleanType, factory.recordType, factory.objectType);
+    recordHashCodeHelperProto = factory.createProto(factory.intType, factory.recordType);
   }
 
   public void scan(
@@ -97,41 +125,168 @@
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
-
-    // TODO(b/179146128): This is a temporary work-around to test desugaring of records
-    // without rewriting the record invoke-custom. This should be removed when the record support
-    // is complete.
-    if (instruction.isInvokeDynamic()
-        && context.getHolder().superType == factory.recordType
-        && (context.getReference().match(factory.recordMembers.toString)
-            || context.getReference().match(factory.recordMembers.hashCode)
-            || context.getReference().match(factory.recordMembers.equals))) {
-      requiresRecordClass(eventConsumer);
-      CfInstruction constant =
-          context.getReference().match(factory.recordMembers.toString)
-              ? new CfConstNull()
-              : new CfConstNumber(0, ValueType.INT);
-      return ImmutableList.of(new CfStackInstruction(CfStackInstruction.Opcode.Pop), constant);
-    }
-
-    CfInstruction desugaredInstruction = desugarInstruction(instruction, context);
-    return desugaredInstruction == null ? null : Collections.singletonList(desugaredInstruction);
-  }
-
-  private CfInstruction desugarInstruction(CfInstruction instruction, ProgramMethod context) {
     assert !instruction.isInitClass();
-    // TODO(b/179146128): Rewrite record invoke-dynamic here.
+    if (instruction.isInvokeDynamic() && needsDesugaring(instruction.asInvokeDynamic(), context)) {
+      return desugarInvokeDynamicOnRecord(
+          instruction.asInvokeDynamic(), context, eventConsumer, methodProcessingContext);
+    }
     if (instruction.isInvoke()) {
       CfInvoke cfInvoke = instruction.asInvoke();
       DexMethod newMethod =
           rewriteMethod(cfInvoke.getMethod(), cfInvoke.isInvokeSuper(context.getHolderType()));
       if (newMethod != cfInvoke.getMethod()) {
-        return new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface());
+        return Collections.singletonList(
+            new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface()));
       }
     }
     return null;
   }
 
+  public List<CfInstruction> desugarInvokeDynamicOnRecord(
+      CfInvokeDynamic invokeDynamic,
+      ProgramMethod context,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    assert needsDesugaring(invokeDynamic, context);
+    DexCallSite callSite = invokeDynamic.getCallSite();
+    DexValueType recordValueType = callSite.bootstrapArgs.get(0).asDexValueType();
+    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
+    DexString fieldNames = valueString.getValue();
+    DexField[] fields = new DexField[callSite.bootstrapArgs.size() - 2];
+    for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
+      DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
+      fields[i - 2] = handle.value.member.asDexField();
+    }
+    DexProgramClass recordClass =
+        appView.definitionFor(recordValueType.getValue()).asProgramClass();
+    if (callSite.methodName == factory.toStringMethodName) {
+      DexString simpleName =
+          ClassNameComputationInfo.ClassNameMapping.SIMPLE_NAME.map(
+              recordValueType.getValue().toDescriptorString(), context.getHolder(), factory);
+      return desugarInvokeRecordToString(
+          recordClass, fieldNames, fields, simpleName, eventConsumer, methodProcessingContext);
+    }
+    if (callSite.methodName == factory.hashCodeMethodName) {
+      return desugarInvokeRecordHashCode(
+          recordClass, fields, eventConsumer, methodProcessingContext);
+    }
+    if (callSite.methodName == factory.equalsMethodName) {
+      return desugarInvokeRecordEquals(recordClass, fields, eventConsumer, methodProcessingContext);
+    }
+    throw new Unreachable("Invoke dynamic needs record desugaring but could not be desugared.");
+  }
+
+  private ProgramMethod synthesizeGetFieldsAsObjectsMethod(
+      DexProgramClass clazz, DexField[] fields, DexMethod method) {
+    MethodAccessFlags methodAccessFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC, false);
+    DexEncodedMethod encodedMethod =
+        new DexEncodedMethod(
+            method,
+            methodAccessFlags,
+            MethodTypeSignature.noSignature(),
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            null,
+            true);
+    encodedMethod.setCode(
+        new RecordGetFieldsAsObjectsCfCodeProvider(appView, factory.recordTagType, fields)
+            .generateCfCode(),
+        appView);
+    return new ProgramMethod(clazz, encodedMethod);
+  }
+
+  private void ensureGetFieldsAsObjects(
+      DexProgramClass clazz, DexField[] fields, RecordDesugaringEventConsumer eventConsumer) {
+    DexMethod method = getFieldsAsObjectsMethod(clazz.type);
+    synchronized (clazz.getMethodCollection()) {
+      ProgramMethod getFieldsAsObjects = clazz.lookupProgramMethod(method);
+      if (getFieldsAsObjects == null) {
+        getFieldsAsObjects = synthesizeGetFieldsAsObjectsMethod(clazz, fields, method);
+        clazz.addVirtualMethod(getFieldsAsObjects.getDefinition());
+        if (eventConsumer != null) {
+          eventConsumer.acceptRecordMethod(getFieldsAsObjects);
+        }
+      }
+    }
+  }
+
+  private DexMethod getFieldsAsObjectsMethod(DexType holder) {
+    return factory.createMethod(
+        holder, factory.createProto(factory.objectArrayType), GET_FIELDS_AS_OBJECTS_METHOD_NAME);
+  }
+
+  private ProgramMethod synthesizeRecordHelper(
+      DexProto helperProto,
+      BiFunction<InternalOptions, DexMethod, CfCode> codeGenerator,
+      MethodProcessingContext methodProcessingContext) {
+    return appView
+        .getSyntheticItems()
+        .createMethod(
+            SyntheticNaming.SyntheticKind.RECORD_HELPER,
+            methodProcessingContext.createUniqueContext(),
+            factory,
+            builder ->
+                builder
+                    .setProto(helperProto)
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setCode(methodSig -> codeGenerator.apply(appView.options(), methodSig)));
+  }
+
+  private List<CfInstruction> desugarInvokeRecordHashCode(
+      DexProgramClass recordClass,
+      DexField[] fields,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    ensureGetFieldsAsObjects(recordClass, fields, eventConsumer);
+    ProgramMethod programMethod =
+        synthesizeRecordHelper(
+            recordHashCodeHelperProto,
+            RecordCfMethods::RecordMethods_hashCode,
+            methodProcessingContext);
+    eventConsumer.acceptRecordMethod(programMethod);
+    return ImmutableList.of(
+        new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
+  }
+
+  private List<CfInstruction> desugarInvokeRecordEquals(
+      DexProgramClass recordClass,
+      DexField[] fields,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    ensureGetFieldsAsObjects(recordClass, fields, eventConsumer);
+    ProgramMethod programMethod =
+        synthesizeRecordHelper(
+            recordEqualsHelperProto,
+            RecordCfMethods::RecordMethods_equals,
+            methodProcessingContext);
+    eventConsumer.acceptRecordMethod(programMethod);
+    return ImmutableList.of(
+        new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
+  }
+
+  private List<CfInstruction> desugarInvokeRecordToString(
+      DexProgramClass recordClass,
+      DexString fieldNames,
+      DexField[] fields,
+      DexString simpleName,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      MethodProcessingContext methodProcessingContext) {
+    ensureGetFieldsAsObjects(recordClass, fields, eventConsumer);
+    ArrayList<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfConstString(simpleName));
+    instructions.add(new CfConstString(fieldNames));
+    ProgramMethod programMethod =
+        synthesizeRecordHelper(
+            recordToStringHelperProto,
+            RecordCfMethods::RecordMethods_toString,
+            methodProcessingContext);
+    eventConsumer.acceptRecordMethod(programMethod);
+    instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
+    return instructions;
+  }
+
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
     assert !instruction.isInitClass();
@@ -161,7 +316,6 @@
 
   @Override
   public boolean needsDesugaring(DexProgramClass clazz) {
-    assert clazz.isRecord() || clazz.superType != factory.recordType;
     return clazz.isRecord();
   }
 
@@ -210,14 +364,85 @@
     return rewriteMethod(method, isSuper) != method;
   }
 
+  private boolean needsDesugaring(CfInvokeDynamic invokeDynamic, ProgramMethod context) {
+    DexCallSite callSite = invokeDynamic.getCallSite();
+    // 1. Validates this is an invoke-static to ObjectMethods#bootstrap.
+    DexMethodHandle bootstrapMethod = callSite.bootstrapMethod;
+    if (!bootstrapMethod.type.isInvokeStatic()) {
+      return false;
+    }
+    if (bootstrapMethod.member != factory.objectMethodsMembers.bootstrap) {
+      return false;
+    }
+    // From there on we assume in the assertions that the invoke to the library method is
+    // well-formed. If the invoke is not well formed assertions will fail but the execution is
+    // correct.
+    if (bootstrapMethod.isInterface) {
+      assert false
+          : "Invoke-dynamic invoking non interface method ObjectMethods#bootstrap as an interface"
+              + " method.";
+      return false;
+    }
+    // 2. Validate the bootstrapArgs include the record type, the instance field names and
+    // the corresponding instance getters.
+    if (callSite.bootstrapArgs.size() < 2) {
+      assert false
+          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with less than 2 parameters.";
+      return false;
+    }
+    DexValueType recordType = callSite.bootstrapArgs.get(0).asDexValueType();
+    if (recordType == null) {
+      assert false : "Invoke-dynamic invoking method ObjectMethods#bootstrap with an invalid type.";
+      return false;
+    }
+    DexClass recordClass = appView.definitionFor(recordType.getValue());
+    if (recordClass == null || recordClass.isNotProgramClass()) {
+      return false;
+    }
+    DexValueString valueString = callSite.bootstrapArgs.get(1).asDexValueString();
+    if (valueString == null) {
+      assert false
+          : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid field names.";
+      return false;
+    }
+    DexString fieldNames = valueString.getValue();
+    assert fieldNames.toString().isEmpty()
+        || (fieldNames.toString().split(";").length == callSite.bootstrapArgs.size() - 2);
+    assert recordClass.instanceFields().size() == callSite.bootstrapArgs.size() - 2;
+    for (int i = 2; i < callSite.bootstrapArgs.size(); i++) {
+      DexValueMethodHandle handle = callSite.bootstrapArgs.get(i).asDexValueMethodHandle();
+      if (handle == null
+          || !handle.value.type.isInstanceGet()
+          || !handle.value.member.isDexField()) {
+        assert false
+            : "Invoke-dynamic invoking method ObjectMethods#bootstrap with invalid getters.";
+        return false;
+      }
+    }
+    // 3. Create the invoke-record instruction.
+    if (callSite.methodName == factory.toStringMethodName) {
+      assert callSite.methodProto == factory.createProto(factory.stringType, recordClass.getType());
+      return true;
+    }
+    if (callSite.methodName == factory.hashCodeMethodName) {
+      assert callSite.methodProto == factory.createProto(factory.intType, recordClass.getType());
+      return true;
+    }
+    if (callSite.methodName == factory.equalsMethodName) {
+      assert callSite.methodProto
+          == factory.createProto(factory.booleanType, recordClass.getType(), factory.objectType);
+      return true;
+    }
+    return false;
+  }
+
   @SuppressWarnings("ConstantConditions")
   private DexMethod rewriteMethod(DexMethod method, boolean isSuper) {
-    if (method.holder != factory.recordType || method.isInstanceInitializer(factory)) {
+    if (!(method == factory.recordMembers.equals
+        || method == factory.recordMembers.hashCode
+        || method == factory.recordMembers.toString)) {
       return method;
     }
-    assert method == factory.recordMembers.equals
-        || method == factory.recordMembers.hashCode
-        || method == factory.recordMembers.toString;
     if (isSuper) {
       // TODO(b/179146128): Support rewriting invoke-super to a Record method.
       throw new CompilationError("Rewrite invoke-super to abstract method error.");
@@ -234,22 +459,61 @@
 
   private DexProgramClass synthesizeR8Record() {
     DexItemFactory factory = appView.dexItemFactory();
+    DexClass r8RecordClass =
+        appView.appInfo().definitionForWithoutExistenceAssert(factory.recordTagType);
+    if (r8RecordClass != null && r8RecordClass.isProgramClass()) {
+      appView
+          .options()
+          .reporter
+          .error(
+              "D8/R8 is compiling a mix of desugared and non desugared input using"
+                  + " java.lang.Record, but the application reader did not import correctly "
+                  + factory.recordTagType.toString());
+    }
     DexClass recordClass =
         appView.appInfo().definitionForWithoutExistenceAssert(factory.recordType);
     if (recordClass != null && recordClass.isProgramClass()) {
       return null;
     }
-    assert recordClass == null || recordClass.isLibraryClass();
+    return synchronizedSynthesizeR8Record();
+  }
+
+  private synchronized DexProgramClass synchronizedSynthesizeR8Record() {
+    DexItemFactory factory = appView.dexItemFactory();
+    DexClass recordClass =
+        appView.appInfo().definitionForWithoutExistenceAssert(factory.recordType);
+    if (recordClass != null && recordClass.isProgramClass()) {
+      return null;
+    }
     DexEncodedMethod init = synthesizeRecordInitMethod();
-    // TODO(b/179146128): We may want to remove here the class from the library classes if present
-    //  in cf to cf.
+    DexEncodedMethod abstractGetFieldsAsObjectsMethod =
+        synthesizeAbstractGetFieldsAsObjectsMethod();
     return appView
         .getSyntheticItems()
         .createFixedClassFromType(
             SyntheticNaming.SyntheticKind.RECORD_TAG,
             factory.recordType,
             factory,
-            builder -> builder.setAbstract().setDirectMethods(Collections.singletonList(init)));
+            builder ->
+                builder
+                    .setAbstract()
+                    .setVirtualMethods(ImmutableList.of(abstractGetFieldsAsObjectsMethod))
+                    .setDirectMethods(ImmutableList.of(init)));
+  }
+
+  private DexEncodedMethod synthesizeAbstractGetFieldsAsObjectsMethod() {
+    MethodAccessFlags methodAccessFlags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT, false);
+    DexMethod fieldsAsObjectsMethod = getFieldsAsObjectsMethod(factory.recordType);
+    return new DexEncodedMethod(
+        fieldsAsObjectsMethod,
+        methodAccessFlags,
+        MethodTypeSignature.noSignature(),
+        DexAnnotationSet.empty(),
+        ParameterAnnotationsList.empty(),
+        null,
+        true);
   }
 
   private DexEncodedMethod synthesizeRecordInitMethod() {
@@ -266,7 +530,7 @@
             null,
             true);
     init.setCode(
-        new CallObjectInitCfCodeProvider(appView, factory.r8RecordType).generateCfCode(), appView);
+        new CallObjectInitCfCodeProvider(appView, factory.recordTagType).generateCfCode(), appView);
     return init;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index b7b0db0..3f7a705 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -2162,6 +2162,113 @@
         ImmutableList.of());
   }
 
+  public static CfCode IntegerMethods_parseIntSubsequenceWithRadixDalvik(
+      InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 1),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfConstNumber(2, ValueType.INT),
+            new CfIfCmp(If.Type.LT, ValueType.INT, label4),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            label1,
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charType, options.itemFactory.intType),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfConstNumber(43, ValueType.INT),
+            new CfIfCmp(If.Type.NE, ValueType.INT, label4),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfConstNumber(1, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            label2,
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charType, options.itemFactory.intType),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.boxedCharType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.intType,
+                        options.itemFactory.charType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("digit")),
+                false),
+            new CfIf(If.Type.LT, ValueType.INT, label4),
+            label3,
+            new CfIinc(1, 1),
+            label4,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charSequenceType,
+                        options.itemFactory.intType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("subSequence")),
+                true),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.intType,
+                        options.itemFactory.stringType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("parseInt")),
+                false),
+            new CfReturn(ValueType.INT),
+            label5),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode IntegerMethods_parseUnsignedInt(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -2737,6 +2844,113 @@
         ImmutableList.of());
   }
 
+  public static CfCode LongMethods_parseLongSubsequenceWithRadixDalvik(
+      InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 1),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfConstNumber(2, ValueType.INT),
+            new CfIfCmp(If.Type.LT, ValueType.INT, label4),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            label1,
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charType, options.itemFactory.intType),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfConstNumber(43, ValueType.INT),
+            new CfIfCmp(If.Type.NE, ValueType.INT, label4),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfConstNumber(1, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            label2,
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charType, options.itemFactory.intType),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.boxedCharType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.intType,
+                        options.itemFactory.charType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("digit")),
+                false),
+            new CfIf(If.Type.LT, ValueType.INT, label4),
+            label3,
+            new CfIinc(1, 1),
+            label4,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charSequenceType,
+                        options.itemFactory.intType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("subSequence")),
+                true),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Long;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.longType,
+                        options.itemFactory.stringType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("parseLong")),
+                false),
+            new CfReturn(ValueType.LONG),
+            label5),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode LongMethods_parseUnsignedLong(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
index 28bbf14..8f9421d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
@@ -13,10 +13,10 @@
   // Returns true if this method is pinned from the perspective of optimizations that attempt to
   // remove method arguments.
   public static boolean isPinned(DexEncodedMethod method, AppView<AppInfoWithLiveness> appView) {
-    return appView.appInfo().isPinned(method.method)
-        || appView.appInfo().isBootstrapMethod(method.method)
-        || appView.appInfo().isFailedResolutionTarget(method.method)
-        || appView.appInfo().isMethodTargetedByInvokeDynamic(method.method)
+    return appView.appInfo().isPinned(method.getReference())
+        || appView.appInfo().isBootstrapMethod(method.getReference())
+        || appView.appInfo().isFailedResolutionTarget(method.getReference())
+        || appView.appInfo().isMethodTargetedByInvokeDynamic(method.getReference())
         || method.accessFlags.isNative();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 5eb23bb..43be45b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -384,12 +384,13 @@
         }
       }
     }
-    assert argumentsSeen == code.method().method.getArity() + (code.method().isStatic() ? 0 : 1)
+    assert argumentsSeen
+            == code.method().getReference().getArity() + (code.method().isStatic() ? 0 : 1)
         : "args: "
             + argumentsSeen
             + " != "
             + "arity: "
-            + code.method().method.getArity()
+            + code.method().getReference().getArity()
             + ", static: "
             + code.method().isStatic();
     // After packed Argument instructions, add Assume and constant instructions.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index b176db1..3551295 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -164,7 +164,7 @@
     // Set initial values for static fields from the definitive static put instructions collected.
     finalFieldPuts.forEach(
         (field, put) -> {
-          DexType fieldType = field.field.type;
+          DexType fieldType = field.getReference().type;
           Value value = put.value().getAliasedValue();
           if (unnecessaryStaticPuts.contains(put)) {
             if (fieldType == dexItemFactory.stringType) {
@@ -253,7 +253,7 @@
                 .map(appInfoWithLiveness::resolveField)
                 .map(FieldResolutionResult::getResolvedField)
                 .filter(appInfoWithLiveness::isStaticFieldWrittenOnlyInEnclosingStaticInitializer)
-                .map(field -> field.field)
+                .map(field -> field.getReference())
                 .collect(Collectors.toSet());
 
         // Then retain only these fields that are actually no longer being written to.
@@ -264,7 +264,7 @@
             DexEncodedField encodedField =
                 appInfoWithLiveness.resolveField(field).getResolvedField();
             if (encodedField != null) {
-              candidates.remove(encodedField.field);
+              candidates.remove(encodedField.getReference());
             }
           }
         }
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 d3dc297..32ec969 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
@@ -263,7 +263,7 @@
           if (appView
               .dexItemFactory()
               .objectsMethods
-              .isRequireNonNullMethod(code.method().method)) {
+              .isRequireNonNullMethod(code.method().getReference())) {
             continue;
           }
 
@@ -524,7 +524,8 @@
     int selfRecursionFanOut = 0;
     Instruction lastSelfRecursiveCall = null;
     for (Instruction i : code.instructions()) {
-      if (i.isInvokeMethod() && i.asInvokeMethod().getInvokedMethod() == code.method().method) {
+      if (i.isInvokeMethod()
+          && i.asInvokeMethod().getInvokedMethod() == code.method().getReference()) {
         selfRecursionFanOut++;
         lastSelfRecursiveCall = i;
       }
@@ -3720,7 +3721,7 @@
     InstructionListIterator iterator = block.listIterator(code);
 
     // Attach some synthetic position to all inserted code.
-    Position position = Position.synthetic(1, method.method, null);
+    Position position = Position.synthetic(1, method.getReference(), null);
     iterator.setInsertionPosition(position);
 
     // Split arguments into their own block.
@@ -3748,7 +3749,7 @@
     Value value = addConstString(code, iterator, "INVOKE ");
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
 
-    value = addConstString(code, iterator, method.method.qualifiedName());
+    value = addConstString(code, iterator, method.getReference().qualifiedName());
     iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
 
     Value openParenthesis = addConstString(code, iterator, "(");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index fa3886e..0cc7497 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -387,7 +387,7 @@
     }
 
     // Change the invoke-virtual instruction to target the refined resolution result instead.
-    return newResolutionResult.getResolvedMethod().method;
+    return newResolutionResult.getResolvedMethod().getReference();
   }
 
   private boolean isRebindingNewClassIntoMainDex(ProgramMethod context, DexMethod reboundMethod) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 20e0515..9b738b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -30,7 +30,7 @@
    * <p>If the method has no normal exits, then null is returned.
    */
   public TypeElement computeDynamicReturnType(DexEncodedMethod method, IRCode code) {
-    assert method.method.proto.returnType.isReferenceType();
+    assert method.getReference().proto.returnType.isReferenceType();
     List<TypeElement> returnedTypes = new ArrayList<>();
     for (BasicBlock block : code.blocks) {
       JumpInstruction exitInstruction = block.exit();
@@ -43,7 +43,7 @@
   }
 
   public ClassTypeElement computeDynamicLowerBoundType(DexEncodedMethod method, IRCode code) {
-    assert method.method.proto.returnType.isReferenceType();
+    assert method.getReference().proto.returnType.isReferenceType();
     ClassTypeElement result = null;
     for (BasicBlock block : code.blocks) {
       JumpInstruction exitInstruction = block.exit();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 976e869..e00c267 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -370,6 +370,17 @@
       // This will fail at runtime.
       return ConstraintWithTarget.NEVER;
     }
+    if (!appView
+        .appInfo()
+        .getClassToFeatureSplitMap()
+        .isInBaseOrSameFeatureAs(
+            resolvedMember.getHolderType(),
+            context.asProgramMethod(),
+            appView.getSyntheticItems())) {
+      // We never inline into the base from a feature (calls should never happen) and we
+      // never inline between features, so this check should be sufficient.
+      return ConstraintWithTarget.NEVER;
+    }
     DexType resolvedHolder = graphLens.lookupType(resolvedMember.getHolderType());
     assert initialResolutionHolder != null;
     ConstraintWithTarget memberConstraintWithTarget =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
index 4f699e6..bd0376b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberPoolCollection.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import java.util.ArrayDeque;
@@ -26,6 +27,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.function.BiFunction;
 import java.util.function.Predicate;
 
 // Per-class collection of member signatures.
@@ -158,14 +160,16 @@
 
   public static class MemberPool<T> {
 
-    private Equivalence<T> equivalence;
+    private final DexClass clazz;
+    private final Equivalence<T> equivalence;
     private MemberPool<T> superType;
     private final Set<MemberPool<T>> interfaces = new HashSet<>();
     private final Set<MemberPool<T>> subTypes = new HashSet<>();
     private final Set<Wrapper<T>> memberPool = new HashSet<>();
 
-    MemberPool(Equivalence<T> equivalence) {
+    MemberPool(Equivalence<T> equivalence, DexClass clazz) {
       this.equivalence = equivalence;
+      this.clazz = clazz;
     }
 
     synchronized void linkSupertype(MemberPool<T> superType) {
@@ -193,36 +197,74 @@
     }
 
     public boolean hasSeen(Wrapper<T> member) {
-      return hasSeenAbove(member, true) || hasSeenStrictlyBelow(member);
+      return fold(member, false, true, (t, ignored) -> true);
     }
 
     public boolean hasSeenDirectly(Wrapper<T> member) {
-      return memberPool.contains(member);
+      return here(member, false, (t, ignored) -> true);
     }
 
     public boolean hasSeenStrictlyAbove(Wrapper<T> member) {
-      return hasSeenAbove(member, false);
-    }
-
-    private boolean hasSeenAbove(Wrapper<T> member, boolean inclusive) {
-      if (inclusive && hasSeenDirectly(member)) {
-        return true;
-      }
-      return (superType != null && superType.hasSeenAbove(member, true))
-          || interfaces.stream().anyMatch(itf -> itf.hasSeenAbove(member, true));
+      return above(member, false, false, true, (t, ignored) -> true);
     }
 
     public boolean hasSeenStrictlyBelow(Wrapper<T> member) {
-      return hasSeenBelow(member, false);
+      return below(member, false, true, (t, ignored) -> true);
     }
 
-    private boolean hasSeenBelow(Wrapper<T> member, boolean inclusive) {
-      if (inclusive
-          && (hasSeenDirectly(member)
-              || interfaces.stream().anyMatch(itf -> itf.hasSeenAbove(member, true)))) {
-        return true;
+    private <S> S above(
+        Wrapper<T> member,
+        boolean inclusive,
+        S value,
+        S terminator,
+        BiFunction<DexClass, S, S> accumulator) {
+      WorkList<MemberPool<T>> workList = WorkList.newIdentityWorkList(this);
+      while (workList.hasNext()) {
+        MemberPool<T> next = workList.next();
+        if (inclusive) {
+          value = next.here(member, value, accumulator);
+          if (value == terminator) {
+            return value;
+          }
+        }
+        inclusive = true;
+        if (next.superType != null) {
+          workList.addIfNotSeen(next.superType);
+        }
+        workList.addIfNotSeen(next.interfaces);
       }
-      return subTypes.stream().anyMatch(subType -> subType.hasSeenBelow(member, true));
+      return value;
+    }
+
+    private <S> S here(Wrapper<T> member, S value, BiFunction<DexClass, S, S> accumulator) {
+      if (memberPool.contains(member)) {
+        return accumulator.apply(clazz, value);
+      }
+      return value;
+    }
+
+    public <S> S below(
+        Wrapper<T> member, S value, S terminator, BiFunction<DexClass, S, S> accumulator) {
+      WorkList<MemberPool<T>> workList = WorkList.newIdentityWorkList(this.subTypes);
+      while (workList.hasNext()) {
+        MemberPool<T> next = workList.next();
+        value = next.here(member, value, accumulator);
+        if (value == terminator) {
+          return value;
+        }
+        workList.addIfNotSeen(next.interfaces);
+        workList.addIfNotSeen(next.subTypes);
+      }
+      return value;
+    }
+
+    public <S> S fold(
+        Wrapper<T> member, S initialValue, S terminator, BiFunction<DexClass, S, S> accumulator) {
+      S value = above(member, true, initialValue, terminator, accumulator);
+      if (value == terminator) {
+        return value;
+      }
+      return below(member, initialValue, terminator, accumulator);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
index 593e827..652c185 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -54,18 +54,19 @@
   Runnable computeMemberPoolForClass(DexClass clazz) {
     return () -> {
       MemberPool<DexMethod> methodPool =
-          memberPools.computeIfAbsent(clazz, k -> new MemberPool<>(equivalence));
+          memberPools.computeIfAbsent(clazz, k -> new MemberPool<>(equivalence, k));
       clazz.forEachMethod(
           encodedMethod -> {
             if (methodTester.test(encodedMethod)) {
-              methodPool.seen(equivalence.wrap(encodedMethod.method));
+              methodPool.seen(equivalence.wrap(encodedMethod.getReference()));
             }
           });
       if (clazz.superType != null) {
         DexClass superClazz = appView.definitionFor(clazz.superType);
         if (superClazz != null) {
           MemberPool<DexMethod> superPool =
-              memberPools.computeIfAbsent(superClazz, k -> new MemberPool<>(equivalence));
+              memberPools.computeIfAbsent(
+                  superClazz, k -> new MemberPool<>(equivalence, superClazz));
           superPool.linkSubtype(methodPool);
           methodPool.linkSupertype(superPool);
         }
@@ -75,7 +76,7 @@
           DexClass subClazz = appView.definitionFor(subtype);
           if (subClazz != null) {
             MemberPool<DexMethod> childPool =
-                memberPools.computeIfAbsent(subClazz, k -> new MemberPool<>(equivalence));
+                memberPools.computeIfAbsent(subClazz, k -> new MemberPool<>(equivalence, subClazz));
             methodPool.linkSubtype(childPool);
             childPool.linkInterface(methodPool);
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index f802efe..014337c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -363,14 +363,14 @@
     fieldInitializationInfos.forEachWithDeterministicOrder(
         appView,
         (field, info) -> {
-          if (!appView.appInfo().withLiveness().mayPropagateValueFor(field.field)) {
+          if (!appView.appInfo().withLiveness().mayPropagateValueFor(field.getReference())) {
             return;
           }
           if (info.isArgumentInitializationInfo()) {
             Value value =
                 invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
             Value object = invoke.getReceiver().getAliasedValue();
-            FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
+            FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
             if (field.isFinal()) {
               activeState.putFinalInstanceField(fieldAndObject, new ExistingValue(value));
             } else {
@@ -380,7 +380,7 @@
             SingleValue value = info.asSingleValue();
             if (value.isMaterializableInContext(appView.withLiveness(), method)) {
               Value object = invoke.getReceiver().getAliasedValue();
-              FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
+              FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
               if (field.isFinal()) {
                 activeState.putFinalInstanceField(fieldAndObject, new MaterializableValue(value));
               } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 6758593..d532b9b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -178,7 +178,7 @@
               });
 
       new Rewriter(code, instructionIterator, serviceLoaderLoad)
-          .perform(classLoaderInvoke, synthesizedMethod.method);
+          .perform(classLoaderInvoke, synthesizedMethod.getReference());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index 7bec65e..5536f0e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -100,7 +100,7 @@
   }
 
   private void extractSwitchMap(DexEncodedField encodedField, IRCode initializer) {
-    DexField field = encodedField.field;
+    DexField field = encodedField.getReference();
     Int2ReferenceMap<DexField> switchMap = new Int2ReferenceArrayMap<>();
 
     // Find each array-put instruction that updates an entry of the array that is stored in
@@ -162,7 +162,7 @@
 
   private boolean maybeIsSwitchMap(DexEncodedField dexEncodedField) {
     // We are looking for synthetic fields of type int[].
-    DexField field = dexEncodedField.field;
+    DexField field = dexEncodedField.getReference();
     return dexEncodedField.accessFlags.isSynthetic()
         && (field.name.startsWith(switchMapPrefix) || field.name.startsWith(kotlinSwitchMapPrefix))
         && field.type == intArrayType;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index d60ba25..df30aac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
@@ -28,11 +28,9 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.BiMap;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.HashMap;
@@ -51,21 +49,13 @@
 
   public static class UninstantiatedTypeOptimizationGraphLens extends NestedGraphLens {
 
-    private final AppView<?> appView;
     private final Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod;
 
     UninstantiatedTypeOptimizationGraphLens(
         BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap,
         Map<DexMethod, ArgumentInfoCollection> removedArgumentsInfoPerMethod,
         AppView<?> appView) {
-      super(
-          ImmutableMap.of(),
-          methodMap.getForwardMap(),
-          new EmptyBidirectionalOneToOneMap<>(),
-          methodMap.getInverseOneToOneMap(),
-          appView.graphLens(),
-          appView.dexItemFactory());
-      this.appView = appView;
+      super(appView, EMPTY_FIELD_MAP, methodMap, EMPTY_TYPE_MAP);
       this.removedArgumentsInfoPerMethod = removedArgumentsInfoPerMethod;
     }
 
@@ -78,7 +68,8 @@
         return prototypeChanges;
       }
       if (method.getReturnType().isVoidType() && !previous.getReturnType().isVoidType()) {
-        prototypeChanges = prototypeChanges.withConstantReturn(previous.getReturnType(), appView);
+        prototypeChanges =
+            prototypeChanges.withConstantReturn(previous.getReturnType(), dexItemFactory());
       }
       return prototypeChanges.withRemovedArguments(
           removedArgumentsInfoPerMethod.getOrDefault(method, ArgumentInfoCollection.empty()));
@@ -158,7 +149,7 @@
       for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
         RewrittenPrototypeDescription prototypeChanges =
             RewrittenPrototypeDescription.createForUninstantiatedTypes(
-                virtualMethod.method,
+                virtualMethod.getReference(),
                 appView,
                 getRemovedArgumentsInfo(virtualMethod, ALLOW_ARGUMENT_REMOVAL));
         if (!prototypeChanges.isEmpty()) {
@@ -186,7 +177,7 @@
     Set<Wrapper<DexMethod>> usedSignatures = new HashSet<>();
     for (DexEncodedMethod method : clazz.methods()) {
       if (!prototypeChangesPerMethod.containsKey(method)) {
-        usedSignatures.add(equivalence.wrap(method.method));
+        usedSignatures.add(equivalence.wrap(method.getReference()));
       }
     }
 
@@ -195,7 +186,7 @@
         .getMethodCollection()
         .replaceDirectMethods(
             encodedMethod -> {
-              DexMethod method = encodedMethod.method;
+              DexMethod method = encodedMethod.getReference();
               RewrittenPrototypeDescription prototypeChanges =
                   prototypeChangesPerMethod.getOrDefault(
                       encodedMethod, RewrittenPrototypeDescription.none());
@@ -230,7 +221,7 @@
         .getMethodCollection()
         .replaceVirtualMethods(
             encodedMethod -> {
-              DexMethod method = encodedMethod.method;
+              DexMethod method = encodedMethod.getReference();
               RewrittenPrototypeDescription prototypeChanges =
                   getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
               ArgumentInfoCollection removedArgumentsInfo =
@@ -262,7 +253,7 @@
         .getMethodCollection()
         .replaceVirtualMethods(
             encodedMethod -> {
-              DexMethod method = encodedMethod.method;
+              DexMethod method = encodedMethod.getReference();
               RewrittenPrototypeDescription prototypeChanges =
                   getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
               ArgumentInfoCollection removedArgumentsInfo =
@@ -299,11 +290,11 @@
   private RewrittenPrototypeDescription getPrototypeChanges(
       DexEncodedMethod encodedMethod, Strategy strategy) {
     if (ArgumentRemovalUtils.isPinned(encodedMethod, appView)
-        || appView.appInfo().isKeepConstantArgumentsMethod(encodedMethod.method)) {
+        || appView.appInfo().isKeepConstantArgumentsMethod(encodedMethod.getReference())) {
       return RewrittenPrototypeDescription.none();
     }
     return RewrittenPrototypeDescription.createForUninstantiatedTypes(
-        encodedMethod.method, appView, getRemovedArgumentsInfo(encodedMethod, strategy));
+        encodedMethod.getReference(), appView, getRemovedArgumentsInfo(encodedMethod, strategy));
   }
 
   private ArgumentInfoCollection getRemovedArgumentsInfo(
@@ -313,7 +304,7 @@
     }
 
     ArgumentInfoCollection.Builder argInfosBuilder = ArgumentInfoCollection.builder();
-    DexProto proto = encodedMethod.method.proto;
+    DexProto proto = encodedMethod.getReference().proto;
     int offset = encodedMethod.isStatic() ? 0 : 1;
     for (int i = 0; i < proto.parameters.size(); ++i) {
       DexType type = proto.parameters.values[i];
@@ -329,7 +320,7 @@
   private DexMethod getNewMethodSignature(
       DexEncodedMethod encodedMethod, RewrittenPrototypeDescription prototypeChanges) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexMethod method = encodedMethod.method;
+    DexMethod method = encodedMethod.getReference();
     DexProto newProto = prototypeChanges.rewriteProto(encodedMethod, dexItemFactory);
 
     return dexItemFactory.createMethod(method.holder, newProto, method.name);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index ec1b904..cc8f5b1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -7,14 +7,12 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ArgumentUse;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
@@ -27,10 +25,8 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Streams;
 import java.util.BitSet;
 import java.util.HashSet;
@@ -57,18 +53,10 @@
     private final Map<DexMethod, ArgumentInfoCollection> removedArguments;
 
     UnusedArgumentsGraphLens(
-        Map<DexMethod, DexMethod> methodMap,
-        BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures,
-        GraphLens previousLens,
-        DexItemFactory dexItemFactory,
+        AppView<?> appView,
+        BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap,
         Map<DexMethod, ArgumentInfoCollection> removedArguments) {
-      super(
-          ImmutableMap.of(),
-          methodMap,
-          new EmptyBidirectionalOneToOneMap<>(),
-          originalMethodSignatures,
-          previousLens,
-          dexItemFactory);
+      super(appView, EMPTY_FIELD_MAP, methodMap, EMPTY_TYPE_MAP);
       this.removedArguments = removedArguments;
     }
 
@@ -103,12 +91,7 @@
     appView.appInfo().classesWithDeterministicOrder().forEach(this::processVirtualMethods);
 
     if (!methodMapping.isEmpty()) {
-      return new UnusedArgumentsGraphLens(
-          methodMapping.getForwardMap(),
-          methodMapping.getInverseOneToOneMap(),
-          appView.graphLens(),
-          appView.dexItemFactory(),
-          removedArguments);
+      return new UnusedArgumentsGraphLens(appView, methodMapping, removedArguments);
     }
 
     return null;
@@ -133,8 +116,8 @@
       DexString newName = null;
       do {
         if (newName == null) {
-          newName = method.method.name;
-        } else if (!appView.dexItemFactory().isConstructor(method.method)) {
+          newName = method.getReference().name;
+        } else if (!appView.dexItemFactory().isConstructor(method.getReference())) {
           newName =
               appView
                   .dexItemFactory()
@@ -142,7 +125,7 @@
                       SymbolGenerationUtils.numberToIdentifier(
                           count,
                           MixedCasing.USE_MIXED_CASE,
-                          method.method.name.toSourceString().toCharArray()));
+                          method.getReference().name.toSourceString().toCharArray()));
         } else {
           // Constructors must be named `<init>`.
           return null;
@@ -156,7 +139,7 @@
 
     DexEncodedMethod removeArguments(
         DexEncodedMethod method, DexMethod newSignature, ArgumentInfoCollection unused) {
-      boolean removed = usedSignatures.remove(equivalence.wrap(method.method));
+      boolean removed = usedSignatures.remove(equivalence.wrap(method.getReference()));
       assert removed;
 
       markSignatureAsUsed(newSignature);
@@ -180,10 +163,12 @@
       DexString newName = null;
       do {
         if (newName == null) {
-          newName = method.method.name;
-        } else if (!appView.dexItemFactory().isConstructor(method.method)) {
+          newName = method.getReference().name;
+        } else if (!appView.dexItemFactory().isConstructor(method.getReference())) {
           newName =
-              appView.dexItemFactory().createString(method.method.name.toSourceString() + count);
+              appView
+                  .dexItemFactory()
+                  .createString(method.getReference().name.toSourceString() + count);
         } else {
           // Constructors must be named `<init>`.
           return null;
@@ -210,7 +195,7 @@
   private void processDirectMethods(DexProgramClass clazz) {
     UsedSignatures signatures = new UsedSignatures();
     for (DexEncodedMethod method : clazz.methods()) {
-      signatures.markSignatureAsUsed(method.method);
+      signatures.markSignatureAsUsed(method.getReference());
     }
 
     clazz
@@ -220,7 +205,7 @@
 
               // If this is a method with known resolution issues, then don't remove any unused
               // arguments.
-              if (appView.appInfo().isFailedResolutionTarget(method.method)) {
+              if (appView.appInfo().isFailedResolutionTarget(method.getReference())) {
                 return method;
               }
 
@@ -229,14 +214,14 @@
                 DexProto newProto = createProtoWithRemovedArguments(method, unused);
                 DexMethod newSignature = signatures.getNewSignature(method, newProto);
                 if (newSignature == null) {
-                  assert appView.dexItemFactory().isConstructor(method.method);
+                  assert appView.dexItemFactory().isConstructor(method.getReference());
                   return method;
                 }
                 DexEncodedMethod newMethod =
                     signatures.removeArguments(method, newSignature, unused);
                 synchronized (this) {
-                  methodMapping.put(method.method, newMethod.method);
-                  removedArguments.put(newMethod.method, unused);
+                  methodMapping.put(method.getReference(), newMethod.getReference());
+                  removedArguments.put(newMethod.getReference(), unused);
                 }
                 return newMethod;
               }
@@ -265,8 +250,8 @@
                     signatures.removeArguments(
                         method, signatures.getNewSignature(method, newProto), unused);
 
-                methodMapping.put(method.method, newMethod.method);
-                removedArguments.put(newMethod.method, unused);
+                methodMapping.put(method.getReference(), newMethod.getReference());
+                removedArguments.put(newMethod.getReference(), unused);
                 return newMethod;
               }
               return method;
@@ -280,7 +265,7 @@
   private ArgumentInfoCollection collectUnusedArguments(
       DexEncodedMethod method, MemberPool<DexMethod> methodPool) {
     if (ArgumentRemovalUtils.isPinned(method, appView)
-        || appView.appInfo().isKeepUnusedArgumentsMethod(method.method)) {
+        || appView.appInfo().isKeepUnusedArgumentsMethod(method.getReference())) {
       return null;
     }
     // Only process classfile code objects.
@@ -292,13 +277,13 @@
       // an unused argument cannot be removed unless it is unused in all of the related methods in
       // the hierarchy.
       assert methodPool != null;
-      Wrapper<DexMethod> wrapper = equivalence.wrap(method.method);
+      Wrapper<DexMethod> wrapper = equivalence.wrap(method.getReference());
       if (methodPool.hasSeenStrictlyAbove(wrapper) || methodPool.hasSeenStrictlyBelow(wrapper)) {
         return null;
       }
     }
     int offset = method.accessFlags.isStatic() ? 0 : 1;
-    int argumentCount = method.method.proto.parameters.size() + offset;
+    int argumentCount = method.getReference().proto.parameters.size() + offset;
     CollectUsedArguments collector = new CollectUsedArguments();
     if (!method.accessFlags.isStatic()) {
       // TODO(65810338): The receiver cannot be removed without transforming the method to being
@@ -313,7 +298,7 @@
         if (!used.get(argumentIndex)) {
           RemovedArgumentInfo removedArg =
               RemovedArgumentInfo.builder()
-                  .setType(method.method.proto.parameters.values[argumentIndex - offset])
+                  .setType(method.getReference().proto.parameters.values[argumentIndex - offset])
                   .build();
           argInfosBuilder.addArgumentInfo(argumentIndex, removedArg);
         }
@@ -326,7 +311,9 @@
   private DexProto createProtoWithRemovedArguments(
       DexEncodedMethod encodedMethod, ArgumentInfoCollection unused) {
     DexType[] parameters = unused.rewriteParameters(encodedMethod);
-    return appView.dexItemFactory().createProto(encodedMethod.method.proto.returnType, parameters);
+    return appView
+        .dexItemFactory()
+        .createProto(encodedMethod.getReference().proto.returnType, parameters);
   }
 
   private static class CollectUsedArguments extends ArgumentUse {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 4857999..f7b79aa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -290,8 +290,8 @@
     // Class must not define finalizer.
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     for (DexEncodedMethod method : clazz.virtualMethods()) {
-      if (method.method.name == dexItemFactory.finalizeMethodName
-          && method.method.proto == dexItemFactory.objectMembers.finalize.proto) {
+      if (method.getReference().name == dexItemFactory.finalizeMethodName
+          && method.getReference().proto == dexItemFactory.objectMembers.finalize.proto) {
         return EligibilityStatus.NOT_ELIGIBLE;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 27deb32..ce781ec 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -341,7 +341,7 @@
           eligibleEnums.add(type);
         }
       } else if (use.isReturn()) {
-        DexType returnType = code.method().method.proto.returnType;
+        DexType returnType = code.method().getReference().proto.returnType;
         if (enumUnboxingCandidatesInfo.isCandidate(returnType)) {
           eligibleEnums.add(returnType);
         }
@@ -500,7 +500,7 @@
     for (DexEncodedField staticField : enumClass.staticFields()) {
       if (factory.enumMembers.isEnumField(staticField, enumClass.type)) {
         ObjectState enumState =
-            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
+            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.getReference());
         if (enumState == null) {
           if (staticField.getOptimizationInfo().isDead()) {
             // We don't care about unused field data.
@@ -514,11 +514,11 @@
           return null;
         }
         int ordinal = optionalOrdinal.getAsInt();
-        unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
+        unboxedValues.put(staticField.getReference(), ordinalToUnboxedInt(ordinal));
         ordinalToObjectState.put(ordinal, enumState);
       } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) {
         ObjectState valuesState =
-            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
+            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.getReference());
         if (valuesState == null) {
           if (staticField.getOptimizationInfo().isDead()) {
             // We don't care about unused field data.
@@ -533,7 +533,7 @@
         assert valuesContents == null
             || valuesContents.equals(valuesState.asEnumValuesObjectState());
         valuesContents = valuesState.asEnumValuesObjectState();
-        valuesField.add(staticField.field);
+        valuesField.add(staticField.getReference());
       }
     }
 
@@ -1012,7 +1012,7 @@
         return Reason.ELIGIBLE;
       }
       // The put value has to be of the field type.
-      if (field.field.type.toBaseType(factory) != enumClass.type) {
+      if (field.getReference().type.toBaseType(factory) != enumClass.type) {
         return Reason.TYPE_MISMATCH_FIELD_PUT;
       }
       return Reason.ELIGIBLE;
@@ -1095,7 +1095,7 @@
 
     // Return is used for valueOf methods.
     if (instruction.isReturn()) {
-      DexType returnType = code.method().method.proto.returnType;
+      DexType returnType = code.method().getReference().proto.returnType;
       if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) {
         return Reason.IMPLICIT_UP_CAST_IN_RETURN;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 88845d9..0d1c0fa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -85,18 +85,18 @@
   }
 
   static boolean isEnumField(DexEncodedField staticField, DexType enumType) {
-    return staticField.field.type == enumType
+    return staticField.getReference().type == enumType
         && staticField.accessFlags.isEnum()
         && staticField.accessFlags.isFinal();
   }
 
   static boolean matchesValuesField(
       DexEncodedField staticField, DexType enumType, DexItemFactory factory) {
-    return staticField.field.type.isArrayType()
-        && staticField.field.type.toArrayElementType(factory) == enumType
+    return staticField.getReference().type.isArrayType()
+        && staticField.getReference().type.toArrayElementType(factory) == enumType
         && staticField.accessFlags.isSynthetic()
         && staticField.accessFlags.isFinal()
-        && staticField.field.name == factory.enumValuesFieldName;
+        && staticField.getReference().name == factory.enumValuesFieldName;
   }
 
   private void removeEnumsInAnnotations() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index c64e51e..1e27370 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -16,34 +16,21 @@
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
-import java.util.Set;
 
-class EnumUnboxingLens extends GraphLens.NestedGraphLens {
+class EnumUnboxingLens extends NestedGraphLens {
 
   private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
-  private final Set<DexType> unboxedEnums;
 
   EnumUnboxingLens(
-      Map<DexType, DexType> typeMap,
-      Map<DexMethod, DexMethod> methodMap,
+      AppView<?> appView,
       BidirectionalOneToOneMap<DexField, DexField> fieldMap,
-      BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures,
-      GraphLens previousLens,
-      DexItemFactory dexItemFactory,
-      Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod,
-      Set<DexType> unboxedEnums) {
-    super(
-        typeMap,
-        methodMap,
-        fieldMap,
-        originalMethodSignatures,
-        previousLens,
-        dexItemFactory);
+      BidirectionalOneToOneMap<DexMethod, DexMethod> methodMap,
+      Map<DexType, DexType> typeMap,
+      Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod) {
+    super(appView, fieldMap, methodMap, typeMap);
     this.prototypeChangesPerMethod = prototypeChangesPerMethod;
-    this.unboxedEnums = unboxedEnums;
   }
 
   @Override
@@ -59,7 +46,7 @@
   @Override
   protected Invoke.Type mapInvocationType(
       DexMethod newMethod, DexMethod originalMethod, Invoke.Type type) {
-    if (unboxedEnums.contains(originalMethod.holder)) {
+    if (typeMap.containsKey(originalMethod.getHolderType())) {
       // Methods moved from unboxed enums to the utility class are either static or statified.
       assert newMethod != originalMethod;
       return Invoke.Type.STATIC;
@@ -76,7 +63,7 @@
     protected final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
     protected final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures =
         new BidirectionalOneToOneHashMap<>();
-    protected final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures =
+    protected final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
         new BidirectionalOneToOneHashMap<>();
 
     private Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
@@ -107,7 +94,7 @@
         boolean toStatic,
         int numberOfExtraNullParameters) {
       assert from != to;
-      originalMethodSignatures.put(to, from);
+      newMethodSignatures.put(from, to);
       int offsetDiff = 0;
       int toOffset = BooleanUtils.intValue(!toStatic);
       RewrittenPrototypeDescription.ArgumentInfoCollection.Builder builder =
@@ -140,18 +127,14 @@
               .withExtraUnusedNullParameters(numberOfExtraNullParameters));
     }
 
-    public EnumUnboxingLens build(
-        DexItemFactory dexItemFactory, GraphLens previousLens, Set<DexType> unboxedEnums) {
+    public EnumUnboxingLens build(AppView<?> appView) {
       assert !typeMap.isEmpty();
       return new EnumUnboxingLens(
-          typeMap,
-          originalMethodSignatures.getInverseOneToOneMap().getForwardMap(),
+          appView,
           newFieldSignatures,
-          originalMethodSignatures,
-          previousLens,
-          dexItemFactory,
-          ImmutableMap.copyOf(prototypeChangesPerMethod),
-          ImmutableSet.copyOf(unboxedEnums));
+          newMethodSignatures,
+          typeMap,
+          ImmutableMap.copyOf(prototypeChangesPerMethod));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 1602032..4fdd8f6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -72,7 +71,7 @@
   private final DexItemFactory factory;
   private final EnumDataMap unboxedEnumsData;
   private final UnboxedEnumMemberRelocator relocator;
-  private NestedGraphLens enumUnboxingLens;
+  private EnumUnboxingLens enumUnboxingLens;
 
   private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
 
@@ -128,7 +127,7 @@
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
   }
 
-  public void setEnumUnboxingLens(NestedGraphLens enumUnboxingLens) {
+  public void setEnumUnboxingLens(EnumUnboxingLens enumUnboxingLens) {
     this.enumUnboxingLens = enumUnboxingLens;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 17d0cc7..8a43d7c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -89,7 +89,7 @@
           DexProgramClass newHolderClass = appView.definitionFor(newHolderType).asProgramClass();
           newHolderClass.addDirectMethods(movedMethods);
         });
-    return lensBuilder.build(factory, appView.graphLens(), enumsToUnbox);
+    return lensBuilder.build(appView);
   }
 
   private void clearEnumToUnboxMethod(DexEncodedMethod enumMethod) {
@@ -103,7 +103,7 @@
 
   private DexEncodedMethod fixupEncodedMethodToUtility(
       DexEncodedMethod encodedMethod, DexType newHolder) {
-    DexMethod method = encodedMethod.method;
+    DexMethod method = encodedMethod.getReference();
     DexString newMethodName =
         factory.createString(
             enumUnboxerRewriter.compatibleName(method.holder)
@@ -137,9 +137,10 @@
         method.getName().toString() + (method.isNonPrivateVirtualMethod() ? "$enumunboxing$" : "");
     DexMethod newMethod = factory.createMethod(method.getHolderType(), newProto, newMethodName);
     newMethod = ensureUniqueMethod(method, newMethod);
-    int numberOfExtraNullParameters = newMethod.getArity() - method.method.getArity();
+    int numberOfExtraNullParameters = newMethod.getArity() - method.getReference().getArity();
     boolean isStatic = method.isStatic();
-    lensBuilder.move(method.method, newMethod, isStatic, isStatic, numberOfExtraNullParameters);
+    lensBuilder.move(
+        method.getReference(), newMethod, isStatic, isStatic, numberOfExtraNullParameters);
     return method.toTypeSubstitutedMethod(
         newMethod,
         builder ->
@@ -195,7 +196,7 @@
     }
     for (int i = 0; i < fields.size(); i++) {
       DexEncodedField encodedField = fields.get(i);
-      DexField field = encodedField.field;
+      DexField field = encodedField.getReference();
       DexType newType = fixupType(field.type);
       if (newType != field.type) {
         DexField newField = factory.createField(field.holder, newType, field.name);
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 7ad0bdb..70b07a6 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
@@ -144,7 +144,7 @@
                 .appInfo()
                 .resolveMethodOnClass(factory.objectMembers.toString, enumFieldType.getClassType())
                 .getSingleTarget();
-        if (singleTarget != null && singleTarget.method != factory.enumMembers.toString) {
+        if (singleTarget != null && singleTarget.getReference() != factory.enumMembers.toString) {
           continue;
         }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index ab2b0ac..83fdf6a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -81,13 +81,13 @@
 
   private TypeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod method) {
     int argOffset = method.isStatic() ? 0 : 1;
-    int size = method.method.getArity() + argOffset;
+    int size = method.getReference().getArity() + argOffset;
     TypeElement[] staticTypes = new TypeElement[size];
     if (!method.isStatic()) {
       staticTypes[0] =
           TypeElement.fromDexType(method.getHolderType(), definitelyNotNull(), appView);
     }
-    for (int i = 0; i < method.method.getArity(); i++) {
+    for (int i = 0; i < method.getReference().getArity(); i++) {
       staticTypes[i + argOffset] =
           TypeElement.fromDexType(method.getParameter(i), maybeNull(), appView);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index aa28923..a48292a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -215,7 +215,7 @@
       IRCode code,
       OptimizationFeedback feedback,
       InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
-    assert !appView.appInfo().isPinned(method.method);
+    assert !appView.appInfo().isPinned(method.getReference());
 
     if (!method.isInstanceInitializer()) {
       return;
@@ -227,7 +227,7 @@
       return;
     }
 
-    if (appView.appInfo().mayHaveSideEffects.containsKey(method.method)) {
+    if (appView.appInfo().mayHaveSideEffects.containsKey(method.getReference())) {
       return;
     }
 
@@ -813,7 +813,7 @@
       DexEncodedMethod method,
       IRCode code) {
     if (dynamicTypeOptimization != null) {
-      DexType staticReturnTypeRaw = method.method.proto.returnType;
+      DexType staticReturnTypeRaw = method.getReference().proto.returnType;
       if (!staticReturnTypeRaw.isReferenceType()) {
         return;
       }
@@ -887,7 +887,7 @@
     if (!options.enableSideEffectAnalysis) {
       return;
     }
-    if (appView.appInfo().mayHaveSideEffects.containsKey(method.method)) {
+    if (appView.appInfo().mayHaveSideEffects.containsKey(method.getReference())) {
       return;
     }
     ProgramMethod context = code.context();
@@ -954,8 +954,8 @@
             .resolveMethodOnClass(appView.dexItemFactory().objectMembers.finalize, clazz);
     DexEncodedMethod target = resolutionResult.getSingleTarget();
     return target != null
-        && target.method != dexItemFactory.enumMembers.finalize
-        && target.method != dexItemFactory.objectMembers.finalize;
+        && target.getReference() != dexItemFactory.enumMembers.finalize
+        && target.getReference() != dexItemFactory.objectMembers.finalize;
   }
 
   private void computeReturnValueOnlyDependsOnArguments(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index df06045..5a12983 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -108,11 +108,10 @@
   public boolean noUpdatesLeft() {
     assert appInfoWithLivenessModifier.isEmpty();
     assert fieldOptimizationInfos.isEmpty()
-        : StringUtils.join(fieldOptimizationInfos.keySet(), ", ");
+        : StringUtils.join(", ", fieldOptimizationInfos.keySet());
     assert methodOptimizationInfos.isEmpty()
-        : StringUtils.join(methodOptimizationInfos.keySet(), ", ");
-    assert processed.isEmpty()
-        : StringUtils.join(processed.keySet(), ", ");
+        : StringUtils.join(", ", methodOptimizationInfos.keySet());
+    assert processed.isEmpty() : StringUtils.join(", ", processed.keySet());
     return true;
   }
 
@@ -151,9 +150,13 @@
   @Override
   public void recordFieldHasAbstractValue(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
-    assert appView.appInfo().getFieldAccessInfoCollection().contains(field.field);
-    assert !appView.appInfo().getFieldAccessInfoCollection().get(field.field).hasReflectiveAccess();
-    if (appView.appInfo().mayPropagateValueFor(field.field)) {
+    assert appView.appInfo().getFieldAccessInfoCollection().contains(field.getReference());
+    assert !appView
+        .appInfo()
+        .getFieldAccessInfoCollection()
+        .get(field.getReference())
+        .hasReflectiveAccess();
+    if (appView.appInfo().mayPropagateValueFor(field.getReference())) {
       getFieldOptimizationInfoForUpdating(field).setAbstractValue(abstractValue);
     }
   }
@@ -190,7 +193,7 @@
   @Override
   public synchronized void methodReturnsAbstractValue(
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue value) {
-    if (appView.appInfo().mayPropagateValueFor(method.method)) {
+    if (appView.appInfo().mayPropagateValueFor(method.getReference())) {
       getMethodOptimizationInfoForUpdating(method).markReturnsAbstractValue(value);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 3fb77d9..5c39d40 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -66,7 +66,7 @@
   @Override
   public void recordFieldHasAbstractValue(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
-    if (appView.appInfo().mayPropagateValueFor(field.field)) {
+    if (appView.appInfo().mayPropagateValueFor(field.getReference())) {
       field.getMutableOptimizationInfo().setAbstractValue(abstractValue);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
index 9fa412a..aca1f76 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -47,7 +47,7 @@
 
     public void recordInitializationInfo(
         DexEncodedField field, InstanceFieldInitializationInfo info) {
-      recordInitializationInfo(field.field, info);
+      recordInitializationInfo(field.getReference(), info);
     }
 
     public Builder recordInitializationInfo(DexField field, InstanceFieldInitializationInfo info) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
index 6fa0363..2081721 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -57,7 +57,8 @@
 
   @Override
   public InstanceFieldInitializationInfo get(DexEncodedField field) {
-    return infos.getOrDefault(field.field, UnknownInstanceFieldInitializationInfo.getInstance());
+    return infos.getOrDefault(
+        field.getReference(), UnknownInstanceFieldInitializationInfo.getInstance());
   }
 
   @Override
@@ -85,7 +86,7 @@
     List<String> strings = new ArrayList<>();
     infos.forEach((field, info) -> strings.add(field.toSourceString() + " -> " + info));
     return "NonTrivialInstanceFieldInitializationInfoCollection("
-        + StringUtils.join(strings, "; ")
+        + StringUtils.join("; ", strings)
         + ")";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index ee2d4b5..257cc15 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -147,7 +147,7 @@
         "not a valid inlining reason (was: "
             + reason
             + ", allowed: one of "
-            + StringUtils.join(validInliningReasons, ", ")
+            + StringUtils.join(", ", validInliningReasons)
             + ").");
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index 2de1eb0..b986800 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -94,7 +94,8 @@
     for (DexEncodedField field : finalLibraryFields) {
       if (field.isStatic()) {
         feedback.recordLibraryFieldHasAbstractValue(
-            field, abstractValueFactory.createSingleFieldValue(field.field, ObjectState.empty()));
+            field,
+            abstractValueFactory.createSingleFieldValue(field.getReference(), ObjectState.empty()));
       }
     }
   }
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 5a2f63a..7761bfa 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
@@ -128,7 +128,7 @@
               // field. The requirements for the initialization of this field will be
               // checked later.
               for (DexEncodedField field : cls.staticFields()) {
-                DexType type = field.field.type;
+                DexType type = field.getReference().type;
                 if (singletonFields.put(type, field) != null) {
                   // There is already candidate singleton field found.
                   markNotEligible(type, notEligible);
@@ -137,7 +137,7 @@
 
               // Don't allow fields with this candidate types.
               for (DexEncodedField field : cls.instanceFields()) {
-                markNotEligible(field.field.type, notEligible);
+                markNotEligible(field.getReference().type, notEligible);
               }
 
               // Don't allow methods that take a value of this type.
@@ -192,11 +192,11 @@
 
   private boolean isPinned(DexClass clazz, DexEncodedField singletonField) {
     AppInfoWithLiveness appInfo = appView.appInfo();
-    if (appInfo.isPinned(clazz.type) || appInfo.isPinned(singletonField.field)) {
+    if (appInfo.isPinned(clazz.type) || appInfo.isPinned(singletonField.getReference())) {
       return true;
     }
     for (DexEncodedMethod method : clazz.methods()) {
-      if (!method.isStatic() && appInfo.isPinned(method.method)) {
+      if (!method.isStatic() && appInfo.isPinned(method.getReference())) {
         return true;
       }
     }
@@ -589,7 +589,7 @@
 
     if (invoke.hasOutValue()
         && candidateInfo.getter.get() != null
-        && candidateInfo.getter.get().method == invoke.getInvokedMethod()) {
+        && candidateInfo.getter.get().getReference() == invoke.getInvokedMethod()) {
       candidateInfo = analyzeAllValueUsers(candidateInfo, invoke.outValue(), false);
     }
     return candidateInfo;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
index 75a78ab..c5f6ddc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
@@ -7,10 +7,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
-import com.google.common.collect.ImmutableMap;
 
 class ClassStaticizerGraphLens extends NestedGraphLens {
 
@@ -18,13 +17,7 @@
       AppView<?> appView,
       BidirectionalOneToOneMap<DexField, DexField> fieldMapping,
       BidirectionalOneToOneMap<DexMethod, DexMethod> methodMapping) {
-    super(
-        ImmutableMap.of(),
-        methodMapping.getForwardMap(),
-        fieldMapping,
-        methodMapping.getInverseOneToOneMap(),
-        appView.graphLens(),
-        appView.dexItemFactory());
+    super(appView, fieldMapping, methodMapping, EMPTY_TYPE_MAP);
   }
 
   @Override
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 27806d5..69d65c8 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
@@ -253,13 +253,15 @@
                 .filter(
                     instruction -> {
                       if (instruction.isStaticGet()
-                          && instruction.asStaticGet().getField() == info.singletonField.field) {
+                          && instruction.asStaticGet().getField()
+                              == info.singletonField.getReference()) {
                         return true;
                       }
                       DexEncodedMethod getter = info.getter.get();
                       return getter != null
                           && instruction.isInvokeStatic()
-                          && instruction.asInvokeStatic().getInvokedMethod() == getter.method;
+                          && instruction.asInvokeStatic().getInvokedMethod()
+                              == getter.getReference();
                     })
                 .collect(Collectors.toList());
         boolean fixableFieldReadsPerUsage = true;
@@ -319,10 +321,10 @@
             return false;
           },
           methodsToBeStaticized::add);
-      singletonFields.put(candidate.singletonField.field, candidate);
+      singletonFields.put(candidate.singletonField.getReference(), candidate);
       DexEncodedMethod getter = candidate.getter.get();
       if (getter != null) {
-        singletonGetters.put(getter.method, candidate);
+        singletonGetters.put(getter.getReference(), candidate);
       }
       ProgramMethodSet referencedFrom =
           materializedReferencedFromCollections.getOrDefault(candidate, ProgramMethodSet.empty());
@@ -447,7 +449,8 @@
         if (newInstance.outValue().hasAnyUsers()) {
           TypeElement type = TypeElement.fromDexType(newInstance.clazz, maybeNull(), appView);
           newInstance.replace(
-              new StaticGet(code.createValue(type), candidateInfo.singletonField.field), code);
+              new StaticGet(code.createValue(type), candidateInfo.singletonField.getReference()),
+              code);
         } else {
           newInstance.removeOrReplaceByDebugLocalRead(code);
         }
@@ -753,11 +756,11 @@
       for (DexEncodedMethod method : candidateClass.methods()) {
         if (method.isStatic()) {
           newDirectMethods.add(method);
-        } else if (!factory().isConstructor(method.method)) {
+        } else if (!factory().isConstructor(method.getReference())) {
           DexEncodedMethod staticizedMethod = method.toStaticMethodWithoutThis();
           newDirectMethods.add(staticizedMethod);
           staticizedMethods.createAndAdd(candidateClass, staticizedMethod);
-          methodMapping.put(method.method, staticizedMethod.method);
+          methodMapping.put(method.getReference(), staticizedMethod.getReference());
         }
       }
       candidateClass.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
@@ -786,8 +789,9 @@
   private boolean classMembersConflict(DexClass a, DexClass b) {
     assert Streams.stream(a.methods()).allMatch(DexEncodedMethod::isStatic);
     assert a.instanceFields().size() == 0;
-    return a.staticFields().stream().anyMatch(fld -> b.lookupField(fld.field) != null)
-        || Streams.stream(a.methods()).anyMatch(method -> b.lookupMethod(method.method) != null);
+    return a.staticFields().stream().anyMatch(fld -> b.lookupField(fld.getReference()) != null)
+        || Streams.stream(a.methods())
+            .anyMatch(method -> b.lookupMethod(method.getReference()) != null);
   }
 
   private boolean hasMembersNotStaticized(
@@ -818,10 +822,10 @@
     List<DexEncodedField> oldFields = hostClass.staticFields();
     for (int i = 0; i < oldFields.size(); i++) {
       DexEncodedField field = oldFields.get(i);
-      DexField newField = mapCandidateField(field.field, candidateClass.type, hostType);
-      if (newField != field.field) {
+      DexField newField = mapCandidateField(field.getReference(), candidateClass.type, hostType);
+      if (newField != field.getReference()) {
         newFields[i] = field.toTypeSubstitutedField(newField);
-        fieldMapping.put(field.field, newField);
+        fieldMapping.put(field.getReference(), newField);
       } else {
         newFields[i] = field;
       }
@@ -830,10 +834,10 @@
       List<DexEncodedField> extraFields = candidateClass.staticFields();
       for (int i = 0; i < extraFields.size(); i++) {
         DexEncodedField field = extraFields.get(i);
-        DexField newField = mapCandidateField(field.field, candidateClass.type, hostType);
-        if (newField != field.field) {
+        DexField newField = mapCandidateField(field.getReference(), candidateClass.type, hostType);
+        if (newField != field.getReference()) {
           newFields[numOfHostStaticFields + i] = field.toTypeSubstitutedField(newField);
-          fieldMapping.put(field.field, newField);
+          fieldMapping.put(field.getReference(), newField);
         } else {
           newFields[numOfHostStaticFields + i] = field;
         }
@@ -852,7 +856,8 @@
     for (DexEncodedMethod method : extraMethods) {
       DexEncodedMethod newMethod =
           method.toTypeSubstitutedMethod(
-              factory().createMethod(hostType, method.method.proto, method.method.name));
+              factory()
+                  .createMethod(hostType, method.getReference().proto, method.getReference().name));
       newMethods.add(newMethod);
       // If the old method from the candidate class has been staticized,
       if (staticizedMethods.remove(method)) {
@@ -860,11 +865,11 @@
         // has just been migrated to the host class.
         staticizedMethods.createAndAdd(hostClass, newMethod);
       }
-      DexMethod originalMethod = methodMapping.getRepresentativeKey(method.method);
+      DexMethod originalMethod = methodMapping.getRepresentativeKey(method.getReference());
       if (originalMethod == null) {
-        methodMapping.put(method.method, newMethod.method);
+        methodMapping.put(method.getReference(), newMethod.getReference());
       } else {
-        methodMapping.put(originalMethod, newMethod.method);
+        methodMapping.put(originalMethod, newMethod.getReference());
       }
     }
     hostClass.addDirectMethods(newMethods);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 5fdaf51..ca49a7d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -540,7 +540,7 @@
       if (contents == null || contents.isEmpty()) {
         return;
       }
-      String result = StringUtils.join(contents, "");
+      String result = StringUtils.join("", contents);
       Integer size = Integer.valueOf(contents.size());
       Integer length = Integer.valueOf(result.length());
       if (isPartial) {
@@ -712,7 +712,7 @@
           builder, optimizationConfiguration)) {
         return null;
       }
-      String result = StringUtils.join(contents, "");
+      String result = StringUtils.join("", contents);
       int estimate = estimateSizeReduction(contents);
       return estimate > result.length() ? result : null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 012bd0e..179dffd 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -609,7 +609,7 @@
     if (intervals == null) {
       throw new CompilationError(
           "Unexpected attempt to get register for a value without a register in method `"
-              + code.method().method.toSourceString()
+              + code.method().getReference().toSourceString()
               + "`.",
           code.origin);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordGetFieldsAsObjectsCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordGetFieldsAsObjectsCfCodeProvider.java
new file mode 100644
index 0000000..4f534da
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordGetFieldsAsObjectsCfCodeProvider.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.synthetic;
+
+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;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Generates a method which answers all field values as an array of objects. If the field value is a
+ * primitive type, it uses the primitive wrapper to wrap it.
+ *
+ * <p>The fields in parameters are in the order where they should be in the array generated by the
+ * method, which is not necessarily the class instanceFields order.
+ *
+ * <p>Example: <code>record Person{ int age; String name;}</code>
+ *
+ * <p><code>Object[] getFieldsAsObjects() {
+ * Object[] fields = new Object[2];
+ * fields[0] = name;
+ * fields[1] = Integer.valueOf(age);
+ * return fields;</code>
+ */
+public class RecordGetFieldsAsObjectsCfCodeProvider extends SyntheticCfCodeProvider {
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("[Ljava/lang/Object;");
+    factory.primitiveToBoxed.forEach(
+        (primitiveType, boxedType) -> {
+          factory.createSynthesizedType(primitiveType.toDescriptorString());
+          factory.createSynthesizedType(boxedType.toDescriptorString());
+        });
+  }
+
+  private final DexField[] fields;
+
+  public RecordGetFieldsAsObjectsCfCodeProvider(
+      AppView<?> appView, DexType holder, DexField[] fields) {
+    super(appView, holder);
+    this.fields = fields;
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    // Stack layout:
+    // 0 : receiver (the record instance)
+    // 1 : the array to return
+    // 2+: spills
+    DexItemFactory factory = appView.dexItemFactory();
+    List<CfInstruction> instructions = new ArrayList<>();
+    // Object[] fields = new Object[*length*];
+    instructions.add(new CfConstNumber(fields.length, ValueType.INT));
+    instructions.add(new CfNewArray(factory.objectArrayType));
+    instructions.add(new CfStore(ValueType.OBJECT, 1));
+    // fields[*i*] = this.*field* || *PrimitiveWrapper*.valueOf(this.*field*);
+    for (int i = 0; i < fields.length; i++) {
+      DexField field = fields[i];
+      instructions.add(new CfLoad(ValueType.OBJECT, 1));
+      instructions.add(new CfConstNumber(i, ValueType.INT));
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, field, field));
+      if (field.type.isPrimitiveType()) {
+        factory.primitiveToBoxed.forEach(
+            (primitiveType, boxedType) -> {
+              if (primitiveType == field.type) {
+                instructions.add(
+                    new CfInvoke(
+                        Opcodes.INVOKESTATIC,
+                        factory.createMethod(
+                            boxedType,
+                            factory.createProto(boxedType, primitiveType),
+                            factory.valueOfMethodName),
+                        false));
+              }
+            });
+      }
+      instructions.add(new CfArrayStore(MemberType.OBJECT));
+    }
+    // return fields;
+    instructions.add(new CfLoad(ValueType.OBJECT, 1));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
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 1587e8a..5ade4de 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -218,9 +218,9 @@
     if (clazz.isRecord()) {
       // TODO(b/169645628): Strip record components if not kept.
       for (DexEncodedField instanceField : clazz.instanceFields()) {
-        String componentName = namingLens.lookupName(instanceField.field).toString();
+        String componentName = namingLens.lookupName(instanceField.getReference()).toString();
         String componentDescriptor =
-            namingLens.lookupDescriptor(instanceField.field.type).toString();
+            namingLens.lookupDescriptor(instanceField.getReference().type).toString();
         String componentSignature =
             instanceField.getGenericSignature().toRenamedString(namingLens, isTypeMissing);
         writer.visitRecordComponent(componentName, componentDescriptor, componentSignature);
@@ -239,7 +239,9 @@
     }
     if (options.desugarSpecificOptions().sortMethodsOnCfOutput) {
       SortedSet<ProgramMethod> programMethodSortedSet =
-          Sets.newTreeSet((a, b) -> a.getDefinition().method.compareTo(b.getDefinition().method));
+          Sets.newTreeSet(
+              (a, b) ->
+                  a.getDefinition().getReference().compareTo(b.getDefinition().getReference()));
       clazz.forEachProgramMethod(programMethodSortedSet::add);
       programMethodSortedSet.forEach(
           method -> writeMethod(method, version, rewriter, writer, defaults));
@@ -268,7 +270,7 @@
       // In this case bridges have been introduced for the Cf back-end,
       // which do not have class file version.
       assert options.isDesugaredLibraryCompilation() || options.cfToCfDesugar
-          : "Expected class file version for " + method.method.toSourceString();
+          : "Expected class file version for " + method.getReference().toSourceString();
       assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(
           options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION));
       // Any desugaring rewrites which cannot meet the default class file version after
@@ -355,8 +357,8 @@
     if (field.isDeprecated()) {
       access = AsmUtils.withDeprecated(access);
     }
-    String name = namingLens.lookupName(field.field).toString();
-    String desc = namingLens.lookupDescriptor(field.field.type).toString();
+    String name = namingLens.lookupName(field.getReference()).toString();
+    String desc = namingLens.lookupDescriptor(field.getReference().type).toString();
     String signature = field.getGenericSignature().toRenamedString(namingLens, isTypeMissing);
     Object value = getStaticValue(field);
     FieldVisitor visitor = writer.visitField(access, name, desc, signature, value);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index d9baf1f..f10902f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -93,11 +93,11 @@
       Consumer<DexEncodedMethod> keepByteCode) {
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
     for (DexEncodedField field : hostClass.fields()) {
-      fieldMap.put(toJvmFieldSignature(field.field).asString(), field);
+      fieldMap.put(toJvmFieldSignature(field.getReference()).asString(), field);
     }
     Map<String, DexEncodedMethod> methodMap = new HashMap<>();
     for (DexEncodedMethod method : hostClass.methods()) {
-      methodMap.put(toJvmMethodSignature(method.method).asString(), method);
+      methodMap.put(toJvmMethodSignature(method.getReference()).asString(), method);
     }
     ImmutableList.Builder<KotlinConstructorInfo> notBackedConstructors = ImmutableList.builder();
     for (KmConstructor kmConstructor : kmClass.getConstructors()) {
@@ -184,7 +184,7 @@
       return;
     }
     for (DexEncodedField field : hostClass.fields()) {
-      if (field.field.name.toString().equals(companionObjectName)) {
+      if (field.getReference().name.toString().equals(companionObjectName)) {
         field.setKotlinMemberInfo(new KotlinCompanionInfo());
         return;
       }
@@ -221,7 +221,10 @@
     // Find a companion object.
     for (DexEncodedField field : clazz.fields()) {
       if (field.getKotlinMemberInfo().isCompanion()) {
-        field.getKotlinMemberInfo().asCompanion().rewrite(kmClass, field.field, namingLens);
+        field
+            .getKotlinMemberInfo()
+            .asCompanion()
+            .rewrite(kmClass, field.getReference(), namingLens);
       }
     }
     // Take all not backed constructors because we will never find them in definitions.
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index 79b4173..7447bbd 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -184,7 +184,7 @@
       }
       KotlinPropertyGroup kotlinPropertyGroup =
           properties.computeIfAbsent(kotlinPropertyInfo, ignored -> new KotlinPropertyGroup());
-      if (method.method.proto.returnType == appView.dexItemFactory().voidType) {
+      if (method.getReference().proto.returnType == appView.dexItemFactory().voidType) {
         // This is a setter.
         kotlinPropertyGroup.setSetter(method);
       } else {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index f5ce062..008177e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -114,8 +114,8 @@
     // TODO(b/154348683): Check method for flags to pass in.
     String finalName = this.name;
     if (method != null) {
-      String methodName = method.method.name.toString();
-      String rewrittenName = namingLens.lookupName(method.method).toString();
+      String methodName = method.getReference().name.toString();
+      String rewrittenName = namingLens.lookupName(method.getReference()).toString();
       if (!methodName.equals(rewrittenName)) {
         finalName = rewrittenName;
       }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
index 974e4a8..2606807 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
@@ -40,8 +40,8 @@
       DexEncodedField field, AppView<?> appView, NamingLens namingLens) {
     String finalName = name;
     if (field != null) {
-      String fieldName = field.field.name.toString();
-      String rewrittenName = namingLens.lookupName(field.field).toString();
+      String fieldName = field.getReference().name.toString();
+      String rewrittenName = namingLens.lookupName(field.getReference()).toString();
       if (!fieldName.equals(rewrittenName)) {
         finalName = rewrittenName;
       }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
index 2469a54..b0314be 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
@@ -79,8 +79,8 @@
     assert returnType != null;
     String finalName = name;
     if (method != null) {
-      String methodName = method.method.name.toString();
-      String rewrittenName = namingLens.lookupName(method.method).toString();
+      String methodName = method.getReference().name.toString();
+      String rewrittenName = namingLens.lookupName(method.getReference()).toString();
       if (!methodName.equals(rewrittenName)) {
         finalName = rewrittenName;
       }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
index cc215d5..01a7c2a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -40,7 +40,7 @@
     JvmMethodSignature signature = JvmExtensionsKt.getSignature(lambda.function);
     if (signature != null) {
       for (DexEncodedMethod method : clazz.methods()) {
-        if (toJvmMethodSignature(method.method).asString().equals(signature.asString())) {
+        if (toJvmMethodSignature(method.getReference()).asString().equals(signature.asString())) {
           method.setKotlinMemberInfo(kotlinFunctionInfo);
           return new KotlinLambdaInfo(kotlinFunctionInfo, true);
         }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 1e3273c..4a5904e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -182,7 +182,10 @@
     if (kotlinMetadata == null || kotlinMetadata.isNotProgramClass()) {
       return true;
     }
-    return kotlinMetadata.methods(method -> method.method.name == fieldName).iterator().hasNext();
+    return kotlinMetadata
+        .methods(method -> method.getReference().name == fieldName)
+        .iterator()
+        .hasNext();
   }
 
   private DexAnnotation createKotlinMetadataAnnotation(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index ca1d394..fbfb3bb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -110,7 +110,7 @@
       KotlinClassMetadata.MultiFileClassFacade kMetadata, String indent) {
     return indent
         + "MetaData.MultiFileClassFacade("
-        + StringUtils.join(kMetadata.getPartClassNames(), ", ")
+        + StringUtils.join(", ", kMetadata.getPartClassNames())
         + ")";
   }
 
@@ -327,16 +327,16 @@
         });
     String companionObject = kmClass.getCompanionObject();
     appendKeyValue(
-        indent, "enumEntries", sb, "[" + StringUtils.join(kmClass.getEnumEntries(), ",") + "]");
+        indent, "enumEntries", sb, "[" + StringUtils.join(",", kmClass.getEnumEntries()) + "]");
     appendKeyValue(
         indent, "companionObject", sb, companionObject == null ? "null" : companionObject);
     appendKeyValue(
         indent,
         "sealedSubclasses",
         sb,
-        "[" + StringUtils.join(kmClass.getSealedSubclasses(), ",") + "]");
+        "[" + StringUtils.join(",", kmClass.getSealedSubclasses()) + "]");
     appendKeyValue(
-        indent, "nestedClasses", sb, "[" + StringUtils.join(kmClass.getNestedClasses(), ",") + "]");
+        indent, "nestedClasses", sb, "[" + StringUtils.join(",", kmClass.getNestedClasses()) + "]");
     appendKeyValue(
         indent,
         "anonymousObjectOriginName",
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index 1ac03db..41ff17d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -47,11 +47,11 @@
       Consumer<DexEncodedMethod> keepByteCode) {
     Map<String, DexEncodedField> fieldMap = new HashMap<>();
     for (DexEncodedField field : clazz.fields()) {
-      fieldMap.put(toJvmFieldSignature(field.field).asString(), field);
+      fieldMap.put(toJvmFieldSignature(field.getReference()).asString(), field);
     }
     Map<String, DexEncodedMethod> methodMap = new HashMap<>();
     for (DexEncodedMethod method : clazz.methods()) {
-      methodMap.put(toJvmMethodSignature(method.method).asString(), method);
+      methodMap.put(toJvmMethodSignature(method.getReference()).asString(), method);
     }
     return new KotlinPackageInfo(
         JvmExtensionsKt.getModuleName(kmPackage),
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 1b91b62..9ab6c15 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -152,11 +152,11 @@
   }
 
   private void renameDanglingTypesInField(DexEncodedField field) {
-    renameDanglingType(field.field.type);
+    renameDanglingType(field.getReference().type);
   }
 
   private void renameDanglingTypesInMethod(DexEncodedMethod method) {
-    DexProto proto = method.method.proto;
+    DexProto proto = method.getReference().proto;
     renameDanglingType(proto.returnType);
     for (DexType type : proto.parameters.values) {
       renameDanglingType(type);
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 55ed725..bec8073 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -106,10 +106,10 @@
                     reservedNames = getOrCreateReservedFieldNamingState(clazz.type);
                   }
                   reservedNames.markReservedDirectly(
-                      reservedName, field.field.name, field.field.type);
+                      reservedName, field.getReference().name, field.getReference().type);
                   // TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
-                  if (reservedName != field.field.name) {
-                    renaming.put(field.field, reservedName);
+                  if (reservedName != field.getReference().name) {
+                    renaming.put(field.getReference(), reservedName);
                   }
                 }
               }
@@ -135,8 +135,8 @@
             clazz -> {
               for (DexEncodedField field : clazz.fields()) {
                 DexString reservedName = strategy.getReservedName(field, clazz);
-                if (reservedName != null && reservedName != field.field.name) {
-                  renaming.put(field.field, reservedName);
+                if (reservedName != null && reservedName != field.getReference().name) {
+                  renaming.put(field.getReference(), reservedName);
                 }
               }
             });
@@ -270,8 +270,10 @@
       return;
     }
     DexEncodedField definition = appView.appInfo().resolveFieldOn(holder, field).getResolvedField();
-    if (definition != null && definition.field != field && renaming.containsKey(definition.field)) {
-      renaming.put(field, renaming.get(definition.field));
+    if (definition != null
+        && definition.getReference() != field
+        && renaming.containsKey(definition.getReference())) {
+      renaming.put(field, renaming.get(definition.getReference()));
     }
   }
 
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 2d9a19d..e0de1d5 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -74,7 +74,7 @@
 
   private void decoupleIdentifierNameStringInStaticField(DexEncodedField encodedField) {
     assert encodedField.accessFlags.isStatic();
-    if (!identifierNameStrings.containsKey(encodedField.field)) {
+    if (!identifierNameStrings.containsKey(encodedField.getReference())) {
       return;
     }
     DexValueString staticValue = encodedField.getStaticValue().asDexValueString();
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 01157b9..3e4078b 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -378,9 +378,9 @@
 
   private static DexField inferFieldInHolder(DexClass holder, String name, DexType fieldType) {
     for (DexEncodedField encodedField : holder.fields()) {
-      if (encodedField.field.name.toString().equals(name)
-          && (fieldType == null || encodedField.field.type == fieldType)) {
-        return encodedField.field;
+      if (encodedField.getReference().name.toString().equals(name)
+          && (fieldType == null || encodedField.getReference().type == fieldType)) {
+        return encodedField.getReference();
       }
     }
     return null;
@@ -388,8 +388,8 @@
 
   private static DexMethod inferMethodNameInHolder(DexClass holder, String name) {
     for (DexEncodedMethod encodedMethod : holder.methods()) {
-      if (encodedMethod.method.name.toString().equals(name)) {
-        return encodedMethod.method;
+      if (encodedMethod.getReference().name.toString().equals(name)) {
+        return encodedMethod.getReference();
       }
     }
     return null;
@@ -399,9 +399,9 @@
       DexClass holder, String name, DexTypeList arguments) {
     assert arguments != null;
     for (DexEncodedMethod encodedMethod : holder.methods()) {
-      if (encodedMethod.method.name.toString().equals(name)
-          && encodedMethod.method.proto.parameters.equals(arguments)) {
-        return encodedMethod.method;
+      if (encodedMethod.getReference().name.toString().equals(name)
+          && encodedMethod.getReference().proto.parameters.equals(arguments)) {
+        return encodedMethod.getReference();
       }
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 0f5924d..3ba2467 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -483,7 +483,8 @@
                   boolean differentName = implementedMethod.getName() != virtualMethod.getName();
                   if (differentName
                       && MethodJavaSignatureEquivalence.getEquivalenceIgnoreName()
-                          .equivalent(implementedMethod.method, virtualMethod.method)) {
+                          .equivalent(
+                              implementedMethod.getReference(), virtualMethod.getReference())) {
                     InterfaceMethodGroupState interfaceMethodGroupState =
                         globalStateMap.computeIfAbsent(
                             definitionEquivalence.wrap(implementedMethod),
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index eb4e9ca..a51edde 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -96,7 +96,7 @@
     // If the method does not have a direct renaming, return the resolutions mapping.
     ResolutionResult resolutionResult = appView.appInfo().unsafeResolveMethodDueToDexFormat(method);
     if (resolutionResult.isSingleResolution()) {
-      return renaming.getOrDefault(resolutionResult.getSingleTarget().method, method.name);
+      return renaming.getOrDefault(resolutionResult.getSingleTarget().getReference(), method.name);
     }
     // If resolution fails, the method must be renamed consistently with the targets that give rise
     // to the failure.
@@ -104,8 +104,9 @@
       List<DexEncodedMethod> targets = new ArrayList<>();
       resolutionResult.asFailedResolution().forEachFailureDependency(targets::add);
       if (!targets.isEmpty()) {
-        DexString firstRename = renaming.get(targets.get(0).method);
-        assert targets.stream().allMatch(target -> renaming.get(target.method) == firstRename);
+        DexString firstRename = renaming.get(targets.get(0).getReference());
+        assert targets.stream()
+            .allMatch(target -> renaming.get(target.getReference()) == firstRename);
         if (firstRename != null) {
           return firstRename;
         }
@@ -138,7 +139,7 @@
       // If we can resolve `item`, then the renaming for `item` and its resolution should be the
       // same.
       DexEncodedMethod resolvedMethod = resolution.asSingleResolution().getResolvedMethod();
-      assert lookupName(method) == lookupName(resolvedMethod.method);
+      assert lookupName(method) == lookupName(resolvedMethod.getReference());
       return true;
     }
 
@@ -162,7 +163,7 @@
         .asFailedResolution()
         .forEachFailureDependency(
             failureDependence -> {
-              assert lookupName(method) == lookupName(failureDependence.method);
+              assert lookupName(method) == lookupName(failureDependence.getReference());
             });
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index c00993f..a542a2f 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -260,22 +260,24 @@
       if (!allowMemberRenaming(holder)
           || holder.accessFlags.isAnnotation()
           || method.accessFlags.isConstructor()
-          || !appView.appInfo().isMinificationAllowed(method.method)) {
-        return method.method.name;
+          || !appView.appInfo().isMinificationAllowed(method.getReference())) {
+        return method.getReference().name;
       }
       if (desugaredLibraryRenaming
           && method.isLibraryMethodOverride().isTrue()
-          && appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
+          && appView.rewritePrefix.hasRewrittenTypeInSignature(
+              method.getReference().proto, appView)) {
         // With desugared library, call-backs names are reserved here.
-        return method.method.name;
+        return method.getReference().name;
       }
       return null;
     }
 
     @Override
     public DexString getReservedName(DexEncodedField field, DexClass holder) {
-      if (holder.isLibraryClass() || !appView.appInfo().isMinificationAllowed(field.field)) {
-        return field.field.name;
+      if (holder.isLibraryClass()
+          || !appView.appInfo().isMinificationAllowed(field.getReference())) {
+        return field.getReference().name;
       }
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index bdd0e70..b6e94ee 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -166,14 +166,14 @@
       }
 
       for (DexEncodedField field : clazz.fields()) {
-        DexField newField = lookupField(field.field, dexItemFactory);
+        DexField newField = lookupField(field.getReference(), dexItemFactory);
         boolean referencesChanged = references.add(newField);
         assert referencesChanged
             : "Duplicate definition of field `" + newField.toSourceString() + "`";
       }
 
       for (DexEncodedMethod method : clazz.methods()) {
-        DexMethod newMethod = lookupMethod(method.method, dexItemFactory);
+        DexMethod newMethod = lookupMethod(method.getReference(), dexItemFactory);
         boolean referencesChanged = references.add(newMethod);
         assert referencesChanged
             : "Duplicate definition of method `" + newMethod.toSourceString() + "`";
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index b75956e..a8e299a 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.naming;
 
 import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
+import static com.android.tools.r8.utils.IterableUtils.fromMethod;
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -51,7 +52,6 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiPredicate;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -99,19 +99,17 @@
 
     timing.begin("MappingInterfaces");
     Set<DexClass> interfaces = new TreeSet<>(Comparator.comparing(DexClass::getType));
-    Consumer<DexClass> consumer =
-        clazz -> {
-          if (clazz.isInterface()) {
-            // Only visit top level interfaces because computeMapping will visit the hierarchy.
-            if (clazz.interfaces.isEmpty()) {
-              computeMapping(clazz.type, nonPrivateMembers, notMappedReferences, subtypingInfo);
-            }
-            interfaces.add(clazz);
-          }
-        };
     // For union-find of interface methods we also need to add the library types above live types.
-    appInfo.forEachTypeInHierarchyOfLiveProgramClasses(consumer);
-    appInfo.forEachReferencedClasspathClass(consumer::accept);
+    appInfo.forEachReachableInterface(
+        iFace -> {
+          assert iFace.isInterface();
+          interfaces.add(iFace);
+          if (iFace.interfaces.isEmpty()) {
+            computeMapping(iFace.type, nonPrivateMembers, notMappedReferences, subtypingInfo);
+          }
+        },
+        fromMethod(appInfo::forEachReferencedClasspathClass, DexClass::getType));
+
     assert nonPrivateMembers.isEmpty();
     timing.end();
 
@@ -504,12 +502,12 @@
 
     @Override
     public DexString getReservedName(DexEncodedMethod method, DexClass holder) {
-      return getReservedName(method, method.method.name, holder);
+      return getReservedName(method, method.getReference().name, holder);
     }
 
     @Override
     public DexString getReservedName(DexEncodedField field, DexClass holder) {
-      return getReservedName(field, field.field.name, holder);
+      return getReservedName(field, field.getReference().name, holder);
     }
 
     private DexString getReservedName(DexDefinition definition, DexString name, DexClass holder) {
diff --git a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
index 83fa84e..e809317 100644
--- a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
@@ -45,7 +45,7 @@
 
   private DexString getRenaming(DexType type) {
     if (type == factory.recordType) {
-      return factory.r8RecordType.descriptor;
+      return factory.recordTagType.descriptor;
     }
     return null;
   }
@@ -77,7 +77,7 @@
   @Override
   public DexString lookupDescriptorForJavaTypeName(String typeName) {
     if (typeName.equals(factory.recordType.toSourceString())) {
-      return factory.r8RecordType.descriptor;
+      return factory.recordTagType.descriptor;
     }
     return namingLens.lookupDescriptorForJavaTypeName(typeName);
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index a675776..dd67f3a 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -167,14 +167,16 @@
 
     if (accessFlags.isPackagePrivate()) {
       // If we publicize a package private method we have to ensure there is no overrides of it. We
-      // could potentially publicize a method if it only has package-private overrides, but for know
-      // we just check if it is seen below.
-      // Note that we will not publize private methods if there exists a package-private override,
-      // and there is therefore no need to check the hierarchy above.
+      // could potentially publicize a method if it only has package-private overrides.
+      // TODO(b/182136236): See if we can break the hierarchy for clusters.
       MemberPool<DexMethod> memberPool = methodPoolCollection.get(method.getHolder());
       Wrapper<DexMethod> methodKey = MethodSignatureEquivalence.get().wrap(method.getReference());
-      if (memberPool.hasSeenStrictlyBelow(methodKey)
-          && appView.options().enablePackagePrivateAwarePublicization) {
+      if (memberPool.below(
+          methodKey,
+          false,
+          true,
+          (clazz, ignored) ->
+              !method.getContextType().getPackageName().equals(clazz.getType().getPackageName()))) {
         return false;
       }
       doPublicize(method);
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 04a24b6..2b9252f 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -188,11 +188,11 @@
           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.method == method) {
+          if (target == null || target.getReference() == method) {
             return;
           }
           DexClass targetClass = appView.definitionFor(target.getHolderType());
-          DexMethod targetMethod = target.method;
+          DexMethod targetMethod = target.getReference();
           if (originalClass.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
@@ -235,7 +235,7 @@
                   target.toForwardingMethod(bridgeHolder, appView);
               bridgeHolder.addMethod(bridgeMethodDefinition);
             }
-            assert lookupTarget.apply(method).method == bridgeMethod;
+            assert lookupTarget.apply(method).getReference() == bridgeMethod;
           }
         });
   }
@@ -267,7 +267,7 @@
     assert bridgeHolder != null;
     assert bridgeHolder != targetClass;
     bridges.accept(bridgeHolder, method, target);
-    return target.method.withHolder(bridgeHolder.getType(), appView.dexItemFactory());
+    return target.getReference().withHolder(bridgeHolder.getType(), appView.dexItemFactory());
   }
 
   private DexProgramClass findHolderForInterfaceMethodBridge(DexProgramClass clazz, DexType iface) {
@@ -316,9 +316,9 @@
           findHolderForVisibilityBridge(originalClass, targetClass, packageDescriptor);
       assert bridgeHolder != null;
       bridges.accept(bridgeHolder, method, target);
-      return target.method.withHolder(bridgeHolder.getType(), appView.dexItemFactory());
+      return target.getReference().withHolder(bridgeHolder.getType(), appView.dexItemFactory());
     }
-    return target.method;
+    return target.getReference();
   }
 
   private DexProgramClass findHolderForVisibilityBridge(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
index 171dcbe..4310236 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.optimize;
 
-import static com.android.tools.r8.graph.GraphLens.NestedGraphLens.mapVirtualInterfaceInvocationTypes;
+import static com.android.tools.r8.graph.NestedGraphLens.mapVirtualInterfaceInvocationTypes;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
index 8ea4e1f..322ccb1 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
@@ -8,10 +8,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
@@ -21,13 +19,7 @@
   private final Set<DexMethod> publicizedMethods;
 
   private PublicizerLens(AppView<?> appView, Set<DexMethod> publicizedMethods) {
-    super(
-        ImmutableMap.of(),
-        ImmutableMap.of(),
-        new EmptyBidirectionalOneToOneMap<>(),
-        new EmptyBidirectionalOneToOneMap<>(),
-        appView.graphLens(),
-        appView.dexItemFactory());
+    super(appView, EMPTY_FIELD_MAP, EMPTY_METHOD_MAP, EMPTY_TYPE_MAP);
     this.appView = appView;
     this.publicizedMethods = publicizedMethods;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 96dd07b..f073314 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -87,7 +87,10 @@
         if (targetMethod != null && targetMethod.accessFlags.isPublic()) {
           if (Log.ENABLED) {
             Log.info(
-                getClass(), "Removing visibility forwarding %s -> %s", method, targetMethod.method);
+                getClass(),
+                "Removing visibility forwarding %s -> %s",
+                method,
+                targetMethod.getReference());
           }
           return true;
         }
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index ea4ee0e..69c59b5 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -140,7 +140,7 @@
       for (DexEncodedMethod method : subclass.virtualMethods()) {
         BridgeInfo bridgeInfo = method.getOptimizationInfo().getBridgeInfo();
         if (bridgeInfo != null) {
-          candidates.add(equivalence.wrap(method.method));
+          candidates.add(equivalence.wrap(method.getReference()));
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
index 4439aea..e1311d5 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoistingResult.java
@@ -23,7 +23,7 @@
 
   // Mapping from non-hoisted bridge methods to hoisted bridge methods.
   private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod>
-      bridgeToHoistedBridgeMap = new BidirectionalManyToOneRepresentativeHashMap<>();
+      bridgeToHoistedBridgeMap = BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
 
   BridgeHoistingResult(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
diff --git a/src/main/java/com/android/tools/r8/references/MethodReference.java b/src/main/java/com/android/tools/r8/references/MethodReference.java
index 9ff0649..fc8f183 100644
--- a/src/main/java/com/android/tools/r8/references/MethodReference.java
+++ b/src/main/java/com/android/tools/r8/references/MethodReference.java
@@ -77,7 +77,7 @@
 
   public String getMethodDescriptor() {
     return StringUtils.join(
-            ListUtils.map(getFormalTypes(), TypeReference::getDescriptor), "", BraceType.PARENS)
+            "", ListUtils.map(getFormalTypes(), TypeReference::getDescriptor), BraceType.PARENS)
         + (getReturnType() == null ? "V" : getReturnType().getDescriptor());
   }
 
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
index e6d16eb..64fe0eb 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
@@ -22,23 +22,17 @@
 
 public class RepackagingLens extends NestedGraphLens {
 
-  private final BiMap<DexType, DexType> originalTypes;
+  private final BiMap<DexType, DexType> newTypes;
   private final Map<String, String> packageRenamings;
 
   private RepackagingLens(
       AppView<AppInfoWithLiveness> appView,
       BidirectionalOneToOneMap<DexField, DexField> newFieldSignatures,
-      BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures,
-      BiMap<DexType, DexType> originalTypes,
+      BidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures,
+      BiMap<DexType, DexType> newTypes,
       Map<String, String> packageRenamings) {
-    super(
-        originalTypes.inverse(),
-        originalMethodSignatures.getInverseOneToOneMap().getForwardMap(),
-        newFieldSignatures,
-        originalMethodSignatures,
-        appView.graphLens(),
-        appView.dexItemFactory());
-    this.originalTypes = originalTypes;
+    super(appView, newFieldSignatures, newMethodSignatures, newTypes);
+    this.newTypes = newTypes;
     this.packageRenamings = packageRenamings;
   }
 
@@ -49,7 +43,7 @@
 
   @Override
   public DexType getOriginalType(DexType type) {
-    DexType previous = originalTypes.getOrDefault(type, type);
+    DexType previous = newTypes.inverse().getOrDefault(type, type);
     return getPrevious().getOriginalType(previous);
   }
 
@@ -73,7 +67,7 @@
   }
 
   private boolean isSimpleTypeRenamingOrEqual(DexType from, DexType to) {
-    return from == to || originalTypes.get(to) == from;
+    return from == to || newTypes.get(from) == to;
   }
 
   private boolean isSimpleTypeRenamingOrEqual(DexMember<?, ?> from, DexMember<?, ?> to) {
@@ -82,16 +76,16 @@
     }
     return IterableUtils.testPairs(
         this::isSimpleTypeRenamingOrEqual,
-        from.getReferencedBaseTypes(dexItemFactory),
-        to.getReferencedBaseTypes(dexItemFactory));
+        from.getReferencedBaseTypes(dexItemFactory()),
+        to.getReferencedBaseTypes(dexItemFactory()));
   }
 
   public static class Builder {
 
-    protected final BiMap<DexType, DexType> originalTypes = HashBiMap.create();
+    protected final BiMap<DexType, DexType> newTypes = HashBiMap.create();
     protected final MutableBidirectionalOneToOneMap<DexField, DexField> newFieldSignatures =
         new BidirectionalOneToOneHashMap<>();
-    protected final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures =
+    protected final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
         new BidirectionalOneToOneHashMap<>();
 
     public void recordMove(DexField from, DexField to) {
@@ -99,18 +93,18 @@
     }
 
     public void recordMove(DexMethod from, DexMethod to) {
-      originalMethodSignatures.put(to, from);
+      newMethodSignatures.put(from, to);
     }
 
     public void recordMove(DexType from, DexType to) {
-      originalTypes.put(to, from);
+      newTypes.put(from, to);
     }
 
     public RepackagingLens build(
         AppView<AppInfoWithLiveness> appView, Map<String, String> packageRenamings) {
-      assert !originalTypes.isEmpty();
+      assert !newTypes.isEmpty();
       return new RepackagingLens(
-          appView, newFieldSignatures, originalMethodSignatures, originalTypes, packageRenamings);
+          appView, newFieldSignatures, newMethodSignatures, newTypes, packageRenamings);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index b64646d..4123c10 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -64,7 +64,7 @@
       DexEncodedMethod method = virtualMethods.get(i);
       if (scope.addMethodIfMoreVisible(method) != AddMethodIfMoreVisibleResult.NOT_ADDED
           || !method.accessFlags.isAbstract()
-          || appView.appInfo().isPinned(method.method)) {
+          || appView.appInfo().isPinned(method.getReference())) {
         if (methods != null) {
           methods.add(method);
         }
@@ -76,7 +76,7 @@
           }
         }
         if (Log.ENABLED) {
-          Log.debug(getClass(), "Removing abstract method %s.", method.method);
+          Log.debug(getClass(), "Removing abstract method %s.", method.getReference());
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 057a80f..34c11ee 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -254,7 +254,7 @@
     boolean liveGetter =
         definition
             .getMethodCollection()
-            .hasVirtualMethods(method -> method.method.name == original.name);
+            .hasVirtualMethods(method -> method.getReference().name == original.name);
     return liveGetter ? original : null;
   }
 
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 b186a52..4622786 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -59,6 +59,7 @@
 import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
@@ -662,7 +663,13 @@
   }
 
   public void forEachReachableInterface(Consumer<DexClass> consumer) {
+    forEachReachableInterface(consumer, ImmutableList.of());
+  }
+
+  public void forEachReachableInterface(
+      Consumer<DexClass> consumer, Iterable<DexType> additionalPaths) {
     WorkList<DexType> worklist = WorkList.newIdentityWorkList();
+    worklist.addIfNotSeen(additionalPaths);
     worklist.addIfNotSeen(objectAllocationInfoCollection.getInstantiatedLambdaInterfaces());
     for (DexProgramClass clazz : classes()) {
       worklist.addIfNotSeen(clazz.type);
@@ -676,10 +683,7 @@
       if (definition.isInterface()) {
         consumer.accept(definition);
       }
-      if (definition.superType != null) {
-        worklist.addIfNotSeen(definition.superType);
-      }
-      worklist.addIfNotSeen(definition.interfaces.values);
+      definition.forEachImmediateSupertype(worklist::addIfNotSeen);
     }
   }
 
@@ -727,7 +731,7 @@
       }
       assert clazz.isInterface();
       for (DexEncodedMethod method : clazz.virtualMethods()) {
-        if (method.method.name == callSite.methodName && method.accessFlags.isAbstract()) {
+        if (method.getReference().name == callSite.methodName && method.accessFlags.isAbstract()) {
           result.add(method);
         }
       }
@@ -806,7 +810,7 @@
 
   public boolean isFieldRead(DexEncodedField encodedField) {
     assert checkIfObsolete();
-    DexField field = encodedField.field;
+    DexField field = encodedField.getReference();
     FieldAccessInfo info = getFieldAccessInfoCollection().get(field);
     if (info != null && info.isRead()) {
       return true;
@@ -820,12 +824,13 @@
 
   public boolean isFieldWritten(DexEncodedField encodedField) {
     assert checkIfObsolete();
-    return isFieldWrittenByFieldPutInstruction(encodedField) || isPinned(encodedField.field);
+    return isFieldWrittenByFieldPutInstruction(encodedField)
+        || isPinned(encodedField.getReference());
   }
 
   public boolean isFieldWrittenByFieldPutInstruction(DexEncodedField encodedField) {
     assert checkIfObsolete();
-    DexField field = encodedField.field;
+    DexField field = encodedField.getReference();
     FieldAccessInfo info = getFieldAccessInfoCollection().get(field);
     if (info != null && info.isWritten()) {
       // The field is written directly by the program itself.
@@ -838,7 +843,7 @@
   public boolean isFieldOnlyWrittenInMethod(DexEncodedField field, DexEncodedMethod method) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
-    if (isPinned(field.field)) {
+    if (isPinned(field.getReference())) {
       return false;
     }
     return isFieldOnlyWrittenInMethodIgnoringPinning(field, method);
@@ -848,7 +853,7 @@
       DexEncodedField field, DexEncodedMethod method) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
-    FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.field);
+    FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.getReference());
     return fieldAccessInfo != null
         && fieldAccessInfo.isWritten()
         && !fieldAccessInfo.isWrittenOutside(method);
@@ -857,10 +862,10 @@
   public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexEncodedField field) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
-    if (isPinned(field.field)) {
+    if (isPinned(field.getReference())) {
       return false;
     }
-    FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.field);
+    FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.getReference());
     if (fieldAccessInfo == null || !fieldAccessInfo.isWritten()) {
       return false;
     }
@@ -995,7 +1000,7 @@
     DexProgramClass clazz = asProgramClassOrNull(definitionFor(type));
     if (clazz != null) {
       for (DexEncodedMethod method : clazz.directMethods()) {
-        if (method.isInstanceInitializer() && isPinned(method.method)) {
+        if (method.isInstanceInitializer() && isPinned(method.getReference())) {
           return true;
         }
       }
@@ -1267,7 +1272,7 @@
       if (refinedReceiverClass.isProgramClass()) {
         DexClassAndMethod clazzAndMethod =
             resolution.lookupVirtualDispatchTarget(refinedReceiverClass.asProgramClass(), this);
-        if (clazzAndMethod == null || isPinned(clazzAndMethod.getDefinition().method)) {
+        if (clazzAndMethod == null || isPinned(clazzAndMethod.getDefinition().getReference())) {
           // TODO(b/150640456): We should maybe only consider program methods.
           return DexEncodedMethod.SENTINEL;
         }
@@ -1279,7 +1284,7 @@
         // implementation, but we use this for library modelling.
         DexEncodedMethod resolvedMethod = resolution.getResolvedMethod();
         DexEncodedMethod targetOnReceiver =
-            refinedReceiverClass.lookupVirtualMethod(resolvedMethod.method);
+            refinedReceiverClass.lookupVirtualMethod(resolvedMethod.getReference());
         if (targetOnReceiver != null && isOverriding(resolvedMethod, targetOnReceiver)) {
           return targetOnReceiver;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
index f032832..03611d6 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInitFieldSynthesizer.java
@@ -54,7 +54,7 @@
       }
       // When compiling for dex, we can't use wide fields since we've only allocated a single
       // register for the out-value of each ClassInit instruction
-      if (staticField.field.type.isWideType()) {
+      if (staticField.getReference().type.isWideType()) {
         continue;
       }
       if (encodedClinitField == null) {
@@ -90,7 +90,7 @@
               d8R8Synthesized);
       clazz.appendStaticField(encodedClinitField);
     }
-    lensBuilder.map(type, encodedClinitField.field);
+    lensBuilder.map(type, encodedClinitField.getReference());
   }
 
   private boolean isMinimumRequiredVisibility(
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 e818526..331466b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -929,17 +929,17 @@
       }
 
       // Check if we have previously created a FieldAccessInfo object for the field definition.
-      info = fieldAccessInfoCollection.get(encodedField.field);
+      info = fieldAccessInfoCollection.get(encodedField.getReference());
 
       // If not, we must create one.
       if (info == null) {
-        info = new FieldAccessInfoImpl(encodedField.field);
-        fieldAccessInfoCollection.extend(encodedField.field, info);
+        info = new FieldAccessInfoImpl(encodedField.getReference());
+        fieldAccessInfoCollection.extend(encodedField.getReference(), info);
       }
 
       // If `field` is an indirect reference, then create a mapping for it, such that we don't have
       // to resolve the field the next time we see the reference.
-      if (field != encodedField.field) {
+      if (field != encodedField.getReference()) {
         fieldAccessInfoCollection.extend(field, info);
       }
     } else if (info == MISSING_FIELD_ACCESS_INFO) {
@@ -2122,7 +2122,7 @@
     // targets another default method in the same interface (see testInvokeSpecialToDefault-
     // Method). In a class, that would lead to a verification error.
     if (encodedMethod.isNonPrivateVirtualMethod()
-        && virtualMethodsTargetedByInvokeDirect.add(encodedMethod.method)) {
+        && virtualMethodsTargetedByInvokeDirect.add(encodedMethod.getReference())) {
       workList.enqueueMarkMethodLiveAction(method, context, reason);
     }
   }
@@ -2442,7 +2442,7 @@
       markVirtualMethodAsLive(
           resolutionMethod,
           graphReporter.reportReachableMethodAsLive(
-              resolutionMethod.getDefinition().method, resolutionMethod));
+              resolutionMethod.getDefinition().getReference(), resolutionMethod));
       return;
     }
     // Otherwise, we set the initial holder type to be the holder of the reachable method, which
@@ -2456,7 +2456,7 @@
           lookup,
           programMethod ->
               graphReporter.reportReachableMethodAsLive(
-                  resolutionMethod.getDefinition().method, programMethod));
+                  resolutionMethod.getDefinition().getReference(), programMethod));
     }
   }
 
@@ -2474,7 +2474,9 @@
       // Note: It would be reasonable to not process methods already seen during the marking of
       // program usages, but that would cause the methods to not be marked as library overrides.
       markLibraryOrClasspathOverrideLive(
-          instantiation, libraryClass, appInfo.resolveMethodOn(libraryClass, method.method));
+          instantiation,
+          libraryClass,
+          appInfo.resolveMethodOn(libraryClass, method.getReference()));
 
       // Due to API conversion, some overrides can be hidden since they will be rewritten. See
       // class comment of DesugaredLibraryAPIConverter and vivifiedType logic.
@@ -2482,11 +2484,12 @@
       // maintains the library override. In the second enqueuer phase, the signature has been
       // desugared, and the second resolution maintains the the library override.
       if (instantiation.isClass()
-          && appView.rewritePrefix.hasRewrittenTypeInSignature(method.method.proto, appView)) {
+          && appView.rewritePrefix.hasRewrittenTypeInSignature(
+              method.getReference().proto, appView)) {
         DexMethod methodToResolve =
             DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-                method.method, method.getHolderType(), appView);
-        assert methodToResolve != method.method;
+                method.getReference(), method.getHolderType(), appView);
+        assert methodToResolve != method.getReference();
         markLibraryOrClasspathOverrideLive(
             instantiation,
             libraryClass,
@@ -2514,7 +2517,7 @@
     if (instantiation.isClass()) {
       // TODO(b/149976493): We need to mark these for lambdas too!
       markOverridesAsLibraryMethodOverrides(
-          instantiation.asClass(), lookup.asMethodTarget().getDefinition().method);
+          instantiation.asClass(), lookup.asMethodTarget().getDefinition().getReference());
     }
   }
 
@@ -2688,7 +2691,7 @@
   }
 
   public boolean isFieldReferenced(DexEncodedField field) {
-    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field);
+    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.getReference());
     return info != null;
   }
 
@@ -2834,7 +2837,7 @@
                     target,
                     programMethod ->
                         graphReporter.reportReachableMethodAsLive(
-                            resolvedMethod.method, programMethod)));
+                            resolvedMethod.getReference(), programMethod)));
   }
 
   private void markVirtualDispatchTargetAsLive(
@@ -2874,7 +2877,7 @@
         method -> {
           DexProgramClass clazz = getProgramClassOrNull(method.getHolderType(), context);
           if (clazz != null) {
-            failedMethodResolutionTargets.add(method.method);
+            failedMethodResolutionTargets.add(method.getReference());
             markMethodAsTargeted(new ProgramMethod(clazz, method), reason);
           }
         });
@@ -2920,7 +2923,7 @@
     // If invoke target is invalid (inaccessible or not an instance-method) record it and stop.
     DexClassAndMethod target = resolution.lookupInvokeSuperTarget(from.getHolder(), appInfo);
     if (target == null) {
-      failedMethodResolutionTargets.add(resolution.getResolvedMethod().method);
+      failedMethodResolutionTargets.add(resolution.getResolvedMethod().getReference());
       return;
     }
 
@@ -2961,7 +2964,7 @@
     MainDexInfo.Builder builder = appView.appInfo().getMainDexInfo().builder();
     liveTypes.getItems().forEach(builder::addRoot);
     if (mode.isInitialMainDexTracing()) {
-      liveMethods.getItems().forEach(method -> builder.addRoot(method.method));
+      liveMethods.getItems().forEach(method -> builder.addRoot(method.getReference()));
     } else {
       assert appView.appInfo().getMainDexInfo().isTracedMethodRootsCleared()
           || mode.isGenerateMainDexList();
@@ -3115,7 +3118,7 @@
     }
 
     void addLiveMethod(ProgramMethod method) {
-      DexMethod signature = method.getDefinition().method;
+      DexMethod signature = method.getDefinition().getReference();
       assert !liveMethods.containsKey(signature);
       liveMethods.put(signature, method);
     }
@@ -3399,7 +3402,7 @@
         : "Expected type to be in live non-program types: " + clazz;
     for (DexEncodedField field : clazz.fields()) {
       if (clazz.isNotProgramClass() || isFieldReferenced(field)) {
-        assert verifyReferencedType(field.field.type, worklist, app);
+        assert verifyReferencedType(field.getReference().type, worklist, app);
       }
     }
     for (DexEncodedMethod method : clazz.methods()) {
@@ -3412,8 +3415,8 @@
 
   private boolean verifyReferencedMethod(
       DexEncodedMethod method, WorkList<DexClass> worklist, DexApplication app) {
-    assert verifyReferencedType(method.method.proto.returnType, worklist, app);
-    for (DexType param : method.method.proto.parameters.values) {
+    assert verifyReferencedType(method.getReference().proto.returnType, worklist, app);
+    for (DexType param : method.getReference().proto.parameters.values) {
       assert verifyReferencedType(param, worklist, app);
     }
     return true;
@@ -3644,16 +3647,19 @@
       return;
     }
     if (methodToKeep != singleTarget
-        && !syntheticInterfaceMethodBridges.containsKey(methodToKeep.getDefinition().method)) {
-      syntheticInterfaceMethodBridges.put(methodToKeep.getDefinition().method, methodToKeep);
-      assert null == methodToKeep.getHolder().lookupMethod(methodToKeep.getDefinition().method);
+        && !syntheticInterfaceMethodBridges.containsKey(
+            methodToKeep.getDefinition().getReference())) {
+      syntheticInterfaceMethodBridges.put(
+          methodToKeep.getDefinition().getReference(), methodToKeep);
+      assert null
+          == methodToKeep.getHolder().lookupMethod(methodToKeep.getDefinition().getReference());
       if (singleTargetMethod.isLibraryMethodOverride().isTrue()) {
         methodToKeep.getDefinition().setLibraryMethodOverride(OptionalBool.TRUE);
       }
       DexProgramClass singleTargetHolder = singleTarget.getHolder();
       assert singleTargetHolder.isInterface();
       markVirtualMethodAsReachable(
-          singleTargetMethod.method,
+          singleTargetMethod.getReference(),
           singleTargetHolder.isInterface(),
           singleTarget,
           graphReporter.fakeReportShouldNotBeUsed());
@@ -3748,7 +3754,7 @@
       DexProgramClass current = worklist.removeFirst();
       assert visited.contains(current);
 
-      if (current.lookupVirtualMethod(method.method) != null) {
+      if (current.lookupVirtualMethod(method.getReference()) != null) {
         continue;
       }
 
@@ -4482,7 +4488,7 @@
       DexEncodedMethod target = holder.lookupDirectMethod(method);
       if (target != null) {
         // There is no dispatch on annotations, so only keep what is directly referenced.
-        if (target.method == method) {
+        if (target.getReference() == method) {
           markDirectStaticOrConstructorMethodAsLive(
               new ProgramMethod(holder, target),
               KeepReason.referencedInAnnotation(annotationHolder));
@@ -4490,7 +4496,7 @@
       } else {
         target = holder.lookupVirtualMethod(method);
         // There is no dispatch on annotations, so only keep what is directly referenced.
-        if (target != null && target.method == method) {
+        if (target != null && target.getReference() == method) {
           markMethodAsTargeted(
               new ProgramMethod(holder, target),
               KeepReason.referencedInAnnotation(annotationHolder));
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 6459b7f..ac0d153 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -136,7 +136,7 @@
     }
     KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
     EdgeKind edgeKind = reportPrecondition(ruleNode);
-    return reportEdge(ruleNode, getMethodGraphNode(method.method), edgeKind);
+    return reportEdge(ruleNode, getMethodGraphNode(method.getReference()), edgeKind);
   }
 
   KeepReasonWitness reportKeepMethod(
@@ -157,7 +157,7 @@
     }
     KeepRuleGraphNode ruleNode = getKeepRuleGraphNode(precondition, rule);
     EdgeKind edgeKind = reportPrecondition(ruleNode);
-    return reportEdge(ruleNode, getFieldGraphNode(field.field), edgeKind);
+    return reportEdge(ruleNode, getFieldGraphNode(field.getReference()), edgeKind);
   }
 
   KeepReasonWitness reportKeepField(
@@ -259,10 +259,11 @@
 
   public KeepReasonWitness reportReachableMethodAsLive(
       DexMethod overriddenMethod, ProgramMethod derivedMethod) {
-    if (keptGraphConsumer != null && overriddenMethod != derivedMethod.getDefinition().method) {
+    if (keptGraphConsumer != null
+        && overriddenMethod != derivedMethod.getDefinition().getReference()) {
       return reportEdge(
           getMethodGraphNode(overriddenMethod),
-          getMethodGraphNode(derivedMethod.getDefinition().method),
+          getMethodGraphNode(derivedMethod.getDefinition().getReference()),
           EdgeKind.OverridingMethod);
     }
     return KeepReasonWitness.INSTANCE;
@@ -276,7 +277,7 @@
     if (keptGraphConsumer != null && instantiation.isClass()) {
       return reportEdge(
           getClassGraphNode(instantiation.asClass().type),
-          getMethodGraphNode(derivedMethod.getDefinition().method),
+          getMethodGraphNode(derivedMethod.getDefinition().getReference()),
           EdgeKind.IsLibraryMethod);
     }
     return KeepReasonWitness.INSTANCE;
@@ -299,8 +300,8 @@
       return KeepReasonWitness.INSTANCE;
     }
     return reportEdge(
-        getMethodGraphNode(definition.method),
-        getMethodGraphNode(implementation.method),
+        getMethodGraphNode(definition.getReference()),
+        getMethodGraphNode(implementation.getReference()),
         EdgeKind.CompanionMethod);
   }
 
@@ -375,14 +376,14 @@
       // TODO(b/120959039): This should be dead code once no library classes are ever enqueued.
       return KeepReasonWitness.INSTANCE;
     }
-    return registerEdge(getMethodGraphNode(method.method), reason);
+    return registerEdge(getMethodGraphNode(method.getReference()), reason);
   }
 
   public KeepReasonWitness registerField(DexEncodedField field, KeepReason reason) {
     if (skipReporting(reason)) {
       return KeepReasonWitness.INSTANCE;
     }
-    return registerEdge(getFieldGraphNode(field.field), reason);
+    return registerEdge(getFieldGraphNode(field.getReference()), reason);
   }
 
   private KeepReasonWitness registerEdge(GraphNode target, KeepReason reason) {
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index e843d0d..aa6844e 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -235,7 +235,7 @@
                 (enqueuer.isFieldLive(f)
                         || enqueuer.isFieldReferenced(f)
                         || f.getOptimizationInfo().valueHasBeenPropagated())
-                    && appView.graphLens().getOriginalFieldSignature(f.field).holder
+                    && appView.graphLens().getOriginalFieldSignature(f.getReference()).holder
                         == sourceClass.type));
     Iterables.addAll(
         filteredMembers,
@@ -244,7 +244,7 @@
                 (enqueuer.isMethodLive(m)
                         || enqueuer.isMethodTargeted(m)
                         || m.getOptimizationInfo().returnValueHasBeenPropagated())
-                    && appView.graphLens().getOriginalMethodSignature(m.method).holder
+                    && appView.graphLens().getOriginalMethodSignature(m.getReference()).holder
                         == sourceClass.type));
 
     // If the number of member rules to hold is more than live members, we can't make it.
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 81239aa..7e9b166 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -307,13 +307,13 @@
     @Override
     public KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder) {
       assert method.getHolderType() == holder.type;
-      return keepMethodInfo.getOrDefault(method.method, KeepMethodInfo.bottom());
+      return keepMethodInfo.getOrDefault(method.getReference(), KeepMethodInfo.bottom());
     }
 
     @Override
     public KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder) {
       assert field.getHolderType() == holder.type;
-      return keepFieldInfo.getOrDefault(field.field, KeepFieldInfo.bottom());
+      return keepFieldInfo.getOrDefault(field.getReference(), KeepFieldInfo.bottom());
     }
 
     public void joinClass(DexProgramClass clazz, Consumer<KeepClassInfo.Joiner> fn) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 53b944f..5e6b3fd 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -96,12 +96,12 @@
     abstract String getKind();
 
     public DexMethod getMethod() {
-      return method.method;
+      return method.getReference();
     }
 
     @Override
     public GraphNode getSourceNode(GraphReporter graphReporter) {
-      return graphReporter.getMethodGraphNode(method.method);
+      return graphReporter.getMethodGraphNode(method.getReference());
     }
   }
 
@@ -264,10 +264,10 @@
       if (holder.isDexClass()) {
         return graphReporter.getClassGraphNode(holder.asDexClass().type);
       } else if (holder.isDexEncodedField()) {
-        return graphReporter.getFieldGraphNode(holder.asDexEncodedField().field);
+        return graphReporter.getFieldGraphNode(holder.asDexEncodedField().getReference());
       } else {
         assert holder.isDexEncodedMethod();
-        return graphReporter.getMethodGraphNode(holder.asDexEncodedMethod().method);
+        return graphReporter.getMethodGraphNode(holder.asDexEncodedMethod().getReference());
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 7760062..ab19637 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -53,7 +53,7 @@
           clazz.annotations(), appInfo.dexItemFactory())) {
         traceAnnotationsDirectDependencies(clazz.annotations());
       }
-      clazz.forEachField(field -> consumer.accept(field.field.type));
+      clazz.forEachField(field -> consumer.accept(field.getReference().type));
       clazz.forEachProgramMethodMatching(
           definition -> {
             traceMethodDirectDependencies(definition.getReference(), consumer);
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index afb39bf..525417c 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -105,7 +105,7 @@
         // Browse annotation values types in search for enum.
         // Each annotation value is represented by a virtual method.
         for (DexEncodedMethod method : clazz.virtualMethods()) {
-          DexProto proto = method.method.proto;
+          DexProto proto = method.getReference().proto;
           if (proto.parameters.isEmpty()) {
             DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory());
             if (valueType.isClassType()) {
@@ -143,7 +143,7 @@
     addDirectDependency(clazz);
     // Add enum classes used for values as direct dependencies.
     for (DexEncodedMethod method : clazz.virtualMethods()) {
-      DexProto proto = method.method.proto;
+      DexProto proto = method.getReference().proto;
       if (proto.parameters.isEmpty()) {
         DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory());
         if (isEnum(valueType)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index c6afc11..8c0d0b4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -75,11 +75,13 @@
     memberRuleBuilder.setRuleType(ProguardMemberType.METHOD);
     memberRuleBuilder.getAccessFlags().setFlags(method.accessFlags);
     memberRuleBuilder.setName(
-        IdentifierPatternWithWildcards.withoutWildcards(method.method.name.toString()));
-    memberRuleBuilder.setTypeMatcher(ProguardTypeMatcher.create(method.method.proto.returnType));
-    List<ProguardTypeMatcher> arguments = Arrays.stream(method.method.proto.parameters.values)
-        .map(ProguardTypeMatcher::create)
-        .collect(Collectors.toList());
+        IdentifierPatternWithWildcards.withoutWildcards(method.getReference().name.toString()));
+    memberRuleBuilder.setTypeMatcher(
+        ProguardTypeMatcher.create(method.getReference().proto.returnType));
+    List<ProguardTypeMatcher> arguments =
+        Arrays.stream(method.getReference().proto.parameters.values)
+            .map(ProguardTypeMatcher::create)
+            .collect(Collectors.toList());
     memberRuleBuilder.setArguments(arguments);
     builder.getMemberRules().add(memberRuleBuilder.build());
     return builder.build();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 1b80a1e..2aba15e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -185,7 +185,8 @@
       AppView<?> appView,
       Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
       DexStringCache stringCache) {
-    DexField originalSignature = appView.graphLens().getOriginalFieldSignature(field.field);
+    DexField originalSignature =
+        appView.graphLens().getOriginalFieldSignature(field.getReference());
     switch (getRuleType()) {
       case ALL:
       case ALL_FIELDS:
@@ -236,7 +237,8 @@
       AppView<?> appView,
       Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
       DexStringCache stringCache) {
-    DexMethod originalSignature = appView.graphLens().getOriginalMethodSignature(method.method);
+    DexMethod originalSignature =
+        appView.graphLens().getOriginalMethodSignature(method.getReference());
     switch (getRuleType()) {
       case ALL_METHODS:
         if (method.isClassInitializer()) {
@@ -415,7 +417,7 @@
       case INIT: {
         result.append(getName());
         result.append('(');
-        result.append(StringUtils.join(getArguments(), ","));
+          result.append(StringUtils.join(",", getArguments()));
         result.append(')');
         break;
       }
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 8848ece..e78f53b 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -387,8 +387,8 @@
           assert !encodedMethod.shouldNotHaveCode();
           continue;
         }
-        propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, noSideEffects);
-        propagateAssumeRules(clazz.type, encodedMethod.method, subTypes, assumedValues);
+        propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, noSideEffects);
+        propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, assumedValues);
       }
     }
 
@@ -414,7 +414,7 @@
         if (target == null || target.getHolderType() == type) {
           continue;
         }
-        ProguardMemberRule ruleInSubType = assumeRulePool.get(target.method);
+        ProguardMemberRule ruleInSubType = assumeRulePool.get(target.getReference());
         // We are looking for the greatest lower bound of assume rules from all sub types.
         // If any subtype doesn't have a matching assume rule, the lower bound is literally nothing.
         if (ruleInSubType == null) {
@@ -578,7 +578,7 @@
         }
         for (DexEncodedMethod method : clazz.virtualMethods()) {
           // Check if we already added this.
-          Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.method);
+          Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.getReference());
           if (!seenMethods.add(wrapped)) {
             continue;
           }
@@ -592,7 +592,10 @@
 
       private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) {
         SingleResolutionResult resolutionResult =
-            appView.appInfo().resolveMethodOn(originalClazz, method.method).asSingleResolution();
+            appView
+                .appInfo()
+                .resolveMethodOn(originalClazz, method.getReference())
+                .asSingleResolution();
         if (resolutionResult == null || !resolutionResult.isVirtualTarget()) {
           return;
         }
@@ -1008,7 +1011,7 @@
         DexDefinition precondition,
         ProguardIfRule ifRule) {
       if (methodsMarked != null
-          && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
+          && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.getReference()))) {
         // Ignore, method is overridden in sub class.
         return;
       }
@@ -1019,7 +1022,7 @@
                 getClass(), "Marking method `%s` due to `%s { %s }`.", method, context, rule);
           }
           if (methodsMarked != null) {
-            methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
+            methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.getReference()));
           }
           addItemToSets(method, context, rule, precondition, ifRule);
         }
@@ -1075,13 +1078,13 @@
 
     private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) {
       if (item.isDexEncodedMethod()) {
-        DexMethod method = item.asDexEncodedMethod().method;
+        DexMethod method = item.asDexEncodedMethod().getReference();
         includeDescriptor(item, method.proto.returnType, context);
         for (DexType value : method.proto.parameters.values) {
           includeDescriptor(item, value, context);
         }
       } else if (item.isDexEncodedField()) {
-        DexField field = item.asDexEncodedField().field;
+        DexField field = item.asDexEncodedField().getReference();
         includeDescriptor(item, field.type, context);
       } else {
         assert item.isDexClass();
@@ -1114,7 +1117,7 @@
             return;
           }
           if (options.isGeneratingDex()
-              && encodedMethod.method.isLambdaDeserializeMethod(appView.dexItemFactory())) {
+              && encodedMethod.getReference().isLambdaDeserializeMethod(appView.dexItemFactory())) {
             // Don't keep lambda deserialization methods.
             return;
           }
@@ -1131,7 +1134,7 @@
                         "The rule `"
                             + rule
                             + "` is ignored because the targeting interface method `"
-                            + encodedMethod.method.toSourceString()
+                            + encodedMethod.getReference().toSourceString()
                             + "` will be desugared."));
               }
               return;
@@ -1244,7 +1247,7 @@
         if (!item.isDexEncodedMethod()) {
           throw new Unreachable();
         }
-        whyAreYouNotInlining.add(item.asDexEncodedMethod().method);
+        whyAreYouNotInlining.add(item.asDexEncodedMethod().getReference());
         context.markAsUsed();
       } else if (context.isClassInlineRule()) {
         ClassInlineRule classInlineRule = context.asClassInlineRule();
@@ -1285,13 +1288,13 @@
             if (item.isDexEncodedField()) {
               DexEncodedField field = item.asDexEncodedField();
               if (field.isProgramField(appView)) {
-                neverPropagateValue.add(item.asDexEncodedField().field);
+                neverPropagateValue.add(item.asDexEncodedField().getReference());
                 context.markAsUsed();
               }
             } else if (item.isDexEncodedMethod()) {
               DexEncodedMethod method = item.asDexEncodedMethod();
               if (method.isProgramMethod(appView)) {
-                neverPropagateValue.add(item.asDexEncodedMethod().method);
+                neverPropagateValue.add(item.asDexEncodedMethod().getReference());
                 context.markAsUsed();
               }
             }
@@ -1301,15 +1304,15 @@
         }
       } else if (context instanceof ProguardIdentifierNameStringRule) {
         if (item.isDexEncodedField()) {
-          identifierNameStrings.add(item.asDexEncodedField().field);
+          identifierNameStrings.add(item.asDexEncodedField().getReference());
           context.markAsUsed();
         } else if (item.isDexEncodedMethod()) {
-          identifierNameStrings.add(item.asDexEncodedMethod().method);
+          identifierNameStrings.add(item.asDexEncodedMethod().getReference());
           context.markAsUsed();
         }
       } else if (context instanceof ConstantArgumentRule) {
         if (item.isDexEncodedMethod()) {
-          keepParametersWithConstantValue.add(item.asDexEncodedMethod().method);
+          keepParametersWithConstantValue.add(item.asDexEncodedMethod().getReference());
           context.markAsUsed();
         }
       } else if (context instanceof ReprocessClassInitializerRule) {
@@ -1317,10 +1320,10 @@
         if (clazz != null && clazz.hasClassInitializer()) {
           switch (context.asReprocessClassInitializerRule().getType()) {
             case ALWAYS:
-              reprocess.add(clazz.getClassInitializer().method);
+              reprocess.add(clazz.getClassInitializer().getReference());
               break;
             case NEVER:
-              neverReprocess.add(clazz.getClassInitializer().method);
+              neverReprocess.add(clazz.getClassInitializer().getReference());
               break;
             default:
               throw new Unreachable();
@@ -1332,10 +1335,10 @@
           DexEncodedMethod method = item.asDexEncodedMethod();
           switch (context.asReprocessMethodRule().getType()) {
             case ALWAYS:
-              reprocess.add(method.method);
+              reprocess.add(method.getReference());
               break;
             case NEVER:
-              neverReprocess.add(method.method);
+              neverReprocess.add(method.getReference());
               break;
             default:
               throw new Unreachable();
@@ -1344,7 +1347,7 @@
         }
       } else if (context instanceof UnusedArgumentRule) {
         if (item.isDexEncodedMethod()) {
-          keepUnusedArguments.add(item.asDexEncodedMethod().method);
+          keepUnusedArguments.add(item.asDexEncodedMethod().getReference());
           context.markAsUsed();
         }
       } else {
diff --git a/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
index d3b7e21..07ad580 100644
--- a/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
+++ b/src/main/java/com/android/tools/r8/shaking/ScopedDexMethodSet.java
@@ -46,7 +46,7 @@
   }
 
   public boolean addMethod(DexEncodedMethod method) {
-    Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method.method);
+    Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method.getReference());
     if (contains(wrapped)) {
       return false;
     }
@@ -55,7 +55,7 @@
   }
 
   public AddMethodIfMoreVisibleResult addMethodIfMoreVisible(DexEncodedMethod method) {
-    Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method.method);
+    Wrapper<DexMethod> wrapped = METHOD_EQUIVALENCE.wrap(method.getReference());
     DexEncodedMethod existing = lookup(wrapped);
     if (existing == null) {
       items.put(wrapped, method);
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 9b7a66b..6974a61 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -276,7 +276,7 @@
     AppInfoWithLiveness appInfo = appView.appInfo();
     InternalOptions options = appView.options();
     int firstUnreachable =
-        firstUnreachableIndex(methods, method -> appInfo.isLiveMethod(method.method));
+        firstUnreachableIndex(methods, method -> appInfo.isLiveMethod(method.getReference()));
     // Return the original array if all methods are used.
     if (firstUnreachable == -1) {
       return null;
@@ -295,7 +295,7 @@
             method.shouldNotHaveCode() && !method.hasCode()
                 ? method
                 : method.toMethodThatLogsError(appView));
-        methodsToKeepForConfigurationDebugging.add(method.method);
+        methodsToKeepForConfigurationDebugging.add(method.getReference());
       } else if (appInfo.isTargetedMethod(method.getReference())) {
         // If the method is already abstract, and doesn't have code, let it be.
         if (method.shouldNotHaveCode() && !method.hasCode()) {
@@ -303,7 +303,7 @@
           continue;
         }
         if (Log.ENABLED) {
-          Log.debug(getClass(), "Making method %s abstract.", method.method);
+          Log.debug(getClass(), "Making method %s abstract.", method.getReference());
         }
         // Final classes cannot be abstract, so we have to keep the method in that case.
         // Also some other kinds of methods cannot be abstract, so keep them around.
@@ -315,7 +315,7 @@
                 && !method.isSynchronized()
                 && !method.accessFlags.isPrivate()
                 && !method.isStatic()
-                && !appInfo.isFailedResolutionTarget(method.method);
+                && !appInfo.isFailedResolutionTarget(method.getReference());
         // Private methods and static methods can only be targeted yet non-live as the result of
         // an invalid invoke. They will not actually be called at runtime but we have to keep them
         // as non-abstract (see above) to produce the same failure mode.
@@ -323,7 +323,7 @@
             allowAbstract ? method.toAbstractMethod() : method.toEmptyThrowingMethod(options));
       } else {
         if (Log.ENABLED) {
-          Log.debug(getClass(), "Removing method %s.", method.method);
+          Log.debug(getClass(), "Removing method %s.", method.getReference());
         }
         unusedItemsPrinter.registerUnusedMethod(method);
       }
@@ -356,7 +356,7 @@
         reachableOrReferencedFields.add(field);
       } else {
         if (Log.ENABLED) {
-          Log.debug(getClass(), "Removing field %s.", field.field);
+          Log.debug(getClass(), "Removing field %s.", field.getReference());
         }
         unusedItemsPrinter.registerUnusedField(field);
       }
@@ -379,7 +379,7 @@
       // Pinned field which type is never instantiated are always null, they are marked as dead
       // so their reads and writes are cleared, but they are still in the program.
       assert !field.getOptimizationInfo().isDead() || appView.appInfo().isPinned(field)
-          : "Expected field `" + field.field.toSourceString() + "` to be absent";
+          : "Expected field `" + field.getReference().toSourceString() + "` to be absent";
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java b/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java
index 5b89abf..6bd61cd 100644
--- a/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java
+++ b/src/main/java/com/android/tools/r8/shaking/UnusedItemsPrinter.java
@@ -105,15 +105,15 @@
       append(accessFlags);
       append(" ");
     }
-    append(method.method.proto.returnType.toSourceString());
+    append(method.getReference().proto.returnType.toSourceString());
     append(" ");
-    append(method.method.name.toSourceString());
+    append(method.getReference().name.toSourceString());
     append("(");
-    for (int i = 0; i < method.method.proto.parameters.values.length; i++) {
+    for (int i = 0; i < method.getReference().proto.parameters.values.length; i++) {
       if (i != 0) {
         append(",");
       }
-      append(method.method.proto.parameters.values[i].toSourceString());
+      append(method.getReference().proto.parameters.values[i].toSourceString());
     }
     append(")");
     newline();
@@ -126,9 +126,9 @@
       append(accessFlags);
       append(" ");
     }
-    append(field.field.type.toSourceString());
+    append(field.getReference().type.toSourceString());
     append(" ");
-    append(field.field.name.toSourceString());
+    append(field.getReference().name.toSourceString());
     newline();
   }
 
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 761b314..3475116 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -208,7 +208,7 @@
 
   // Map from source class to target class.
   private final MutableBidirectionalManyToOneMap<DexType, DexType> mergedClasses =
-      new BidirectionalManyToOneHashMap<>();
+      BidirectionalManyToOneHashMap.newIdentityHashMap();
 
   // Set of types that must not be merged into their subtype.
   private final Set<DexType> pinnedTypes = Sets.newIdentityHashSet();
@@ -512,7 +512,7 @@
       }
       // Check if the target is overriding and narrowing the access.
       if (method.isPublic()) {
-        DexEncodedMethod targetOverride = target.lookupVirtualMethod(method.method);
+        DexEncodedMethod targetOverride = target.lookupVirtualMethod(method.getReference());
         if (targetOverride != null && !targetOverride.isPublic()) {
           return true;
         }
@@ -565,7 +565,7 @@
       Set<Wrapper<DexMethod>> filteredSignatures = new HashSet<>();
       for (DexProgramClass clazz : appInfo.classes()) {
         for (DexEncodedMethod encodedMethod : clazz.methods()) {
-          DexMethod method = encodedMethod.method;
+          DexMethod method = encodedMethod.getReference();
           DexClass definition = appInfo.definitionFor(method.holder);
           if (definition != null
               && definition.isProgramClass()
@@ -695,7 +695,7 @@
 
     for (DexProgramClass clazz : appInfo.classes()) {
       for (DexEncodedMethod encodedMethod : clazz.methods()) {
-        DexMethod method = encodedMethod.method;
+        DexMethod method = encodedMethod.getReference();
         DexMethod originalMethod = graphLens.getOriginalMethodSignature(method);
         DexMethod renamedMethod = graphLens.getRenamedMethodSignature(originalMethod);
 
@@ -727,7 +727,8 @@
 
   private boolean methodResolutionMayChange(DexProgramClass source, DexProgramClass target) {
     for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) {
-      DexEncodedMethod directTargetMethod = target.lookupDirectMethod(virtualSourceMethod.method);
+      DexEncodedMethod directTargetMethod =
+          target.lookupDirectMethod(virtualSourceMethod.getReference());
       if (directTargetMethod != null) {
         // A private method shadows a virtual method. This situation is rare, since it is not
         // allowed by javac. Therefore, we just give up in this case. (In principle, it would be
@@ -763,7 +764,7 @@
         // Conservatively find all possible targets for this method.
         LookupResultSuccess lookupResult =
             appInfo
-                .resolveMethodOnInterface(method.getHolderType(), method.method)
+                .resolveMethodOnInterface(method.getHolderType(), method.getReference())
                 .lookupVirtualDispatchTargets(target, appInfo)
                 .asLookupResultSuccess();
         assert lookupResult != null;
@@ -873,11 +874,12 @@
       for (DexType interfaceType : target.interfaces.values) {
         DexClass clazz = appInfo.definitionFor(interfaceType);
         for (DexEncodedField staticField : clazz.staticFields()) {
-          staticFieldsInInterfacesOfTarget.add(equivalence.wrap(staticField.field));
+          staticFieldsInInterfacesOfTarget.add(equivalence.wrap(staticField.getReference()));
         }
       }
       for (DexEncodedField instanceField : source.instanceFields()) {
-        if (staticFieldsInInterfacesOfTarget.contains(equivalence.wrap(instanceField.field))) {
+        if (staticFieldsInInterfacesOfTarget.contains(
+            equivalence.wrap(instanceField.getReference()))) {
           // An instruction "iget Target.f" or "iput Target.f" that used to hit a static field in an
           // interface would now hit an instance field from [source], so that an IncompatibleClass-
           // ChangeError would no longer be thrown. Abort merge.
@@ -926,7 +928,7 @@
           DexEncodedMethod resultingConstructor =
               renameConstructor(directMethod, availableMethodSignatures);
           add(directMethods, resultingConstructor, MethodSignatureEquivalence.get());
-          blockRedirectionOfSuperCalls(resultingConstructor.method);
+          blockRedirectionOfSuperCalls(resultingConstructor.getReference());
         } else {
           DexEncodedMethod resultingDirectMethod =
               renameMethod(
@@ -934,9 +936,10 @@
                   availableMethodSignatures,
                   directMethod.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
           add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
-          deferredRenamings.map(directMethod.method, resultingDirectMethod.method);
-          deferredRenamings.recordMove(directMethod.method, resultingDirectMethod.method);
-          blockRedirectionOfSuperCalls(resultingDirectMethod.method);
+          deferredRenamings.map(directMethod.getReference(), resultingDirectMethod.getReference());
+          deferredRenamings.recordMove(
+              directMethod.getReference(), resultingDirectMethod.getReference());
+          blockRedirectionOfSuperCalls(resultingDirectMethod.getReference());
         }
       }
 
@@ -945,7 +948,7 @@
         if (shadowedBy != null) {
           if (virtualMethod.isAbstract()) {
             // Remove abstract/interface methods that are shadowed.
-            deferredRenamings.map(virtualMethod.method, shadowedBy.method);
+            deferredRenamings.map(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.
@@ -977,8 +980,10 @@
                 renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
             resultingVirtualMethod.setLibraryMethodOverride(
                 virtualMethod.isLibraryMethodOverride());
-            deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
-            deferredRenamings.recordMove(virtualMethod.method, resultingVirtualMethod.method);
+            deferredRenamings.map(
+                virtualMethod.getReference(), resultingVirtualMethod.getReference());
+            deferredRenamings.recordMove(
+                virtualMethod.getReference(), resultingVirtualMethod.getReference());
             add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
             continue;
           }
@@ -1005,7 +1010,7 @@
           makeStatic(resultingDirectMethod);
 
           // Update method pool collection now that we are adding a new public method.
-          methodPoolForTarget.seen(resultingDirectMethod.method);
+          methodPoolForTarget.seen(resultingDirectMethod.getReference());
         } else {
           // This virtual method could be called directly from a sub class via an invoke-super in-
           // struction. Therefore, we translate this virtual method into a direct method, such that
@@ -1019,8 +1024,9 @@
 
         // Record that invoke-super instructions in the target class should be redirected to the
         // newly created direct method.
-        redirectSuperCallsInTarget(virtualMethod.method, resultingDirectMethod.method);
-        blockRedirectionOfSuperCalls(resultingDirectMethod.method);
+        redirectSuperCallsInTarget(
+            virtualMethod.getReference(), resultingDirectMethod.getReference());
+        blockRedirectionOfSuperCalls(resultingDirectMethod.getReference());
 
         if (shadowedBy == null) {
           // In addition to the newly added direct method, create a virtual method such that we do
@@ -1029,12 +1035,14 @@
           // it turns out that the method is never used, it will be removed by the final round
           // of tree shaking.
           shadowedBy = buildBridgeMethod(virtualMethod, resultingDirectMethod);
-          deferredRenamings.recordCreationOfBridgeMethod(virtualMethod.method, shadowedBy.method);
+          deferredRenamings.recordCreationOfBridgeMethod(
+              virtualMethod.getReference(), shadowedBy.getReference());
           add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
         }
 
-        deferredRenamings.map(virtualMethod.method, shadowedBy.method);
-        deferredRenamings.recordMove(virtualMethod.method, resultingDirectMethod.method);
+        deferredRenamings.map(virtualMethod.getReference(), shadowedBy.getReference());
+        deferredRenamings.recordMove(
+            virtualMethod.getReference(), resultingDirectMethod.getReference());
       }
 
       if (abortMerge) {
@@ -1046,7 +1054,7 @@
       // Step 2: Merge fields
       Set<DexString> existingFieldNames = new HashSet<>();
       for (DexEncodedField field : target.fields()) {
-        existingFieldNames.add(field.field.name);
+        existingFieldNames.add(field.getReference().name);
       }
 
       // In principle, we could allow multiple fields with the same name, and then only rename the
@@ -1223,8 +1231,8 @@
     private DexEncodedMethod buildBridgeMethod(
         DexEncodedMethod method, DexEncodedMethod invocationTarget) {
       DexType holder = target.type;
-      DexProto proto = method.method.proto;
-      DexString name = method.method.name;
+      DexProto proto = method.getReference().proto;
+      DexString name = method.getReference().name;
       DexMethod newMethod = application.dexItemFactory.createMethod(holder, proto, name);
       MethodAccessFlags accessFlags = method.accessFlags.copy();
       accessFlags.setBridge();
@@ -1235,8 +1243,8 @@
       SynthesizedBridgeCode code =
           new SynthesizedBridgeCode(
               newMethod,
-              appView.graphLens().getOriginalMethodSignature(method.method),
-              invocationTarget.method,
+              appView.graphLens().getOriginalMethodSignature(method.getReference()),
+              invocationTarget.getReference(),
               invocationTarget.isPrivateMethod() ? DIRECT : STATIC,
               target.isInterface());
 
@@ -1265,7 +1273,7 @@
 
     // Returns the method that shadows the given method, or null if method is not shadowed.
     private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
-      ResolutionResult resolutionResult = appInfo.resolveMethodOn(target, method.method);
+      ResolutionResult resolutionResult = appInfo.resolveMethodOn(target, method.getReference());
       if (!resolutionResult.isSingleResolution()) {
         // May happen in case of missing classes, or if multiple implementations were found.
         abortMerge = true;
@@ -1285,7 +1293,7 @@
               "The non-abstract type `"
                   + target.type.toSourceString()
                   + "` does not implement the method `"
-                  + method.method.toSourceString()
+                  + method.getReference().toSourceString()
                   + "`.");
         }
       }
@@ -1321,8 +1329,8 @@
       int i = 0;
       for (DexEncodedField field : sourceFields) {
         DexEncodedField resultingField = renameFieldIfNeeded(field, availableFieldSignatures);
-        existingFieldNames.add(resultingField.field.name);
-        deferredRenamings.map(field.field, resultingField.field);
+        existingFieldNames.add(resultingField.getReference().name);
+        deferredRenamings.map(field.getReference(), resultingField.getReference());
         result[i] = resultingField;
         i++;
       }
@@ -1354,14 +1362,15 @@
       do {
         DexString newName = getFreshName(TEMPORARY_INSTANCE_INITIALIZER_PREFIX, count, oldHolder);
         newSignature =
-            application.dexItemFactory.createMethod(target.type, method.method.proto, newName);
+            application.dexItemFactory.createMethod(
+                target.type, method.getReference().proto, newName);
         count++;
       } while (!availableMethodSignatures.test(newSignature));
 
       DexEncodedMethod result = method.toTypeSubstitutedMethod(newSignature);
       result.getMutableOptimizationInfo().markForceInline();
-      deferredRenamings.map(method.method, result.method);
-      deferredRenamings.recordMove(method.method, result.method);
+      deferredRenamings.map(method.getReference(), result.getReference());
+      deferredRenamings.recordMove(method.getReference(), result.getReference());
       // Renamed constructors turn into ordinary private functions. They can be private, as
       // they are only references from their direct subclass, which they were merged into.
       result.accessFlags.unsetConstructor();
@@ -1371,7 +1380,7 @@
 
     private DexEncodedMethod renameMethod(
         DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures, Rename strategy) {
-      return renameMethod(method, availableMethodSignatures, strategy, method.method.proto);
+      return renameMethod(method, availableMethodSignatures, strategy, method.getReference().proto);
     }
 
     private DexEncodedMethod renameMethod(
@@ -1382,7 +1391,7 @@
       // We cannot handle renaming static initializers yet and constructors should have been
       // renamed already.
       assert !method.accessFlags.isConstructor() || strategy == Rename.NEVER;
-      DexString oldName = method.method.name;
+      DexString oldName = method.getReference().name;
       DexType oldHolder = method.getHolderType();
 
       DexMethod newSignature;
@@ -1417,17 +1426,18 @@
 
     private DexEncodedField renameFieldIfNeeded(
         DexEncodedField field, Predicate<DexField> availableFieldSignatures) {
-      DexString oldName = field.field.name;
+      DexString oldName = field.getReference().name;
       DexType oldHolder = field.getHolderType();
 
       DexField newSignature =
-          application.dexItemFactory.createField(target.type, field.field.type, oldName);
+          application.dexItemFactory.createField(target.type, field.getReference().type, oldName);
       if (!availableFieldSignatures.test(newSignature)) {
         int count = 1;
         do {
           DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
           newSignature =
-              application.dexItemFactory.createField(target.type, field.field.type, newName);
+              application.dexItemFactory.createField(
+                  target.type, field.getReference().type, newName);
           count++;
         } while (!availableFieldSignatures.test(newSignature));
       }
@@ -1520,7 +1530,7 @@
     @Override
     public DexEncodedMethod recordMethodChange(
         DexEncodedMethod method, DexEncodedMethod newMethod) {
-      recordMethodChange(method.method, newMethod.method);
+      recordMethodChange(method.getReference(), newMethod.getReference());
       if (newMethod.isNonPrivateVirtualMethod()) {
         // Since we changed the return type or one of the parameters, this method cannot be a
         // classpath or library method override, since we only class merge program classes.
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 008380c..467c168 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -11,8 +11,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -75,16 +74,9 @@
       Set<DexMethod> mergedMethods,
       Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
           contextualVirtualToDirectMethodMaps,
-      BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures,
-      Map<DexMethod, DexMethod> originalMethodSignaturesForBridges,
-      GraphLens previousLens) {
-    super(
-        mergedClasses.getForwardMap(),
-        methodMap,
-        fieldMap,
-        originalMethodSignatures,
-        previousLens,
-        appView.dexItemFactory());
+      BidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures,
+      Map<DexMethod, DexMethod> originalMethodSignaturesForBridges) {
+    super(appView, fieldMap, methodMap, mergedClasses.getForwardMap(), newMethodSignatures);
     this.appView = appView;
     this.mergedClasses = mergedClasses;
     this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
@@ -172,7 +164,7 @@
     private final Map<DexType, Map<DexMethod, GraphLensLookupResultProvider>>
         contextualVirtualToDirectMethodMaps = new IdentityHashMap<>();
 
-    private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures =
+    private final MutableBidirectionalOneToOneMap<DexMethod, DexMethod> newMethodSignatures =
         new BidirectionalOneToOneHashMap<>();
     private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges =
         new IdentityHashMap<>();
@@ -216,8 +208,8 @@
               context);
         }
       }
-      builder.originalMethodSignatures.forEach(
-          (renamedMethodSignature, originalMethodSignature) ->
+      builder.newMethodSignatures.forEach(
+          (originalMethodSignature, renamedMethodSignature) ->
               newBuilder.recordMove(
                   originalMethodSignature,
                   builder.getMethodSignatureAfterClassMerging(
@@ -244,9 +236,8 @@
           methodMap,
           mergedMethodsBuilder.build(),
           contextualVirtualToDirectMethodMaps,
-          originalMethodSignatures,
-          originalMethodSignaturesForBridges,
-          appView.graphLens());
+          newMethodSignatures,
+          originalMethodSignaturesForBridges);
     }
 
     private DexField getFieldSignatureAfterClassMerging(
@@ -309,7 +300,7 @@
     }
 
     public boolean hasOriginalSignatureMappingFor(DexMethod method) {
-      return originalMethodSignatures.containsKey(method)
+      return newMethodSignatures.containsValue(method)
           || originalMethodSignaturesForBridges.containsKey(method);
     }
 
@@ -327,7 +318,7 @@
     }
 
     public void recordMove(DexMethod from, DexMethod to) {
-      originalMethodSignatures.put(to, from);
+      newMethodSignatures.put(from, to);
     }
 
     public void recordCreationOfBridgeMethod(DexMethod from, DexMethod to) {
@@ -345,7 +336,7 @@
       fieldMap.putAll(builder.fieldMap);
       methodMap.putAll(builder.methodMap);
       mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
-      originalMethodSignatures.putAll(builder.originalMethodSignatures);
+      newMethodSignatures.putAll(builder.newMethodSignatures);
       originalMethodSignaturesForBridges.putAll(builder.originalMethodSignaturesForBridges);
       for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
         Map<DexMethod, GraphLensLookupResultProvider> current =
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
index d56e455..1099f62 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -211,8 +211,8 @@
           + '.'
           + method.getMethodName()
           + StringUtils.join(
-              ListUtils.map(method.getFormalTypes(), TypeReference::getTypeName),
               ",",
+              ListUtils.map(method.getFormalTypes(), TypeReference::getTypeName),
               BraceType.PARENS);
     }
     if (node instanceof FieldGraphNode) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index d610175..ed9d843 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.code.NumberGenerator;
@@ -28,11 +28,9 @@
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToManyRepresentativeHashMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalOneToManyRepresentativeMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
@@ -72,13 +70,11 @@
   public static class SyntheticFinalizationGraphLens extends NestedGraphLens {
 
     private SyntheticFinalizationGraphLens(
-        GraphLens previous,
-        Map<DexType, DexType> typeMap,
+        AppView<?> appView,
         BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
-        Map<DexMethod, DexMethod> methodMap,
-        BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures,
-        DexItemFactory factory) {
-      super(typeMap, methodMap, fieldMap, originalMethodSignatures, previous, factory);
+        BidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap,
+        Map<DexType, DexType> typeMap) {
+      super(appView, fieldMap, methodMap, typeMap);
     }
 
     @Override
@@ -89,13 +85,11 @@
 
   private static class Builder {
 
-    Map<DexType, DexType> typeMap = new IdentityHashMap<>();
-    BidirectionalManyToOneRepresentativeHashMap<DexField, DexField> fieldMap =
-        new BidirectionalManyToOneRepresentativeHashMap<>();
-    Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
-
-    protected final MutableBidirectionalOneToManyRepresentativeMap<DexMethod, DexMethod>
-        originalMethodSignatures = new BidirectionalOneToManyRepresentativeHashMap<>();
+    private final BidirectionalManyToOneRepresentativeHashMap<DexField, DexField> fieldMap =
+        BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
+    private final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap =
+        BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
+    private final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
 
     void move(DexType from, DexType to) {
       DexType old = typeMap.put(from, to);
@@ -108,22 +102,14 @@
     }
 
     void move(DexMethod from, DexMethod to) {
-      DexMethod old = methodMap.put(from, to);
-      assert old == null || old == to;
-      originalMethodSignatures.put(to, from);
+      methodMap.put(from, to);
     }
 
-    SyntheticFinalizationGraphLens build(GraphLens previous, DexItemFactory factory) {
+    SyntheticFinalizationGraphLens build(AppView<?> appView) {
       if (typeMap.isEmpty() && fieldMap.isEmpty() && methodMap.isEmpty()) {
         return null;
       }
-      return new SyntheticFinalizationGraphLens(
-          previous,
-          typeMap,
-          fieldMap,
-          methodMap,
-          originalMethodSignatures,
-          factory);
+      return new SyntheticFinalizationGraphLens(appView, fieldMap, methodMap, typeMap);
     }
   }
 
@@ -262,7 +248,7 @@
             new CommittedSyntheticsCollection(
                 committed.getLegacyTypes(), finalMethods, finalClasses),
             ImmutableList.of()),
-        lensBuilder.build(appView.graphLens(), appView.dexItemFactory()),
+        lensBuilder.build(appView),
         PrunedItems.builder().setPrunedApp(application).addRemovedClasses(prunedSynthetics).build(),
         mainDexInfoBuilder.build());
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 7148738..cb1e91b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -31,6 +31,7 @@
     HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", false, true),
     // Method synthetics.
+    RECORD_HELPER("Record", true),
     BACKPORT("Backport", true),
     STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
     TO_STRING_IF_NOT_NULL("ToStringIfNotNull", true),
diff --git a/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java b/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java
index c28ef5d..b768c52 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/KeepRuleFormatter.java
@@ -73,7 +73,7 @@
   @Override
   protected void printPackageNames(List<String> packageNames) {
     if (!packageNames.isEmpty()) {
-      append("-keeppackagenames " + StringUtils.join(packageNames, ",") + System.lineSeparator());
+      append("-keeppackagenames " + StringUtils.join(",", packageNames) + System.lineSeparator());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 039f28d..16a6bfc 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -301,7 +301,7 @@
       addType(field.type);
       DexEncodedField baseField = appInfo.resolveField(field).getResolvedField();
       if (baseField != null && baseField.getHolderType() != field.holder) {
-        field = baseField.field;
+        field = baseField.getReference();
       }
       addType(field.holder);
       TracedFieldImpl tracedField = new TracedFieldImpl(field, baseField);
@@ -381,9 +381,9 @@
       ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
       DexEncodedMethod target =
           resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
-      if (target != null && target.method != method) {
+      if (target != null && target.getReference() != method) {
         addType(method.holder);
-        addMethod(target.method);
+        addMethod(target.getReference());
       } else {
         addMethod(method);
       }
@@ -397,9 +397,9 @@
     @Override
     public void registerInvokeStatic(DexMethod method) {
       DexEncodedMethod target = appInfo.unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
-      if (target != null && target.method != method) {
+      if (target != null && target.getReference() != method) {
         addType(method.holder);
-        addMethod(target.method);
+        addMethod(target.getReference());
       } else {
         addMethod(method);
       }
@@ -456,7 +456,7 @@
     }
 
     private void registerField(DexEncodedField field) {
-      registerTypeReference(field.field.type);
+      registerTypeReference(field.getReference().type);
     }
 
     private void registerMethod(ProgramMethod method) {
@@ -488,10 +488,11 @@
       clazz.forEachMethod(
           method -> {
             ResolutionResult resolutionResult =
-                appInfo.resolveMethodOn(superType, method.method, superType != clazz.superType);
+                appInfo.resolveMethodOn(
+                    superType, method.getReference(), superType != clazz.superType);
             DexEncodedMethod dexEncodedMethod = resolutionResult.getSingleTarget();
             if (dexEncodedMethod != null) {
-              addMethod(dexEncodedMethod.method);
+              addMethod(dexEncodedMethod.getReference());
             }
           });
     }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index d1f6e4c..d58387f 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -510,7 +510,7 @@
         for (String mainDexClass : getMainDexClasses()) {
           mainDexList.add(mainDexClass.replace(".", "/") + CLASS_EXTENSION);
         }
-        String join = StringUtils.join(mainDexList, "\n");
+        String join = StringUtils.join("\n", mainDexList);
         writeToZipStream(out, dumpMainDexListResourceFileName, join.getBytes(), ZipEntry.DEFLATED);
       }
       if (options.hasMainDexKeepRules()) {
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanBox.java b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
index f6b790d..5ad9494 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanBox.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
@@ -9,6 +9,7 @@
 public class BooleanBox {
 
   private boolean value;
+  private boolean assigned = false;
 
   public BooleanBox() {}
 
@@ -39,10 +40,23 @@
   }
 
   public void set(boolean value) {
+    assigned = true;
     this.value = value;
   }
 
   public void unset() {
     set(false);
   }
+
+  public void and(boolean value) {
+    set(value && this.value);
+  }
+
+  public void or(boolean value) {
+    set(value || this.value);
+  }
+
+  public boolean isAssigned() {
+    return assigned;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index c885d9e..23d71ee 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -120,9 +120,7 @@
    * has to be run at a join point, concurrent accesses may be confused.
    */
   public void clearType(DexType type) {
-    if (classes.containsKey(type)) {
-      classes.remove(type);
-    }
+    classes.remove(type);
     ClassProvider<T> provider = classProvider.get();
     if (provider == null) {
       return;
diff --git a/src/main/java/com/android/tools/r8/utils/ClassProvider.java b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
index af272b8..e46606e 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
@@ -72,7 +72,7 @@
   }
 
   public FilteringClassProvider<T> without(Set<DexType> filteredTypes) {
-    return new FilteringClassProvider(classKind, this, filteredTypes);
+    return new FilteringClassProvider<>(classKind, this, filteredTypes);
   }
 
   /** Create class provider for preloaded classes. */
@@ -167,7 +167,7 @@
     public FilteringClassProvider<T> without(Set<DexType> filteredTypes) {
       ImmutableSet<DexType> newSet =
           ImmutableSet.<DexType>builder().addAll(filteredOut).addAll(filteredTypes).build();
-      return new FilteringClassProvider(getClassKind(), provider, newSet);
+      return new FilteringClassProvider<>(getClassKind(), provider, newSet);
     }
 
     @Override
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 54bbbcc..7a99160 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -270,8 +270,6 @@
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
   public boolean cfToCfDesugar = false;
-  // TODO(b/172496438): Temporarily enable publicizing package-private overrides.
-  public boolean enablePackagePrivateAwarePublicization = false;
 
   public int callGraphLikelySpuriousCallEdgeThreshold = 50;
 
@@ -1909,4 +1907,12 @@
   public boolean canHaveSwitchMaxIntBug() {
     return isGeneratingDex() && minApiLevel < AndroidApiLevel.K.getLevel();
   }
+
+  // On Dalvik the methods Integer.parseInt and Long.parseLong does not support strings with a '+'
+  // prefix
+  //
+  // See b/182137865.
+  public boolean canParseNumbersWithPlusPrefix() {
+    return minApiLevel > AndroidApiLevel.K.getLevel();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index ce8396f..1354a33 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -12,6 +12,7 @@
 import java.util.List;
 import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -151,4 +152,10 @@
     }
     return !iterator.hasNext();
   }
+
+  public static <T, R> Iterable<R> fromMethod(Consumer<Consumer<T>> method, Function<T, R> mapper) {
+    List<R> ts = new ArrayList<>();
+    method.accept(t -> ts.add(mapper.apply(t)));
+    return ts;
+  }
 }
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 b1099e1..47ccd91 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -357,11 +357,12 @@
             }
           }
 
-          DexMethod originalMethod = appView.graphLens().getOriginalMethodSignature(method.method);
+          DexMethod originalMethod =
+              appView.graphLens().getOriginalMethodSignature(method.getReference());
           MethodSignature originalSignature =
               MethodSignature.fromDexMethod(originalMethod, originalMethod.holder != originalType);
 
-          DexString obfuscatedNameDexString = namingLens.lookupName(method.method);
+          DexString obfuscatedNameDexString = namingLens.lookupName(method.getReference());
           String obfuscatedName = obfuscatedNameDexString.toString();
 
           // Add simple "a() -> b" mapping if we won't have any other with concrete line numbers
@@ -467,7 +468,7 @@
       }
       allSeenAreInstanceInitializers = false;
       // If the method is pinned, we cannot minify it.
-      if (!keepInfo.isMinificationAllowed(method.method, appView, appView.options())) {
+      if (!keepInfo.isMinificationAllowed(method.getReference(), appView, appView.options())) {
         continue;
       }
       // With desugared library, call-backs names are reserved here.
@@ -477,16 +478,17 @@
       // We use the same name for interface names even if it has different types.
       DexProgramClass clazz = appView.definitionForProgramType(method.getHolderType());
       DexClassAndMethod lookupResult =
-          appView.appInfo().lookupMaximallySpecificMethod(clazz, method.method);
+          appView.appInfo().lookupMaximallySpecificMethod(clazz, method.getReference());
       if (lookupResult == null) {
         // We cannot rename methods we cannot look up.
         continue;
       }
-      String errorString = method.method.qualifiedName() + " is not kept but is overloaded";
+      String errorString = method.getReference().qualifiedName() + " is not kept but is overloaded";
       assert lookupResult.getHolder().isInterface() : errorString;
       // TODO(b/159113601): Reenable assert.
-      assert true || originalName == null || originalName.equals(method.method.name) : errorString;
-      originalName = method.method.name;
+      assert true || originalName == null || originalName.equals(method.getReference().name)
+          : errorString;
+      originalName = method.getReference().name;
     }
     return true;
   }
@@ -544,7 +546,7 @@
       Supplier<Builder> onDemandClassNamingBuilder) {
     clazz.forEachField(
         dexEncodedField -> {
-          DexField dexField = dexEncodedField.field;
+          DexField dexField = dexEncodedField.getReference();
           DexField originalField = graphLens.getOriginalFieldSignature(dexField);
           DexString renamedName = namingLens.lookupName(dexField);
           if (renamedName != originalField.name || originalField.holder != originalType) {
@@ -562,7 +564,7 @@
         new IdentityHashMap<>(clazz.getMethodCollection().size());
     for (DexEncodedMethod encodedMethod : clazz.methods()) {
       // Add method only if renamed, moved, or contains positions.
-      DexMethod method = encodedMethod.method;
+      DexMethod method = encodedMethod.getReference();
       DexString renamedName = namingLens.lookupName(method);
       if (renamedName != method.name
           || graphLens.getOriginalMethodSignature(method) != method
@@ -626,7 +628,7 @@
     PositionEventEmitter positionEventEmitter =
         new PositionEventEmitter(
             application.dexItemFactory,
-            appView.graphLens().getOriginalMethodSignature(method.method),
+            appView.graphLens().getOriginalMethodSignature(method.getReference()),
             processedEvents);
 
     Box<Boolean> inlinedOriginalPosition = new Box<>(false);
@@ -634,7 +636,8 @@
     // Debug event visitor to map line numbers.
     DexDebugEventVisitor visitor =
         new DexDebugPositionState(
-            debugInfo.startLine, appView.graphLens().getOriginalMethodSignature(method.method)) {
+            debugInfo.startLine,
+            appView.graphLens().getOriginalMethodSignature(method.getReference())) {
 
           // Keep track of what PC has been emitted.
           private int emittedPc = 0;
@@ -734,7 +737,7 @@
     Pair<Integer, Position> lastPosition = new Pair<>();
 
     DexDebugEventVisitor visitor =
-        new DexDebugPositionState(debugInfo.startLine, method.method) {
+        new DexDebugPositionState(debugInfo.startLine, method.getReference()) {
           @Override
           public void visit(Default defaultEvent) {
             super.visit(defaultEvent);
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index fff5425..8c3e592 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -62,10 +62,7 @@
     // All other conflicts are reported as a fatal error.
     return (DexProgramClass a, DexProgramClass b) -> {
       assert a.type == b.type;
-      if (a.originatesFromDexResource()
-          && b.originatesFromDexResource()
-          && a.accessFlags.isSynthetic()
-          && b.accessFlags.isSynthetic()) {
+      if (a.accessFlags.isSynthetic() && b.accessFlags.isSynthetic()) {
         return mergeClasses(reporter, a, b);
       }
       throw reportDuplicateTypes(reporter, a, b);
@@ -82,7 +79,8 @@
 
   private static DexProgramClass mergeClasses(
       Reporter reporter, DexProgramClass a, DexProgramClass b) {
-    if (a.type.isLegacySynthesizedTypeAllowedDuplication()) {
+    if (a.type.isLegacySynthesizedTypeAllowedDuplication()
+        || a.type.isSynthesizedTypeAllowedDuplication()) {
       assert assertEqualClasses(a, b);
       return a;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 8f398ac..9be7a5e 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -124,20 +124,20 @@
     return builder;
   }
 
-  public static <T> String join(Collection<T> collection, String separator) {
-    return join(collection, separator, BraceType.NONE);
+  public static String join(String separator, String... strings) {
+    return join(separator, Arrays.asList(strings));
+  }
+
+  public static <T> String join(String separator, Iterable<T> iterable) {
+    return join(separator, iterable, BraceType.NONE);
   }
 
   public static <T> String join(String separator, Iterable<T> iterable, Function<T, String> fn) {
     return join(separator, iterable, fn, BraceType.NONE);
   }
 
-  public static String join(String separator, String... strings) {
-    return join(Arrays.asList(strings), separator, BraceType.NONE);
-  }
-
-  public static <T> String join(Collection<T> collection, String separator, BraceType brace) {
-    return join(separator, collection, Object::toString, brace);
+  public static <T> String join(String separator, Iterable<T> iterable, BraceType brace) {
+    return join(separator, iterable, Object::toString, brace);
   }
 
   public static <T> String join(
@@ -174,7 +174,7 @@
   }
 
   public static <T> String joinLines(Collection<T> collection) {
-    return join(collection, LINE_SEPARATOR, BraceType.NONE);
+    return join(LINE_SEPARATOR, collection, BraceType.NONE);
   }
 
   public static List<String> splitLines(String content) {
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index e5fb33f..4279cb7 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -27,7 +27,7 @@
   }
 
   public static <T> WorkList<T> newIdentityWorkList() {
-    return new WorkList<T>(EqualityTest.IDENTITY);
+    return new WorkList<>(EqualityTest.IDENTITY);
   }
 
   public static <T> WorkList<T> newIdentityWorkList(T item) {
@@ -60,7 +60,7 @@
     items.forEach(workingList::addLast);
   }
 
-  public void addIfNotSeen(Iterable<T> items) {
+  public void addIfNotSeen(Iterable<? extends T> items) {
     items.forEach(this::addIfNotSeen);
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
index b75d224..1e53a14 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
@@ -17,11 +17,11 @@
   private final Map<K, V> backing;
   private final Map<V, Set<K>> inverse;
 
-  public BidirectionalManyToOneHashMap() {
-    this(new IdentityHashMap<>(), new IdentityHashMap<>());
+  public static <K, V> BidirectionalManyToOneHashMap<K, V> newIdentityHashMap() {
+    return new BidirectionalManyToOneHashMap<>(new IdentityHashMap<>(), new IdentityHashMap<>());
   }
 
-  private BidirectionalManyToOneHashMap(Map<K, V> backing, Map<V, Set<K>> inverse) {
+  protected BidirectionalManyToOneHashMap(Map<K, V> backing, Map<V, Set<K>> inverse) {
     this.backing = backing;
     this.inverse = inverse;
   }
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 4c2f3a8..ffd653e 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
@@ -13,7 +13,18 @@
     extends BidirectionalManyToOneHashMap<K, V>
     implements MutableBidirectionalManyToOneRepresentativeMap<K, V> {
 
-  private final Map<V, K> representatives = new IdentityHashMap<>();
+  private final Map<V, K> representatives;
+
+  public static <K, V> BidirectionalManyToOneRepresentativeHashMap<K, V> newIdentityHashMap() {
+    return new BidirectionalManyToOneRepresentativeHashMap<>(
+        new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
+  }
+
+  private BidirectionalManyToOneRepresentativeHashMap(
+      Map<K, V> backing, Map<V, Set<K>> inverse, Map<V, K> representatives) {
+    super(backing, inverse);
+    this.representatives = representatives;
+  }
 
   @Override
   public void clear() {
diff --git a/src/test/examplesJava9/backport/IntegerBackportJava9Main.java b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
index 30e9086..8188fe1 100644
--- a/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
+++ b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
@@ -22,10 +22,10 @@
   };
 
   public static void main(String[] args) {
-    testParseIntegerSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4."));
+    testParseIntegerSubsequenceWithRadix();
   }
 
-  private static void testParseIntegerSubsequenceWithRadix(boolean supportsPlusPrefix) {
+  private static void testParseIntegerSubsequenceWithRadix() {
     for (int value : interestingValues) {
       for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
         for (String prefix : new String[] {"", "x", "xxx"}) {
@@ -34,7 +34,7 @@
             int start = prefix.length();
             int end = valueString.length() - postfix.length();
             assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix));
-            if (value > 0 && supportsPlusPrefix) {
+            if (value > 0) {
               valueString = prefix + '+' + Long.toString(value, radix) + postfix;
               end++;
               assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix));
@@ -45,24 +45,29 @@
     }
 
     try {
-      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+      throw new AssertionError(Integer.parseInt("0", 0, 1, Character.MIN_RADIX - 1));
     } catch (IllegalArgumentException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+      throw new AssertionError(Integer.parseInt("0", 0, 1, Character.MAX_RADIX + 1));
     } catch (IllegalArgumentException expected) {
     }
 
     try {
-      throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+      throw new AssertionError(Integer.parseInt("", 0, 0, 16));
     } catch (NumberFormatException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16));
+      throw new AssertionError(Integer.parseInt("-", 0, 1, 16));
     } catch (NumberFormatException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+      throw new AssertionError(Integer.parseInt("+", 0, 1, 16));
+    } catch (NumberFormatException expected) {
+    }
+
+    try {
+      throw new AssertionError(Integer.parseInt("+a", 0, 2, 10));
     } catch (NumberFormatException expected) {
     }
 
diff --git a/src/test/examplesJava9/backport/LongBackportJava9Main.java b/src/test/examplesJava9/backport/LongBackportJava9Main.java
index 1634a43..f24c337 100644
--- a/src/test/examplesJava9/backport/LongBackportJava9Main.java
+++ b/src/test/examplesJava9/backport/LongBackportJava9Main.java
@@ -24,11 +24,11 @@
   };
 
   public static void main(String[] args) {
-    testParseLongSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4."));
+    testParseLongSubsequenceWithRadix();
     testParseUnsignedLongSubsequenceWithRadix();
   }
 
-  private static void testParseLongSubsequenceWithRadix(boolean supportsPlusPrefix) {
+  private static void testParseLongSubsequenceWithRadix() {
     for (long value : interestingValues) {
       for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
         for (String prefix : new String[] {"", "x", "xxx"}) {
@@ -37,7 +37,7 @@
             int start = prefix.length();
             int end = valueString.length() - postfix.length();
             assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix));
-            if (value > 0 && supportsPlusPrefix) {
+            if (value > 0) {
               valueString = prefix + "+" + Long.toString(value, radix) + postfix;
               end++;
               assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix));
@@ -48,24 +48,29 @@
     }
 
     try {
-      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+      throw new AssertionError(Long.parseLong("0", 0, 1, Character.MIN_RADIX - 1));
     } catch (IllegalArgumentException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+      throw new AssertionError(Long.parseLong("0", 0, 1, Character.MAX_RADIX + 1));
     } catch (IllegalArgumentException expected) {
     }
 
     try {
-      throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+      throw new AssertionError(Long.parseLong("", 0, 0, 16));
     } catch (NumberFormatException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16));
+      throw new AssertionError(Long.parseLong("-", 0, 1, 16));
     } catch (NumberFormatException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+      throw new AssertionError(Long.parseLong("+", 0, 1, 16));
+    } catch (NumberFormatException expected) {
+    }
+
+    try {
+      throw new AssertionError(Long.parseLong("+a", 0, 2, 10));
     } catch (NumberFormatException expected) {
     }
 
@@ -122,6 +127,11 @@
     } catch (NumberFormatException expected) {
     }
 
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("+a", 0, 2, 10));
+    } catch (NumberFormatException expected) {
+    }
+
     BigInteger overflow = new BigInteger("18446744073709551616");
     for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
       for (String prefix : new String[] {"", "x", "xxx", "+", "x+", "xxx+"}) {
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
index 8e33480..8e5dd69 100644
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
@@ -95,7 +95,17 @@
     ProcessBuilder builder = new ProcessBuilder(command);
     ProcessResult result = ToolHelper.runProcess(builder);
     assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
-    Assert.assertTrue(result.stdout, result.stdout.isEmpty());
-    Assert.assertTrue(result.stderr, result.stderr.isEmpty());
+    Assert.assertEquals("", filterOutMainDexListWarnings(result.stdout));
+    Assert.assertEquals("", result.stderr);
+  }
+
+  public static String filterOutMainDexListWarnings(String output) {
+    StringBuilder builder = new StringBuilder();
+    for (String line : output.split("\n")) {
+      if (!line.contains("Unsupported usage of main-dex list")) {
+        builder.append(line).append("\n");
+      }
+    }
+    return builder.toString();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/DeviceRunner.java b/src/test/java/com/android/tools/r8/DeviceRunner.java
index c68c8f2..fb46cfd 100644
--- a/src/test/java/com/android/tools/r8/DeviceRunner.java
+++ b/src/test/java/com/android/tools/r8/DeviceRunner.java
@@ -167,7 +167,8 @@
       throw new DeviceRunnerConfigurationException(
           "Running tests on more than one device is not yet supported. "
               + "Currently connected devices: ["
-              + StringUtils.join(Arrays.asList(connectedDevices), ",") + "]");
+              + StringUtils.join(",", Arrays.asList(connectedDevices))
+              + "]");
     }
 
     int exitStatus = -1;
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
index 0700a5f..03ba4b8 100644
--- a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
@@ -89,4 +89,9 @@
   public GenerateMainDexListTestBuilder addDataEntryResources(DataEntryResource... resources) {
     return addDataResources(Arrays.asList(resources));
   }
+
+  public GenerateMainDexListTestBuilder setMainDexListOutputPath(Path output) {
+    builder.setMainDexListOutputPath(output);
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
index ac3d89f..67753da 100644
--- a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -82,7 +81,7 @@
     ProcessBuilder builder = new ProcessBuilder(command);
     ProcessResult result = ToolHelper.runProcess(builder);
     assertEquals(result.stderr + "\n" + result.stdout, 0, result.exitCode);
-    assertTrue(result.stdout, result.stdout.isEmpty());
-    assertTrue(result.stderr, result.stderr.isEmpty());
+    assertEquals("", D8ApiBinaryCompatibilityTests.filterOutMainDexListWarnings(result.stdout));
+    assertEquals("", result.stderr);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index e1d8658..0d90a34 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -229,7 +229,7 @@
     return addMainDexRuleFiles(Arrays.asList(files));
   }
 
-  public T addMainDexClassRules(Class<?>... classes) {
+  public T addMainDexKeepClassRules(Class<?>... classes) {
     for (Class<?> clazz : classes) {
       addMainDexRules("-keep class " + clazz.getTypeName());
     }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index ba56c6f..97b1a5c 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -477,7 +477,7 @@
         .add(out.toString())
         .build();
     Consumer<ArtCommandBuilder> commandConsumer =
-        withArt6Plus64BitsLib && vm.getVersion().isAtLeast(DexVm.Version.V6_0_1)
+        withArt6Plus64BitsLib && vm.getVersion().isNewerThanOrEqual(DexVm.Version.V6_0_1)
             ? builder -> builder.appendArtOption("--64")
             : builder -> {};
     commandConsumer =
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index be45d1c..5a7423a 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -36,6 +36,13 @@
             .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport());
   }
 
+  public boolean canUseDefaultAndStaticInterfaceMethodsWhenDesugaring() {
+    assert isCfRuntime() || isDexRuntime();
+    assert apiLevel != null;
+    return getApiLevel()
+        .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport());
+  }
+
   // Convenience predicates.
   public boolean isDexRuntime() {
     return runtime.isDex();
@@ -49,6 +56,10 @@
     return runtime.isCf() && runtime.asCf().getVm() == vm;
   }
 
+  public boolean isDexRuntimeVersion(DexVm.Version vm) {
+    return isDexRuntime() && vm == getDexRuntimeVersion();
+  }
+
   public boolean isNoneRuntime() {
     return runtime == NoneRuntime.getInstance();
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 7721c08..9d0e9b5 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -180,10 +180,12 @@
 
   public static final Path DESUGAR_LIB_CONVERSIONS =
       Paths.get(LIBS_DIR, "library_desugar_conversions.zip");
+  public static final String DESUGAR_LIB_JSON_DIR =
+      System.getProperty("desugar_jdk_json_dir", "src/library_desugar");
   public static final Path DESUGAR_LIB_JSON_FOR_TESTING =
-      Paths.get("src/library_desugar/desugar_jdk_libs.json");
+      Paths.get(DESUGAR_LIB_JSON_DIR, "desugar_jdk_libs.json");
   public static final Path DESUGAR_LIB_JSON_FOR_TESTING_ALTERNATIVE_3 =
-      Paths.get("src/library_desugar/desugar_jdk_libs_alternative_3.json");
+      Paths.get(DESUGAR_LIB_JSON_DIR, "desugar_jdk_libs_alternative_3.json");
 
   public static boolean isLocalDevelopment() {
     return System.getProperty("local_development", "0").equals("1");
@@ -266,7 +268,7 @@
       }
 
       public boolean isLatest() {
-        return this == V10_0_0;
+        return this == last();
       }
 
       public boolean isNewerThan(Version other) {
@@ -277,10 +279,6 @@
         return compareTo(other) >= 0;
       }
 
-      public boolean isAtLeast(Version other) {
-        return compareTo(other) >= 0;
-      }
-
       public boolean isOlderThan(Version other) {
         return compareTo(other) < 0;
       }
@@ -289,11 +287,16 @@
         return compareTo(other) <= 0;
       }
 
+      public boolean isInRangeInclusive(Version start, Version end) {
+        assert start.isOlderThanOrEqual(end);
+        return isNewerThanOrEqual(start) && isOlderThanOrEqual(end);
+      }
+
       public String toString() {
         return shortName;
       }
 
-      private String shortName;
+      private final String shortName;
 
       public static Version first() {
         return V4_0_4;
diff --git a/src/test/java/com/android/tools/r8/VmTestRunner.java b/src/test/java/com/android/tools/r8/VmTestRunner.java
index a3e76a4..e2028a4 100644
--- a/src/test/java/com/android/tools/r8/VmTestRunner.java
+++ b/src/test/java/com/android/tools/r8/VmTestRunner.java
@@ -78,7 +78,7 @@
     IgnoreIfVmOlderThan ignoreIfVmOlderThan =
         child.getAnnotation(IgnoreIfVmOlderThan.class);
     if (ignoreIfVmOlderThan != null
-        && !currentVersion.isAtLeast(ignoreIfVmOlderThan.value())) {
+        && !currentVersion.isNewerThanOrEqual(ignoreIfVmOlderThan.value())) {
       return true;
     }
     IgnoreIfVmOlderOrEqualThan ignoreIfVmOlderOrEqualThan =
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerBottomTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerBottomTest.java
index 3c71dbe..6dc8328 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerBottomTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerBottomTest.java
@@ -4,6 +4,10 @@
 
 package com.android.tools.r8.accessrelaxation;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
@@ -12,6 +16,8 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -58,8 +64,17 @@
         .enableNeverClassInliningAnnotations()
         .allowAccessModification()
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/181328496): This should be EXPECTED.
-        .assertSuccessWithOutputLines(EXPECTED_ART_4);
+        // TODO(b/182185057): This is an error in the devirtualizer
+        .assertSuccessWithOutputLines(EXPECTED_ART_4)
+        .inspect(
+            inspector -> {
+              ClassSubject subViewModelSubject =
+                  inspector.clazz(DescriptorUtils.descriptorToJavaType(NEW_DESCRIPTOR));
+              assertThat(subViewModelSubject, isPresent());
+              MethodSubject clearSubject = subViewModelSubject.uniqueMethodWithName("clear");
+              assertThat(clearSubject, isPresent());
+              assertTrue(clearSubject.isPublic());
+            });
   }
 
   private byte[] getSubViewModelInAnotherPackage() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
index 5a9da04..ccd0b21 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverridePublicizerTest.java
@@ -23,7 +23,6 @@
 
   private final TestParameters parameters;
   private final String[] EXPECTED = new String[] {"SubViewModel.clear()", "ViewModel.clear()"};
-  private final String[] R8_OUT = new String[] {"SubViewModel.clear()", "SubViewModel.clear()"};
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -52,8 +51,7 @@
         .enableNeverClassInliningAnnotations()
         .allowAccessModification()
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/172496438): This should be EXPECTED.
-        .assertSuccessWithOutputLines(R8_OUT);
+        .apply(this::assertSuccessOutput);
   }
 
   private void assertSuccessOutput(TestRunResult<?> result) {
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
index 30b2a78..492e21e 100644
--- a/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/cf/stackmap/UninitializedInstanceOfTest.java
@@ -59,7 +59,7 @@
   @Test()
   public void testD8Dex() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    boolean expectFailure = parameters.getDexRuntimeVersion().isAtLeast(Version.V7_0_0);
+    boolean expectFailure = parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0);
     testForD8(parameters.getBackend())
         .addProgramClassFileData(dump())
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index fcc79dc..2c58d15 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -111,7 +111,7 @@
                   continue;
                 }
                 String holderName = method.getHolderType().getName();
-                String methodName = method.method.name.toString();
+                String methodName = method.getReference().name.toString();
                 String generatedMethodName = holderName + "_" + methodName;
                 CfCode code = getCode(holderName, methodName, method.getCode().asCfCode());
                 if (code != null) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingNonTrivialTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingNonTrivialTest.java
index 42df5c0..fbd058a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingNonTrivialTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingNonTrivialTest.java
@@ -12,9 +12,8 @@
 
 public class AbstractMethodMergingNonTrivialTest extends HorizontalClassMergingTestBase {
 
-  public AbstractMethodMergingNonTrivialTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public AbstractMethodMergingNonTrivialTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,11 +21,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingTest.java
index 3b0af4f..a30ca9e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AbstractMethodMergingTest.java
@@ -12,9 +12,8 @@
 
 public class AbstractMethodMergingTest extends HorizontalClassMergingTestBase {
 
-  public AbstractMethodMergingTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public AbstractMethodMergingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,11 +21,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptResourceFileContentsTest.java
index db8d4c7..cdd0df8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptResourceFileContentsTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
@@ -23,9 +23,8 @@
 import org.junit.Test;
 
 public class AdaptResourceFileContentsTest extends HorizontalClassMergingTestBase {
-  public AdaptResourceFileContentsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public AdaptResourceFileContentsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -35,9 +34,6 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(getClass())
             .addKeepMainRule(Main.class)
-            .addOptionsModification(
-                options ->
-                    options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
             .addOptionsModification(options -> options.dataResourceConsumer = dataResourceConsumer)
             .enableNeverClassInliningAnnotations()
             .addDataEntryResources(
@@ -45,8 +41,7 @@
                     "foo.txt", Origin.unknown(), A.class.getTypeName(), B.class.getTypeName()))
             .addKeepRules("-adaptresourcefilecontents foo.txt")
             .setMinApi(parameters.getApiLevel())
-            .addHorizontallyMergedClassesInspectorIf(
-                enableHorizontalClassMerging,
+            .addHorizontallyMergedClassesInspector(
                 inspector -> inspector.assertMergedInto(B.class, A.class))
             .compile()
             .run(parameters.getRuntime(), Main.class)
@@ -57,11 +52,10 @@
     assertThat(aClassSubject, isPresent());
 
     ClassSubject bClassSubject = codeInspector.clazz(B.class);
-    assertThat(bClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
+    assertThat(bClassSubject, isAbsent());
 
     // Check that the class name has been rewritten.
-    String newClassBName =
-        (enableHorizontalClassMerging ? aClassSubject : bClassSubject).getFinalName();
+    String newClassBName = aClassSubject.getFinalName();
     assertEquals(
         dataResourceConsumer.get("foo.txt"),
         ImmutableList.of(aClassSubject.getFinalName(), newClassBName));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java
index 090cb9e..1cbf805 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -21,9 +20,8 @@
 import org.junit.Test;
 
 public class AdaptVerticallyMergedResourceFileContentsTest extends HorizontalClassMergingTestBase {
-  public AdaptVerticallyMergedResourceFileContentsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public AdaptVerticallyMergedResourceFileContentsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -33,9 +31,6 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(getClass())
             .addKeepMainRule(Main.class)
-            .addOptionsModification(
-                options ->
-                    options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
             .addOptionsModification(options -> options.dataResourceConsumer = dataResourceConsumer)
             .enableNeverClassInliningAnnotations()
             .addDataEntryResources(
@@ -47,8 +42,7 @@
                     B.class.getTypeName()))
             .addKeepRules("-adaptresourcefilecontents foo.txt")
             .setMinApi(parameters.getApiLevel())
-            .addHorizontallyMergedClassesInspectorIf(
-                enableHorizontalClassMerging,
+            .addHorizontallyMergedClassesInspector(
                 inspector -> inspector.assertMergedInto(B.class, A.class))
             .addVerticallyMergedClassesInspector(
                 inspector -> inspector.assertMergedIntoSubtype(Parent.class))
@@ -63,11 +57,10 @@
     assertThat(aClassSubject, isPresent());
 
     ClassSubject bClassSubject = codeInspector.clazz(B.class);
-    assertThat(bClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
+    assertThat(bClassSubject, not(isPresent()));
 
     // Check that the class name has been rewritten.
-    String newClassName =
-        (enableHorizontalClassMerging ? aClassSubject : bClassSubject).getFinalName();
+    String newClassName = aClassSubject.getFinalName();
     assertEquals(
         dataResourceConsumer.get("foo.txt"),
         ImmutableList.of(aClassSubject.getFinalName(), aClassSubject.getFinalName(), newClassName));
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassWithInstanceFieldsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassWithInstanceFieldsTest.java
index 31a49cc..41071d8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassWithInstanceFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassWithInstanceFieldsTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -14,9 +14,8 @@
 import org.junit.Test;
 
 public class ClassWithInstanceFieldsTest extends HorizontalClassMergingTestBase {
-  public ClassWithInstanceFieldsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassWithInstanceFieldsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,21 +23,17 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A. field: 5, v: a, j: 1", "B. field: b, v: 2, j: 3")
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByDirectCheckCastTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByDirectCheckCastTest.java
index bc5380e..bbf9149 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByDirectCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByDirectCheckCastTest.java
@@ -13,9 +13,8 @@
 import org.junit.Test;
 
 public class ClassesDistinguishedByDirectCheckCastTest extends HorizontalClassMergingTestBase {
-  public ClassesDistinguishedByDirectCheckCastTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesDistinguishedByDirectCheckCastTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -23,9 +22,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByDirectInstanceOfTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByDirectInstanceOfTest.java
index 6551ad2..d380c43 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByDirectInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByDirectInstanceOfTest.java
@@ -13,9 +13,8 @@
 import org.junit.Test;
 
 public class ClassesDistinguishedByDirectInstanceOfTest extends HorizontalClassMergingTestBase {
-  public ClassesDistinguishedByDirectInstanceOfTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesDistinguishedByDirectInstanceOfTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -23,9 +22,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByIndirectCheckCastToInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByIndirectCheckCastToInterfaceTest.java
index 6017cbe..9847ac8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByIndirectCheckCastToInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistinguishedByIndirectCheckCastToInterfaceTest.java
@@ -15,9 +15,8 @@
 
 public class ClassesDistinguishedByIndirectCheckCastToInterfaceTest
     extends HorizontalClassMergingTestBase {
-  public ClassesDistinguishedByIndirectCheckCastToInterfaceTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesDistinguishedByIndirectCheckCastToInterfaceTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,9 +24,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNoVerticalClassMergingAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast.java
index cf05da6..8bf955c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast.java
@@ -15,9 +15,8 @@
 
 public class ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast
     extends HorizontalClassMergingTestBase {
-  public ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesDistuingishedByIndirectInstanceOfInterfaceCheckCast(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,9 +24,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentFieldsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentFieldsTest.java
index d322729..7e90337 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentFieldsTest.java
@@ -6,8 +6,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -16,9 +16,8 @@
 import org.junit.Test;
 
 public class ClassesWithDifferentFieldsTest extends HorizontalClassMergingTestBase {
-  public ClassesWithDifferentFieldsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesWithDifferentFieldsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -26,22 +25,18 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .compile()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A. v: a", "B. i: 2")
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
index 5abdc09..497e4df 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -15,9 +15,8 @@
 import org.junit.Test;
 
 public class ClassesWithDifferentInterfacesTest extends HorizontalClassMergingTestBase {
-  public ClassesWithDifferentInterfacesTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesWithDifferentInterfacesTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,15 +24,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(Z.class, Y.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(Z.class, Y.class))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("bar", "foo y", "bar")
         .inspect(
@@ -41,8 +37,7 @@
               assertThat(codeInspector.clazz(I.class), isPresent());
               assertThat(codeInspector.clazz(X.class), isPresent());
               assertThat(codeInspector.clazz(Y.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(Z.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(Z.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentVisibilityFieldsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentVisibilityFieldsTest.java
index 68a81b1..269b793 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentVisibilityFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentVisibilityFieldsTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static com.android.tools.r8.utils.codeinspector.Matchers.readsInstanceField;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -21,9 +21,8 @@
 import org.junit.Test;
 
 public class ClassesWithDifferentVisibilityFieldsTest extends HorizontalClassMergingTestBase {
-  public ClassesWithDifferentVisibilityFieldsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesWithDifferentVisibilityFieldsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -31,14 +30,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(
             "a. v1: 10, v2: 20", "b. v1: 60, v2: 100", "c. v1: 210, v2: 330")
@@ -46,31 +42,28 @@
             codeInspector -> {
               ClassSubject aClassSubject = codeInspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
               assertThat(codeInspector.clazz(C.class), isPresent());
 
-              if (enableHorizontalClassMerging) {
-                FieldSubject v1Subject = aClassSubject.uniqueFieldWithName("v1");
-                FieldSubject v2Subject = aClassSubject.uniqueFieldWithName("v2");
+              FieldSubject v1Subject = aClassSubject.uniqueFieldWithName("v1");
+              FieldSubject v2Subject = aClassSubject.uniqueFieldWithName("v2");
 
-                MethodSubject methodSubject = aClassSubject.uniqueMethodWithName("getAV1");
-                assertThat(methodSubject, isPresent());
-                assertThat(methodSubject, readsInstanceField(v1Subject.getDexField()));
+              MethodSubject methodSubject = aClassSubject.uniqueMethodWithName("getAV1");
+              assertThat(methodSubject, isPresent());
+              assertThat(methodSubject, readsInstanceField(v1Subject.getDexField()));
 
-                methodSubject = aClassSubject.uniqueMethodWithName("getAV2");
-                assertThat(methodSubject, isPresent());
-                assertThat(methodSubject, readsInstanceField(v2Subject.getDexField()));
+              methodSubject = aClassSubject.uniqueMethodWithName("getAV2");
+              assertThat(methodSubject, isPresent());
+              assertThat(methodSubject, readsInstanceField(v2Subject.getDexField()));
 
-                // The fields v1 and v2 are swapped, because their access modifiers are swapped.
-                methodSubject = aClassSubject.uniqueMethodWithName("getBV1");
-                assertThat(methodSubject, isPresent());
-                assertThat(methodSubject, readsInstanceField(v2Subject.getDexField()));
+              // The fields v1 and v2 are swapped, because their access modifiers are swapped.
+              methodSubject = aClassSubject.uniqueMethodWithName("getBV1");
+              assertThat(methodSubject, isPresent());
+              assertThat(methodSubject, readsInstanceField(v2Subject.getDexField()));
 
-                methodSubject = aClassSubject.uniqueMethodWithName("getBV2");
-                assertThat(methodSubject, isPresent());
-                assertThat(methodSubject, readsInstanceField(v1Subject.getDexField()));
-              }
+              methodSubject = aClassSubject.uniqueMethodWithName("getBV2");
+              assertThat(methodSubject, isPresent());
+              assertThat(methodSubject, readsInstanceField(v1Subject.getDexField()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
index 9ac569b..3f0fac0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
@@ -5,30 +5,26 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runners.Parameterized;
 
 public class ClassesWithFeatureSplitTest extends HorizontalClassMergingTestBase {
-  public ClassesWithFeatureSplitTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesWithFeatureSplitTest(TestParameters parameters) {
+    super(parameters);
   }
 
-  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   @Test
@@ -41,9 +37,6 @@
             .addFeatureSplit(Feature2Class.class, Feature2Main.class)
             .addKeepFeatureMainRule(Feature1Main.class)
             .addKeepFeatureMainRule(Feature2Main.class)
-            .addOptionsModification(
-                options ->
-                    options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
             .enableNeverClassInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
@@ -68,8 +61,7 @@
   private void inspectFeature1(CodeInspector inspector) {
     assertThat(inspector.clazz(Feature1Main.class), isPresent());
     assertThat(inspector.clazz(Feature1Class1.class), isPresent());
-    assertThat(
-        inspector.clazz(Feature1Class2.class), notIf(isPresent(), enableHorizontalClassMerging));
+    assertThat(inspector.clazz(Feature1Class2.class), not(isPresent()));
     assertThat(inspector.clazz(Feature2Main.class), not(isPresent()));
     assertThat(inspector.clazz(Feature2Class.class), not(isPresent()));
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
index 6714388..9a5f503 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -14,9 +14,8 @@
 import org.junit.Test;
 
 public class ClassesWithIdenticalInterfacesTest extends HorizontalClassMergingTestBase {
-  public ClassesWithIdenticalInterfacesTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesWithIdenticalInterfacesTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,14 +23,10 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector.assertMergedInto(Y.class, X.class).assertMergedInto(Z.class, X.class))
         .run(parameters.getRuntime(), Main.class)
@@ -40,10 +35,8 @@
             codeInspector -> {
               assertThat(codeInspector.clazz(I.class), isPresent());
               assertThat(codeInspector.clazz(X.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(Y.class), notIf(isPresent(), enableHorizontalClassMerging));
-              assertThat(
-                  codeInspector.clazz(Z.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(Y.class), isAbsent());
+              assertThat(codeInspector.clazz(Z.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
index c551a26..dcdd8f8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
@@ -16,9 +16,8 @@
 import org.junit.Test;
 
 public class ClassesWithNativeMethodsTest extends HorizontalClassMergingTestBase {
-  public ClassesWithNativeMethodsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesWithNativeMethodsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -26,9 +25,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java
index 608f6fe..db0ce63 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithOverlappingVisibilitiesTest.java
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPackagePrivate;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -18,9 +18,8 @@
 import org.junit.Test;
 
 public class ClassesWithOverlappingVisibilitiesTest extends HorizontalClassMergingTestBase {
-  public ClassesWithOverlappingVisibilitiesTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesWithOverlappingVisibilitiesTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -28,9 +27,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -45,23 +41,18 @@
 
               ClassSubject bClassSubject = codeInspector.clazz(B.class);
               assertThat(bClassSubject, isPresent());
-              if (enableHorizontalClassMerging) {
-                methodSubject = bClassSubject.method("void", "foo$bridge");
-                assertThat(methodSubject, isPackagePrivate());
-              }
+              methodSubject = bClassSubject.method("void", "foo$bridge");
+              assertThat(methodSubject, isPackagePrivate());
 
-              assertThat(
-                  codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(C.class), isAbsent());
 
               ClassSubject dClassSubject = codeInspector.clazz(D.class);
               assertThat(dClassSubject, isPresent());
-              if (enableHorizontalClassMerging) {
-                methodSubject = dClassSubject.method("void", "foo$bridge");
-                assertThat(methodSubject, isPublic());
-              }
+              methodSubject = dClassSubject.method("void", "foo$bridge");
+              assertThat(methodSubject, isPublic());
 
               ClassSubject eClassSubject = codeInspector.clazz(E.class);
-              assertThat(eClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(eClassSubject, isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithStaticFields.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithStaticFields.java
index 2810ce8..d66a630 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithStaticFields.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithStaticFields.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -13,8 +13,8 @@
 import org.junit.Test;
 
 public class ClassesWithStaticFields extends HorizontalClassMergingTestBase {
-  public ClassesWithStaticFields(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ClassesWithStaticFields(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,20 +22,16 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("a: 0", "b: 0", "a: 1", "b: 1")
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.java
index 9aca9fe..364f7ed 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompanionClassMergingTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -16,9 +16,8 @@
 import org.junit.Test;
 
 public class CompanionClassMergingTest extends HorizontalClassMergingTestBase {
-  public CompanionClassMergingTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public CompanionClassMergingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -26,14 +25,10 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .addOptionsModification(options -> options.enableClassInlining = false)
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertMergedInto(B.Companion.class, A.Companion.class))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("foo a 0", "foo b 1")
@@ -43,9 +38,7 @@
               assertThat(codeInspector.clazz(B.class), isPresent());
 
               assertThat(codeInspector.clazz(A.Companion.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.Companion.class),
-                  notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.Companion.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
index c03361f..ccec2d8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -14,9 +14,8 @@
 import org.junit.Test;
 
 public class CompatKeepConstructorLiveTest extends HorizontalClassMergingTestBase {
-  public CompatKeepConstructorLiveTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public CompatKeepConstructorLiveTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,9 +23,6 @@
     testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
@@ -39,8 +35,7 @@
               assertThat(aClassSubject.init(), isPresent());
 
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
index 369ebfd..07cea40 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
@@ -5,20 +5,17 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.IsNot.not;
 
 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;
 
 public class ConstructorCantInlineTest extends HorizontalClassMergingTestBase {
-  public ConstructorCantInlineTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ConstructorCantInlineTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -26,9 +23,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -39,8 +33,7 @@
               assertThat(codeInspector.clazz(A.class), not(isPresent()));
               assertThat(codeInspector.clazz(B.class), isPresent());
               assertThat(codeInspector.clazz(C.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(D.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(D.class), not(isPresent()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingAfterUnusedArgumentRemovalTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingAfterUnusedArgumentRemovalTest.java
index d38efb3..fd1a5ac 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingAfterUnusedArgumentRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingAfterUnusedArgumentRemovalTest.java
@@ -11,9 +11,8 @@
 public class ConstructorMergingAfterUnusedArgumentRemovalTest
     extends HorizontalClassMergingTestBase {
 
-  public ConstructorMergingAfterUnusedArgumentRemovalTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ConstructorMergingAfterUnusedArgumentRemovalTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -21,13 +20,9 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector
                     .assertMergedInto(B.class, A.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
index 8936dc1..055ab8d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingOverlapTest.java
@@ -21,9 +21,8 @@
 
 public class ConstructorMergingOverlapTest extends HorizontalClassMergingTestBase {
 
-  public ConstructorMergingOverlapTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ConstructorMergingOverlapTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -31,9 +30,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -41,7 +37,6 @@
         .assertSuccessWithOutputLines("42", "13", "7", "print a", "print b")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 ClassSubject aClassSubject = codeInspector.clazz(A.class);
                 assertThat(aClassSubject, isPresent());
                 FieldSubject classIdFieldSubject =
@@ -66,12 +61,6 @@
                 assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
-
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
index 4d62a05..e0c6c9e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
@@ -22,9 +22,8 @@
 
 public class ConstructorMergingPreoptimizedTest extends HorizontalClassMergingTestBase {
 
-  public ConstructorMergingPreoptimizedTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ConstructorMergingPreoptimizedTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -32,11 +31,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -46,38 +42,29 @@
             "changed", "13", "42", "foo", "7", "foo", "print a", "print b")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
-                ClassSubject changedClassSubject = codeInspector.clazz(Changed.class);
-                assertThat(changedClassSubject, isPresent());
+              ClassSubject changedClassSubject = codeInspector.clazz(Changed.class);
+              assertThat(changedClassSubject, isPresent());
 
-                ClassSubject aClassSubject = codeInspector.clazz(A.class);
-                assertThat(aClassSubject, isPresent());
-                FieldSubject classIdFieldSubject =
-                    aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
-                assertThat(classIdFieldSubject, isPresent());
+              ClassSubject aClassSubject = codeInspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              FieldSubject classIdFieldSubject =
+                  aClassSubject.uniqueFieldWithName(ClassMerger.CLASS_ID_FIELD_NAME);
+              assertThat(classIdFieldSubject, isPresent());
 
-                MethodSubject firstInitSubject = aClassSubject.init("int");
-                assertThat(firstInitSubject, isPresent());
-                assertThat(
-                    firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject firstInitSubject = aClassSubject.init("int");
+              assertThat(firstInitSubject, isPresent());
+              assertThat(firstInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject otherInitSubject =
-                    aClassSubject.init(changedClassSubject.getFinalName(), "int");
-                assertThat(otherInitSubject, isPresent());
-                assertThat(
-                    otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject otherInitSubject =
+                  aClassSubject.init(changedClassSubject.getFinalName(), "int");
+              assertThat(otherInitSubject, isPresent());
+              assertThat(otherInitSubject, writesInstanceField(classIdFieldSubject.getDexField()));
 
-                MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
-                assertThat(printSubject, isPresent());
-                assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
+              MethodSubject printSubject = aClassSubject.method("void", "print$bridge");
+              assertThat(printSubject, isPresent());
+              assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
-                assertThat(codeInspector.clazz(B.class), not(isPresent()));
-
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
+              assertThat(codeInspector.clazz(B.class), not(isPresent()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTest.java
index 21c3486..b76442b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTest.java
@@ -13,8 +13,8 @@
 import org.junit.Test;
 
 public class ConstructorMergingTest extends HorizontalClassMergingTestBase {
-  public ConstructorMergingTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ConstructorMergingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,23 +22,14 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("foo", "bar")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(A.class), isPresent());
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
index 50da81e..0dd1f3e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingTrivialOverlapTest.java
@@ -21,9 +21,8 @@
 
 public class ConstructorMergingTrivialOverlapTest extends HorizontalClassMergingTestBase {
 
-  public ConstructorMergingTrivialOverlapTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ConstructorMergingTrivialOverlapTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -31,9 +30,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -41,7 +37,6 @@
         .assertSuccessWithOutputLines("7", "42", "13", "print a", "print b")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 ClassSubject aClassSubject = codeInspector.clazz(A.class);
                 assertThat(aClassSubject, isPresent());
                 FieldSubject classIdFieldSubject =
@@ -66,12 +61,6 @@
                 assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
-
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingWithArgumentsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingWithArgumentsTest.java
index 7891de2..30a84ca 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingWithArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingWithArgumentsTest.java
@@ -15,9 +15,8 @@
 import org.junit.Test;
 
 public class ConstructorMergingWithArgumentsTest extends HorizontalClassMergingTestBase {
-  public ConstructorMergingWithArgumentsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ConstructorMergingWithArgumentsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,16 +24,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("foo hello", "bar world")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 ClassSubject aClassSubject = codeInspector.clazz(A.class);
 
                 assertThat(aClassSubject, isPresent());
@@ -42,11 +37,6 @@
 
                 MethodSubject initSubject = aClassSubject.init(String.class.getName(), "int");
                 assertThat(initSubject, isPresent());
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
index 749ae58..e063a4b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
@@ -11,9 +11,8 @@
 import org.junit.Test;
 
 public class DistinguishExceptionClassesTest extends HorizontalClassMergingTestBase {
-  public DistinguishExceptionClassesTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public DistinguishExceptionClassesTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -21,9 +20,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("test success")
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
index 5d63188..d33b180 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -15,8 +15,8 @@
 import org.junit.Test;
 
 public class EmptyClassTest extends HorizontalClassMergingTestBase {
-  public EmptyClassTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public EmptyClassTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,20 +24,16 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("a", "b: foo")
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldTypeMergedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldTypeMergedTest.java
index 58434a9..e688559 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldTypeMergedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldTypeMergedTest.java
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isFieldOfArrayType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isFieldOfType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -19,8 +19,8 @@
 import org.junit.Test;
 
 public class FieldTypeMergedTest extends HorizontalClassMergingTestBase {
-  public FieldTypeMergedTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public FieldTypeMergedTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -28,9 +28,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -40,28 +37,21 @@
             codeInspector -> {
               ClassSubject aClassSubject = codeInspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
 
               ClassSubject cClassSubject = codeInspector.clazz(C.class);
               assertThat(codeInspector.clazz(C.class), isPresent());
 
               FieldSubject fieldSubject = cClassSubject.uniqueFieldWithName("fieldB");
               assertThat(fieldSubject, isPresent());
-              if (enableHorizontalClassMerging) {
-                assertThat(
-                    fieldSubject, isFieldOfType(aClassSubject.getDexProgramClass().getType()));
-              }
+              assertThat(fieldSubject, isFieldOfType(aClassSubject.getDexProgramClass().getType()));
 
               fieldSubject = cClassSubject.uniqueFieldWithName("fieldArrayB");
               assertThat(fieldSubject, isPresent());
               assertTrue(fieldSubject.getDexField().type.isArrayType());
-              if (enableHorizontalClassMerging) {
-                assertThat(
-                    fieldSubject,
-                    isFieldOfArrayType(
-                        codeInspector, aClassSubject.getDexProgramClass().getType()));
-              }
+              assertThat(
+                  fieldSubject,
+                  isFieldOfArrayType(codeInspector, aClassSubject.getDexProgramClass().getType()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldsWithDifferentAccessFlagsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldsWithDifferentAccessFlagsTest.java
index 58acdbf..7468ce5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldsWithDifferentAccessFlagsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/FieldsWithDifferentAccessFlagsTest.java
@@ -14,9 +14,8 @@
 
 public class FieldsWithDifferentAccessFlagsTest extends HorizontalClassMergingTestBase {
 
-  public FieldsWithDifferentAccessFlagsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public FieldsWithDifferentAccessFlagsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,11 +23,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/GenericStaticFieldTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/GenericStaticFieldTest.java
index af72afe..e1248e1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/GenericStaticFieldTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/GenericStaticFieldTest.java
@@ -9,8 +9,8 @@
 import org.junit.Test;
 
 public class GenericStaticFieldTest extends HorizontalClassMergingTestBase {
-  public GenericStaticFieldTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public GenericStaticFieldTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -19,9 +19,6 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepRules("-keepattributes Signatures")
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         // .addHorizontallyMergedClassesInspectorIf(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
index 5b2dbe7..fa19056 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingTestBase.java
@@ -6,29 +6,25 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.util.List;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
 public abstract class HorizontalClassMergingTestBase extends TestBase {
-  protected final TestParameters parameters;
-  protected final boolean enableHorizontalClassMerging;
 
-  protected HorizontalClassMergingTestBase(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
+  protected final TestParameters parameters;
+
+  protected HorizontalClassMergingTestBase(TestParameters parameters) {
     this.parameters = parameters;
-    this.enableHorizontalClassMerging = enableHorizontalClassMerging;
   }
 
-  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   protected ClassSubject getSynthesizedArgumentClassSubject(CodeInspector codeInspector) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/IdenticalFieldMembersTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/IdenticalFieldMembersTest.java
index 9dae7a2..08a3bd7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/IdenticalFieldMembersTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/IdenticalFieldMembersTest.java
@@ -4,17 +4,16 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.*;
 import org.junit.Test;
 
 public class IdenticalFieldMembersTest extends HorizontalClassMergingTestBase {
-  public IdenticalFieldMembersTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public IdenticalFieldMembersTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,9 +21,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -33,8 +29,7 @@
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java
index b069bb1..ef0cedf 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritInterfaceWithDefaultTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -18,9 +18,8 @@
 
 public class InheritInterfaceWithDefaultTest extends HorizontalClassMergingTestBase {
 
-  public InheritInterfaceWithDefaultTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public InheritInterfaceWithDefaultTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -29,22 +28,18 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .allowStdoutMessages()
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(
             "print interface", "print interface", "print interface", "print interface")
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritOverrideInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritOverrideInterfaceTest.java
index 8c7644d..8bea286 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritOverrideInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritOverrideInterfaceTest.java
@@ -13,9 +13,8 @@
 import org.junit.Test;
 
 public class InheritOverrideInterfaceTest extends HorizontalClassMergingTestBase {
-  public InheritOverrideInterfaceTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public InheritOverrideInterfaceTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -23,14 +22,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A", "B", "A")
         .inspect(codeInspector -> {});
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritsFromLibraryClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritsFromLibraryClassTest.java
index 575c2cd..33a1909 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritsFromLibraryClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InheritsFromLibraryClassTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -19,9 +19,8 @@
 import org.junit.Test;
 
 public class InheritsFromLibraryClassTest extends HorizontalClassMergingTestBase {
-  public InheritsFromLibraryClassTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public InheritsFromLibraryClassTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -29,9 +28,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -41,8 +37,7 @@
             codeInspector -> {
               assertThat(codeInspector.clazz(Parent.class), isPresent());
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
               assertThat(codeInspector.clazz(C.class), isPresent());
             });
   }
@@ -74,7 +69,7 @@
   }
 
   @NeverClassInline
-  public static class C extends ArrayList {
+  public static class C extends ArrayList<Object> {
     public C() {}
 
     public void fooB(B b) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
index 98e74e4..a6a93ad 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
@@ -12,8 +12,8 @@
 import org.junit.Test;
 
 public class InnerOuterClassesTest extends HorizontalClassMergingTestBase {
-  public InnerOuterClassesTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public InnerOuterClassesTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -21,9 +21,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .addKeepAttributes("InnerClasses", "EnclosingMethod")
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
index 59e77dd..d6b6643 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
@@ -16,9 +16,8 @@
 
 public class InstantiatedAndUninstantiatedClassMergingTest extends HorizontalClassMergingTestBase {
 
-  public InstantiatedAndUninstantiatedClassMergingTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public InstantiatedAndUninstantiatedClassMergingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -35,9 +34,6 @@
     testBuilder
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .addHorizontallyMergedClassesInspector(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
index f7aacb5..6f96433 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
@@ -18,8 +18,8 @@
 
 public class JavaLambdaMergingTest extends HorizontalClassMergingTestBase {
 
-  public JavaLambdaMergingTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public JavaLambdaMergingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -27,11 +27,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging && parameters.isDexRuntime(),
+            parameters.isDexRuntime(),
             inspector -> {
               Set<DexType> lambdaSources =
                   inspector.getSources().stream()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java
index a553abe..00b1f38 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
@@ -15,9 +15,8 @@
 import org.junit.Test;
 
 public class LargeConstructorsMergingTest extends HorizontalClassMergingTestBase {
-  public LargeConstructorsMergingTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public LargeConstructorsMergingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,14 +24,10 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .addOptionsModification(options -> options.testing.verificationSizeLimitInBytesOverride = 4)
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector.assertMergedInto(B.class, A.class).assertMergedInto(C.class, A.class))
         .run(parameters.getRuntime(), Main.class)
@@ -41,15 +36,11 @@
             codeInspector -> {
               ClassSubject aClassSubject = codeInspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
-              assertThat(
-                  codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
+              assertThat(codeInspector.clazz(C.class), isAbsent());
 
-              if (enableHorizontalClassMerging) {
-                // There should be three constructors on class A after merging.
-                assertEquals(3, aClassSubject.allMethods().size());
-              }
+              // There should be three constructors on class A after merging.
+              assertEquals(3, aClassSubject.allMethods().size());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergeNonFinalAndFinalClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergeNonFinalAndFinalClassTest.java
index c8a36b6..1285ea1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergeNonFinalAndFinalClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergeNonFinalAndFinalClassTest.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -14,9 +14,8 @@
 import org.junit.Test;
 
 public class MergeNonFinalAndFinalClassTest extends HorizontalClassMergingTestBase {
-  public MergeNonFinalAndFinalClassTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public MergeNonFinalAndFinalClassTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,19 +23,13 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(
-            inspector ->
-                assertThat(
-                    inspector.clazz(A.class), notIf(isFinal(), enableHorizontalClassMerging)))
+        .inspect(inspector -> assertThat(inspector.clazz(A.class), not(isFinal())))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("a", "b", "b", "c");
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergePackagePrivateWithPublicClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergePackagePrivateWithPublicClassTest.java
index 04d69b1d..409ea48 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergePackagePrivateWithPublicClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergePackagePrivateWithPublicClassTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestParameters;
@@ -14,9 +14,8 @@
 
 public class MergePackagePrivateWithPublicClassTest extends HorizontalClassMergingTestBase {
 
-  public MergePackagePrivateWithPublicClassTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public MergePackagePrivateWithPublicClassTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -26,9 +25,6 @@
         .addProgramClasses(
             PackagePrivateClassRunner.class, PackagePrivateClassRunner.getPrivateClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -39,8 +35,7 @@
             codeInspector -> {
               assertThat(codeInspector.clazz(PackagePrivateClassRunner.class), isPresent());
               assertThat(
-                  codeInspector.clazz(PackagePrivateClassRunner.getPrivateClass()),
-                  notIf(isPresent(), enableHorizontalClassMerging));
+                  codeInspector.clazz(PackagePrivateClassRunner.getPrivateClass()), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
index f913560..5484fd8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorForwardingTest.java
@@ -21,9 +21,8 @@
 
 public class MergedConstructorForwardingTest extends HorizontalClassMergingTestBase {
 
-  public MergedConstructorForwardingTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public MergedConstructorForwardingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -31,9 +30,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -41,7 +37,6 @@
         .assertSuccessWithOutputLines("42", "13", "21", "39", "print a", "print b")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 ClassSubject aClassSubject = codeInspector.clazz(A.class);
                 assertThat(aClassSubject, isPresent());
                 FieldSubject classIdFieldSubject =
@@ -63,12 +58,6 @@
                 assertThat(printSubject, readsInstanceField(classIdFieldSubject.getDexField()));
 
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
-
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
index a11a7d6..e785238 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedConstructorStackTraceTest.java
@@ -22,9 +22,8 @@
 
   public StackTrace expectedStackTrace;
 
-  public MergedConstructorStackTraceTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public MergedConstructorStackTraceTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Before
@@ -45,9 +44,6 @@
         .addKeepMainRule(Main.class)
         .addKeepAttributeLineNumberTable()
         .addKeepAttributeSourceFile()
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -56,7 +52,6 @@
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              if (enableHorizontalClassMerging) {
                 StackTrace expectedStackTraceWithMergedConstructor =
                     StackTrace.builder()
                         .add(expectedStackTrace)
@@ -73,10 +68,6 @@
                         .build();
                 assertThat(stackTrace, isSame(expectedStackTraceWithMergedConstructor));
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
-              } else {
-                assertThat(stackTrace, isSame(expectedStackTrace));
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
index 8c9d11d..0375885 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStackTraceTest.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -19,9 +19,8 @@
 import org.junit.Test;
 
 public class MergedVirtualMethodStackTraceTest extends HorizontalClassMergingTestBase {
-  public MergedVirtualMethodStackTraceTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public MergedVirtualMethodStackTraceTest(TestParameters parameters) {
+    super(parameters);
   }
 
   public StackTrace expectedStackTrace;
@@ -45,38 +44,30 @@
         .addKeepAttributeLineNumberTable()
         .addKeepAttributeSourceFile()
         .addDontWarn(C.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertMergedInto(Program.B.class, Program.A.class))
         .run(parameters.getRuntime(), Program.Main.class)
         .inspectStackTrace(
             (stackTrace, codeInspector) -> {
               assertThat(codeInspector.clazz(Program.A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(Program.B.class),
-                  notIf(isPresent(), enableHorizontalClassMerging));
-              if (enableHorizontalClassMerging) {
-                StackTrace expectedStackTraceWithMergedMethod =
-                    StackTrace.builder()
-                        .add(expectedStackTrace)
-                        .add(
-                            1,
-                            StackTraceLine.builder()
-                                .setClassName(Program.A.class.getTypeName())
-                                .setMethodName("foo$bridge")
-                                .setFileName("Program.java")
-                                .setFileName(getClass().getSimpleName() + ".java")
-                                .setLineNumber(stackTrace.get(1).lineNumber)
-                                .build())
-                        .build();
-                assertThat(stackTrace, isSame(expectedStackTraceWithMergedMethod));
-              }
+              assertThat(codeInspector.clazz(Program.B.class), isAbsent());
+              StackTrace expectedStackTraceWithMergedMethod =
+                  StackTrace.builder()
+                      .add(expectedStackTrace)
+                      .add(
+                          1,
+                          StackTraceLine.builder()
+                              .setClassName(Program.A.class.getTypeName())
+                              .setMethodName("foo$bridge")
+                              .setFileName("Program.java")
+                              .setFileName(getClass().getSimpleName() + ".java")
+                              .setLineNumber(stackTrace.get(1).lineNumber)
+                              .build())
+                      .build();
+              assertThat(stackTrace, isSame(expectedStackTraceWithMergedMethod));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java
index e7eda20..3ffb5a2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergedVirtualMethodStaticizerTest.java
@@ -10,9 +10,8 @@
 import org.junit.Test;
 
 public class MergedVirtualMethodStaticizerTest extends HorizontalClassMergingTestBase {
-  public MergedVirtualMethodStaticizerTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public MergedVirtualMethodStaticizerTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -20,14 +19,10 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(Program.class)
         .addKeepClassAndMembersRules(Program.Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertMergedInto(Program.B.class, Program.A.class))
         .run(parameters.getRuntime(), Program.Main.class)
         .assertSuccessWithOutputLines("A::foo", "Staticized::foo", "B::foo");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
index 13cc54f..06242c1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
@@ -4,22 +4,20 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import org.junit.Test;
 
 public class MergingProducesFieldCollisionTest extends HorizontalClassMergingTestBase {
-  public MergingProducesFieldCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public MergingProducesFieldCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -30,9 +28,6 @@
         .addKeepMainRule(Main.class)
         .addProgramClassFileData(transformedC)
         .addProgramClasses(Parent.class, A.class, B.class, Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -46,16 +41,13 @@
               assertThat(aClassSubject, isPresent());
 
               ClassSubject bClassSubject = codeInspector.clazz(B.class);
-              assertThat(bClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(bClassSubject, isAbsent());
 
               ClassSubject cClassSubject = codeInspector.clazz(C.class);
               assertThat(cClassSubject, isPresent());
 
-              if (enableHorizontalClassMerging) {
-                assertEquals(
-                    cClassSubject.allFields().get(0).type(),
-                    cClassSubject.allFields().get(1).type());
-              }
+              assertEquals(
+                  cClassSubject.allFields().get(0).type(), cClassSubject.allFields().get(1).type());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MinimizeFieldCastsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MinimizeFieldCastsTest.java
index 90ae326..319e1c0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MinimizeFieldCastsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MinimizeFieldCastsTest.java
@@ -12,8 +12,8 @@
 
 public class MinimizeFieldCastsTest extends HorizontalClassMergingTestBase {
 
-  public MinimizeFieldCastsTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public MinimizeFieldCastsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -21,15 +21,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector ->
                 // Two merge groups are expected since we attempt to merge classes in a way that
                 // avoids merging fields with different types unless strictly required for merging.
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java
index a043e68..215ce7a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassTest.java
@@ -4,24 +4,23 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPrivate;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.Jdk9TestUtils;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.classmerging.horizontal.NestClassTest.R.horizontalclassmerging.BasicNestHostHorizontalClassMerging;
 import com.android.tools.r8.classmerging.horizontal.NestClassTest.R.horizontalclassmerging.BasicNestHostHorizontalClassMerging2;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesClass;
 import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesJava11RootPackage;
 import com.android.tools.r8.utils.ReflectiveBuildPathUtils.ExamplesPackage;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runners.Parameterized;
 
@@ -42,15 +41,13 @@
     }
   }
 
-  public NestClassTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public NestClassTest(TestParameters parameters) {
+    super(parameters);
   }
 
-  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build(),
-        BooleanUtils.values());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK11).build();
   }
 
   @Test
@@ -59,9 +56,6 @@
         .addKeepMainRule(examplesTypeName(BasicNestHostHorizontalClassMerging.class))
         .addExamplesProgramFiles(R.class)
         .applyIf(parameters.isCfRuntime(), Jdk9TestUtils.addJdk9LibraryFiles(temp))
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .compile()
@@ -98,11 +92,11 @@
               assertThat(
                   codeInspector.clazz(
                       examplesTypeName(BasicNestHostHorizontalClassMerging.B.class)),
-                  notIf(isPresent(), enableHorizontalClassMerging));
+                  isAbsent());
               assertThat(
                   codeInspector.clazz(
                       examplesTypeName(BasicNestHostHorizontalClassMerging2.B.class)),
-                  notIf(isPresent(), enableHorizontalClassMerging));
+                  isAbsent());
 
               // TODO(b/165517236): Explicitly check 1.B is merged into 1.A, and 2.B into 2.A.
             });
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java
index 5566f45..4ebbd51 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoAbstractClassesWithNonAbstractClassesTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -15,9 +15,8 @@
 import org.junit.Test;
 
 public class NoAbstractClassesWithNonAbstractClassesTest extends HorizontalClassMergingTestBase {
-  public NoAbstractClassesWithNonAbstractClassesTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public NoAbstractClassesWithNonAbstractClassesTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,9 +24,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
@@ -39,8 +35,7 @@
               assertThat(codeInspector.clazz(A.class), isPresent());
               assertThat(codeInspector.clazz(B.class), isPresent());
               assertThat(codeInspector.clazz(C.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(D.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(D.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
index 441934e..385a3fe 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -20,9 +20,9 @@
 import org.junit.Test;
 
 public class NoClassesOrMembersWithAnnotationsTest extends HorizontalClassMergingTestBase {
-  public NoClassesOrMembersWithAnnotationsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+
+  public NoClassesOrMembersWithAnnotationsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -31,11 +31,7 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(A.class, C.class))
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
@@ -49,8 +45,7 @@
               assertThat(codeInspector.clazz(MethodAnnotation.class), isPresent());
               assertThat(codeInspector.clazz(A.class), isPresent());
               assertThat(codeInspector.clazz(B.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(C.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(C.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java
index 5c0e207..1382648 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java
@@ -13,9 +13,8 @@
 import org.junit.Test;
 
 public class NoHorizontalClassMergingTest extends HorizontalClassMergingTestBase {
-  public NoHorizontalClassMergingTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public NoHorizontalClassMergingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -23,9 +22,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNoHorizontalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -33,13 +29,8 @@
         .assertSuccessWithOutputLines("a", "b")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(A.class), isPresent());
                 assertThat(codeInspector.clazz(B.class), isPresent());
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonFinalOverrideOfFinalMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonFinalOverrideOfFinalMethodTest.java
index d73ffcf..110f7f6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonFinalOverrideOfFinalMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonFinalOverrideOfFinalMethodTest.java
@@ -12,24 +12,21 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runners.Parameterized;
 
 public class NonFinalOverrideOfFinalMethodTest extends HorizontalClassMergingTestBase {
 
-  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.trueValues());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public NonFinalOverrideOfFinalMethodTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public NonFinalOverrideOfFinalMethodTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -37,15 +34,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .compile()
         .inspect(
             inspector -> {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
index 029035e..a54102f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
@@ -14,9 +14,8 @@
 @RunWith(Parameterized.class)
 public class NonReboundFieldAccessOnMergedClassTest extends HorizontalClassMergingTestBase {
 
-  public NonReboundFieldAccessOnMergedClassTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public NonReboundFieldAccessOnMergedClassTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,11 +24,8 @@
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessOnMergedClassTestClasses.class)
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(D.class, C.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(D.class, C.class))
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
index 03b99e0..4b17a09 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessWithMergedTypeTest.java
@@ -14,9 +14,8 @@
 @RunWith(Parameterized.class)
 public class NonReboundFieldAccessWithMergedTypeTest extends HorizontalClassMergingTestBase {
 
-  public NonReboundFieldAccessWithMergedTypeTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public NonReboundFieldAccessWithMergedTypeTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,11 +24,7 @@
         .addInnerClasses(getClass())
         .addInnerClasses(NonReboundFieldAccessWithMergedTypeTestClasses.class)
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertMergedInto(WorldGreeting.class, HelloGreeting.class))
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/OverlappingConstructorsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/OverlappingConstructorsTest.java
index c97e384..2927247 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/OverlappingConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/OverlappingConstructorsTest.java
@@ -14,9 +14,8 @@
 
 public class OverlappingConstructorsTest extends HorizontalClassMergingTestBase {
 
-  public OverlappingConstructorsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public OverlappingConstructorsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,24 +23,14 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(A.class), isPresent());
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
                 assertThat(codeInspector.clazz(C.class), not(isPresent()));
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-                assertThat(codeInspector.clazz(C.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
index 2732240..820af37 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
@@ -15,9 +15,8 @@
 import org.junit.Test;
 
 public class PackagePrivateMemberAccessTest extends HorizontalClassMergingTestBase {
-  public PackagePrivateMemberAccessTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public PackagePrivateMemberAccessTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -27,9 +26,6 @@
         .addProgramClasses(A.class)
         .addProgramClasses(B.class)
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .allowAccessModification(false)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
@@ -38,16 +34,9 @@
         .assertSuccessWithOutputLines("foo", "B", "bar", "5", "foobar")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(A.class), isPresent());
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
                 assertThat(codeInspector.clazz(C.class), isPresent());
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-                assertThat(codeInspector.clazz(C.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
index 4d7c0c4..bb756d0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
@@ -14,9 +14,8 @@
 import org.junit.Test;
 
 public class PackagePrivateMembersAccessedTest extends HorizontalClassMergingTestBase {
-  public PackagePrivateMembersAccessedTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public PackagePrivateMembersAccessedTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -26,9 +25,6 @@
         .addProgramClasses(C.class)
         .addProgramClasses(D.class)
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .allowAccessModification(false)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberReferenceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberReferenceTest.java
index 2f01f90..41694f6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberReferenceTest.java
@@ -18,18 +18,14 @@
 import org.junit.Test;
 
 public class PinnedClassMemberReferenceTest extends HorizontalClassMergingTestBase {
-  public PinnedClassMemberReferenceTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public PinnedClassMemberReferenceTest(TestParameters parameters) {
+    super(parameters);
   }
 
   private R8FullTestBuilder testCommon() throws Exception {
     return testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .noMinification()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
@@ -46,7 +42,6 @@
   @Test
   public void testWithoutKeepRules() throws Exception {
     // This is just a small check ensure that without the keep rules the classes are merged.
-    assumeTrue(enableHorizontalClassMerging);
     assumeTrue(parameters.isCfRuntime());
 
     runAndAssertOutput(testCommon())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java
index 1e09a5a..ca080ae 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java
@@ -12,8 +12,8 @@
 import org.junit.Test;
 
 public class PinnedClassMemberTest extends HorizontalClassMergingTestBase {
-  public PinnedClassMemberTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public PinnedClassMemberTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,9 +22,6 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepRules("-keepclassmembers class " + B.class.getTypeName() + " { void foo(); }")
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassTest.java
index 0ba06e6..af092cc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassTest.java
@@ -12,8 +12,8 @@
 import org.junit.Test;
 
 public class PinnedClassTest extends HorizontalClassMergingTestBase {
-  public PinnedClassTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public PinnedClassTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,9 +22,6 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepClassRules(B.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
index e3e1b85..2aeacb8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexListTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.IsNot.not;
@@ -12,48 +13,46 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.A;
-import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.B;
-import com.android.tools.r8.classmerging.horizontal.EmptyClassTest.Main;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
 public class PreventMergeMainDexListTest extends HorizontalClassMergingTestBase {
-  public PreventMergeMainDexListTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public PreventMergeMainDexListTest(TestParameters parameters) {
+    super(parameters);
   }
 
-  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters()
-            .withDexRuntimes()
-            .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
-            .build(),
-        BooleanUtils.values());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
   }
 
+  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
+  //  Ensure the main-dex-rules variant of this test (PreventMergeMainDexTracingTest) is sufficient.
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepClassAndMembersRules(Main.class)
         .addMainDexListClasses(A.class, Main.class)
-        .addOptionsModification(
-            options -> {
-              options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging);
-              options.minimalMainDex = true;
-            })
+        .addOptionsModification(options -> options.minimalMainDex = true)
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .compile()
+        .allowDiagnosticMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)))
         .apply(this::checkCompileResult)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("main dex");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java
index 13d8a62..de0bd57 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PreventMergeMainDexTracingTest.java
@@ -13,29 +13,25 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
 public class PreventMergeMainDexTracingTest extends HorizontalClassMergingTestBase {
-  public PreventMergeMainDexTracingTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public PreventMergeMainDexTracingTest(TestParameters parameters) {
+    super(parameters);
   }
 
-  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters()
-            .withDexRuntimes()
-            .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
-            .build(),
-        BooleanUtils.values());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
   }
 
   @Test
@@ -44,12 +40,8 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepClassAndMembersRules(Other.class)
-        .addMainDexClassRules(Main.class)
-        .addOptionsModification(
-            options -> {
-              options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging);
-              options.minimalMainDex = true;
-            })
+        .addMainDexKeepClassRules(Main.class)
+        .addOptionsModification(options -> options.minimalMainDex = true)
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
index 4553469..f440689 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
@@ -14,9 +14,8 @@
 
 public class PrivateAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
 
-  public PrivateAndInterfaceMethodCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public PrivateAndInterfaceMethodCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,9 +23,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .addHorizontallyMergedClassesInspector(
             HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java
index 3c7b799..a646bf3 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndStaticMethodCollisionTest.java
@@ -11,9 +11,8 @@
 
 public class PrivateAndStaticMethodCollisionTest extends HorizontalClassMergingTestBase {
 
-  public PrivateAndStaticMethodCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public PrivateAndStaticMethodCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -21,11 +20,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ReferencedInAnnotationTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ReferencedInAnnotationTest.java
index 385d782..08f1f0d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ReferencedInAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ReferencedInAnnotationTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -26,9 +26,8 @@
 
 public class ReferencedInAnnotationTest extends HorizontalClassMergingTestBase {
 
-  public ReferencedInAnnotationTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ReferencedInAnnotationTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -37,9 +36,6 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .addKeepClassAndMembersRules(Annotation.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .addKeepRuntimeVisibleAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -58,7 +54,7 @@
 
     // B should have been merged into A if horizontal class merging is enabled.
     ClassSubject bClassSubject = inspector.clazz(B.class);
-    assertThat(bClassSubject, notIf(isPresent(), enableHorizontalClassMerging));
+    assertThat(bClassSubject, isAbsent());
 
     // The annotation on TestClass should now refer to A instead of B.
     AnnotationSubject annotationSubject =
@@ -72,11 +68,7 @@
     assertTrue(annotationElementValue.isDexValueType());
 
     DexType annotationElementValueType = annotationElementValue.asDexValueType().getValue();
-    assertEquals(
-        (enableHorizontalClassMerging ? aClassSubject : bClassSubject)
-            .getDexProgramClass()
-            .getType(),
-        annotationElementValueType);
+    assertEquals(aClassSubject.getDexProgramClass().getType(), annotationElementValueType);
   }
 
   @Annotation(B.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapFieldTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapFieldTest.java
index 168d6ee..3abb15d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapFieldTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapFieldTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -14,8 +14,8 @@
 import org.junit.Test;
 
 public class RemapFieldTest extends HorizontalClassMergingTestBase {
-  public RemapFieldTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public RemapFieldTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -23,14 +23,10 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector.assertMergedInto(B.class, A.class).assertMergedInto(D.class, C.class))
         .run(parameters.getRuntime(), Main.class)
@@ -38,11 +34,9 @@
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
               assertThat(codeInspector.clazz(C.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(D.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(D.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapMethodTest.java
index c640ffc..71e3e74 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/RemapMethodTest.java
@@ -14,8 +14,8 @@
 import org.junit.Test;
 
 public class RemapMethodTest extends HorizontalClassMergingTestBase {
-  public RemapMethodTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public RemapMethodTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -23,9 +23,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -36,14 +33,8 @@
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
               assertThat(codeInspector.clazz(C.class), isPresent());
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
                 assertThat(codeInspector.clazz(D.class), not(isPresent()));
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(B.class), isPresent());
-                assertThat(codeInspector.clazz(D.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ServiceLoaderParentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ServiceLoaderParentTest.java
index 5eab9c9..311d835 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ServiceLoaderParentTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ServiceLoaderParentTest.java
@@ -18,8 +18,8 @@
 import org.junit.Test;
 
 public class ServiceLoaderParentTest extends HorizontalClassMergingTestBase {
-  public ServiceLoaderParentTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ServiceLoaderParentTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -37,9 +37,6 @@
                 "META-INF/services/" + A.class.getTypeName(),
                 Origin.unknown()))
         .enableNoVerticalClassMergingAnnotations()
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccess()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ServiceLoaderTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ServiceLoaderTest.java
index f9ae4bd..0b4a848 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ServiceLoaderTest.java
@@ -17,8 +17,8 @@
 import org.junit.Test;
 
 public class ServiceLoaderTest extends HorizontalClassMergingTestBase {
-  public ServiceLoaderTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public ServiceLoaderTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -35,9 +35,6 @@
                 StringUtils.lines(serviceImplementations).getBytes(),
                 "META-INF/services/" + A.class.getTypeName(),
                 Origin.unknown()))
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccess()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
index e76dd447..48844ff 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
@@ -14,9 +14,8 @@
 
 public class StaticAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
 
-  public StaticAndInterfaceMethodCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public StaticAndInterfaceMethodCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,9 +23,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .addHorizontallyMergedClassesInspector(
             HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
index 35a15e5..993dd65 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndVirtualMethodCollisionTest.java
@@ -12,9 +12,8 @@
 
 public class StaticAndVirtualMethodCollisionTest extends HorizontalClassMergingTestBase {
 
-  public StaticAndVirtualMethodCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public StaticAndVirtualMethodCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,11 +21,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertMergedInto(B.class, A.class))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StrictMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StrictMethodMergingTest.java
index 762824f..4d793a4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/StrictMethodMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StrictMethodMergingTest.java
@@ -12,23 +12,21 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runners.Parameterized;
 
 public class StrictMethodMergingTest extends HorizontalClassMergingTestBase {
 
-  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.trueValues());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public StrictMethodMergingTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public StrictMethodMergingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -36,14 +34,10 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector.assertMergedInto(B.class, A.class).assertMergedInto(D.class, C.class))
         .compile()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java
index ecbd84b..821d644 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SuperConstructorCallsVirtualMethodTest.java
@@ -14,9 +14,8 @@
 import org.junit.Test;
 
 public class SuperConstructorCallsVirtualMethodTest extends HorizontalClassMergingTestBase {
-  public SuperConstructorCallsVirtualMethodTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public SuperConstructorCallsVirtualMethodTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,9 +23,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -34,16 +30,9 @@
         .assertSuccessWithOutputLines("5", "foo hello", "B", "bar world", "5", "B")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(Parent.class), isPresent());
                 assertThat(codeInspector.clazz(A.class), isPresent());
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(Parent.class), isPresent());
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
index 6f458b7..a88c32b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
@@ -15,8 +15,8 @@
 import org.junit.Test;
 
 public class SynchronizedClassesTest extends HorizontalClassMergingTestBase {
-  public SynchronizedClassesTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public SynchronizedClassesTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,10 +24,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options -> {
-              options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging);
-            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -38,7 +34,6 @@
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
               assertThat(codeInspector.clazz(B.class), isPresent());
-              if (enableHorizontalClassMerging) {
                 // C has been merged into A.
                 assertThat(codeInspector.clazz(C.class), not(isPresent()));
                 assertThat(codeInspector.clazz(A.class).init("long"), isPresent());
@@ -47,10 +42,6 @@
                 assertThat(codeInspector.clazz(D.class), not(isPresent()));
                 ClassSubject bClassSubject = codeInspector.clazz(B.class);
                 assertThat(bClassSubject.init("boolean"), isPresent());
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedMethodMergingTest.java
index 8d802c4..4811a41 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedMethodMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedMethodMergingTest.java
@@ -12,24 +12,21 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runners.Parameterized;
 
 public class SynchronizedMethodMergingTest extends HorizontalClassMergingTestBase {
 
-  @Parameterized.Parameters(name = "{0}, horizontalClassMerging:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.trueValues());
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public SynchronizedMethodMergingTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public SynchronizedMethodMergingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -37,14 +34,10 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging,
+        .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector.assertMergedInto(B.class, A.class).assertMergedInto(D.class, C.class))
         .compile()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticConstructorArgumentsMerged.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticConstructorArgumentsMerged.java
index 6567764..eb3fe19 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticConstructorArgumentsMerged.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SyntheticConstructorArgumentsMerged.java
@@ -14,9 +14,8 @@
 import org.junit.Test;
 
 public class SyntheticConstructorArgumentsMerged extends HorizontalClassMergingTestBase {
-  public SyntheticConstructorArgumentsMerged(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public SyntheticConstructorArgumentsMerged(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,10 +23,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options -> {
-              options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging);
-            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -36,14 +31,8 @@
         .assertSuccessWithOutputLines("5", "42")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(A.class), isPresent());
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerCollisionTest.java
index 97e41db..2b56278 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerCollisionTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -16,8 +16,8 @@
 import org.junit.Test;
 
 public class TreeFixerCollisionTest extends HorizontalClassMergingTestBase {
-  public TreeFixerCollisionTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public TreeFixerCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,9 +25,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
@@ -39,13 +36,11 @@
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
               assertThat(codeInspector.clazz(Group2.class), isPresent());
               assertThat(codeInspector.clazz(C.class), isPresent());
               assertThat(codeInspector.clazz(D.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(E.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(E.class), isAbsent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
index 5239543..c5dff9d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -15,9 +15,8 @@
 import org.junit.Test;
 
 public class TreeFixerConstructorCollisionTest extends HorizontalClassMergingTestBase {
-  public TreeFixerConstructorCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public TreeFixerConstructorCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,9 +24,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -43,8 +39,7 @@
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
               assertThat(codeInspector.clazz(C.class), isPresent());
             });
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
index dbc83c8..fb7e05e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceCollisionTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -22,9 +22,8 @@
  * changed.
  */
 public class TreeFixerInterfaceCollisionTest extends HorizontalClassMergingTestBase {
-  public TreeFixerInterfaceCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public TreeFixerInterfaceCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -33,9 +32,6 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .noMinification()
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
@@ -46,15 +42,12 @@
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
 
               ClassSubject parentClassSubject = codeInspector.clazz(Parent.class);
               assertThat(parentClassSubject, isPresent());
-              if (enableHorizontalClassMerging) {
-                // Parent#foo is renamed to Parent#foo1 to prevent collision.
-                assertThat(parentClassSubject.uniqueMethodWithName("foo$1"), isPresent());
-              }
+              // Parent#foo is renamed to Parent#foo1 to prevent collision.
+              assertThat(parentClassSubject.uniqueMethodWithName("foo$1"), isPresent());
 
               ClassSubject cClassSubject = codeInspector.clazz(C.class);
               assertThat(cClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
index 169a59b..4dfb6df 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceFixedCollisionTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -22,9 +22,8 @@
  * changed.
  */
 public class TreeFixerInterfaceFixedCollisionTest extends HorizontalClassMergingTestBase {
-  public TreeFixerInterfaceFixedCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public TreeFixerInterfaceFixedCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -33,9 +32,6 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .noMinification()
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
@@ -46,15 +42,12 @@
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
 
               ClassSubject parentClassSubject = codeInspector.clazz(Parent.class);
               assertThat(parentClassSubject, isPresent());
-              if (enableHorizontalClassMerging) {
-                // Parent#foo is renamed to Parent#foo1 to prevent collision.
-                assertThat(parentClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
-              }
+              // Parent#foo is renamed to Parent#foo1 to prevent collision.
+              assertThat(parentClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
 
               ClassSubject cClassSubject = codeInspector.clazz(C.class);
               assertThat(cClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceImplementedByParentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceImplementedByParentTest.java
index afee32c..65b57aa 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceImplementedByParentTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerInterfaceImplementedByParentTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -22,9 +22,8 @@
  * changed.
  */
 public class TreeFixerInterfaceImplementedByParentTest extends HorizontalClassMergingTestBase {
-  public TreeFixerInterfaceImplementedByParentTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public TreeFixerInterfaceImplementedByParentTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -33,9 +32,6 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .noMinification()
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
@@ -46,17 +42,14 @@
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
 
               ClassSubject parentClassSubject = codeInspector.clazz(Parent.class);
               assertThat(parentClassSubject, isPresent());
               // Parent #foo(A) is kept as is.
-              if (enableHorizontalClassMerging) {
-                // Parent#foo(B) is renamed to Parent#foo1 to prevent collision.
-                assertThat(parentClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
-                assertThat(parentClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
-              }
+              // Parent#foo(B) is renamed to Parent#foo1 to prevent collision.
+              assertThat(parentClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
+              assertThat(parentClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
 
               assertThat(codeInspector.clazz(C.class), isPresent());
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
index b68f291..5e8b88d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerSubClassCollisionTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -17,9 +17,8 @@
 import org.junit.Test;
 
 public class TreeFixerSubClassCollisionTest extends HorizontalClassMergingTestBase {
-  public TreeFixerSubClassCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public TreeFixerSubClassCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -28,9 +27,6 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .noMinification()
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
@@ -42,24 +38,18 @@
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), isAbsent());
 
               ClassSubject cClassSubject = codeInspector.clazz(C.class);
               assertThat(cClassSubject, isPresent());
               // C#foo(B) is renamed to C#foo$1(A).
-              if (enableHorizontalClassMerging) {
-                assertThat(cClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
-                assertThat(cClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
-              }
+              assertThat(cClassSubject.uniqueMethodWithFinalName("foo"), isPresent());
+              assertThat(cClassSubject.uniqueMethodWithFinalName("foo$1"), isPresent());
 
               ClassSubject dClassSubject = codeInspector.clazz(D.class);
               assertThat(dClassSubject, isPresent());
               // D#foo$1(B) is renamed to D#foo$2(A).
-              assertThat(
-                  dClassSubject.uniqueMethodWithFinalName(
-                      enableHorizontalClassMerging ? "foo$1$1" : "foo$1"),
-                  isPresent());
+              assertThat(dClassSubject.uniqueMethodWithFinalName("foo$1$1"), isPresent());
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticalMergingPreoptimizedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticalMergingPreoptimizedTest.java
index b1c0b4a..db15d9c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticalMergingPreoptimizedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticalMergingPreoptimizedTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.classmerging.horizontal;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.IsNot.not;
 
@@ -17,9 +16,8 @@
 
 public class VerticalMergingPreoptimizedTest extends HorizontalClassMergingTestBase {
 
-  public VerticalMergingPreoptimizedTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public VerticalMergingPreoptimizedTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -27,9 +25,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -42,8 +37,7 @@
               assertThat(codeInspector.clazz(Parent.class), not(isPresent()));
               assertThat(codeInspector.clazz(Changed.class), isPresent());
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(
-                  codeInspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(codeInspector.clazz(B.class), not(isPresent()));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java
index eba6de8..0c13090 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java
@@ -12,9 +12,8 @@
 public class VerticallyMergedClassDistinguishedByCheckCastTest
     extends HorizontalClassMergingTestBase {
 
-  public VerticallyMergedClassDistinguishedByCheckCastTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public VerticallyMergedClassDistinguishedByCheckCastTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,9 +21,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java
index 47994ef..a7a9f96 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java
@@ -12,9 +12,8 @@
 public class VerticallyMergedClassDistinguishedByInstanceOfTest
     extends HorizontalClassMergingTestBase {
 
-  public VerticallyMergedClassDistinguishedByInstanceOfTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public VerticallyMergedClassDistinguishedByInstanceOfTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,9 +21,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
index abd38f5..ab48235 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
@@ -15,9 +15,8 @@
 import org.junit.Test;
 
 public class VerticallyMergedClassTest extends HorizontalClassMergingTestBase {
-  public VerticallyMergedClassTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public VerticallyMergedClassTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,9 +24,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableNoHorizontalClassMergingAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java
index 6399f3e..0c51c89 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfFinalAndNonFinalMethodTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -17,9 +17,8 @@
 public class VirtualMethodMergingOfFinalAndNonFinalMethodTest
     extends HorizontalClassMergingTestBase {
 
-  public VirtualMethodMergingOfFinalAndNonFinalMethodTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public VirtualMethodMergingOfFinalAndNonFinalMethodTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -27,9 +26,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
@@ -38,8 +34,7 @@
         .inspect(
             inspector -> {
               assertThat(inspector.clazz(A.class), isPresent());
-              assertThat(
-                  inspector.clazz(B.class), notIf(isPresent(), enableHorizontalClassMerging));
+              assertThat(inspector.clazz(B.class), isAbsent());
               assertThat(inspector.clazz(C.class), isPresent());
             })
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java
index 5e23840..d3665e0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VirtualMethodMergingOfPublicizedMethodsTest.java
@@ -14,9 +14,8 @@
 @RunWith(Parameterized.class)
 public class VirtualMethodMergingOfPublicizedMethodsTest extends HorizontalClassMergingTestBase {
 
-  public VirtualMethodMergingOfPublicizedMethodsTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public VirtualMethodMergingOfPublicizedMethodsTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -24,9 +23,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .allowAccessModification()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/NotOverlappingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/NotOverlappingTest.java
index e405aa9..3d0fe4a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/NotOverlappingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/NotOverlappingTest.java
@@ -13,8 +13,8 @@
 import org.junit.Test;
 
 public class NotOverlappingTest extends HorizontalClassMergingTestBase {
-  public NotOverlappingTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public NotOverlappingTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -22,9 +22,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -32,14 +29,8 @@
         .assertSuccessWithOutputLines("foo", "bar")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(A.class), isPresent());
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
index 11164b1..3787201 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -17,9 +17,8 @@
 
 public class OverrideAbstractMethodWithDefaultTest extends HorizontalClassMergingTestBase {
 
-  public OverrideAbstractMethodWithDefaultTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public OverrideAbstractMethodWithDefaultTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -27,15 +26,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("J", "B2")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
index 15ca564..d74ae0a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -16,9 +16,8 @@
 import org.junit.Test;
 
 public class OverrideDefaultMethodTest extends HorizontalClassMergingTestBase {
-  public OverrideDefaultMethodTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public OverrideDefaultMethodTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -26,15 +25,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("I", "B", "J")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
index 2aea3d0..95dc561 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -17,9 +17,8 @@
 import org.junit.Test;
 
 public class OverrideDefaultOnSuperMethodTest extends HorizontalClassMergingTestBase {
-  public OverrideDefaultOnSuperMethodTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public OverrideDefaultOnSuperMethodTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -27,16 +26,13 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("I", "B", "J")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
index 9a727f5..6f5e761 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
@@ -16,8 +16,8 @@
 import org.junit.Test;
 
 public class OverrideMergeAbsentTest extends HorizontalClassMergingTestBase {
-  public OverrideMergeAbsentTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public OverrideMergeAbsentTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,15 +25,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A", "B", "A", "J")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideParentCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideParentCollisionTest.java
index 639cf19..c62f59b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideParentCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideParentCollisionTest.java
@@ -16,9 +16,8 @@
 
 public class OverrideParentCollisionTest extends HorizontalClassMergingTestBase {
 
-  public OverrideParentCollisionTest(
-      TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public OverrideParentCollisionTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -26,9 +25,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(this.getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
@@ -36,14 +32,8 @@
         .assertSuccessWithOutputLines("foo", "bar", "foo", "parent")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(A.class), isPresent());
                 assertThat(codeInspector.clazz(B.class), not(isPresent()));
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(A.class), isPresent());
-                assertThat(codeInspector.clazz(B.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/SuperMethodMergedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/SuperMethodMergedTest.java
index 44973bc..fb2189d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/SuperMethodMergedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/SuperMethodMergedTest.java
@@ -16,8 +16,8 @@
 import org.junit.Test;
 
 public class SuperMethodMergedTest extends HorizontalClassMergingTestBase {
-  public SuperMethodMergedTest(TestParameters parameters, boolean enableHorizontalClassMerging) {
-    super(parameters, enableHorizontalClassMerging);
+  public SuperMethodMergedTest(TestParameters parameters) {
+    super(parameters);
   }
 
   @Test
@@ -25,9 +25,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(this.getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options ->
-                options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
@@ -36,18 +33,10 @@
         .assertSuccessWithOutputLines("foo", "parent b", "parent b", "x", "parent b")
         .inspect(
             codeInspector -> {
-              if (enableHorizontalClassMerging) {
                 assertThat(codeInspector.clazz(ParentA.class), isPresent());
                 assertThat(codeInspector.clazz(ParentB.class), not(isPresent()));
                 assertThat(codeInspector.clazz(X.class), isPresent());
                 assertThat(codeInspector.clazz(Y.class), not(isPresent()));
-                // TODO(b/165517236): Explicitly check classes have been merged.
-              } else {
-                assertThat(codeInspector.clazz(ParentA.class), isPresent());
-                assertThat(codeInspector.clazz(ParentB.class), isPresent());
-                assertThat(codeInspector.clazz(X.class), isPresent());
-                assertThat(codeInspector.clazz(Y.class), isPresent());
-              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java b/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java
index 97f84ef..d840b52 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java
@@ -77,8 +77,8 @@
     assertThat(set, isPresent());
 
     assertEquals(
-        set.getMethod().method.proto.parameters.values[0],
-        storage.getField().field.type.toBaseType(inspector.getFactory()));
+        set.getMethod().getReference().proto.parameters.values[0],
+        storage.getField().getReference().type.toBaseType(inspector.getFactory()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
index beed8fc..a292274 100644
--- a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
@@ -105,7 +105,7 @@
   }
 
   public static boolean fromAndroidN(Version dexVmVersion) {
-    return dexVmVersion.isAtLeast(Version.V7_0_0);
+    return dexVmVersion.isNewerThanOrEqual(Version.V7_0_0);
   }
 
   private static List<Path> findAllJarsIn(Path root) {
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 a083bcf..c34c633 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -1050,8 +1050,8 @@
               artCommandBuilder.appendArtOption("-Xcompiler-option");
               artCommandBuilder.appendArtOption("--debuggable");
             }
-            if (ToolHelper.getDexVm().getVersion().isAtLeast(DexVm.Version.V9_0_0) &&
-                ToolHelper.getDexVm().getVersion() != DexVm.Version.DEFAULT) {
+            if (ToolHelper.getDexVm().getVersion().isNewerThanOrEqual(DexVm.Version.V9_0_0)
+                && ToolHelper.getDexVm().getVersion() != DexVm.Version.DEFAULT) {
               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/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 67e62e8..12c6bc8 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -338,7 +338,7 @@
     // See verifyStateLocation in DebugTestBase.
     Assume.assumeTrue(
         "Streaming on Dalvik DEX runtimes has some unknown interference issue",
-        ToolHelper.getDexVm().getVersion().isAtLeast(Version.V6_0_1));
+        ToolHelper.getDexVm().getVersion().isNewerThanOrEqual(Version.V6_0_1));
     Assume.assumeTrue(
         "Skipping test "
             + testName.getMethodName()
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
index cfd0a98..3e7367e 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -72,7 +72,7 @@
     // See verifyStateLocation in DebugTestBase.
     Assume.assumeTrue(
         "Streaming on Dalvik DEX runtimes has some unknown interference issue",
-        ToolHelper.getDexVm().getVersion().isAtLeast(Version.V6_0_1));
+        ToolHelper.getDexVm().getVersion().isNewerThanOrEqual(Version.V6_0_1));
     Assume.assumeTrue(
         "Skipping test "
             + testName.getMethodName()
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPrivateSuperClassTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPrivateSuperClassTest.java
new file mode 100644
index 0000000..6ba2e12
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPrivateSuperClassTest.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.desugar;
+
+import static org.junit.Assume.assumeTrue;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Regression test code for ensuring that desugared code behaves at least kind of like
+ * https://bugs.openjdk.java.net/browse/JDK-8021581
+ */
+@RunWith(Parameterized.class)
+public class DefaultLambdaMethodWithPrivateSuperClassTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public DefaultLambdaMethodWithPrivateSuperClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+  }
+
+  @Test
+  public void testDesugar() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.isCfRuntime()
+                || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M),
+            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class),
+            // TODO(b/152199517): Should be illegal access for DEX.
+            r -> r.assertSuccessWithOutputLines("interface"));
+  }
+
+  interface Named {
+
+    default String name() {
+      return "interface";
+    }
+
+    class PrivateNameBase {
+      private String name() {
+        throw new AssertionError("shouldn't get here");
+      }
+    }
+
+    class PrivateName extends PrivateNameBase implements Named {}
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      String unexpected = new Named.PrivateName().name();
+      System.out.println(unexpected);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPublicSuperClassTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPublicSuperClassTest.java
new file mode 100644
index 0000000..a439984
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaMethodWithPublicSuperClassTest.java
@@ -0,0 +1,71 @@
+// 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;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DefaultLambdaMethodWithPublicSuperClassTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String EXPECTED = "PublicNameBase::name";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public DefaultLambdaMethodWithPublicSuperClassTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testDesugar() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  interface Named {
+
+    default String name() {
+      return "interface";
+    }
+
+    class PublicNameBase {
+      public String name() {
+        return "PublicNameBase::name";
+      }
+    }
+
+    class PublicName extends PublicNameBase implements Named {}
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new Named.PublicName().name());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
index 477af87..409d776 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -178,8 +178,8 @@
         DisassembleCommand.builder().addProgramFiles(out1).setOutputPath(dissasemble1).build());
     Disassemble.disassemble(
         DisassembleCommand.builder().addProgramFiles(out2).setOutputPath(dissasemble2).build());
-    String content1 = StringUtils.join(Files.readAllLines(dissasemble1), "\n");
-    String content2 = StringUtils.join(Files.readAllLines(dissasemble2), "\n");
+    String content1 = StringUtils.join("\n", Files.readAllLines(dissasemble1));
+    String content2 = StringUtils.join("\n", Files.readAllLines(dissasemble2));
     assertEquals(content1, content2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 784e875..646dbf9 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -102,10 +102,7 @@
           .apply(this::configureProgram)
           .setIncludeClassesChecksum(true)
           .compile()
-          .run(
-              parameters.getRuntime(),
-              testClassName,
-              parameters.getRuntime().asDex().getVm().getVersion().toString())
+          .run(parameters.getRuntime(), testClassName)
           .assertSuccess()
           .inspect(this::assertDesugaring);
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomMapHierarchyTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomMapHierarchyTest.java
new file mode 100644
index 0000000..57d8867
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomMapHierarchyTest.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CustomMapHierarchyTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED =
+      StringUtils.lines("B::getOrDefault", "default 1", "B::getOrDefault", "default 2");
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
+  }
+
+  public CustomMapHierarchyTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.parameters = parameters;
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+  }
+
+  @Test
+  public void testWithoutLibraryDesugaring() throws Exception {
+    Assume.assumeTrue(
+        parameters.getRuntime().isCf()
+            || parameters
+                .getApiLevel()
+                .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport()));
+    testForRuntime(parameters)
+        .addInnerClasses(CustomMapHierarchyTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(CustomMapHierarchyTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8Cf2Cf() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(CustomMapHierarchyTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .setIncludeClassesChecksum(true)
+            .setMinApi(parameters.getApiLevel())
+            .allowStdoutMessages()
+            .compile()
+            .writeToZip();
+    String desugaredLibraryKeepRules = "";
+    if (shrinkDesugaredLibrary && keepRuleConsumer.get() != null) {
+      // Collection keep rules is only implemented in the DEX writer.
+      assertEquals(0, keepRuleConsumer.get().length());
+      desugaredLibraryKeepRules = "-keep class * { *; }";
+    }
+    if (parameters.getRuntime().isDex()) {
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .disableDesugaring()
+          .allowStdoutMessages()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary,
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED);
+    } else {
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutput(EXPECTED);
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(CustomMapHierarchyTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepAllClassesRuleWithAllowObfuscation()
+        .addKeepAttributes(
+            ProguardKeepAttributes.SIGNATURE,
+            ProguardKeepAttributes.INNER_CLASSES,
+            ProguardKeepAttributes.ENCLOSING_METHOD)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      System.out.println(new B<String>().getOrDefault("Not found", "default 1"));
+      System.out.println(
+          ((Map<String, String>) new B<String>()).getOrDefault("Not found", "default 2"));
+    }
+  }
+
+  abstract static class A<T> extends LinkedHashMap<T, T> {}
+
+  // AbstractSequentialList implements List further up the hierarchy.
+  static class B<T> extends A<T> {
+    // Need at least one overridden default method for emulated dispatch.
+    @Override
+    public T getOrDefault(Object key, T defaultValue) {
+      System.out.println("B::getOrDefault");
+      return super.getOrDefault(key, defaultValue);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
index a3b768d..af6ca9e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
@@ -9,15 +9,23 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.Serializable;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.nio.file.Path;
 import java.time.LocalDate;
+import java.util.AbstractSequentialList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.function.UnaryOperator;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,7 +37,6 @@
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
-  private static final String EXPECTED = StringUtils.lines("1970", "1", "2");
 
   @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
   public static List<Object[]> data() {
@@ -60,7 +67,7 @@
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(EXPECTED);
+        .assertSuccessWithOutput(expected(parameters, false));
   }
 
   @Test
@@ -96,13 +103,13 @@
               desugaredLibraryKeepRules,
               shrinkDesugaredLibrary)
           .run(parameters.getRuntime(), Main.class)
-          .assertSuccessWithOutput(EXPECTED);
+          .assertSuccessWithOutput(expected(parameters, true));
     } else {
       testForJvm()
           .addProgramFiles(jar)
           .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
           .run(parameters.getRuntime(), Main.class)
-          .assertSuccessWithOutput(EXPECTED);
+          .assertSuccessWithOutput(expected(parameters, true));
     }
   }
 
@@ -129,7 +136,7 @@
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(EXPECTED);
+        .assertSuccessWithOutput(expected(parameters, false));
   }
 
   private void checkRewrittenSignature(CodeInspector inspector) {
@@ -149,6 +156,7 @@
   }
 
   public interface Box<T> {
+
     T addOne(T t);
   }
 
@@ -161,6 +169,61 @@
     }
   }
 
+  private static String expected(
+      TestParameters parameters, boolean genericSignaturesOnEmulatedInterfaces) {
+    final String EXPECTED = StringUtils.lines("Box", "1970", "1", "2");
+    final String STRING_KEY_HASH_MAP_EXPECTED =
+        StringUtils.lines(
+            "StringKeyHashMap", "1", "j$.util.Map<java.lang.String, T>", "2", "true", "true");
+    final String SAME_KEY_AND_VALUE_TYPE_HASH_MAP_EXPECTED =
+        StringUtils.lines(
+            "SameKeyAndValueTypeHashMap", "1", "j$.util.Map<T, T>", "2", "true", "true");
+    final String TRANSFORMING_SEQUENTIAL_LIST_EXPECTED =
+        StringUtils.lines("TransformingSequentialList", "2", "j$.util.List<T>");
+
+    final String EXPECTED_WITH_EMULATED_INTERFACE =
+        STRING_KEY_HASH_MAP_EXPECTED
+            + SAME_KEY_AND_VALUE_TYPE_HASH_MAP_EXPECTED
+            + TRANSFORMING_SEQUENTIAL_LIST_EXPECTED;
+    final String EXPECTED_WITHOUT_EMULATED_INTERFACE_ART_BEFORE_O =
+        StringUtils.lines(
+            "StringKeyHashMap",
+            "1",
+            "interface j$.util.Map",
+            "SameKeyAndValueTypeHashMap",
+            "1",
+            "interface j$.util.Map",
+            "TransformingSequentialList",
+            "2",
+            "interface j$.util.List");
+    final String EXPECTED_WITHOUT_EMULATED_INTERFACE_JVM_AND_ART_FROM_O =
+        StringUtils.lines(
+            "StringKeyHashMap",
+            "0",
+            "SameKeyAndValueTypeHashMap",
+            "0",
+            "TransformingSequentialList",
+            "1");
+
+    return EXPECTED
+        + (genericSignaturesOnEmulatedInterfaces
+                && !parameters
+                    .getApiLevel()
+                    .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport())
+            ? EXPECTED_WITH_EMULATED_INTERFACE
+            : (parameters.isDexRuntime()
+                    && (parameters
+                            .getRuntime()
+                            .asDex()
+                            .getMinApiLevel()
+                            .isLessThan(AndroidApiLevel.N)
+                        || parameters
+                            .getApiLevel()
+                            .isLessThan(TestBase.apiLevelWithDefaultInterfaceMethodsSupport())))
+                ? EXPECTED_WITHOUT_EMULATED_INTERFACE_ART_BEFORE_O
+                : EXPECTED_WITHOUT_EMULATED_INTERFACE_JVM_AND_ART_FROM_O);
+  }
+
   public static class Main {
 
     public static Box<java.time.LocalDate> bar() {
@@ -168,10 +231,96 @@
     }
 
     public static void main(String[] args) {
+      testBox();
+      testEmulatedInterfaceGenericSignatureStringKeyHashMap();
+      testEmulatedInterfaceGenericSignatureSameKeyAndValueTypeHashMap();
+      testEmulatedInterfaceGenericSignatureTransformingSequentialList();
+    }
+
+    public static void testBox() {
+      System.out.println("Box");
       LocalDate localDate = bar().addOne(LocalDate.of(1970, 1, 1));
       System.out.println(localDate.getYear());
       System.out.println(localDate.getMonthValue());
       System.out.println(localDate.getDayOfMonth());
     }
+
+    public static void testEmulatedInterfaceGenericSignatureStringKeyHashMap() {
+      System.out.println("StringKeyHashMap");
+      Class<?> clazz = StringKeyHashMap.class;
+      System.out.println(clazz.getGenericInterfaces().length);
+      if (clazz.getGenericInterfaces().length == 0) {
+        return;
+      }
+      Type genericInterface = clazz.getGenericInterfaces()[0];
+      System.out.println(genericInterface);
+      if (genericInterface instanceof ParameterizedType) {
+        // The j$.util.Map emulated interface has the generic type arguments <String, T>.
+        Type[] actualTypeArguments =
+            ((ParameterizedType) genericInterface).getActualTypeArguments();
+        System.out.println(actualTypeArguments.length);
+        System.out.println(actualTypeArguments[0].equals(String.class));
+        System.out.println(actualTypeArguments[1].equals(clazz.getTypeParameters()[0]));
+      }
+    }
+
+    public static void testEmulatedInterfaceGenericSignatureSameKeyAndValueTypeHashMap() {
+      System.out.println("SameKeyAndValueTypeHashMap");
+      Class<?> clazz = SameKeyAndValueTypeHashMap.class;
+      System.out.println(clazz.getGenericInterfaces().length);
+      if (clazz.getGenericInterfaces().length == 0) {
+        return;
+      }
+      Type genericInterface = clazz.getGenericInterfaces()[0];
+      System.out.println(genericInterface);
+      if (genericInterface instanceof ParameterizedType) {
+        // The j$.util.Map emulated interface has the generic type arguments <T, T>.
+        Type[] actualTypeArguments =
+            ((ParameterizedType) genericInterface).getActualTypeArguments();
+        System.out.println(actualTypeArguments.length);
+        System.out.println(actualTypeArguments[0].equals(clazz.getTypeParameters()[0]));
+        System.out.println(actualTypeArguments[1].equals(clazz.getTypeParameters()[0]));
+      }
+    }
+
+    public static void testEmulatedInterfaceGenericSignatureTransformingSequentialList() {
+      System.out.println("TransformingSequentialList");
+      Class<?> clazz = TransformingSequentialList.class;
+      System.out.println(clazz.getGenericInterfaces().length);
+      if (clazz.getGenericInterfaces().length == 1) {
+        return;
+      }
+      // j$.util.List emulated interface has the generic type argument <T>.
+      System.out.println(clazz.getGenericInterfaces()[1]);
+    }
+  }
+
+  // LinkedHashMap implements Map.
+  abstract static class StringKeyHashMap<T> extends LinkedHashMap<String, T> {
+
+    // Need at least one overridden default method for emulated dispatch.
+    @Override
+    public T getOrDefault(Object key, T defaultValue) {
+      return super.getOrDefault(key, defaultValue);
+    }
+  }
+
+  // LinkedHashMap implements Map.
+  abstract static class SameKeyAndValueTypeHashMap<T> extends LinkedHashMap<T, T> {
+
+    // Need at least one overridden default method for emulated dispatch.
+    @Override
+    public T getOrDefault(Object key, T defaultValue) {
+      return super.getOrDefault(key, defaultValue);
+    }
+  }
+
+  // AbstractSequentialList implements List further up the hierarchy.
+  abstract static class TransformingSequentialList<F, T> extends AbstractSequentialList<T>
+      implements Serializable {
+
+    // Need at least one overridden default method for emulated dispatch.
+    @Override
+    public void replaceAll(UnaryOperator<T> operator) {}
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java
index 62d2d3a..ae4eda0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DumpCoreLibUsage.java
@@ -85,13 +85,13 @@
         if (!field.accessFlags.isPublic()) {
           continue;
         }
-        if (filter.contains(field.field)) {
+        if (filter.contains(field.getReference())) {
           continue;
         }
-        if (usesTypeFromPackage(pkg, field.field)) {
+        if (usesTypeFromPackage(pkg, field.getReference())) {
           headerWritten = writeHeaderIfNeeded(libraryClass.type, headerWritten);
           System.out.println("  " + field.toSourceString());
-          found.add(field.field);
+          found.add(field.getReference());
         }
       }
 
@@ -105,13 +105,13 @@
         if (method.isSyntheticMethod()) {
           continue;
         }
-        if (filter.contains(method.method)) {
+        if (filter.contains(method.getReference())) {
           continue;
         }
-        if (usesTypeFromPackage(pkg, method.method)) {
+        if (usesTypeFromPackage(pkg, method.getReference())) {
           headerWritten = writeHeaderIfNeeded(libraryClass.type, headerWritten);
-          System.out.println("  " + method.method.toSourceStringWithoutHolder());
-          found.add(method.method);
+          System.out.println("  " + method.getReference().toSourceStringWithoutHolder());
+          found.add(method.getReference());
         }
       }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java
index e345069..32d9d95 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaUtilFunctionTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.List;
 import java.util.function.Function;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -51,7 +50,7 @@
         classSubject
             .uniqueMethodWithName("applyFunction")
             .getMethod()
-            .method
+            .getReference()
             .proto
             .parameters
             .values[0]
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index 43f53ab..624e8a0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.GenerateLintFiles;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
@@ -26,9 +28,22 @@
 import java.util.List;
 import org.junit.Assume;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public class LintFilesTest extends TestBase {
 
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public LintFilesTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
   private void checkFileContent(AndroidApiLevel minApiLevel, Path lintFile) throws Exception {
     // Just do some light probing in the generated lint files.
     List<String> methods = FileUtils.readAllLines(lintFile);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
index cb8c24f..e23e86d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -64,7 +64,7 @@
             .anyMatch(
                 m ->
                     m.getMethod()
-                        .method
+                        .getReference()
                         .proto
                         .parameters
                         .values[0]
@@ -75,7 +75,7 @@
             .anyMatch(
                 m ->
                     m.getMethod()
-                        .method
+                        .getReference()
                         .proto
                         .parameters
                         .values[0]
@@ -210,9 +210,9 @@
         virtualMethods.stream()
             .anyMatch(
                 m ->
-                    m.getMethod().method.name.toString().equals("foo")
+                    m.getMethod().getReference().name.toString().equals("foo")
                         && m.getMethod()
-                            .method
+                            .getReference()
                             .proto
                             .parameters
                             .values[0]
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index 0ee446d..5cd8c8d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -121,7 +121,7 @@
         IterableUtils.size(
             myCollection
                 .getDexProgramClass()
-                .virtualMethods(m -> m.method.name.toString().equals("forEach"))));
+                .virtualMethods(m -> m.getReference().name.toString().equals("forEach"))));
   }
 
   private void assertWrapperMethodsPresent(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
index b38e710..47320d8 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
@@ -86,16 +86,19 @@
   private boolean isNestBridge(FoundMethodSubject methodSubject) {
     DexEncodedMethod method = methodSubject.getMethod();
     if (method.isInstanceInitializer()) {
-      if (method.method.proto.parameters.isEmpty()) {
+      if (method.getReference().proto.parameters.isEmpty()) {
         return false;
       }
-      DexType[] formals = method.method.proto.parameters.values;
+      DexType[] formals = method.getReference().proto.parameters.values;
       DexType lastFormal = formals[formals.length - 1];
       return lastFormal.isClassType()
           && SyntheticItemsTestUtils.isInitializerTypeArgument(
               Reference.classFromDescriptor(lastFormal.toDescriptorString()));
     }
-    return method.method.name.toString()
+    return method
+        .getReference()
+        .name
+        .toString()
         .startsWith(NestBasedAccessDesugaring.NEST_ACCESS_NAME_PREFIX);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 2e97d1e..9f9dd85 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -35,20 +36,35 @@
   public static List<Object[]> data() {
     // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+        getTestParameters()
+            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withDexRuntimes()
+            .withAllApiLevelsAlsoForCf()
+            .build());
   }
 
   @Test
-  public void testJvm() throws Exception {
-    testForJvm()
+  public void testD8AndJvm() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(PROGRAM_DATA)
+          .enablePreview()
+          .run(parameters.getRuntime(), MAIN_TYPE)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    }
+    testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
-        .enablePreview()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+        .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testR8Cf() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
     Path output =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
new file mode 100644
index 0000000..53d6201
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/GenerateRecordMethods.java
@@ -0,0 +1,118 @@
+// 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.records;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfTypeInstruction;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.RecordRewriter;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenerateRecordMethods extends MethodGenerationBase {
+  private final DexType GENERATED_TYPE =
+      factory.createType("Lcom/android/tools/r8/ir/desugar/RecordCfMethods;");
+  private final List<Class<?>> METHOD_TEMPLATE_CLASSES = ImmutableList.of(RecordMethods.class);
+
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+  }
+
+  public GenerateRecordMethods(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Override
+  protected DexType getGeneratedType() {
+    return GENERATED_TYPE;
+  }
+
+  @Override
+  protected List<Class<?>> getMethodTemplateClasses() {
+    return METHOD_TEMPLATE_CLASSES;
+  }
+
+  @Override
+  protected int getYear() {
+    return 2021;
+  }
+
+  @Override
+  protected CfCode getCode(String holderName, String methodName, CfCode code) {
+    DexType recordStubType =
+        factory.createType("Lcom/android/tools/r8/desugar/records/RecordMethods$RecordStub;");
+    code.setInstructions(
+        code.getInstructions().stream()
+            .map(instruction -> rewriteRecordStub(factory, instruction, recordStubType))
+            .collect(Collectors.toList()));
+    return code;
+  }
+
+  private CfInstruction rewriteRecordStub(
+      DexItemFactory factory, CfInstruction instruction, DexType recordStubType) {
+    if (instruction.isTypeInstruction()) {
+      CfTypeInstruction typeInstruction = instruction.asTypeInstruction();
+      return typeInstruction.withType(
+          rewriteType(factory, recordStubType, typeInstruction.getType()));
+    }
+    if (instruction.isInvoke()) {
+      CfInvoke cfInvoke = instruction.asInvoke();
+      DexMethod method = cfInvoke.getMethod();
+      DexMethod newMethod =
+          factory.createMethod(
+              rewriteType(factory, recordStubType, method.holder),
+              method.proto,
+              rewriteName(method.name));
+      return new CfInvoke(cfInvoke.getOpcode(), newMethod, cfInvoke.isInterface());
+    }
+    return instruction;
+  }
+
+  private String rewriteName(DexString name) {
+    return name.toString().equals("getFieldsAsObjects")
+        ? RecordRewriter.GET_FIELDS_AS_OBJECTS_METHOD_NAME
+        : name.toString();
+  }
+
+  private DexType rewriteType(DexItemFactory factory, DexType recordStubType, DexType type) {
+    return type == recordStubType ? factory.recordType : type;
+  }
+
+  @Test
+  public void testRecordMethodsGenerated() throws Exception {
+    ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+    sorted.sort(Comparator.comparing(Class::getTypeName));
+    assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+    assertEquals(
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new GenerateRecordMethods(null).generateMethodsAndWriteThemToFile();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
index 5fce5e2..d388ac4 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -35,20 +36,35 @@
   public static List<Object[]> data() {
     // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+        getTestParameters()
+            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withDexRuntimes()
+            .withAllApiLevelsAlsoForCf()
+            .build());
   }
 
   @Test
-  public void testJvm() throws Exception {
-    testForJvm()
+  public void testD8AndJvm() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(PROGRAM_DATA)
+          .enablePreview()
+          .run(parameters.getRuntime(), MAIN_TYPE)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    }
+    testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
-        .enablePreview()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+        .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testR8Cf() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
     Path output =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index 071af85..711909f 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -48,20 +49,35 @@
   public static List<Object[]> data() {
     // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+        getTestParameters()
+            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withDexRuntimes()
+            .withAllApiLevelsAlsoForCf()
+            .build());
   }
 
   @Test
-  public void testJvm() throws Exception {
-    testForJvm()
+  public void testD8AndJvm() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(PROGRAM_DATA)
+          .enablePreview()
+          .run(parameters.getRuntime(), MAIN_TYPE)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    }
+    testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
-        .enablePreview()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+        .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testR8Cf() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
     Path output =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
new file mode 100644
index 0000000..bd08a25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -0,0 +1,101 @@
+// 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.records;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordMergeTest extends TestBase {
+
+  private static final String RECORD_NAME_1 = "RecordWithMembers";
+  private static final byte[][] PROGRAM_DATA_1 = RecordTestUtils.getProgramData(RECORD_NAME_1);
+  private static final String MAIN_TYPE_1 = RecordTestUtils.getMainType(RECORD_NAME_1);
+  private static final String EXPECTED_RESULT_1 =
+      StringUtils.lines(
+          "BobX", "43", "BobX", "43", "FelixX", "-1", "FelixX", "-1", "print", "Bob43", "extra");
+
+  private static final String RECORD_NAME_2 = "SimpleRecord";
+  private static final byte[][] PROGRAM_DATA_2 = RecordTestUtils.getProgramData(RECORD_NAME_2);
+  private static final String MAIN_TYPE_2 = RecordTestUtils.getMainType(RECORD_NAME_2);
+  private static final String EXPECTED_RESULT_2 =
+      StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
+
+  private final TestParameters parameters;
+
+  public RecordMergeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
+    return buildParameters(
+        getTestParameters()
+            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withDexRuntimes()
+            .withAllApiLevelsAlsoForCf()
+            .build());
+  }
+
+  @Test
+  public void testMergeDesugaredInputs() throws Exception {
+    Path output1 =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA_1)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+            .compile()
+            .writeToZip();
+    Path output2 =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA_2)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+            .compile()
+            .writeToZip();
+    D8TestCompileResult result =
+        testForD8(parameters.getBackend())
+            .addProgramFiles(output1, output2)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .compile();
+    result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
+    result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
+  }
+
+  @Test
+  public void testMergeDesugaredAndNonDesugaredInputs() throws Exception {
+    Path output1 =
+        testForD8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA_1)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+            .compile()
+            .writeToZip();
+    D8TestCompileResult result =
+        testForD8(parameters.getBackend())
+            .addProgramFiles(output1)
+            .addProgramClassFileData(PROGRAM_DATA_2)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+            .compile();
+    result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
+    result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
new file mode 100644
index 0000000..7b1b2e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
@@ -0,0 +1,43 @@
+// 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.records;
+
+import java.util.Arrays;
+
+// This class implements support methods for record desugaring. The RecordRewriter
+// rewrites relevant calls to one of the following methods.
+public class RecordMethods {
+
+  public static String toString(RecordStub recordInstance, String simpleName, String fieldNames) {
+    // Example: "Person[name=Jane Doe, age=42]"
+    String[] fieldNamesSplit = fieldNames.isEmpty() ? new String[0] : fieldNames.split(";");
+    Object[] fields = recordInstance.getFieldsAsObjects();
+    StringBuilder builder = new StringBuilder();
+    builder.append(simpleName).append("[");
+    for (int i = 0; i < fieldNamesSplit.length; i++) {
+      builder.append(fieldNamesSplit[i]).append("=").append(fields[i]);
+      if (i != fieldNamesSplit.length - 1) {
+        builder.append(", ");
+      }
+    }
+    builder.append("]");
+    return builder.toString();
+  }
+
+  public static int hashCode(RecordStub recordInstance) {
+    return 31 * Arrays.hashCode(recordInstance.getFieldsAsObjects())
+        + recordInstance.getClass().hashCode();
+  }
+
+  public static boolean equals(RecordStub recordInstance, Object other) {
+    return recordInstance.getClass() == other.getClass()
+        && Arrays.equals(
+            ((RecordStub) other).getFieldsAsObjects(), recordInstance.getFieldsAsObjects());
+  }
+
+  public abstract static class RecordStub {
+    abstract Object[] getFieldsAsObjects();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
index 610b061..5472f22 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
 
-import com.android.tools.r8.Jdk9TestUtils;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
@@ -14,6 +13,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -38,24 +38,38 @@
   public static List<Object[]> data() {
     // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk15).
     return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk15()).build());
+        getTestParameters()
+            .withCustomRuntime(CfRuntime.getCheckedInJdk15())
+            .withDexRuntimes()
+            .withAllApiLevelsAlsoForCf()
+            .build());
   }
 
   @Test
-  public void testJvm() throws Exception {
-    testForJvm()
+  public void testD8AndJvm() throws Exception {
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addProgramClassFileData(PROGRAM_DATA)
+          .enablePreview()
+          .run(parameters.getRuntime(), MAIN_TYPE)
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+    }
+    testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
-        .enablePreview()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+        .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testR8Cf() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
     Path output =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
-            .addLibraryFiles(Jdk9TestUtils.getJdk9LibraryFiles(temp))
             .setMinApi(parameters.getApiLevel())
             .addKeepRules(RECORD_KEEP_RULE)
             .addKeepMainRule(MAIN_TYPE)
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index d5d2b1a..4820c08 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -40,7 +40,7 @@
         getTestParameters()
             .withCustomRuntime(CfRuntime.getCheckedInJdk15())
             .withDexRuntimes()
-            .withAllApiLevels()
+            .withAllApiLevelsAlsoForCf()
             .build());
   }
 
@@ -52,10 +52,8 @@
           .enablePreview()
           .run(parameters.getRuntime(), MAIN_TYPE)
           .assertSuccessWithOutput(EXPECTED_RESULT);
-      // TODO(b/179146128): Add a test for D8 cf to cf.
-      return;
     }
-    testForD8()
+    testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
rename to src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java
index e2474ca..cd2b968 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest.java
@@ -22,7 +22,7 @@
 import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
-public class DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest
+public class DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest
     extends TestBase {
 
   private static final String EXPECTED = StringUtils.lines("I.m()");
@@ -36,7 +36,7 @@
         getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
-  public DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest(
+  public DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionInvokeVirtualTest(
       TestParameters parameters, boolean invalidInvoke) {
     this.parameters = parameters;
     this.invalidInvoke = invalidInvoke;
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionTest.java
similarity index 75%
rename from src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
rename to src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionTest.java
index c649c8d..3a55d74 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionTest.java
@@ -1,3 +1,6 @@
+// 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.desugaring.interfacemethods;
 
 import static org.junit.Assume.assumeTrue;
@@ -11,7 +14,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class DefaultInterfaceMethodDesugaringWithStaticResolutionTest extends TestBase {
+public class DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionTest extends TestBase {
 
   private static final String EXPECTED = StringUtils.lines("I.m()");
 
@@ -22,7 +25,8 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public DefaultInterfaceMethodDesugaringWithStaticResolutionTest(TestParameters parameters) {
+  public DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionTest(
+      TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -39,7 +43,7 @@
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     testForD8()
-        .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
+        .addInnerClasses(DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionTest.class)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
@@ -49,7 +53,7 @@
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
+        .addInnerClasses(DefaultInterfaceMethodDesugaringWithPrivateStaticResolutionTest.class)
         .addKeepAllClassesRule()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
similarity index 64%
copy from src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
copy to src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
index e2474ca..b968127 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -22,10 +21,11 @@
 import org.objectweb.asm.Opcodes;
 
 @RunWith(Parameterized.class)
-public class DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest
+public class DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest
     extends TestBase {
 
   private static final String EXPECTED = StringUtils.lines("I.m()");
+  private static final String EXPECTED_R8 = StringUtils.lines("B.m()");
 
   private final TestParameters parameters;
   private final boolean invalidInvoke;
@@ -33,10 +33,11 @@
   @Parameterized.Parameters(name = "{0}, invalid:{1}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
   }
 
-  public DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest(
+  public DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest(
       TestParameters parameters, boolean invalidInvoke) {
     this.parameters = parameters;
     this.invalidInvoke = invalidInvoke;
@@ -53,8 +54,6 @@
                 B.class.getDeclaredMethod("m"),
                 flags -> {
                   assert flags.isPublic();
-                  flags.unsetPublic();
-                  flags.setPrivate();
                   flags.setStatic();
                 })
             .transform(),
@@ -83,7 +82,8 @@
         testForRuntime(parameters)
             .addProgramClasses(getProgramClasses())
             .addProgramClassFileData(getProgramClassData())
-            .run(parameters.getRuntime(), TestClass.class));
+            .run(parameters.getRuntime(), TestClass.class),
+        false);
   }
 
   @Test
@@ -95,56 +95,36 @@
             .addKeepAllClassesRule()
             .setMinApi(parameters.getApiLevel())
             .compile()
-            .run(parameters.getRuntime(), TestClass.class));
+            .run(parameters.getRuntime(), TestClass.class),
+        true);
   }
 
-  private void checkResult(TestRunResult<?> result) {
+  private void checkResult(TestRunResult<?> result, boolean isR8) {
     // Invalid invoke case is where the invoke-virtual targets C.m.
     if (invalidInvoke) {
-      // Up to 4.4 the exception for targeting a private static was ICCE.
-      if (isDexOlderThanOrEqual(Version.V4_4_4)) {
+      if (parameters.isCfRuntime()) {
         result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
         return;
       }
-      // Then up to 6.0 the runtime just ignores privates leading to incorrectly hitting I.m
-      if (isDexOlderThanOrEqual(Version.V6_0_1)) {
+      if (parameters.getDexRuntimeVersion().isInRangeInclusive(Version.V5_1_1, Version.V7_0_0)) {
         result.assertSuccessWithOutput(EXPECTED);
         return;
       }
-      if (!unexpectedArtFailure() && !parameters.canUseDefaultAndStaticInterfaceMethods()) {
-        assert false : "Dead code until future ART behavior change. See b/152199517";
-        // Desugaring will insert a forwarding bridge which will hide the "invalid invoke" case.
-        // Thus, a future ART runtime that does not have the invalid IAE for the private override
-        // will end up calling the forward method to I.m.
-        result.assertSuccessWithOutput(EXPECTED);
-      }
-      // The expected behavior is IAE since the resolved method is private.
-      result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
       return;
     }
 
-    // The non-invalid case is where the invoke-virtual targets A.m.
-
-    // In the successful case ART since 6.0 incorrectly throws IAE due to the private override.
-    if (unexpectedArtFailure()) {
-      result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+    if (isR8
+        && parameters.isDexRuntime()
+        && parameters.getDexRuntimeVersion().isNewerThan(Version.V6_0_1)) {
+      // TODO(b/1822553980: This should be EXPECTED.
+      result.assertSuccessWithOutput(EXPECTED_R8);
       return;
     }
 
-    // The expected behavior is that the resolution of A.m will resolve and hit I.m.
     result.assertSuccessWithOutput(EXPECTED);
   }
 
-  private boolean isDexOlderThanOrEqual(Version version) {
-    return parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(version);
-  }
-
-  private boolean unexpectedArtFailure() {
-    return parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_6_0_1_HOST);
-  }
-
   static class TestClass {
 
     public static void main(String[] args) {
@@ -165,7 +145,7 @@
 
   static class B extends A {
 
-    public /* will be: private static */ void m() {
+    public /* will be: public static */ void m() {
       System.out.println("B.m()");
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java
new file mode 100644
index 0000000..1684c5a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java
@@ -0,0 +1,115 @@
+// 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.desugaring.interfacemethods;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+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 com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest
+    extends TestBase {
+
+  private static final String EXPECTED_INVALID = StringUtils.lines("I.m()");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private Collection<Class<?>> getProgramClasses() {
+    return ImmutableList.of(TestClass.class, I.class, B.class);
+  }
+
+  private Collection<byte[]> getProgramClassData() throws Exception {
+    return ImmutableList.of(
+        transformer(A.class)
+            .setAccessFlags(
+                A.class.getDeclaredMethod("m"),
+                flags -> {
+                  assertTrue(flags.isPrivate());
+                  assertTrue(flags.isStatic());
+                  flags.promoteToPublic();
+                })
+            .transform());
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassData())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassData())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        // TODO(b/182335909): Ideally, this should also throw ICCE when desugaring.
+        .applyIf(
+            !parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()
+                || parameters.isDexRuntimeVersion(Version.V7_0_0),
+            r -> r.assertSuccessWithOutput(EXPECTED_INVALID),
+            r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassData())
+        .addKeepAllClassesRule()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new B().m();
+    }
+  }
+
+  interface I {
+
+    default void m() {
+      System.out.println("I.m()");
+    }
+  }
+
+  public static class A {
+
+    private static /* will be: public static */ void m() {
+      System.out.println("A.m()");
+    }
+  }
+
+  static class B extends A implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest.java
new file mode 100644
index 0000000..c442b75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest.java
@@ -0,0 +1,108 @@
+// 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.desugaring.interfacemethods;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest extends TestBase {
+
+  private static final String EXPECTED = StringUtils.lines("I.m()");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private Collection<Class<?>> getProgramClasses() {
+    return ImmutableList.of(TestClass.class, I.class, B.class);
+  }
+
+  private Collection<byte[]> getProgramClassData() throws Exception {
+    return ImmutableList.of(
+        transformer(A.class)
+            .setAccessFlags(
+                A.class.getDeclaredMethod("m"),
+                flags -> {
+                  assertTrue(flags.isPrivate());
+                  assertTrue(flags.isStatic());
+                  flags.promoteToPublic();
+                })
+            .transform());
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassData())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassData())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassData())
+        .addKeepAllClassesRule()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I b = new B();
+      b.m();
+    }
+  }
+
+  interface I {
+
+    default void m() {
+      System.out.println("I.m()");
+    }
+  }
+
+  public static class A {
+
+    private static /* will be: public static */ void m() {
+      System.out.println("A.m()");
+    }
+  }
+
+  static class B extends A implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
index 7c8eafe..1d01be7 100644
--- a/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
+++ b/src/test/java/com/android/tools/r8/dump/DumpMainDexInputsTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dump;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static java.util.stream.Collectors.toList;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -12,7 +13,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;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -89,19 +89,32 @@
         "pre-native-multidex only",
         parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.K));
     Path dumpDir = temp.newFolder().toPath();
+    // Pre-compile to DEX and do main-dex list on DEX inputs only.
+    Path dexed =
+        testForD8()
+            .addInnerClasses(DumpMainDexInputsTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+
     testForD8()
+        .addProgramFiles(dexed)
         .setMinApi(parameters.getApiLevel())
-        .addInnerClasses(DumpMainDexInputsTest.class)
         .addMainDexListFiles(
             mainDexListForMainDexFile1AndMainDexFile2(), mainDexListForMainDexFile3())
         .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
-        .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
         .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
-        .compile()
-        .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
-        .assertAllWarningMessagesMatch(
-            containsString(
-                "Dumping main dex list resources may have side effects due to I/O on Paths."))
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertNoErrors()
+                    .assertInfosMatch(
+                        diagnosticMessage(containsString("Dumped compilation inputs to:")))
+                    .assertWarningsMatch(
+                        diagnosticMessage(
+                            containsString(
+                                "Dumping main dex list resources may have side effects due to I/O"
+                                    + " on Paths."))))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello, world");
     verifyDumpDir(dumpDir, true);
@@ -117,7 +130,7 @@
         .setMinApi(parameters.getApiLevel())
         .addInnerClasses(DumpMainDexInputsTest.class)
         .addMainDexRulesFiles(newMainDexRulesPath1(), newMainDexRulesPath2())
-        .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
+        .addMainDexKeepClassRules(MainDexClass1.class, MainDexClass2.class, TestClass.class)
         .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
         .compile()
         .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
@@ -136,7 +149,7 @@
         .setMinApi(parameters.getApiLevel())
         .addInnerClasses(DumpMainDexInputsTest.class)
         .addMainDexRuleFiles(newMainDexRulesPath1(), newMainDexRulesPath2())
-        .addMainDexListClasses(MainDexClass1.class, MainDexClass2.class, TestClass.class)
+        .addMainDexKeepClassRules(MainDexClass1.class, MainDexClass2.class, TestClass.class)
         .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
         .addKeepAllClassesRule()
         .allowDiagnosticMessages()
@@ -168,9 +181,10 @@
     assertTrue(Files.exists(unzipped.resolve("r8-version")));
     assertTrue(Files.exists(unzipped.resolve("program.jar")));
     assertTrue(Files.exists(unzipped.resolve("library.jar")));
-    assertTrue(Files.exists(unzipped.resolve("main-dex-list.txt")));
     if (checkMainDexList) {
-      String mainDex = new String(Files.readAllBytes(unzipped.resolve("main-dex-list.txt")));
+      Path mainDexListFile = unzipped.resolve("main-dex-list.txt");
+      assertTrue(Files.exists(mainDexListFile));
+      String mainDex = new String(Files.readAllBytes(mainDexListFile));
       List<String> mainDexLines =
           Arrays.stream(mainDex.split("\n")).filter(s -> !s.isEmpty()).collect(toList());
       assertEquals(6, mainDexLines.size());
@@ -186,7 +200,9 @@
               .collect(toList());
       assertEquals(expected, mainDexLines);
     } else {
-      String mainDexRules = new String(Files.readAllBytes(unzipped.resolve("main-dex-rules.txt")));
+      Path mainDexRulesFile = unzipped.resolve("main-dex-rules.txt");
+      assertTrue(Files.exists(mainDexRulesFile));
+      String mainDexRules = new String(Files.readAllBytes(mainDexRulesFile));
       assertThat(mainDexRules, containsString(mainDexRulesForMainDexFile1AndMainDexFile2));
       assertThat(mainDexRules, containsString(mainDexRulesForMainDexFile3));
     }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
index 1e5bd5b..750c6fe 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingClassStaticizerTest.java
@@ -76,7 +76,7 @@
     MethodSubject method =
         codeInspector.clazz(CompanionHost.class).uniqueMethodWithName(renamedMethodName);
     assertThat(method, isPresent());
-    assertEquals("int", method.getMethod().method.proto.parameters.toString());
+    assertEquals("int", method.getMethod().getReference().proto.parameters.toString());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index 1abfea6f..644fc9c 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -60,7 +60,8 @@
   String getExpectedOutput() {
     if (parameters.isDexRuntime()) {
       Version version = parameters.getRuntime().asDex().getVm().getVersion();
-      if (version.isAtLeast(Version.V5_1_1) && version.isOlderThanOrEqual(Version.V6_0_1)) {
+      if (version.isNewerThanOrEqual(Version.V5_1_1)
+          && version.isOlderThanOrEqual(Version.V6_0_1)) {
         return UNEXPECTED_DEX_5_AND_6_OUTPUT;
       }
     }
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index b1d9990..06666db 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -227,7 +227,7 @@
     DexField aFieldOnInterface = factory
         .createField(factory.createType("LInterface;"), factory.intType, "aField");
 
-    assertEquals(aFieldOnInterface, appInfo.lookupStaticTarget(aFieldOnSubClass).field);
+    assertEquals(aFieldOnInterface, appInfo.lookupStaticTarget(aFieldOnSubClass).getReference());
 
     assertEquals("42", runArt(application));
 
diff --git a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
new file mode 100644
index 0000000..f989728
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
@@ -0,0 +1,111 @@
+// 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.invokevirtual;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.isCfRuntime(CfVm.JDK11),
+            r -> r.assertSuccessWithOutputLines("I::foo"),
+            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class));
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::assertResultIsCorrect);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(I.class)
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/182189123): This should have the same behavior as D8.
+        .assertSuccessWithOutputLines("I::foo");
+  }
+
+  public void assertResultIsCorrect(SingleTestRunResult<?> result) {
+    if (parameters.isCfRuntime(CfVm.JDK11)
+        && parameters.getApiLevel().isGreaterThan(AndroidApiLevel.M)) {
+      result.assertSuccessWithOutputLines("I::foo");
+      return;
+    }
+    // TODO(b/152199517): Should be illegal access for DEX.
+    if (parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThan(AndroidApiLevel.M)) {
+      result.assertSuccessWithOutputLines("I::foo");
+      return;
+    }
+    result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+  }
+
+  @NoVerticalClassMerging
+  public interface I {
+
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  @NoVerticalClassMerging
+  public static class Base {
+
+    private void foo() {
+      System.out.println("Base::foo");
+    }
+  }
+
+  public static class Sub extends Base implements I {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      runFoo(new Sub());
+    }
+
+    private static void runFoo(I i) {
+      i.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java
new file mode 100644
index 0000000..9c9926d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultTest.java
@@ -0,0 +1,98 @@
+// 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.invokevirtual;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualPrivateBaseWithDefaultTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public InvokeVirtualPrivateBaseWithDefaultTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.isCfRuntime()
+                || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M),
+            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class),
+            // TODO(b/152199517): Should be illegal access for DEX.
+            r -> r.assertSuccessWithOutputLines("I::foo"));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(I.class)
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.isCfRuntime()
+                || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M),
+            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class),
+            // TODO(b/152199517): Should be illegal access for DEX.
+            r -> r.assertSuccessWithOutputLines("I::foo"));
+  }
+
+  @NoVerticalClassMerging
+  public interface I {
+
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  @NoVerticalClassMerging
+  public static class Base {
+
+    private void foo() {
+      System.out.println("Base::foo");
+    }
+  }
+
+  public static class Sub extends Base implements I {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new Sub().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index 51b5323..57078d1 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -70,12 +70,13 @@
 
   private void testVirtualLookup(DexProgramClass clazz, DexEncodedMethod method) {
     // Check lookup will produce the same result.
-    DexMethod id = method.method;
+    DexMethod id = method.getReference();
     assertEquals(
-        appInfo().resolveMethodOnClass(method.method, id.holder).getSingleTarget(), method);
+        appInfo().resolveMethodOnClass(method.getReference(), id.holder).getSingleTarget(), method);
 
     // Check lookup targets with include method.
-    ResolutionResult resolutionResult = appInfo().resolveMethodOnClass(method.method, clazz);
+    ResolutionResult resolutionResult =
+        appInfo().resolveMethodOnClass(method.getReference(), clazz);
     AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
     LookupResult lookupResult =
         resolutionResult.lookupVirtualDispatchTargets(
@@ -96,7 +97,7 @@
     AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
     LookupResultSuccess lookupResult =
         appInfo()
-            .resolveMethodOnInterface(clazz, method.method)
+            .resolveMethodOnInterface(clazz, method.getReference())
             .lookupVirtualDispatchTargets(clazz, appInfo(), appInfo, dexReference -> false)
             .asLookupResultSuccess();
     assertNotNull(lookupResult);
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 024dd38..ec665d3 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
@@ -140,7 +140,7 @@
   private DexEncodedField uniqueFieldByName(DexProgramClass clazz, String name) {
     DexEncodedField result = null;
     for (DexEncodedField field : clazz.fields()) {
-      if (field.field.name.toSourceString().equals(name)) {
+      if (field.getReference().name.toSourceString().equals(name)) {
         assertNull(result);
         result = field;
       }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
index 009d4e3d..36acfe9 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/PartialCallGraphTest.java
@@ -137,7 +137,7 @@
 
   private Node findNode(Iterable<Node> nodes, String name) {
     for (Node n : nodes) {
-      if (n.getMethod().method.name.toString().equals(name)) {
+      if (n.getMethod().getReference().name.toString().equals(name)) {
         return n;
       }
     }
@@ -147,7 +147,7 @@
   private ProgramMethod findMethod(String name) {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedMethod method : clazz.methods()) {
-        if (method.method.name.toString().equals(name)) {
+        if (method.getReference().name.toString().equals(name)) {
           return new ProgramMethod(clazz, method);
         }
       }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
index a3bd157..bc84108 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
@@ -62,4 +62,15 @@
       CharSequence s, int beginIndex, int endIndex, int radix) throws NumberFormatException {
     return Integer.parseInt(s.subSequence(beginIndex, endIndex).toString(), radix);
   }
+
+  public static int parseIntSubsequenceWithRadixDalvik(
+      CharSequence s, int beginIndex, int endIndex, int radix) throws NumberFormatException {
+    // Dalvik (API level 19 and below) does not support a '+' prefix.
+    if (endIndex - beginIndex >= 2
+        && s.charAt(beginIndex) == '+'
+        && Character.digit(s.charAt(beginIndex + 1), radix) >= 0) {
+      beginIndex++;
+    }
+    return Integer.parseInt(s.subSequence(beginIndex, endIndex).toString(), radix);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
index 65a60ef..089fa86 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
@@ -15,6 +15,17 @@
     return Long.parseLong(s.subSequence(beginIndex, endIndex).toString(), radix);
   }
 
+  public static long parseLongSubsequenceWithRadixDalvik(
+      CharSequence s, int beginIndex, int endIndex, int radix) {
+    // Dalvik (API level 19 and below) does not support a '+' prefix.
+    if (endIndex - beginIndex >= 2
+        && s.charAt(beginIndex) == '+'
+        && Character.digit(s.charAt(beginIndex + 1), radix) >= 0) {
+      beginIndex++;
+    }
+    return Long.parseLong(s.subSequence(beginIndex, endIndex).toString(), radix);
+  }
+
   public static long divideUnsigned(long dividend, long divisor) {
     // This implementation is adapted from Guava's UnsignedLongs.java and Longs.java.
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 7049089..aad2f6f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -276,7 +276,7 @@
     for (int i = 0; i < 3; ++i) {
       MethodSubject markerSubject = clazz.method("void", "marker" + i, Collections.emptyList());
       assertTrue(markerSubject.isPresent());
-      markers[i] = markerSubject.getMethod().method;
+      markers[i] = markerSubject.getMethod().getReference();
     }
 
     // Count invokes to callee between markers.
@@ -290,7 +290,7 @@
 
       DexMethod target = ((InvokeInstructionSubject) instruction).invokedMethod();
 
-      if (target == callee.getMethod().method) {
+      if (target == callee.getMethod().getReference()) {
         assertTrue(phase == 0 || phase == 1);
         ++counters[phase];
         continue;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
index 589fa3f..709a44b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
@@ -73,7 +73,7 @@
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
             .map(InstructionSubject::getField)
-            .filter(enumFieldSubject.getField().field::equals)
+            .filter(enumFieldSubject.getField().getReference()::equals)
             .count());
     assertEquals(
         1,
@@ -81,7 +81,9 @@
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
             .map(InstructionSubject::getField)
-            .filter(enumWithClassInitializationSideEffectsFieldSubject.getField().field::equals)
+            .filter(
+                enumWithClassInitializationSideEffectsFieldSubject.getField().getReference()
+                    ::equals)
             .count());
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/PackagePrivateOverrideDeVirtualizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/PackagePrivateOverrideDeVirtualizerTest.java
new file mode 100644
index 0000000..b3ca988
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/PackagePrivateOverrideDeVirtualizerTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.devirtualize;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PackagePrivateOverrideDeVirtualizerTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String[] EXPECTED = new String[] {"SubViewModel.clear()", "ViewModel.clear()"};
+  private final String[] EXPECTED_DALVIK =
+      new String[] {"SubViewModel.clear()", "SubViewModel.clear()"};
+  private final String NEW_DESCRIPTOR = "Lfoo/bar/baz/SubViewModel;";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public PackagePrivateOverrideDeVirtualizerTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(ViewModel.class)
+        .addProgramClassFileData(
+            getSubViewModelInAnotherPackage(), getRewrittenSubViewModelInMain())
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::assertSuccessOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(ViewModel.class)
+        .addProgramClassFileData(
+            getSubViewModelInAnotherPackage(), getRewrittenSubViewModelInMain())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/182185057): This should be EXPECTED.
+        .assertSuccessWithOutputLines(EXPECTED_DALVIK);
+  }
+
+  private byte[] getSubViewModelInAnotherPackage() throws Exception {
+    return transformer(SubViewModel.class).setClassDescriptor(NEW_DESCRIPTOR).transform();
+  }
+
+  private byte[] getRewrittenSubViewModelInMain() throws Exception {
+    return transformer(Main.class)
+        .replaceClassDescriptorInMethodInstructions(descriptor(SubViewModel.class), NEW_DESCRIPTOR)
+        .transform();
+  }
+
+  private void assertSuccessOutput(TestRunResult<?> result) {
+    if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+      result.assertSuccessWithOutputLines(EXPECTED_DALVIK);
+    } else {
+      result.assertSuccessWithOutputLines(EXPECTED);
+    }
+  }
+
+  @SuppressWarnings("override") /* after changing the package the clear method is not overridden */
+  @NoVerticalClassMerging
+  public static class ViewModel {
+
+    @NeverInline
+    void clear() {
+      System.out.println("ViewModel.clear()");
+    }
+  }
+
+  @NeverClassInline
+  @SuppressWarnings("override") /* after changing the package the clear method is not overridden */
+  public static class /* foo.bar.baz. */ SubViewModel extends ViewModel {
+
+    @NeverInline
+    public void clear() {
+      System.out.println("SubViewModel.clear()");
+    }
+
+    public void callBridge() {
+      clear();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      SubViewModel viewModel = new SubViewModel();
+      viewModel.callBridge();
+      ((ViewModel) viewModel).clear();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
index 50e52e2..fb3d7e4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagationWithClassInitializationTest.java
@@ -84,7 +84,7 @@
         mainMethodSubject
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
-            .anyMatch(x -> x.getField() == clinitFieldSubject.getField().field));
+            .anyMatch(x -> x.getField() == clinitFieldSubject.getField().getReference()));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
index 329e574..cad2c50 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalInstanceFieldLoadAfterStoreTest.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.ir.optimize.redundantfieldloadelimination.RedundantFinalStaticFieldLoadAfterStoreTest.A;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -80,7 +79,7 @@
         .streamInstructions()
         .filter(InstructionSubject::isInstanceGet)
         .map(InstructionSubject::getField)
-        .filter(fieldSubject.getField().field::equals)
+        .filter(fieldSubject.getField().getReference()::equals)
         .count();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
index 2b910f5..701c43f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
@@ -77,7 +77,7 @@
         .streamInstructions()
         .filter(InstructionSubject::isStaticGet)
         .map(InstructionSubject::getField)
-        .filter(fieldSubject.getField().field::equals)
+        .filter(fieldSubject.getField().getReference()::equals)
         .count();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 6e91c38..d268888 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -345,7 +345,7 @@
     assertThat(clazz, isPresent());
     return Streams.stream(clazz.getDexProgramClass().methods())
         .filter(method -> !method.isStatic())
-        .map(method -> method.method.toSourceString())
+        .map(method -> method.getReference().toSourceString())
         .sorted()
         .collect(Collectors.toList());
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
index b254b43..0125189 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/ParameterRewritingTest.java
@@ -64,7 +64,7 @@
     MethodSubject createStaticMethodSubject =
         factoryClassSubject.uniqueMethodWithName("createStatic");
     assertThat(createStaticMethodSubject, isPresent());
-    assertEquals(1, createStaticMethodSubject.getMethod().method.proto.parameters.size());
+    assertEquals(1, createStaticMethodSubject.getMethod().getReference().proto.parameters.size());
 
     for (int i = 1; i <= 3; ++i) {
       String createStaticWithUnusedMethodName = "createStaticWithUnused" + i;
@@ -72,7 +72,7 @@
           factoryClassSubject.uniqueMethodWithName(createStaticWithUnusedMethodName);
       assertThat(createStaticWithUnusedMethodSubject, isPresent());
 
-      DexMethod method = createStaticWithUnusedMethodSubject.getMethod().method;
+      DexMethod method = createStaticWithUnusedMethodSubject.getMethod().getReference();
       assertEquals(1, method.proto.parameters.size());
       assertEquals("java.lang.String", method.proto.parameters.toString());
     }
@@ -81,7 +81,7 @@
         factoryClassSubject.uniqueMethodWithName("createStaticWithUnused4");
     assertThat(createStaticWithUnusedMethodSubject, isPresent());
 
-    DexMethod method = createStaticWithUnusedMethodSubject.getMethod().method;
+    DexMethod method = createStaticWithUnusedMethodSubject.getMethod().getReference();
     assertEquals(3, method.proto.parameters.size());
     assertEquals(
         "java.lang.String java.lang.String java.lang.String", method.proto.parameters.toString());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
index 0de94c9..8dc29fb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/PrivateInstanceMethodCollisionTest.java
@@ -81,7 +81,7 @@
       assertEquals(2, aClassSubject.allMethods(FoundMethodSubject::isVirtual).size());
       String name = null;
       for (FoundMethodSubject m : aClassSubject.allMethods(FoundMethodSubject::isVirtual)) {
-        assertEquals(1, m.getMethod().method.proto.parameters.size());
+        assertEquals(1, m.getMethod().getReference().proto.parameters.size());
         if (name == null) {
           name = m.getFinalName();
         } else {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
index c6dfe83..2e3904a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
@@ -95,7 +95,7 @@
 
       // TODO(b/131735725): Should also remove arguments from the virtual methods.
       if (keepUninstantiatedArguments || methodSubject.getOriginalName().contains("Virtual")) {
-        assertEquals(3, methodSubject.getMethod().method.proto.parameters.size());
+        assertEquals(3, methodSubject.getMethod().getReference().proto.parameters.size());
         assertEquals(3, methodSubject.getMethod().parameterAnnotationsList.size());
 
         for (int i = 0; i < 3; ++i) {
@@ -115,7 +115,7 @@
           }
         }
       } else {
-        assertEquals(2, methodSubject.getMethod().method.proto.parameters.size());
+        assertEquals(2, methodSubject.getMethod().getReference().proto.parameters.size());
         assertEquals(2, methodSubject.getMethod().parameterAnnotationsList.size());
 
         for (int i = 0; i < 2; ++i) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
index 0ac4efc..5e5345a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/VoidReturnTypeRewritingTest.java
@@ -63,16 +63,16 @@
     MethodSubject createStaticMethodSubject =
         factoryClassSubject.uniqueMethodWithName("createStatic");
     assertThat(createStaticMethodSubject, isPresent());
-    assertTrue(createStaticMethodSubject.getMethod().method.proto.returnType.isVoidType());
+    assertTrue(createStaticMethodSubject.getMethod().getReference().proto.returnType.isVoidType());
     MethodSubject createVirtualMethodSubject =
         factoryClassSubject.uniqueMethodWithName("createVirtual");
     assertThat(createVirtualMethodSubject, isPresent());
-    assertTrue(createVirtualMethodSubject.getMethod().method.proto.returnType.isVoidType());
+    assertTrue(createVirtualMethodSubject.getMethod().getReference().proto.returnType.isVoidType());
 
     createVirtualMethodSubject =
         inspector.clazz(SubFactory.class).uniqueMethodWithName("createVirtual");
     assertThat(createVirtualMethodSubject, isPresent());
-    assertTrue(createVirtualMethodSubject.getMethod().method.proto.returnType.isVoidType());
+    assertTrue(createVirtualMethodSubject.getMethod().getReference().proto.returnType.isVoidType());
 
     ClassSubject subSubFactoryClassSubject = inspector.clazz(SubSubFactory.class);
     assertThat(subSubFactoryClassSubject.method("void", "createVirtual"), isPresent());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
index 45d858d..14efc77 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/CollisionWithLibraryMethodsTest.java
@@ -65,10 +65,10 @@
 
     if (minification) {
       assertEquals("a", methodSubject.getFinalName());
-      assertEquals(0, methodSubject.getMethod().method.proto.parameters.size());
+      assertEquals(0, methodSubject.getMethod().getReference().proto.parameters.size());
     } else {
       assertEquals("toString1", methodSubject.getFinalName());
-      assertEquals(0, methodSubject.getMethod().method.proto.parameters.size());
+      assertEquals(0, methodSubject.getMethod().getReference().proto.parameters.size());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
index 073c3b4..b7ddd9e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/PrivateInstanceMethodCollisionTest.java
@@ -79,7 +79,7 @@
       assertEquals(2, aClassSubject.allMethods(FoundMethodSubject::isVirtual).size());
       String name = null;
       for (FoundMethodSubject m : aClassSubject.allMethods(FoundMethodSubject::isVirtual)) {
-        assertEquals(1, m.getMethod().method.proto.parameters.size());
+        assertEquals(1, m.getMethod().getReference().proto.parameters.size());
         if (name == null) {
           name = m.getFinalName();
         } else {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
index af6296b..138f0a9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAndUninstantiatedTypesTest.java
@@ -58,8 +58,8 @@
     List<FoundMethodSubject> methods = i.clazz(Main.class).allMethods();
     assertEquals(9, methods.size());
     for (FoundMethodSubject method : methods) {
-      if (!method.getMethod().method.name.toString().equals("main")) {
-        assertEquals(0, method.getMethod().method.getArity());
+      if (!method.getMethod().getReference().name.toString().equals("main")) {
+        assertEquals(0, method.getMethod().getReference().getArity());
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
index bd1b805..955c5d4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
@@ -91,7 +91,7 @@
       assertThat(methodSubject, isPresent());
 
       if (keepUnusedArguments) {
-        assertEquals(3, methodSubject.getMethod().method.proto.parameters.size());
+        assertEquals(3, methodSubject.getMethod().getReference().proto.parameters.size());
         assertEquals(3, methodSubject.getMethod().parameterAnnotationsList.size());
 
         for (int i = 0; i < 3; ++i) {
@@ -109,7 +109,7 @@
           }
         }
       } else {
-        assertEquals(2, methodSubject.getMethod().method.proto.parameters.size());
+        assertEquals(2, methodSubject.getMethod().getReference().proto.parameters.size());
         assertEquals(2, methodSubject.getMethod().parameterAnnotationsList.size());
 
         for (int i = 0; i < 2; ++i) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
index 7456d35..281db3e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentRemovalWithOverridingTest.java
@@ -63,7 +63,8 @@
 
     MethodSubject methodSubject = classSubject.uniqueMethodWithName("greeting");
     assertThat(methodSubject, isPresent());
-    assertEquals("java.lang.String", methodSubject.getMethod().method.proto.parameters.toString());
+    assertEquals(
+        "java.lang.String", methodSubject.getMethod().getReference().proto.parameters.toString());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
index 2c7fe55..1137a01 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsCollisionTest.java
@@ -85,7 +85,7 @@
     MethodSubject methodB1Subject =
         bClassSubject.allMethods().stream().filter(FoundMethodSubject::isStatic).findFirst().get();
     assertThat(methodB1Subject, isPresent());
-    assertEquals(0, methodB1Subject.getMethod().method.proto.parameters.size());
+    assertEquals(0, methodB1Subject.getMethod().getReference().proto.parameters.size());
 
     // TODO(b/129933280): Determine if we should use member pool collection for unused argument
     //  removal for private and static methods.
@@ -97,7 +97,7 @@
     MethodSubject methodB2Subject =
         bClassSubject.allMethods().stream().filter(FoundMethodSubject::isVirtual).findFirst().get();
     assertThat(methodB2Subject, isPresent());
-    assertEquals(0, methodB2Subject.getMethod().method.proto.parameters.size());
+    assertEquals(0, methodB2Subject.getMethod().getReference().proto.parameters.size());
 
     // Verify that the virtual method B.method2() does not collide with a method in A.
     assertNotEquals(methodB2Subject.getFinalName(), methodA1Subject.getFinalName());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInstanceConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInstanceConstructorTest.java
index ea67170..c890e97 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInstanceConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInstanceConstructorTest.java
@@ -59,7 +59,7 @@
 
     MethodSubject methodSubject = classSubject.uniqueMethodWithName("<init>");
     assertThat(methodSubject, isPresent());
-    assertTrue(methodSubject.getMethod().method.proto.parameters.isEmpty());
+    assertTrue(methodSubject.getMethod().getReference().proto.parameters.isEmpty());
 
     assertThat(inspector.clazz(B.class), not(isPresent()));
     assertThat(inspector.clazz(C.class), not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 23c4628..7f41f05 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -213,9 +213,14 @@
         String returnType,
         String... lines) {
       StringBuilder builder = new StringBuilder();
-      builder.append(".method ").append(access).append(" ").append(name)
-          .append(StringUtils.join(argumentTypes, "", BraceType.PARENS))
-          .append(returnType).append("\n");
+      builder
+          .append(".method ")
+          .append(access)
+          .append(" ")
+          .append(name)
+          .append(StringUtils.join("", argumentTypes, BraceType.PARENS))
+          .append(returnType)
+          .append("\n");
       for (String line : lines) {
         builder.append(line).append("\n");
       }
@@ -382,7 +387,7 @@
       }
       builder
           .append(name)
-          .append(StringUtils.join(argumentTypes, "", BraceType.PARENS))
+          .append(StringUtils.join("", argumentTypes, BraceType.PARENS))
           .append(returnType)
           .append(System.lineSeparator());
       builder.append(".limit locals ").append(localsLimit).append(System.lineSeparator());
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index ab2d643..73f7a6f 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -219,7 +219,7 @@
     List<String> args = new ArrayList<>();
     args.add("--output=" + dex.toString());
     args.add(classes.toString());
-    System.out.println("running: dx " + StringUtils.join(args, " "));
+    System.out.println("running: dx " + StringUtils.join(" ", args));
     return ToolHelper.runDX(args.toArray(new String[args.size()]));
   }
 
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index d21bd3a..a831fb0 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -70,7 +70,7 @@
   }
 
   static boolean isAndroidKOrAbove(DexVm dexVm, Tool tool) {
-    return dexVm.getVersion().isAtLeast(Version.V4_4_4);
+    return dexVm.getVersion().isNewerThanOrEqual(Version.V4_4_4);
   }
 
   static boolean isAndroidLOrAbove(DexVm dexVm, Tool tool) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
index b76eaa0..872b5f0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -129,7 +129,7 @@
 
               MethodSubject method = main.uniqueMethodWithName(methodName);
               assertThat(method, isPresent());
-              int arity = method.getMethod().method.getArity();
+              int arity = method.getMethod().getReference().getArity();
               // One from the method's own argument, if any, and
               // Two from Array utils, `contains` and `indexOf`, if inlined with access relaxation.
               assertEquals(
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
index 4db441f..19458e2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
@@ -52,7 +52,7 @@
 
                         MethodSubject invoke = classSubject.uniqueMethodWithName("invoke");
                         assertThat(invoke, isPresent());
-                        assertEquals(2, invoke.getMethod().method.proto.parameters.size());
+                        assertEquals(2, invoke.getMethod().getReference().proto.parameters.size());
                       }
                     }));
   }
@@ -68,7 +68,7 @@
                       if (classSubject.getOriginalDescriptor().contains("$js")) {
                         MethodSubject get = classSubject.uniqueMethodWithName("get");
                         assertThat(get, isPresent());
-                        assertEquals(3, get.getMethod().method.proto.parameters.size());
+                        assertEquals(3, get.getMethod().getReference().proto.parameters.size());
                       }
                     }));
   }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
index 6cb7ab4..ad0cf84 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
@@ -79,13 +79,16 @@
         });
   }
 
+  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
   @Test
   public void testMainDexClasses() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
     runTest(
         r8FullTestBuilder ->
-            r8FullTestBuilder.addMainDexListClasses(I.class, Provider.class, Main.class),
+            r8FullTestBuilder
+                .addMainDexListClasses(I.class, Provider.class, Main.class)
+                .allowDiagnosticWarningMessages(),
         this::inspect);
   }
 
@@ -94,7 +97,7 @@
     assumeTrue(parameters.isDexRuntime());
     assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
     runTest(
-        r8FullTestBuilder -> r8FullTestBuilder.addMainDexClassRules(Main.class, I.class),
+        r8FullTestBuilder -> r8FullTestBuilder.addMainDexKeepClassRules(Main.class, I.class),
         this::inspect);
   }
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java
index 5f21fee..3290031 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningSpuriousRootTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.maindexlist;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -59,6 +61,7 @@
     this.parameters = parameters;
   }
 
+  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
   @Test
   public void test() throws Exception {
     // The generated main dex list should contain Main (which is a root) and A (which is a direct
@@ -82,7 +85,13 @@
             .enableNoHorizontalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .noMinification()
-            .compile();
+            .allowDiagnosticMessages()
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics
+                        .assertOnlyWarnings()
+                        .assertWarningsMatch(
+                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
     CodeInspector inspector = compileResult.inspector();
     ClassSubject mainClassSubject = inspector.clazz(Main.class);
     assertThat(mainClassSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
index 98ec426..013fdd3 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.maindexlist;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -61,6 +63,7 @@
     this.parameters = parameters;
   }
 
+  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
   @Test
   public void test() throws Exception {
     // The generated main dex list should contain Main (which is a root) and A (which is a direct
@@ -80,7 +83,13 @@
             .enableNoHorizontalClassMergingAnnotations()
             .enableNoHorizontalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
-            .compile();
+            .allowDiagnosticMessages()
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics
+                        .assertOnlyWarnings()
+                        .assertWarningsMatch(
+                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
 
     CodeInspector inspector = compileResult.inspector();
     ClassSubject mainClassSubject = inspector.clazz(Main.class);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java
index f5971f9..bb3b81c 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningWithTracingTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.maindexlist;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -61,6 +63,7 @@
     this.parameters = parameters;
   }
 
+  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
   @Test
   public void test() throws Exception {
     // The generated main dex list should contain Main (which is a root) and A (which is a direct
@@ -84,7 +87,13 @@
             .enableNoHorizontalClassMergingAnnotations()
             .noMinification()
             .setMinApi(parameters.getApiLevel())
-            .compile();
+            .allowDiagnosticMessages()
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics
+                        .assertOnlyWarnings()
+                        .assertWarningsMatch(
+                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
 
     CodeInspector inspector = compileResult.inspector();
     ClassSubject mainClassSubject = inspector.clazz(Main.class);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
index c2c96ec..4d59d9b 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainHorizontalMergingTest.java
@@ -65,6 +65,7 @@
     this.parameters = parameters;
   }
 
+  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
   @Test
   public void testMainDexList() throws Exception {
     assertEquals(3, mainDexList.size());
@@ -73,7 +74,9 @@
     assertTrue(mainDexReferences.contains(A.class.getTypeName()));
     assertTrue(mainDexReferences.contains(B.class.getTypeName()));
     assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
-    runTest(builder -> builder.addMainDexListClassReferences(mainDexList));
+    runTest(
+        builder ->
+            builder.addMainDexListClassReferences(mainDexList).allowDiagnosticWarningMessages());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
index ec50d1e..480abb7 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainVerticalMergingTest.java
@@ -65,6 +65,7 @@
     this.parameters = parameters;
   }
 
+  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
   @Test
   public void testMainDexList() throws Exception {
     assertEquals(3, mainDexList.size());
@@ -73,7 +74,9 @@
     assertTrue(mainDexReferences.contains(A.class.getTypeName()));
     assertTrue(mainDexReferences.contains(B.class.getTypeName()));
     assertTrue(mainDexReferences.contains(Main.class.getTypeName()));
-    runTest(builder -> builder.addMainDexListClassReferences(mainDexList));
+    runTest(
+        builder ->
+            builder.addMainDexListClassReferences(mainDexList).allowDiagnosticWarningMessages());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
index 09df9d4..731d0ec 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.maindexlist;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
@@ -37,6 +39,7 @@
     this.parameters = parameters;
   }
 
+  // TODO(b/181858113): This test should be converted to a main-dex-rules test.
   @Test
   public void test() throws Exception {
     R8TestCompileResult compileResult =
@@ -47,7 +50,13 @@
             .collectMainDexClasses()
             .enableNoHorizontalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
-            .compile();
+            .allowDiagnosticMessages()
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics
+                        .assertOnlyWarnings()
+                        .assertWarningsMatch(
+                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
 
     CodeInspector inspector = compileResult.inspector();
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java
index 8f5d997..e72cc3e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.maindexlist;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -11,6 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
@@ -34,17 +36,24 @@
     this.parameters = parameters;
   }
 
+  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
   @Test
   public void test() throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addInnerClasses(getClass())
             .addMainDexListClasses(A.class)
-            .addMainDexClassRules(B.class)
+            .addMainDexKeepClassRules(B.class)
             .collectMainDexClasses()
             .noTreeShaking()
             .setMinApi(parameters.getApiLevel())
-            .compile();
+            .allowDiagnosticMessages()
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics
+                        .assertOnlyWarnings()
+                        .assertWarningsMatch(
+                            diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)));
 
     CodeInspector inspector = compileResult.inspector();
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
index e2d7dd2..c10a7a6 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexPrunedReferenceTest.java
@@ -48,11 +48,12 @@
     testMainDex(builder -> {}, Assert::assertNull);
   }
 
+  // TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
   @Test
   public void testMainDexClassesList() throws Exception {
     assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
     testMainDex(
-        builder -> builder.addMainDexListClasses(Main.class),
+        builder -> builder.addMainDexListClasses(Main.class).allowDiagnosticWarningMessages(),
         mainDexClasses -> assertEquals(ImmutableSet.of(Main.class.getTypeName()), mainDexClasses));
   }
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
index 8252f4d..16dc8db 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.maindexlist;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -11,11 +14,17 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Files;
@@ -81,23 +90,67 @@
     checkCompilationResult(compileResult);
   }
 
+  @Test
+  public void testSupportedMainDexListD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    // It remains a supported mode to first compile to DEX and then use tracing on the compiled
+    // output. Neither the compilation, the trace or the merge should issue any diagnostics.
+    Path dexed =
+        testForD8()
+            .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertNoMessages)
+            .writeToZip();
+
+    Path mainDexFile = temp.newFile("maindex.list").toPath();
+    testForMainDexListGenerator()
+        .addLibraryFiles(ToolHelper.getFirstSupportedAndroidJar(parameters.getApiLevel()))
+        .addProgramFiles(dexed)
+        .addMainDexRules("-keep class " + typeName(TestClass.class) + "{ *; }")
+        .setMainDexListOutputPath(mainDexFile)
+        .run();
+
+    D8TestCompileResult compileResult =
+        testForD8()
+            .addProgramFiles(dexed)
+            .addMainDexListFiles(mainDexFile)
+            .setMinApi(parameters.getApiLevel())
+            .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertNoMessages);
+    checkCompilationResult(compileResult);
+  }
+
   /**
    * This test checks for maintained support of including synthetics from main-dex-list entries in
    * the main-dex file. This test simulates that the tracing done at the class-file level has
    * determined that TestClass and A are both traced. Thus the synthetic lambda from A will be
    * included in the main-dex file.
    *
-   * <p>TODO(b/181858113): Remove once deprecated main-dex-list is removed.
+   * <p>TODO(b/181858113): Update to assert an error is raised once deprecated period is over.
    */
   @Test
   public void testDeprecatedSyntheticsFromMainDexListD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
+    Path mainDexFile = temp.newFile("maindex.list").toPath();
+    FileUtils.writeTextFile(mainDexFile, binaryName(A.class) + ".class");
     D8TestCompileResult compileResult =
         testForD8()
             .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
-            .addMainDexListClasses(TestClass.class, A.class)
+            .addMainDexListClasses(TestClass.class)
+            .addMainDexListFiles(mainDexFile)
             .setMinApi(parameters.getApiLevel())
-            .compile();
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics
+                        .assertOnlyWarnings()
+                        .assertWarningsMatch(
+                            // The "classes" addition has no origin.
+                            allOf(
+                                diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
+                                diagnosticOrigin(Origin.unknown())),
+                            // The "file" addition must have the file origin.
+                            allOf(
+                                diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
+                                diagnosticOrigin(new PathOrigin(mainDexFile)))));
     checkCompilationResult(compileResult);
   }
 
@@ -112,15 +165,31 @@
   @Test
   public void testDeprecatedSyntheticsFromMainDexListR8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
+    Path mainDexFile = temp.newFile("maindex.list").toPath();
+    FileUtils.writeTextFile(mainDexFile, binaryName(A.class) + ".class");
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addInnerClasses(MainDexWithSynthesizedClassesTest.class)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(o -> o.minimalMainDex = true)
-            .addMainDexListClasses(TestClass.class, A.class)
+            .addMainDexListClasses(TestClass.class)
+            .addMainDexListFiles(mainDexFile)
             .noMinification()
             .noTreeShaking()
-            .compile();
+            .allowDiagnosticWarningMessages()
+            .compileWithExpectedDiagnostics(
+                diagnostics ->
+                    diagnostics
+                        .assertOnlyWarnings()
+                        .assertWarningsMatch(
+                            // The "classes" addition has no origin.
+                            allOf(
+                                diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
+                                diagnosticOrigin(Origin.unknown())),
+                            // The "file" addition must have the file origin.
+                            allOf(
+                                diagnosticType(UnsupportedMainDexListUsageDiagnostic.class),
+                                diagnosticOrigin(new PathOrigin(mainDexFile)))));
     checkCompilationResult(compileResult, compileResult.app);
   }
 
@@ -128,7 +197,7 @@
     checkCompilationResult(compileResult, compileResult.app);
   }
 
-  private void checkCompilationResult(TestCompileResult compileResult, AndroidApp app)
+  private void checkCompilationResult(TestCompileResult<?, ?> compileResult, AndroidApp app)
       throws Exception {
     if (parameters.getRuntime().asDex().getMinApiLevel().getLevel()
         < nativeMultiDexLevel.getLevel()) {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
index 22bad85..19e75b3 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
@@ -56,7 +56,7 @@
         .addProgramClasses(testClasses)
         .addKeepMainRule(mainClass)
         // Include main dex rule for class Static.
-        .addMainDexClassRules(Main.class, Static.class)
+        .addMainDexKeepClassRules(Main.class, Static.class)
         .enableForceInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -93,7 +93,7 @@
         // Include explicit main dex entry for class Static.
         .addMainDexListClasses(Main.class, Static.class)
         // Include main dex rule for class Static2.
-        .addMainDexClassRules(Static2.class)
+        .addMainDexKeepClassRules(Static2.class)
         .addDontWarn(Static.class)
         .allowDiagnosticWarningMessages()
         .enableForceInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingClassPathInterfaceInheritTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingClassPathInterfaceInheritTest.java
new file mode 100644
index 0000000..5270eab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingClassPathInterfaceInheritTest.java
@@ -0,0 +1,84 @@
+// 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.naming.applymapping;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+// This is a reproduction of b/181887416.
+public class ApplyMappingClassPathInterfaceInheritTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean minifyLibrary;
+
+  @Parameters(name = "{0}, minifyLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public ApplyMappingClassPathInterfaceInheritTest(
+      TestParameters parameters, boolean minifyLibrary) {
+    this.parameters = parameters;
+    this.minifyLibrary = minifyLibrary;
+  }
+
+  @Test
+  public void testApplyMapping() throws Exception {
+    R8TestCompileResult libraryResult =
+        testForR8(parameters.getBackend())
+            .addLibraryClasses(LibI.class)
+            .addDefaultRuntimeLibrary(parameters)
+            .addProgramClasses(ClassPathI.class)
+            .applyIf(
+                minifyLibrary,
+                TestShrinkerBuilder::addKeepAllClassesRuleWithAllowObfuscation,
+                TestShrinkerBuilder::addKeepAllClassesRule)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    Path libraryJar = libraryResult.writeToZip();
+    testForR8(parameters.getBackend())
+        .addLibraryClasses(LibI.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .addClasspathClasses(ClassPathI.class)
+        .addProgramClasses(Main.class)
+        .addKeepAllClassesRule()
+        .addApplyMapping(libraryResult.getProguardMap())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathClasses(LibI.class)
+        .addRunClasspathFiles(libraryJar)
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            minifyLibrary,
+            r -> r.assertSuccessWithOutputLines("a.a"),
+            r -> r.assertSuccessWithOutputLines(ClassPathI.class.getTypeName()));
+  }
+
+  public interface LibI {}
+
+  public interface ClassPathI extends LibI {}
+
+  public static class Main {
+
+    public static void main(String[] args) throws ClassNotFoundException {
+      System.out.println(
+          Class.forName(
+                  "com.android.tools.r8.naming.applymapping"
+                      + ".ApplyMappingClassPathInterfaceInheritTest$ClassPathI")
+              .getName());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index ddf20ef..c4c9fdf 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -104,7 +104,7 @@
   }
 
   private static boolean vmVersionIgnored() {
-    return !ToolHelper.getDexVm().getVersion().isAtLeast(Version.V7_0_0);
+    return !ToolHelper.getDexVm().getVersion().isNewerThanOrEqual(Version.V7_0_0);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
index ff61668..5c48866 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -91,13 +91,13 @@
     assertNotNull(f2);
     // TODO(b/72858955): due to the potential reflective access, they should have different names
     //   by R8's improved reflective access detection or via keep rules.
-    assertEquals(overloadaggressively, f1.field.name == f2.field.name);
+    assertEquals(overloadaggressively, f1.getReference().name == f2.getReference().name);
     DexEncodedField f3 = a.field(B.class.getCanonicalName(), "f3").getField();
     assertNotNull(f3);
     // TODO(b/72858955): ditto
-    assertEquals(overloadaggressively, f1.field.name == f3.field.name);
+    assertEquals(overloadaggressively, f1.getReference().name == f3.getReference().name);
     // TODO(b/72858955): ditto
-    assertEquals(overloadaggressively, f2.field.name == f3.field.name);
+    assertEquals(overloadaggressively, f2.getReference().name == f3.getReference().name);
 
     String main = FieldUpdater.class.getCanonicalName();
     ProcessResult javaOutput = runOnJavaRaw(main, classes);
@@ -143,7 +143,7 @@
     assertNotNull(f3);
     // TODO(b/72858955): due to the potential reflective access, they should have different names
     //   by R8's improved reflective access detection or via keep rules.
-    assertEquals(overloadaggressively, f1.field.name == f3.field.name);
+    assertEquals(overloadaggressively, f1.getReference().name == f3.getReference().name);
 
     String main = FieldResolution.class.getCanonicalName();
     ProcessResult javaOutput = runOnJavaRaw(main, classes);
@@ -188,14 +188,14 @@
     DexEncodedMethod m2 =
         b.method("java.lang.Object", "getF2", ImmutableList.of()).getMethod();
     // TODO(b/72858955): due to the potential reflective access, they should have different names.
-    assertEquals(overloadaggressively, m1.method.name == m2.method.name);
+    assertEquals(overloadaggressively, m1.getReference().name == m2.getReference().name);
     DexEncodedMethod m3 =
         b.method("java.lang.String", "getF3", ImmutableList.of()).getMethod();
     assertNotNull(m3);
     // TODO(b/72858955): ditto
-    assertEquals(overloadaggressively, m1.method.name == m3.method.name);
+    assertEquals(overloadaggressively, m1.getReference().name == m3.getReference().name);
     // TODO(b/72858955): ditto
-    assertEquals(overloadaggressively, m2.method.name == m3.method.name);
+    assertEquals(overloadaggressively, m2.getReference().name == m3.getReference().name);
 
     String main = MethodResolution.class.getCanonicalName();
     ProcessResult javaOutput = runOnJavaRaw(main, classes);
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 668206c..34d84c4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.retrace.Retrace;
 import com.android.tools.r8.retrace.RetraceCommand;
 import com.android.tools.r8.utils.StringUtils;
@@ -17,6 +18,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -54,6 +56,10 @@
       return addWithoutFileNameAndLineNumber(clazz.getTypeName(), methodName);
     }
 
+    public Builder addWithoutFileNameAndLineNumber(ClassReference clazz, String methodName) {
+      return addWithoutFileNameAndLineNumber(clazz.getTypeName(), methodName);
+    }
+
     public Builder addWithoutFileNameAndLineNumber(String className, String methodName) {
       stackTraceLines.add(
           StackTraceLine.builder().setClassName(className).setMethodName(methodName).build());
@@ -65,12 +71,19 @@
       return this;
     }
 
+    public Builder applyIf(boolean condition, Consumer<Builder> fn) {
+      if (condition) {
+        fn.accept(this);
+      }
+      return this;
+    }
+
     public StackTrace build() {
       return new StackTrace(
           stackTraceLines,
           StringUtils.join(
-              stackTraceLines.stream().map(StackTraceLine::toString).collect(Collectors.toList()),
-              "\n"));
+              "\n",
+              stackTraceLines.stream().map(StackTraceLine::toString).collect(Collectors.toList())));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/regress/Regress181837660.java b/src/test/java/com/android/tools/r8/regress/Regress181837660.java
index 7fd9e77..b5e0a7c 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress181837660.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress181837660.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.regress;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -14,7 +16,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dexsplitter.SplitterTestBase;
-import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableSet;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -28,8 +29,6 @@
 @RunWith(Parameterized.class)
 public class Regress181837660 extends SplitterTestBase {
 
-  public static final String EXPECTED = StringUtils.lines("42");
-
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
     return getTestParameters().withDexRuntimes().withAllApiLevels().build();
@@ -67,10 +66,26 @@
             FeatureClass.class,
             b -> {},
             this::configureNoInlineAnnotations);
-    // TODO(b/181571571): This should not succeed as illustrated by non inlining case
-    assertEquals(0, processResult.exitCode);
+    // This should not succeed as illustrated by non inlining case
+    assertEquals(1, processResult.exitCode);
     // We can't actually read the field since it is in the feature.
-    assertFalse(processResult.stderr.contains("NoClassDefFoundError"));
+    assertThat(processResult.stderr, containsString("NoClassDefFoundError"));
+  }
+
+  @Test
+  public void testRegress181571571StillInlineValid() throws Exception {
+    ProcessResult processResult =
+        testR8Splitter(
+            parameters,
+            ImmutableSet.of(Base2Class.class),
+            ImmutableSet.of(Feature2Class.class),
+            Feature2Class.class,
+            r8TestCompileResult ->
+                r8TestCompileResult.inspect(
+                    base -> assertFalse(base.clazz(Base2Class.class).isPresent())),
+            this::configureNoInlineAnnotations);
+    assertEquals(0, processResult.exitCode);
+    assertEquals(processResult.stdout, "42\n");
   }
 
   private void configure(R8FullTestBuilder testBuilder) throws NoSuchMethodException {
@@ -83,20 +98,52 @@
   }
 
   public static class BaseClass {
+
     @NeverInline
     public static String getFromFeature() {
       return FeatureClass.featureString;
     }
+
+    @NeverInline
+    public static String getSecondFromFeature() {
+      return FeatureClass.getFeatureString();
+    }
   }
 
   public static class FeatureClass implements RunInterface {
 
     public static String featureString = "22";
 
+    public static String getFeatureString() {
+      return "42";
+    }
+
     public static String getAString() {
       return BaseClass.getFromFeature();
     }
 
+    public static String getSecondString() {
+      return BaseClass.getSecondFromFeature();
+    }
+
+    @Override
+    public void run() {
+      System.out.println(getAString());
+      System.out.println(getSecondString());
+    }
+  }
+
+  public static class Base2Class {
+    public static String getFromFeature() {
+      return System.currentTimeMillis() > 2 ? "42" : "-19";
+    }
+  }
+
+  public static class Feature2Class implements RunInterface {
+    public static String getAString() {
+      return Base2Class.getFromFeature();
+    }
+
     @Override
     public void run() {
       System.out.println(getAString());
diff --git a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
index 0556439..33bef6d 100644
--- a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
+++ b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
@@ -232,7 +232,7 @@
     return Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
         .filter(instruction -> instruction instanceof IgetObject)
         .map(instruction -> (IgetObject) instruction)
-        .filter(get -> get.getField() == field.getField().field)
+        .filter(get -> get.getField() == field.getField().getReference())
         .count();
   }
 
@@ -268,23 +268,33 @@
     MethodSubject msvOnB = classB.method("void", "msv", ImmutableList.of());
     assertThat(msvOnB, isPresent());
     // Field load of volatile fields are never eliminated.
-    assertEquals(5, countIget(mvOnA.getMethod().getCode().asDexCode(), vOnA.getField().field));
-    assertEquals(5, countSget(msvOnA.getMethod().getCode().asDexCode(), svOnA.getField().field));
-    assertEquals(5, countIget(mvOnB.getMethod().getCode().asDexCode(), vOnA.getField().field));
-    assertEquals(5, countSget(msvOnB.getMethod().getCode().asDexCode(), svOnA.getField().field));
+    assertEquals(
+        5, countIget(mvOnA.getMethod().getCode().asDexCode(), vOnA.getField().getReference()));
+    assertEquals(
+        5, countSget(msvOnA.getMethod().getCode().asDexCode(), svOnA.getField().getReference()));
+    assertEquals(
+        5, countIget(mvOnB.getMethod().getCode().asDexCode(), vOnA.getField().getReference()));
+    assertEquals(
+        5, countSget(msvOnB.getMethod().getCode().asDexCode(), svOnA.getField().getReference()));
     // For fields on the same class both separate compilation (D8) and whole program
     // compilation (R8) will eliminate field loads on non-volatile fields.
-    assertEquals(1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
-    assertEquals(1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().field));
     assertEquals(
-        2, countIget(mfWithMonitorOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+        1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().getReference()));
+    assertEquals(
+        1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().getReference()));
+    assertEquals(
+        2,
+        countIget(
+            mfWithMonitorOnA.getMethod().getCode().asDexCode(), fOnA.getField().getReference()));
 
     // For fields on other class both separate compilation (D8) and whole program
     // compilation (R8) will differ in the eliminated field loads of non-volatile fields.
-    assertEquals(mfOnBGets,
-        countIget(mfOnB.getMethod().getCode().asDexCode(), fOnA.getField().field));
-    assertEquals(msfOnBGets,
-        countSget(msfOnB.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+    assertEquals(
+        mfOnBGets,
+        countIget(mfOnB.getMethod().getCode().asDexCode(), fOnA.getField().getReference()));
+    assertEquals(
+        msfOnBGets,
+        countSget(msfOnB.getMethod().getCode().asDexCode(), sfOnA.getField().getReference()));
   }
 
   @Test
@@ -320,12 +330,17 @@
 
 
     for (FieldSubject field : new FieldSubject[]{years, months, days}) {
-      assertEquals(1,
-          countIget(totalDays.getMethod().getCode().asDexCode(), field.getField().field));
-      assertEquals(2,
-          countIget(totalDaysTimes2.getMethod().getCode().asDexCode(), field.getField().field));
-      assertEquals(3,
-          countIget(totalDaysTimes3.getMethod().getCode().asDexCode(), field.getField().field));
+      assertEquals(
+          1,
+          countIget(totalDays.getMethod().getCode().asDexCode(), field.getField().getReference()));
+      assertEquals(
+          2,
+          countIget(
+              totalDaysTimes2.getMethod().getCode().asDexCode(), field.getField().getReference()));
+      assertEquals(
+          3,
+          countIget(
+              totalDaysTimes3.getMethod().getCode().asDexCode(), field.getField().getReference()));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
index 9a1ad72..788bb0a 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageTestBase.java
@@ -125,7 +125,7 @@
         if (isFlattenPackageHierarchy()) {
           expectedPackageNames.add(packageName != null ? packageName : "a");
         }
-        return StringUtils.join(expectedPackageNames, ".");
+        return StringUtils.join(".", expectedPackageNames);
       }
     };
   }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
index e374f2a..624bdfe 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithDexItemBasedConstStringTest.java
@@ -46,7 +46,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
-        .addMainDexClassRules(TestClass.class)
+        .addMainDexKeepClassRules(TestClass.class)
         .apply(this::configureRepackaging)
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
index 4fc28f5..81183f5 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithMainDexListTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.repackage;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
 import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -13,6 +14,7 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.UnsupportedMainDexListUsageDiagnostic;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -22,6 +24,7 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
+// TODO(b/181858113): This test is likely obsolete once main-dex-list support is removed.
 @RunWith(Parameterized.class)
 public class RepackageWithMainDexListTest extends RepackageTestBase {
 
@@ -54,7 +57,13 @@
         // Debug mode to enable minimal main dex.
         .debug()
         .setMinApi(parameters.getApiLevel())
-        .compile()
+        .allowDiagnosticMessages()
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyWarnings()
+                    .assertWarningsMatch(
+                        diagnosticType(UnsupportedMainDexListUsageDiagnostic.class)))
         .apply(this::checkCompileResult)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello world!");
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index 75d4bbc..4d4419f 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -4,17 +4,17 @@
 package com.android.tools.r8.resolution;
 
 import static com.android.tools.r8.ToolHelper.getMostRecentAndroidJar;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 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;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -30,17 +30,24 @@
 public class VirtualOverrideOfPrivateStaticMethodTest extends TestBase {
 
   public interface I {
-    default void f() {}
+    default void f() {
+      System.out.println("I::f");
+    }
   }
 
   public static class A {
-    private static void f() {}
+    private static void f() {
+      System.out.println("A::f");
+    }
   }
 
   public static class B extends A implements I {}
 
   public static class C extends B {
-    public void f() {}
+    @Override
+    public void f() {
+      System.out.println("C::f");
+    }
   }
 
   public static class Main {
@@ -64,13 +71,13 @@
             .appInfo();
   }
 
-  private static DexMethod buildMethod(Class clazz, String name) {
+  private static DexMethod buildMethod(Class<?> clazz, String name) {
     return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
   }
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
   private final TestParameters parameters;
@@ -85,30 +92,48 @@
   @Test
   public void resolveTarget() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
-    assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
+    DexClass context = appInfo.definitionFor(methodOnB.holder);
+    assertTrue(resolutionResult.isIllegalAccessErrorResult(context, appInfo));
   }
 
   @Test
-  public void runTest() throws ExecutionException, CompilationFailedException, IOException {
-    if (parameters.isCfRuntime()) {
-      testForJvm()
-          .addProgramClasses(CLASSES)
-          .run(parameters.getRuntime(), Main.class)
-          .assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
-    }
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(CLASSES)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+  }
+
+  @Test
+  public void testD8() throws ExecutionException, CompilationFailedException, IOException {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/182335909): Ideally, this should IllegalAccessError.
+        .applyIf(
+            parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()
+                && parameters.isCfRuntime(),
+            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class),
+            r -> r.assertSuccessWithOutputLines("C::f"));
+  }
+
+  @Test
+  public void testR8() throws ExecutionException, CompilationFailedException, IOException {
     testForR8(parameters.getBackend())
         .addProgramClasses(CLASSES)
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+        .assertFailureWithErrorThatThrows(expectedRuntimeError());
   }
 
-  private String expectedRuntimeError() {
+  private Class<? extends Throwable> expectedRuntimeError() {
     if (parameters.isDexRuntime()
         && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
-      return "IncompatibleClassChangeError";
+      return IncompatibleClassChangeError.class;
     }
-    return "IllegalAccessError";
+    return IllegalAccessError.class;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index c92f813..edc4c42 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -132,7 +132,7 @@
     ResolutionResult resolutionResult =
         appInfo.resolveMethodOnInterface(methodOnBReference.holder, methodOnBReference);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
-    assertEquals(methodOnBReference, resolved.method);
+    assertEquals(methodOnBReference, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
     DexEncodedMethod singleVirtualTarget =
         appInfo.lookupSingleVirtualTarget(methodOnBReference, methodOnB, false);
@@ -144,7 +144,7 @@
     ResolutionResult resolutionResult =
         appInfo.resolveMethodOnInterface(methodOnBReference.holder, methodOnBReference);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
-    assertEquals(methodOnBReference, resolved.method);
+    assertEquals(methodOnBReference, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index 2ffd43d..7843995 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -176,7 +176,7 @@
     DexProgramClass bClass = appInfo.definitionForProgramType(methodOnB.holder);
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
-    assertEquals(methodOnA, resolved.method);
+    assertEquals(methodOnA, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
     DexEncodedMethod singleVirtualTarget =
         appInfo.lookupSingleVirtualTarget(methodOnB, bClass.getProgramDefaultInitializer(), false);
@@ -187,7 +187,7 @@
   public void lookupVirtualTargets() {
     ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
     DexEncodedMethod resolved = resolutionResult.getSingleTarget();
-    assertEquals(methodOnA, resolved.method);
+    assertEquals(methodOnA, resolved.getReference());
     assertFalse(resolutionResult.isVirtualTarget());
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index ce6f4d7..5498d06 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -124,7 +123,11 @@
 
     // Resolution fails when there is a mismatch between the symbolic reference and the definition.
     if (!symbolicReferenceIsDefiningType) {
-      assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
+      if (inSameNest) {
+        assertTrue(resolutionResult.isNoSuchMethodErrorResult(callerClassDefinition, appInfo));
+      } else {
+        assertTrue(resolutionResult.isIllegalAccessErrorResult(callerClassDefinition, appInfo));
+      }
       return;
     }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
index c636ef9..e116329 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
@@ -67,7 +67,7 @@
         appInfo.lookupSingleVirtualTarget(fooA, mainMethod, false, t -> false, typeA, latticeB);
     assertNotNull(singleTarget);
     DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
-    assertEquals(fooB, singleTarget.method);
+    assertEquals(fooB, singleTarget.getReference());
   }
 
   @Test
@@ -94,7 +94,7 @@
         appInfo.lookupSingleVirtualTarget(fooA, mainMethod, false, t -> false, typeA, latticeB);
     assertNotNull(singleTarget);
     DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
-    assertEquals(fooB, singleTarget.method);
+    assertEquals(fooB, singleTarget.getReference());
   }
 
   @Test
@@ -133,7 +133,7 @@
     lookupResult
         .asLookupResultSuccess()
         .forEach(
-            clazzAndMethod -> actual.add(clazzAndMethod.getDefinition().method),
+            clazzAndMethod -> actual.add(clazzAndMethod.getDefinition().getReference()),
             lambdaTarget -> {
               assert false;
             });
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java
index a4b183c..85bd2ca 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/SuccessAndInvalidLookupTest.java
@@ -55,7 +55,7 @@
     DexEncodedMethod singleTarget =
         appInfo.lookupSingleVirtualTarget(fooA, mainMethod, false, t -> false, typeA, null);
     assertNotNull(singleTarget);
-    assertEquals(fooA, singleTarget.method);
+    assertEquals(fooA, singleTarget.getReference());
     DexEncodedMethod invalidSingleTarget =
         appInfo.lookupSingleVirtualTarget(fooA, mainMethod, true, t -> false, typeA, null);
     assertNull(invalidSingleTarget);
@@ -82,7 +82,7 @@
     DexEncodedMethod singleTarget =
         appInfo.lookupSingleVirtualTarget(fooI, mainMethod, true, t -> false, typeA, null);
     assertNotNull(singleTarget);
-    assertEquals(fooA, singleTarget.method);
+    assertEquals(fooA, singleTarget.getReference());
     DexEncodedMethod invalidSingleTarget =
         appInfo.lookupSingleVirtualTarget(fooI, mainMethod, false, t -> false, typeA, null);
     assertNull(invalidSingleTarget);
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
new file mode 100644
index 0000000..aa543c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
@@ -0,0 +1,161 @@
+// 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.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceLambdaTest extends TestBase {
+
+  private static final String JAVAC_LAMBDA_METHOD = "lambda$main$0";
+
+  // TODO(b/172014416): These should not be needed once fixed.
+  private static final String LAMBDA_BRIDGE_METHOD = "$r8$lambda$dX5OYTAgq4ijGUv_zaGoVsFINMs";
+  private static final String INTERNAL_LAMBDA_CLASS =
+      Main.class.getTypeName()
+          + "$$InternalSyntheticLambda$0$11a5d582ed94e937718cf3ed497d4d164b60dfa85d606466457007fade57dce8$0";
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection parameters() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private final TestParameters parameters;
+
+  public RetraceLambdaTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .inspectStackTrace(
+            stackTrace -> {
+              assertThat(
+                  stackTrace,
+                  isSameExceptForFileNameAndLineNumber(
+                      StackTrace.builder()
+                          .addWithoutFileNameAndLineNumber(Main.class, JAVAC_LAMBDA_METHOD)
+                          // TODO(b/172014416): Support a D8 mapping and prune the synthetic.
+                          .applyIf(
+                              parameters.isDexRuntime(),
+                              b ->
+                                  b.addWithoutFileNameAndLineNumber(
+                                      SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0),
+                                      "run"))
+                          .addWithoutFileNameAndLineNumber(Main.class, "runIt")
+                          .addWithoutFileNameAndLineNumber(Main.class, "main")
+                          .build()));
+            });
+  }
+
+  @Test
+  public void testEverythingInlined() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .inspectStackTrace(
+            stackTrace -> {
+              int frames = parameters.isCfRuntime() ? 2 : 1;
+              checkRawStackTraceFrameCount(stackTrace, frames, "Expected everything to be inlined");
+              checkCurrentlyIncorrectStackTrace(stackTrace, JAVAC_LAMBDA_METHOD);
+            });
+  }
+
+  @Test
+  public void testNothingInlined() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepPackageNamesRule(getClass().getPackage())
+        .noTreeShaking()
+        .addKeepAttributeSourceFile()
+        .addKeepAttributeLineNumberTable()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .inspectStackTrace(
+            stackTrace -> {
+              int frames = parameters.isCfRuntime() ? 3 : 5;
+              checkRawStackTraceFrameCount(stackTrace, frames, "Expected nothing to be inlined");
+              checkCurrentlyIncorrectStackTrace(stackTrace, "lambda$main$0");
+            });
+  }
+
+  private void checkRawStackTraceFrameCount(
+      StackTrace stackTrace, int expectedFrames, String message) {
+    int linesFromTest = 0;
+    for (String line : stackTrace.getOriginalStderr().split("\n")) {
+      if (line.trim().startsWith("at " + getClass().getPackage().getName())) {
+        linesFromTest++;
+      }
+    }
+    assertEquals(message + stackTrace.getOriginalStderr(), expectedFrames, linesFromTest);
+  }
+
+  private void checkCurrentlyIncorrectStackTrace(StackTrace stackTrace, String javacLambdaMethod) {
+    assertThat(
+        stackTrace,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .addWithoutFileNameAndLineNumber(Main.class, javacLambdaMethod)
+                .applyIf(
+                    parameters.isDexRuntime(),
+                    b ->
+                        b
+                            // TODO(b/172014416): Lambda bridges should be marked synthetic
+                            //  and removed.
+                            .addWithoutFileNameAndLineNumber(Main.class, LAMBDA_BRIDGE_METHOD)
+                            // TODO(b/172014416): The frame mapping should have removed this
+                            //  entry.
+                            // TODO(b/172014416): Synthetics should not map back to internal
+                            //  names.
+                            .addWithoutFileNameAndLineNumber(INTERNAL_LAMBDA_CLASS, "run"))
+                .addWithoutFileNameAndLineNumber(Main.class, "runIt")
+                .addWithoutFileNameAndLineNumber(Main.class, "main")
+                .build()));
+  }
+
+  public interface MyRunner {
+    void run();
+  }
+
+  public static class Main {
+
+    public static void runIt(MyRunner runner) {
+      runner.run();
+    }
+
+    public static void main(String[] args) {
+      if (args.length == 0) {
+        runIt(
+            () -> {
+              throw new RuntimeException("Hello World!");
+            });
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/AbstractSuperClassLiveMethodTest.java b/src/test/java/com/android/tools/r8/shaking/AbstractSuperClassLiveMethodTest.java
new file mode 100644
index 0000000..8cdd48e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/AbstractSuperClassLiveMethodTest.java
@@ -0,0 +1,103 @@
+// 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.shaking;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AbstractSuperClassLiveMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String NEW_DESCRIPTOR = "Lfoo/A;";
+  private final String[] EXPECTED = new String[] {"A::foo", "Base::foo"};
+  private final String[] EXPECTED_DALVIK = new String[] {"A::foo", "A::foo"};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public AbstractSuperClassLiveMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  public List<byte[]> getProgramClassFileData() throws Exception {
+    return ImmutableList.of(
+        transformer(A.class).setClassDescriptor(NEW_DESCRIPTOR).transform(),
+        transformer(Main.class)
+            .replaceClassDescriptorInMethodInstructions(descriptor(A.class), NEW_DESCRIPTOR)
+            .transform());
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Base.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik(),
+            r -> r.assertSuccessWithOutputLines(EXPECTED_DALVIK),
+            r -> r.assertSuccessWithOutputLines(EXPECTED));
+  }
+
+  @Test
+  public void testForR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Base.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addOptionsModification(options -> options.enableDevirtualization = false)
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik(),
+            r -> r.assertSuccessWithOutputLines(EXPECTED_DALVIK),
+            // TODO(b/182444403): Should succeed with EXPECTED.
+            r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class));
+  }
+
+  @NoVerticalClassMerging
+  public abstract static class Base {
+
+    @NeverInline
+    void foo() {
+      System.out.println("Base::foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class /* will be foo.A */ A extends Base {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Base a = new A();
+      ((A) a).foo();
+      a.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
index ddaf47f..aa2df28 100644
--- a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -99,9 +99,9 @@
     DexClass clazz = classSubject.getDexProgramClass();
     assertEquals(3, clazz.getMethodCollection().numberOfVirtualMethods());
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
-      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      assertTrue(encodedMethod.getReference().name.toString().startsWith("foo"));
       MethodSubject methodSubject =
-          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.getReference()));
       assertThat(methodSubject, isPresentAndNotRenamed());
     }
   }
@@ -135,9 +135,9 @@
     DexClass clazz = classSubject.getDexProgramClass();
     assertEquals(3, clazz.getMethodCollection().numberOfVirtualMethods());
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
-      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      assertTrue(encodedMethod.getReference().name.toString().startsWith("foo"));
       MethodSubject methodSubject =
-          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.getReference()));
       assertThat(methodSubject, isPresentAndNotRenamed());
     }
   }
@@ -155,9 +155,9 @@
     DexClass clazz = classSubject.getDexProgramClass();
     assertEquals(3, clazz.getMethodCollection().numberOfVirtualMethods());
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
-      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      assertTrue(encodedMethod.getReference().name.toString().startsWith("foo"));
       MethodSubject methodSubject =
-          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.getReference()));
       assertThat(methodSubject, isPresentAndNotRenamed());
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
index 3f27bc6..c3e53cf 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -254,7 +254,9 @@
                   .filter(InstructionSubject::isStaticGet)
                   .anyMatch(
                       instruction ->
-                          instruction.getField().equals(clinitFieldSubject.getField().field)));
+                          instruction
+                              .getField()
+                              .equals(clinitFieldSubject.getField().getReference())));
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
index 437cdd2..42268b2 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInInterfaceMarkingTest.java
@@ -58,7 +58,7 @@
       AppInfoWithLiveness appInfo, DexType type) {
     DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
     DexEncodedMethod method =
-        clazz.lookupVirtualMethod(m -> m.method.name.toString().equals("isEmpty"));
+        clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("isEmpty"));
     assertTrue(method.isLibraryMethodOverride().isTrue());
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
index 331eb12..563a312 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
@@ -61,7 +61,7 @@
       AppInfoWithLiveness appInfo, DexType type) {
     DexProgramClass clazz = appInfo.definitionFor(type).asProgramClass();
     DexEncodedMethod method =
-        clazz.lookupVirtualMethod(m -> m.method.name.toString().equals("iterator"));
+        clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("iterator"));
     // TODO(b/149976493): Mark library overrides from lambda instances.
     if (parameters.isCfRuntime()) {
       assertTrue(method.isLibraryMethodOverride().isFalse());
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index f98e5ea..4a8306a 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -144,7 +144,7 @@
       return false;
     }
     Version version = parameters.getRuntime().asDex().getVm().getVersion();
-    return version.isOlderThanOrEqual(Version.V7_0_0) && version.isAtLeast(Version.V5_1_1);
+    return version.isOlderThanOrEqual(Version.V7_0_0) && version.isNewerThanOrEqual(Version.V5_1_1);
   }
 
   public static R8TestCompileResult compile(Dimensions dimensions) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/InnerClassesSimpleTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/InnerClassesSimpleTest.java
new file mode 100644
index 0000000..0474186
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/InnerClassesSimpleTest.java
@@ -0,0 +1,76 @@
+// 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.shaking.attributes;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InnerClassesSimpleTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean minify;
+
+  @Parameters(name = "{0}, minify: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withCfRuntimes().build(), BooleanUtils.values());
+  }
+
+  public InnerClassesSimpleTest(TestParameters parameters, boolean minify) {
+    this.parameters = parameters;
+    this.minify = minify;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Path path =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addKeepMainRule(Main.class)
+            .addKeepPackageNamesRule(getClass().getPackage())
+            .noTreeShaking()
+            .applyIf(!minify, TestShrinkerBuilder::noMinification)
+            .compile()
+            .writeToZip();
+    testForR8(parameters.getBackend())
+        .addProgramFiles(path)
+        .addKeepAllClassesRule()
+        .allowDiagnosticInfoMessages(minify)
+        .compile()
+        // TODO(b/182524171): Prune inner class attributes if they are not kept.
+        .assertAllInfoMessagesMatch(containsString("Malformed inner-class attribute"))
+        .assertInfosCount(minify ? 4 : 0)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello World");
+  }
+
+  public interface MyRunner {
+    void run();
+  }
+
+  public static class Main {
+
+    public static void runIt(MyRunner runner) {
+      runner.run();
+    }
+
+    public static void main(String[] args) {
+      runIt(
+          () -> {
+            System.out.println("Hello World");
+          });
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
index 7111f51..34da969 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
@@ -74,13 +74,13 @@
   }
 
   private static void checkFieldInDictionary(FieldSubject field) {
-    if (!names.contains(field.getField().field.name.toSourceString())) {
+    if (!names.contains(field.getField().getReference().name.toSourceString())) {
       throw new AssertionError();
     }
   }
 
   private static void checkMethodInDictionary(MethodSubject method) {
-    String name = method.getMethod().method.name.toSourceString();
+    String name = method.getMethod().getReference().name.toSourceString();
     if (!names.contains(name) && !name.equals("<init>") && !name.equals("main")) {
       throw new AssertionError();
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java
index 311d6a6..e841ffc 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java
@@ -65,7 +65,8 @@
         inspector.clazz(IfRuleWithApplyMappingTestClass.class).uniqueMethodWithName("method");
     assertThat(methodSubject, isPresent());
     assertEquals(
-        A.class.getTypeName(), methodSubject.getMethod().method.proto.parameters.toSourceString());
+        A.class.getTypeName(),
+        methodSubject.getMethod().getReference().proto.parameters.toSourceString());
   }
 }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
index 230c0cf..221f99c 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
@@ -100,7 +100,7 @@
 
   @Test
   public void testKeptMethod() throws Exception {
-    assumeTrue(ToolHelper.getDexVm().getVersion().isAtLeast(Version.V7_0_0));
+    assumeTrue(ToolHelper.getDexVm().getVersion().isNewerThanOrEqual(Version.V7_0_0));
 
     MethodReference mainMethod =
         methodFromMethod(Main.class.getDeclaredMethod("main", String[].class));
diff --git a/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java b/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
index e5a8027..b9a9dc8 100644
--- a/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
@@ -55,10 +55,12 @@
       assertEquals(1, getNumberOfProgramClasses(processdApplication));
       CodeInspector inspector = new CodeInspector(processdApplication);
       ClassSubject clazz = inspector.clazz(DEFAULT_CLASS_NAME);
-      clazz.forAllMethods(method -> {
-        int index = Integer.parseInt(method.getMethod().method.name.toString().substring(1));
-        checkers.get(index).accept(method.getMethod(), values.get(index));
-      });
+      clazz.forAllMethods(
+          method -> {
+            int index =
+                Integer.parseInt(method.getMethod().getReference().name.toString().substring(1));
+            checkers.get(index).accept(method.getMethod(), values.get(index));
+          });
     }
   }
 
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 a7b67bf..9324b87 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -870,7 +870,7 @@
     List<DexType> r = new ArrayList<>();
     for (DexEncodedMethod directMethod : outlineMethods) {
       if (directMethod.getCode().asDexCode().instructions[0] instanceof InvokeVirtual) {
-        r.add(directMethod.method.proto.returnType);
+        r.add(directMethod.getReference().proto.returnType);
       }
     }
     assertEquals(2, r.size());
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
index e9368e9..0a2c7ab 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -42,8 +42,14 @@
 
     @Override
     public String toString() {
-      return returnType + " " + clazz + "." + name
-          + "(" + StringUtils.join(parameterTypes, ",") + ")";
+      return returnType
+          + " "
+          + clazz
+          + "."
+          + name
+          + "("
+          + StringUtils.join(",", parameterTypes)
+          + ")";
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/testing/StackTraceTest.java b/src/test/java/com/android/tools/r8/testing/StackTraceTest.java
index 33915ef..41b6de3 100644
--- a/src/test/java/com/android/tools/r8/testing/StackTraceTest.java
+++ b/src/test/java/com/android/tools/r8/testing/StackTraceTest.java
@@ -40,12 +40,12 @@
   public void testJvmStackTrace() throws Exception {
     String stderr =
         StringUtils.join(
+            "\n",
             ImmutableList.of(
                 "Exception in thread \"main\" java.lang.RuntimeException",
                 "\tat com.example.A.method2(Test.java:30)",
                 "\tat com.example.A.method1(Test.java:20)",
-                "\tat com.example.Main.main(Test.java:10)"),
-            "\n");
+                "\tat com.example.Main.main(Test.java:10)"));
     checkStackTrace(StackTrace.extractFromJvm(stderr));
   }
 
@@ -95,8 +95,7 @@
             "\tat com.example.Main.main(Test.java:10)",
             "dex2oat I 10-30 11:41:40 232588 232588 dex2oat.cc:2808] dex2oat took 94.860ms"
                 + " (71.941ms cpu) (threads: 72) arena alloc=3KB (3312B) java alloc=32KB (32800B)"
-                + " native alloc=440KB (450720B) free=9MB (9539424B)"
-            );
+                + " native alloc=440KB (450720B) free=9MB (9539424B)");
     checkStackTrace(StackTrace.extractFromArt(stderr, DexVm.ART_5_1_1_HOST));
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/AppComparator.java b/src/test/java/com/android/tools/r8/utils/AppComparator.java
index 3436b9a..9dcec97 100644
--- a/src/test/java/com/android/tools/r8/utils/AppComparator.java
+++ b/src/test/java/com/android/tools/r8/utils/AppComparator.java
@@ -60,10 +60,11 @@
     CodeInspector inspect2 = new CodeInspector(app2, Paths.get(MAP_2));
 
     // Define your own tester to pick methods to inspect.
-    Predicate<DexEncodedMethod> methodTester = encodedMethod -> {
-      return encodedMethod.method.name.toString().equals("run")
-          && encodedMethod.method.getArity() == 0;
-    };
+    Predicate<DexEncodedMethod> methodTester =
+        encodedMethod -> {
+          return encodedMethod.getReference().name.toString().equals("run")
+              && encodedMethod.getReference().getArity() == 0;
+        };
 
     inspect1.forAllClasses(clazz1 -> {
       clazz1.forAllMethods(method1 -> {
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 693af00..26e85e5 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -79,7 +79,7 @@
 
       if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
         throw new RuntimeException(
-            "Error occured while compiling text:\n" + StringUtils.join(smaliTexts, "\n"));
+            "Error occured while compiling text:\n" + StringUtils.join("\n", smaliTexts));
       }
 
       CommonTree t = result.getTree();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 7059ae1..2b57e91 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -19,7 +19,7 @@
     if (!targetSubject.isPresent()) {
       throw new IllegalArgumentException();
     }
-    DexField target = targetSubject.getField().field;
+    DexField target = targetSubject.getField().getReference();
     return new TypeSafeMatcher<MethodSubject>() {
       @Override
       protected boolean matchesSafely(MethodSubject subject) {
@@ -79,7 +79,7 @@
     if (!targetSubject.isPresent()) {
       throw new IllegalArgumentException();
     }
-    DexMethod target = targetSubject.getMethod().method;
+    DexMethod target = targetSubject.getMethod().getReference();
     return new TypeSafeMatcher<MethodSubject>() {
       @Override
       protected boolean matchesSafely(MethodSubject subject) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
index ef44c64..aae1912 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -15,7 +15,7 @@
   public abstract DexEncodedField getField();
 
   public DexField getDexField() {
-    return getField().field;
+    return getField().getReference();
   }
 
   public abstract DexValue getStaticValue();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index ed04f35..be358de 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -121,7 +121,7 @@
 
   private DexEncodedMethod findMethod(Iterable<DexEncodedMethod> methods, DexMethod dexMethod) {
     for (DexEncodedMethod method : methods) {
-      if (method.method.equals(dexMethod)) {
+      if (method.getReference().equals(dexMethod)) {
         return method;
       }
     }
@@ -320,7 +320,7 @@
 
   private DexEncodedField findField(List<DexEncodedField> fields, DexField dexField) {
     for (DexEncodedField field : fields) {
-      if (field.field.equals(dexField)) {
+      if (field.getReference().equals(dexField)) {
         return field;
       }
     }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index b071916..023f412 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -43,7 +43,7 @@
   }
 
   public TypeSubject type() {
-    return new TypeSubject(codeInspector, dexField.field.type);
+    return new TypeSubject(codeInspector, dexField.getReference().type);
   }
 
   @Override
@@ -79,7 +79,7 @@
 
   @Override
   public FieldSignature getFinalSignature() {
-    return FieldSignature.fromDexField(dexField.field);
+    return FieldSignature.fromDexField(dexField.getReference());
   }
 
   @Override
@@ -123,7 +123,9 @@
 
   @Override
   public String getJvmFieldSignatureAsString() {
-    return dexField.field.name.toString() + ":" + dexField.field.type.toDescriptorString();
+    return dexField.getReference().name.toString()
+        + ":"
+        + dexField.getReference().type.toDescriptorString();
   }
 
   @Override
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 dfccf5a..0eb0de1 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
@@ -155,7 +155,7 @@
 
   @Override
   public MethodSignature getFinalSignature() {
-    return MethodSignature.fromDexMethod(dexMethod.method);
+    return MethodSignature.fromDexMethod(dexMethod.getReference());
   }
 
   @Override
@@ -273,7 +273,7 @@
     }
     Object2IntMap<InstructionSubject> lineNumberTable = new Object2IntOpenHashMap<>();
     DexDebugPositionState state =
-        new DexDebugPositionState(debugInfo.startLine, getMethod().method);
+        new DexDebugPositionState(debugInfo.startLine, getMethod().getReference());
     Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator();
     for (Instruction insn : code.instructions) {
       int offset = insn.getOffset();
@@ -337,7 +337,7 @@
   }
 
   public MethodReference asMethodReference() {
-    DexMethod method = dexMethod.method;
+    DexMethod method = dexMethod.getReference();
     return Reference.method(
         Reference.classFromDescriptor(method.holder.toDescriptorString()),
         method.name.toString(),
@@ -349,13 +349,15 @@
 
   @Override
   public String getJvmMethodSignatureAsString() {
-    return dexMethod.method.name.toString()
+    return dexMethod.getName().toString()
         + "("
         + StringUtils.join(
-            Arrays.stream(dexMethod.method.proto.parameters.values)
-                .map(DexType::toDescriptorString).collect(Collectors.toList()), "")
+            "",
+            Arrays.stream(dexMethod.getParameters().values)
+                .map(DexType::toDescriptorString)
+                .collect(Collectors.toList()))
         + ")"
-        + dexMethod.method.proto.returnType.toDescriptorString();
+        + dexMethod.returnType().toDescriptorString();
   }
 
   @Override
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 403212f..825cb91 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -85,10 +85,12 @@
     'https://github.com/'
         + github_account + '/' + LIBRARY_NAME, checkout_dir)
 
-def BuildDesugaredLibrary(checkout_dir):
+def BuildDesugaredLibrary(checkout_dir, variant):
+  if (variant != 'jdk8' and variant != 'jdk11'):
+    raise Exception('Variant ' + variant + 'is not supported')
   with utils.ChangedWorkingDirectory(checkout_dir):
     bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
-    cmd = [bazel, 'build', 'maven_release']
+    cmd = [bazel, 'build', 'maven_release' + ('_jdk11' if variant == 'jdk11' else '')]
     utils.PrintCmd(cmd)
     subprocess.check_call(cmd)
     cmd = [bazel, 'shutdown']
@@ -97,9 +99,16 @@
 
     # Locate the library jar and the maven zip with the jar from the
     # bazel build.
-    library_jar = os.path.join(
-        checkout_dir, 'bazel-bin', 'src', 'share', 'classes', 'java', 'libjava.jar')
-    maven_zip = os.path.join(checkout_dir, 'bazel-bin', LIBRARY_NAME +'.zip')
+    if variant == 'jdk8':
+      library_jar = os.path.join(
+          checkout_dir, 'bazel-bin', 'src', 'share', 'classes', 'java', 'libjava.jar')
+    else:
+      library_jar = os.path.join(
+          checkout_dir, 'bazel-bin', 'jdk11', 'src', 'java_base_selected.jar')
+    maven_zip = os.path.join(
+      checkout_dir,
+      'bazel-bin',
+      LIBRARY_NAME + ('_jdk11' if variant != 'jdk11' else '') +'.zip')
     return (library_jar, maven_zip)
 
 
@@ -124,11 +133,12 @@
   # Make sure bazel is extracted in third_party.
   utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
   utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
+  utils.DownloadFromGoogleCloudStorage(utils.JAVA11_SHA_FILE)
 
   if options.build_only:
     with utils.TempDir() as checkout_dir:
       CloneDesugaredLibrary(options.github_account, checkout_dir)
-      (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir)
+      (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir, "jdk8")
       shutil.copyfile(
         library_jar,
         os.path.join(options.build_only, os.path.basename(library_jar)))
@@ -150,7 +160,7 @@
       raise Exception(
           'Target archive directory %s already exists' % destination)
 
-    (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir)
+    (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir, "jdk8")
 
     storage_path = LIBRARY_NAME + '/' + version
     # Upload the jar file with the library.
diff --git a/tools/test.py b/tools/test.py
index 9e2a3e2..f7b204f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -162,6 +162,8 @@
       help='Enable Java debug agent and suspend compilation (default disabled)',
       default=False,
       action='store_true')
+  result.add_option('--desugared-library-configuration', '--desugared_library-configuration',
+      help='Use alternative desugared library configuration.')
   result.add_option('--desugared-library', '--desugared_library',
       help='Build and use desugared library from GitHub.')
   return result.parse_args()
@@ -181,6 +183,13 @@
   if utils.is_bot():
     gradle.RunGradle(['--no-daemon', 'clean'])
 
+  desugar_jdk_json_dir = None
+  if options.desugared_library_configuration:
+    if options.desugared_library_configuration != 'jdk11':
+      print("Only value supported for --desugared-library is 'jdk11'")
+      exit(1)
+    desugar_jdk_json_dir = 'src/library_desugar/jdk11'
+
   desugar_jdk_libs = None
   if options.desugared_library:
     if options.desugared_library != 'HEAD':
@@ -195,12 +204,12 @@
       # Make sure bazel is extracted in third_party.
       utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
       utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
-      (library_jar, maven_zip) = archive_desugar_jdk_libs.BuildDesugaredLibrary(checkout_dir)
+      utils.DownloadFromGoogleCloudStorage(utils.JAVA11_SHA_FILE)
+      (library_jar, maven_zip) = archive_desugar_jdk_libs.BuildDesugaredLibrary(checkout_dir, 'jdk11' if options.desugared_library_configuration == 'jdk11' else 'jdk8')
       desugar_jdk_libs = os.path.join(desugar_jdk_libs_dir, os.path.basename(library_jar))
       shutil.copyfile(library_jar, desugar_jdk_libs)
       print('Desugared library for test in ' + desugar_jdk_libs)
 
-
   gradle_args = ['--stacktrace']
   if utils.is_bot():
     # Bots don't like dangling processes.
@@ -284,6 +293,8 @@
     gradle_args.append('--no-daemon')
   if options.debug_agent:
     gradle_args.append('--no-daemon')
+  if desugar_jdk_json_dir:
+    gradle_args.append('-Pdesugar_jdk_json_dir=' + desugar_jdk_json_dir)
   if desugar_jdk_libs:
     gradle_args.append('-Pdesugar_jdk_libs=' + desugar_jdk_libs)
 
diff --git a/tools/utils.py b/tools/utils.py
index 1820991..da4a0e4 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -83,6 +83,7 @@
 BAZEL_SHA_FILE = os.path.join(THIRD_PARTY, 'bazel.tar.gz.sha1')
 BAZEL_TOOL = os.path.join(THIRD_PARTY, 'bazel')
 JAVA8_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk8', 'linux-x86.tar.gz.sha1')
+JAVA11_SHA_FILE = os.path.join(THIRD_PARTY, 'openjdk', 'jdk-11', 'linux.tar.gz.sha1')
 IGNORE_WARNINGS_RULES = os.path.join(REPO_ROOT, 'src', 'test', 'ignorewarnings.rules')
 
 ANDROID_HOME_ENVIROMENT_NAME = "ANDROID_HOME"